Merge branch '41502-28' of git://github.com/samhemelryk/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 8 Jul 2014 22:51:22 +0000 (00:51 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 8 Jul 2014 22:51:22 +0000 (00:51 +0200)
155 files changed:
admin/roles/classes/capability_table_base.php
admin/roles/module.js
admin/tool/installaddon/lang/en/tool_installaddon.php
admin/tool/task/scheduledtasks.php
admin/user.php
auth/shibboleth/index.php
badges/renderer.php
blocks/navigation/tests/behat/expand_my_courses_setting.feature
cache/classes/store.php
cache/stores/mongodb/lib.php
cache/stores/mongodb/tests/mongodb_test.php
cache/tests/fixtures/stores.php
calendar/renderer.php
calendar/yui/build/moodle-calendar-eventmanager/moodle-calendar-eventmanager-debug.js
calendar/yui/build/moodle-calendar-eventmanager/moodle-calendar-eventmanager-min.js
calendar/yui/build/moodle-calendar-eventmanager/moodle-calendar-eventmanager.js
calendar/yui/src/eventmanager/js/eventmanager.js
course/management.php
course/tests/behat/behat_course.php
course/tests/behat/category_change_visibility.feature
course/tests/behat/category_management.feature
course/tests/behat/category_resort.feature
course/tests/behat/course_category_management_listing.feature
course/tests/behat/course_change_visibility.feature
course/tests/behat/course_resort.feature
course/tests/behat/create_delete_course.feature
enrol/meta/tests/plugin_test.php
grade/edit/scale/edit_form.php
grade/edit/tree/category_form.php
grade/grading/form/guide/guideeditor.php
grade/grading/form/guide/js/guideeditor.js
grade/grading/form/guide/renderer.php
grade/grading/form/rubric/js/rubriceditor.js
grade/grading/form/rubric/renderer.php
grade/grading/form/rubric/rubriceditor.php
lang/en/admin.php
lang/en/cache.php
lang/en/calendar.php
lang/en/message.php
lang/en/plagiarism.php
lang/en/plugin.php
lang/en/repository.php
lib/accesslib.php
lib/classes/event/user_login_failed.php
lib/classes/text.php
lib/db/caches.php
lib/db/upgrade.php
lib/ddl/mssql_sql_generator.php
lib/ddl/tests/ddl_test.php
lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button-debug.js
lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button-min.js
lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button.js
lib/editor/atto/plugins/collapse/yui/src/button/js/button.js
lib/enrollib.php
lib/form/editor.php
lib/form/filemanager.php
lib/grade/tests/grade_scale_test.php
lib/graphlib.php
lib/moodlelib.php
lib/outputrenderers.php
lib/phpunit/classes/util.php
lib/testing/classes/util.php
lib/tests/text_test.php
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-debug.js
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-min.js
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js
lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-debug.js
lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-min.js
lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception.js
lib/yui/src/notification/js/ajaxexception.js
lib/yui/src/notification/js/exception.js
lib/yui/src/notification/meta/notification.json
mod/assign/locallib.php
mod/forum/classes/event/subscription_created.php
mod/forum/classes/event/subscription_deleted.php
mod/forum/tests/events_test.php
mod/imscp/locallib.php
mod/lti/db/install.xml
mod/lti/db/upgrade.php
mod/lti/return.php
mod/lti/service.php
mod/lti/upgrade.txt
mod/lti/version.php
mod/quiz/lang/en/quiz.php
mod/quiz/renderer.php
mod/quiz/report/attemptsreport_form.php
mod/quiz/report/attemptsreport_options.php
mod/quiz/report/attemptsreport_table.php
mod/quiz/report/overview/lang/en/quiz_overview.php
mod/quiz/report/overview/overview_form.php
mod/quiz/report/overview/overview_options.php
mod/quiz/report/overview/report.php
mod/quiz/report/overview/tests/report_test.php [new file with mode: 0644]
mod/quiz/report/reportlib.php
mod/quiz/styles.css
mod/quiz/tests/reportlib_test.php
notes/index.php
phpunit.xml.dist
question/category_class.php
question/classes/bank/view.php
question/editlib.php
question/engine/bank.php
question/engine/datalib.php
question/engine/lib.php
question/engine/questionattempt.php
question/engine/questionusage.php
question/engine/tests/helpers.php
question/export.php
question/export_form.php
question/format.php
question/format/blackboard_six/formatbase.php
question/format/gift/format.php
question/format/webct/format.php
question/format/xhtml/format.php
question/format/xml/format.php
question/import_form.php
question/previewlib.php
question/question.php
question/tests/behat/behat_question.php
question/type/calculated/datasetdefinitions_form.php
question/type/calculated/datasetitems_form.php
question/type/calculated/edit_calculated_form.php
question/type/calculated/question.php
question/type/calculated/questiontype.php
question/type/calculated/tests/formula_validation_test.php [new file with mode: 0644]
question/type/calculatedmulti/edit_calculatedmulti_form.php
question/type/calculatedmulti/questiontype.php
question/type/calculatedsimple/edit_calculatedsimple_form.php
question/type/calculatedsimple/questiontype.php
question/type/edit_question_form.php
question/type/essay/db/upgrade.php
question/type/match/question.php
question/type/multianswer/edit_multianswer_form.php
question/type/multianswer/questiontype.php
question/type/numerical/edit_numerical_form.php
question/type/numerical/questiontype.php
question/type/questiontypebase.php
question/type/random/questiontype.php
question/type/randomsamatch/questiontype.php
question/type/shortanswer/edit_shortanswer_form.php
repository/coursefiles/lib.php
repository/equella/callback.php
repository/equella/lib.php
repository/filepicker.php
repository/lib.php
repository/local/lib.php
repository/recent/lib.php
repository/repository_ajax.php
repository/upgrade.txt
repository/user/lib.php
user/edit.php
user/profile.php

index 11cadee..b46c137 100644 (file)
@@ -74,8 +74,16 @@ abstract class core_role_capability_table_base {
     public function display() {
         if (count($this->capabilities) > self::NUM_CAPS_FOR_SEARCH) {
             global $PAGE;
-            $PAGE->requires->strings_for_js(array('filter', 'clear'), 'moodle');
-            $PAGE->requires->js_init_call('M.core_role.init_cap_table_filter', array($this->id, $this->context->id));
+            $jsmodule = array(
+                'name' => 'rolescapfilter',
+                'fullpath' => '/admin/roles/module.js',
+                'strings' => array(
+                    array('filter', 'moodle'),
+                    array('clear', 'moodle'),                ),
+                'requires' => array('node', 'cookie', 'escape')
+            );
+            $PAGE->requires->js_init_call('M.core_role.init_cap_table_filter', array($this->id, $this->context->id), false,
+                $jsmodule);
         }
         echo '<table class="' . implode(' ', $this->classes) . '" id="' . $this->id . '">' . "\n<thead>\n";
         echo '<tr><th class="name" align="left" scope="col">' . get_string('capability', 'core_role') . '</th>';
index 7326cf3..0cac01a 100644 (file)
@@ -51,7 +51,7 @@ M.core_role.init_cap_table_filter = function(Y, tableid, contextid) {
                 marginRight : 'auto'
             });
             // Create the capability search input.
-            this.input = Y.Node.create('<input type="text" id="'+this.table.get('id')+'capabilitysearch" value="'+filtervalue+'" />');
+            this.input = Y.Node.create('<input type="text" id="'+this.table.get('id')+'capabilitysearch" value="'+Y.Escape.html(filtervalue)+'" />');
             // Create a label for the search input.
             this.label = Y.Node.create('<label for="'+this.input.get('id')+'">'+M.str.moodle.filter+' </label>');
             // Create a clear button to clear the input.
index a55199a..f162e05 100644 (file)
@@ -28,20 +28,20 @@ defined('MOODLE_INTERNAL') || die();
 
 $string['acknowledgement'] = 'Acknowledgement';
 $string['acknowledgementmust'] = 'You must acknowledge this';
-$string['acknowledgementtext'] = 'I understand that it is my responsibility to have full backups of this site prior to installing add-ons. I accept and understand that add-ons (especially but not only those originating in unofficial sources) may contain security holes, can make the site unavailable, or cause private data leaks or loss.';
-$string['featuredisabled'] = 'The add-on installer is disabled on this site.';
-$string['installaddon'] = 'Install add-on!';
-$string['installaddons'] = 'Install add-ons';
-$string['installexception'] = 'Oops... An error occurred while trying to install the add-on. Turn debugging mode on to see details of the error.';
-$string['installfromrepo'] = 'Install add-ons from the Moodle plugins directory';
-$string['installfromrepo_help'] = 'You will be redirected to the Moodle plugins directory to search for and install an add-on. Note that your site full name, URL and Moodle version will be sent as well, to make the installation process easier for you.';
-$string['installfromzip'] = 'Install add-on from ZIP file';
-$string['installfromzip_help'] = 'An alternative to installing an add-on directly from the Moodle plugins directory is to upload a ZIP package of the add-on. The ZIP package should have the same structure as a package downloaded from the Moodle plugins directory.';
+$string['acknowledgementtext'] = 'I understand that it is my responsibility to have full backups of this site prior to installing additional plugins. I accept and understand that plugins (especially but not only those originating in unofficial sources) may contain security holes, can make the site unavailable, or cause private data leaks or loss.';
+$string['featuredisabled'] = 'The plugin installer is disabled on this site.';
+$string['installaddon'] = 'Install plugin!';
+$string['installaddons'] = 'Install plugins';
+$string['installexception'] = 'Oops... An error occurred while trying to install the plugin. Turn debugging mode on to see details of the error.';
+$string['installfromrepo'] = 'Install plugins from the Moodle plugins directory';
+$string['installfromrepo_help'] = 'You will be redirected to the Moodle plugins directory to search for and install a plugin. Note that your site full name, URL and Moodle version will be sent as well, to make the installation process easier for you.';
+$string['installfromzip'] = 'Install plugin from ZIP file';
+$string['installfromzip_help'] = 'An alternative to installing a plugin directly from the Moodle plugins directory is to upload a ZIP package of the plugin. The ZIP package should have the same structure as a package downloaded from the Moodle plugins directory.';
 $string['installfromzipfile'] = 'ZIP package';
 $string['installfromzipfile_help'] = 'The plugin ZIP package must contain just one directory, named to match the plugin. The ZIP will be extracted into an appropriate location for the plugin type. If the package has been downloaded from the Moodle plugins directory then it will have this structure.';
 $string['installfromziprootdir'] = 'Rename the root directory';
 $string['installfromziprootdir_help'] = 'Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. If so, the correct name may be entered here.';
-$string['installfromzipsubmit'] = 'Install add-on from the ZIP file';
+$string['installfromzipsubmit'] = 'Install plugin from the ZIP file';
 $string['installfromziptype'] = 'Plugin type';
 $string['installfromziptype_help'] = 'Choose the correct type of plugin you are about to install. Warning: The installation procedure can fail badly if an incorrect plugin type is specified.';
 $string['permcheck'] = 'Make sure the plugin type root location is writable by the web server process.';
@@ -49,54 +49,54 @@ $string['permcheckerror'] = 'Error while checking for write permission';
 $string['permcheckprogress'] = 'Checking for write permission ...';
 $string['permcheckresultno'] = 'Plugin type location <em>{$a->path}</em> is not writable';
 $string['permcheckresultyes'] = 'Plugin type location <em>{$a->path}</em> is writable';
-$string['pluginname'] = 'Add-on installer';
-$string['remoterequestalreadyinstalled'] = 'There is a request to install add-on {$a->name} ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. However, this plugin is <strong>already installed</strong> on the site.';
-$string['remoterequestconfirm'] = 'There is a request to install add-on <strong>{$a->name}</strong> ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. If you continue, the add-on ZIP package will be downloaded for validation. Nothing will be installed yet.';
-$string['remoterequestinvalid'] = 'There is a request to install an add-on from the Moodle plugins directory on this site. Unfortunately the request is not valid and so the add-on cannot be installed.';
-$string['remoterequestpermcheck'] = 'There is a request to install add-on {$a->name} ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. However, the plugin type location <strong>{$a->typepath}</strong> is <strong>not writable</strong>. You need to give write access for the web server user to the plugin type location, then press the continue button to repeat the check.';
-$string['remoterequestpluginfoexception'] = 'Oops... An error occurred while trying to obtain information about the add-on {$a->name} ({$a->component}) version {$a->version}. The add-on cannot be installed. Turn debugging mode on to see details of the error.';
-$string['validation'] = 'Add-on package validation';
+$string['pluginname'] = 'Plugin installer';
+$string['remoterequestalreadyinstalled'] = 'There is a request to install plugin {$a->name} ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. However, this plugin is <strong>already installed</strong> on the site.';
+$string['remoterequestconfirm'] = 'There is a request to install plugin <strong>{$a->name}</strong> ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. If you continue, the plugin ZIP package will be downloaded for validation. Nothing will be installed yet.';
+$string['remoterequestinvalid'] = 'There is a request to install a plugin from the Moodle plugins directory on this site. Unfortunately the request is not valid and so the plugin cannot be installed.';
+$string['remoterequestpermcheck'] = 'There is a request to install plugin {$a->name} ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. However, the location <strong>{$a->typepath}</strong> is <strong>not writable</strong>. You need to give write access for the web server user to the location, then press the continue button to repeat the check.';
+$string['remoterequestpluginfoexception'] = 'Oops... An error occurred while trying to obtain information about the plugin {$a->name} ({$a->component}) version {$a->version}. The plugin cannot be installed. Turn debugging mode on to see details of the error.';
+$string['validation'] = 'Plugin package validation';
 $string['validationmsg_componentmatch'] = 'Full component name';
-$string['validationmsg_componentmismatchname'] = 'Add-on name mismatch';
-$string['validationmsg_componentmismatchname_help'] = 'Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. You need to fix the name of the root directory to match the declared add-on name.';
-$string['validationmsg_componentmismatchname_info'] = 'The add-on declares its name is \'{$a}\' but that does not match the name of the root directory.';
-$string['validationmsg_componentmismatchtype'] = 'Add-on type mismatch';
-$string['validationmsg_componentmismatchtype_info'] = 'You have selected the type \'{$a->expected}\' but the add-on declares its type is \'{$a->found}\'.';
+$string['validationmsg_componentmismatchname'] = 'Plugin name mismatch';
+$string['validationmsg_componentmismatchname_help'] = 'Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. You need to fix the name of the root directory to match the declared plugin name.';
+$string['validationmsg_componentmismatchname_info'] = 'The plugin declares its name is \'{$a}\' but that does not match the name of the root directory.';
+$string['validationmsg_componentmismatchtype'] = 'Plugin type mismatch';
+$string['validationmsg_componentmismatchtype_info'] = 'You have selected the type \'{$a->expected}\' but the plugin declares its type is \'{$a->found}\'.';
 $string['validationmsg_filenotexists'] = 'Extracted file not found';
 $string['validationmsg_filesnumber'] = 'Not enough files found in the package';
 $string['validationmsg_filestatus'] = 'Unable to extract all files';
 $string['validationmsg_filestatus_info'] = 'Attempting to extract file {$a->file} resulted in error \'{$a->status}\'.';
 $string['validationmsg_maturity'] = 'Declared maturity level';
-$string['validationmsg_maturity_help'] = 'The add-on can declare its maturity level. If the maintainer considers the add-on stable, the declared maturity level will read MATURITY_STABLE. All other maturity levels (such as alpha or beta) should be considered unstable and a warning is raised.';
+$string['validationmsg_maturity_help'] = 'The plugin can declare its maturity level. If the maintainer considers the plugin stable, the declared maturity level will read MATURITY_STABLE. All other maturity levels (such as alpha or beta) should be considered unstable and a warning is raised.';
 $string['validationmsg_missingexpectedlangenfile'] = 'English language file name mismatch';
-$string['validationmsg_missingexpectedlangenfile_info'] = 'The given add-on type is missing the expected English language file {$a}.';
+$string['validationmsg_missingexpectedlangenfile_info'] = 'The given plugin type is missing the expected English language file {$a}.';
 $string['validationmsg_missinglangenfile'] = 'No English language file found';
 $string['validationmsg_missinglangenfolder'] = 'Missing English language folder';
-$string['validationmsg_missingversion'] = 'Add-on does not declare its version';
+$string['validationmsg_missingversion'] = 'Plugin does not declare its version';
 $string['validationmsg_missingversionphp'] = 'File version.php not found';
 $string['validationmsg_multiplelangenfiles'] = 'Multiple English language files found';
 $string['validationmsg_onedir'] = 'Invalid structure of the ZIP package.';
-$string['validationmsg_onedir_help'] = 'The ZIP package must contain just one root directory that holds the add-on code. The name of that root directory must match the name of the plugin.';
+$string['validationmsg_onedir_help'] = 'The ZIP package must contain just one root directory that holds the plugin code. The name of that root directory must match the name of the plugin.';
 $string['validationmsg_pathwritable'] = 'Write access check';
-$string['validationmsg_pluginversion'] = 'Add-on version';
-$string['validationmsg_release'] = 'Add-on release';
+$string['validationmsg_pluginversion'] = 'Plugin version';
+$string['validationmsg_release'] = 'Plugin release';
 $string['validationmsg_requiresmoodle'] = 'Required Moodle version';
-$string['validationmsg_rootdir'] = 'Name of the add-on to be installed';
-$string['validationmsg_rootdir_help'] = 'The name of the root directory in the ZIP package forms the name of the add-on to be installed. If the name is not correct, you may wish to rename the root directory in the ZIP prior to installing the add-on.';
-$string['validationmsg_rootdirinvalid'] = 'Invalid add-on name';
-$string['validationmsg_rootdirinvalid_help'] = 'The name of the root directory in the ZIP package violates formal syntax requirements. Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. You need to fix the name of the root directory to match the add-on name.';
+$string['validationmsg_rootdir'] = 'Name of the plugin to be installed';
+$string['validationmsg_rootdir_help'] = 'The name of the root directory in the ZIP package forms the name of the plugin to be installed. If the name is not correct, you may wish to rename the root directory in the ZIP prior to installing the plugin.';
+$string['validationmsg_rootdirinvalid'] = 'Invalid plugin name';
+$string['validationmsg_rootdirinvalid_help'] = 'The name of the root directory in the ZIP package violates formal syntax requirements. Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. You need to fix the name of the root directory to match the plugin name.';
 $string['validationmsg_targetexists'] = 'Target location already exists';
-$string['validationmsg_targetexists_help'] = 'The directory that the add-on is to be installed to, must not exist yet.';
+$string['validationmsg_targetexists_help'] = 'The directory that the plugin is to be installed to must not yet exist.';
 $string['validationmsg_unknowntype'] = 'Unknown plugin type';
 $string['validationmsglevel_debug'] = 'Debug';
 $string['validationmsglevel_error'] = 'Error';
 $string['validationmsglevel_info'] = 'OK';
 $string['validationmsglevel_warning'] = 'Warning';
 $string['validationresult0'] = 'Validation failed!';
-$string['validationresult0_help'] = 'A serious problem was detected and so it is not safe to install the add-on. See the validation log messages for details.';
+$string['validationresult0_help'] = 'A serious problem was detected and so it is not safe to install the plugin. See the validation log messages for details.';
 $string['validationresult1'] = 'Validation passed!';
-$string['validationresult1_help'] = 'No serious problems were detected. You can continue with the add-on installation. See the validation log messages for more details and eventual warnings.';
-$string['validationresult1_help'] = 'The add-on package has been validated and no serious problems were detected.';
+$string['validationresult2_help'] = 'No serious problems were detected. You can continue with the plugin installation. See the validation log messages for more details and eventual warnings.';
+$string['validationresult1_help'] = 'The plugin package has been validated and no serious problems were detected.';
 $string['validationresultinfo'] = 'Info';
 $string['validationresultmsg'] = 'Message';
 $string['validationresultstatus'] = 'Status';
index 768d4bc..3963aaf 100644 (file)
@@ -104,11 +104,11 @@ if ($mform && ($mform->is_cancelled() || !empty($CFG->preventscheduledtaskchange
 
 } else {
     echo $OUTPUT->header();
-    $error = optional_param('error', '', PARAM_RAW);
+    $error = optional_param('error', '', PARAM_NOTAG);
     if ($error) {
         echo $OUTPUT->notification($error, 'notifyerror');
     }
-    $success = optional_param('success', '', PARAM_RAW);
+    $success = optional_param('success', '', PARAM_NOTAG);
     if ($success) {
         echo $OUTPUT->notification($success, 'notifysuccess');
     }
index cfbbe22..a7e1e64 100644 (file)
     $ufiltering->display_add();
     $ufiltering->display_active();
 
-    if (has_capability('moodle/user:create', $sitecontext)) {
-        echo $OUTPUT->heading('<a href="'.$securewwwroot.'/user/editadvanced.php?id=-1">'.get_string('addnewuser').'</a>');
-    }
     if (!empty($table)) {
         echo html_writer::start_tag('div', array('class'=>'no-overflow'));
         echo html_writer::table($table);
         echo html_writer::end_tag('div');
         echo $OUTPUT->paging_bar($usercount, $page, $perpage, $baseurl);
-        if (has_capability('moodle/user:create', $sitecontext)) {
-            echo $OUTPUT->heading('<a href="'.$securewwwroot.'/user/editadvanced.php?id=-1">'.get_string('addnewuser').'</a>');
-        }
+    }
+    if (has_capability('moodle/user:create', $sitecontext)) {
+        $url = new moodle_url($securewwwroot . '/user/editadvanced.php', array('id' => -1));
+        echo $OUTPUT->single_button($url, get_string('addnewuser'), 'get');
     }
 
     echo $OUTPUT->footer();
-
-
-
index f521442..ef38337 100644 (file)
     if (!empty($_SERVER[$pluginconfig->user_attribute])) {    // Shibboleth auto-login
         $frm = new stdClass();
         $frm->username = strtolower($_SERVER[$pluginconfig->user_attribute]);
-        $frm->password = substr(base64_encode($_SERVER[$pluginconfig->user_attribute]),0,8);
-        // The random password consists of the first 8 letters of the base 64 encoded user ID
-        // This password is never used unless the user account is converted to manual
+        // The password is never actually used, but needs to be passed to the functions 'user_login' and
+        // 'authenticate_user_login'. Shibboleth returns true for the function 'prevent_local_password', which is
+        // used when setting the password in 'update_internal_user_password'. When 'prevent_local_password'
+        // returns true, the password is set to 'not cached' (AUTH_PASSWORD_NOT_CACHED) in the Moodle DB. However,
+        // rather than setting the password to a hard-coded value, we will generate one each time, in case there are
+        // changes to the Shibboleth plugin and it is actually used.
+        $frm->password = generate_password(8);
 
     /// Check if the user has actually submitted login data to us
 
index c96cf88..52f7205 100644 (file)
@@ -41,7 +41,7 @@ class core_badges_renderer extends plugin_renderer_base {
                 $bname = $badge->name;
                 $imageurl = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', 'f1', false);
             } else {
-                $bname = $badge->assertion->badge->name;
+                $bname = s($badge->assertion->badge->name);
                 $imageurl = $badge->imageUrl;
             }
 
@@ -381,20 +381,20 @@ class core_badges_renderer extends plugin_renderer_base {
         $issuer = $assertion->badge->issuer;
         $userinfo = $ibadge->recipient;
         $table = new html_table();
-        $today_date = date('Y-m-d');
-        $today = strtotime($today_date);
-        $expiration = isset($assertion->badge->expire) ? strtotime($assertion->badge->expire) : $today + 86400;
+        $today = strtotime(date('Y-m-d'));
 
         $output = '';
         $output .= html_writer::start_tag('div', array('id' => 'badge'));
         $output .= html_writer::start_tag('div', array('id' => 'badge-image'));
-        if ($expiration < $today) {
-            $output .= $this->output->pix_icon('i/expired',
-                    get_string('expireddate', 'badges', $assertion->badge->expire),
-                    'moodle',
-                    array('class' => 'expireimage'));
-        } else {
-            $output .= html_writer::empty_tag('img', array('src' => $issued->imageUrl));
+        $output .= html_writer::empty_tag('img', array('src' => $issued->imageUrl));
+        if (isset($assertion->expires)) {
+            $expiration = !strtotime($assertion->expires) ? s($assertion->expires) : strtotime($assertion->expires);
+            if ($expiration < $today) {
+                $output .= $this->output->pix_icon('i/expired',
+                        get_string('expireddate', 'badges', userdate($expiration)),
+                        'moodle',
+                        array('class' => 'expireimage'));
+            }
         }
         $output .= html_writer::end_tag('div');
 
@@ -419,7 +419,7 @@ class core_badges_renderer extends plugin_renderer_base {
 
         $output .= $this->output->heading(get_string('issuerdetails', 'badges'), 3);
         $dl = array();
-        $dl[get_string('issuername', 'badges')] = $issuer->name;
+        $dl[get_string('issuername', 'badges')] = s($issuer->name);
         $dl[get_string('issuerurl', 'badges')] = html_writer::tag('a', $issuer->origin, array('href' => $issuer->origin));
 
         if (isset($issuer->contact)) {
@@ -429,25 +429,26 @@ class core_badges_renderer extends plugin_renderer_base {
 
         $output .= $this->output->heading(get_string('badgedetails', 'badges'), 3);
         $dl = array();
-        $dl[get_string('name')] = $assertion->badge->name;
-        $dl[get_string('description', 'badges')] = $assertion->badge->description;
-        $dl[get_string('bcriteria', 'badges')] = html_writer::tag('a', $assertion->badge->criteria, array('href' => $assertion->badge->criteria));
+        $dl[get_string('name')] = s($assertion->badge->name);
+        $dl[get_string('description', 'badges')] = s($assertion->badge->description);
+        $dl[get_string('bcriteria', 'badges')] = html_writer::tag('a', s($assertion->badge->criteria), array('href' => $assertion->badge->criteria));
         $output .= $this->definition_list($dl);
 
         $output .= $this->output->heading(get_string('issuancedetails', 'badges'), 3);
         $dl = array();
         if (isset($assertion->issued_on)) {
-            $dl[get_string('dateawarded', 'badges')] = $assertion->issued_on;
+            $issuedate = !strtotime($assertion->issued_on) ? s($assertion->issued_on) : strtotime($assertion->issued_on);
+            $dl[get_string('dateawarded', 'badges')] = userdate($issuedate);
         }
-        if (isset($assertion->badge->expire)) {
+        if (isset($assertion->expires)) {
             if ($expiration < $today) {
-                $dl[get_string('expirydate', 'badges')] = $assertion->badge->expire . get_string('warnexpired', 'badges');
+                $dl[get_string('expirydate', 'badges')] = userdate($expiration) . get_string('warnexpired', 'badges');
             } else {
-                $dl[get_string('expirydate', 'badges')] = $assertion->badge->expire;
+                $dl[get_string('expirydate', 'badges')] = userdate($expiration);
             }
         }
         if (isset($assertion->evidence)) {
-            $dl[get_string('evidence', 'badges')] = html_writer::tag('a', $assertion->evidence, array('href' => $assertion->evidence));
+            $dl[get_string('evidence', 'badges')] = html_writer::tag('a', s($assertion->evidence), array('href' => $assertion->evidence));
         }
         $output .= $this->definition_list($dl);
         $output .= html_writer::end_tag('div');
index 79eeb0e..b1a0b7b 100644 (file)
@@ -31,7 +31,7 @@ Feature: Test expand my courses navigation setting
   Scenario: The My Courses branch is collapsed when expand my courses is off
     Given I log in as "admin"
     And I set the following administration settings values:
-      | Expand My Courses initially on My Moodle page | 0 |
+      | Show My courses expanded on My home | 0 |
     And I log out
     Given I log in as "student1"
     And I follow "My home"
@@ -43,7 +43,7 @@ Feature: Test expand my courses navigation setting
   Scenario: My Courses can be expanded on the My Moodle page when expand my courses is off
     Given I log in as "admin"
     And I set the following administration settings values:
-      | Expand My Courses initially on My Moodle page | 0 |
+      | Show My courses expanded on My home | 0 |
     And I log out
     Given I log in as "student1"
     And I follow "My home"
@@ -53,4 +53,4 @@ Feature: Test expand my courses navigation setting
     And I expand "My courses" node
     Then I should see "c1" in the "Navigation" "block"
     And I should see "c2" in the "Navigation" "block"
-    And I should not see "c3" in the "Navigation" "block"
\ No newline at end of file
+    And I should not see "c3" in the "Navigation" "block"
index 22c20db..b5cfcc1 100644 (file)
@@ -356,13 +356,13 @@ abstract class cache_store implements cache_store_interface {
      * Initialises a test instance for unit tests.
      *
      * This differs from initialise_test_instance in that it doesn't rely on interacting with the config table.
-     * By default however it calls initialise_test_instance to support backwards compatability.
+     * By default however it calls initialise_test_instance to support backwards compatibility.
      *
      * @since 2.8
      * @param cache_definition $definition
      * @return cache_store|false
      */
     public static function initialise_unit_test_instance(cache_definition $definition) {
-        return self::initialise_test_instance($definition);
+        return static::initialise_test_instance($definition);
     }
 }
index db8653f..4405da4 100644 (file)
@@ -204,7 +204,7 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
             throw new coding_exception('This mongodb instance has already been initialised.');
         }
         $this->database = $this->connection->selectDB($this->databasename);
-        $this->definitionhash = $definition->generate_definition_hash();
+        $this->definitionhash = 'm'.$definition->generate_definition_hash();
         $this->collection = $this->database->selectCollection($this->definitionhash);
 
         $options = array('name' => 'idx_key');
@@ -578,6 +578,7 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
 
         $configuration = array();
         $configuration['servers'] = explode("\n", TEST_CACHESTORE_MONGODB_TESTSERVER);
+        $configuration['usesafe'] = 1;
 
         $store = new cachestore_mongodb('Test mongodb', $configuration);
         $store->initialise($definition);
index 97bfb9d..5186175 100644 (file)
@@ -49,4 +49,26 @@ class cachestore_mongodb_test extends cachestore_tests {
     protected function get_class_name() {
         return 'cachestore_mongodb';
     }
+
+    /**
+     * A small additional test to make sure definitions that hash a hash starting with a number work OK
+     */
+    public function test_collection_name() {
+        // This generates a definition that has a hash starting with a number. MDL-46208.
+        $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_mongodb', 'abc');
+        $instance = cachestore_mongodb::initialise_unit_test_instance($definition);
+
+        if (!$instance) {
+            $this->markTestSkipped();
+        }
+
+        $this->assertTrue($instance->set(1, 'alpha'));
+        $this->assertTrue($instance->set(2, 'beta'));
+        $this->assertEquals('alpha', $instance->get(1));
+        $this->assertEquals('beta', $instance->get(2));
+        $this->assertEquals(array(
+            1 => 'alpha',
+            2 => 'beta'
+        ), $instance->get_many(array(1, 2)));
+    }
 }
\ No newline at end of file
index a350f6d..ee5f373 100644 (file)
@@ -54,7 +54,7 @@ abstract class cachestore_tests extends advanced_testcase {
         $modes = $class::get_supported_modes();
         if ($modes & cache_store::MODE_APPLICATION) {
             $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, $class, 'phpunit_test');
-            $instance = $class::initialise_test_instance($definition);
+            $instance = $class::initialise_unit_test_instance($definition);
             if (!$instance) {
                 $this->markTestSkipped('Could not test '.$class.'. No test instance configured for application caches.');
             } else {
@@ -63,7 +63,7 @@ abstract class cachestore_tests extends advanced_testcase {
         }
         if ($modes & cache_store::MODE_SESSION) {
             $definition = cache_definition::load_adhoc(cache_store::MODE_SESSION, $class, 'phpunit_test');
-            $instance = $class::initialise_test_instance($definition);
+            $instance = $class::initialise_unit_test_instance($definition);
             if (!$instance) {
                 $this->markTestSkipped('Could not test '.$class.'. No test instance configured for session caches.');
             } else {
@@ -72,7 +72,7 @@ abstract class cachestore_tests extends advanced_testcase {
         }
         if ($modes & cache_store::MODE_REQUEST) {
             $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, $class, 'phpunit_test');
-            $instance = $class::initialise_test_instance($definition);
+            $instance = $class::initialise_unit_test_instance($definition);
             if (!$instance) {
                 $this->markTestSkipped('Could not test '.$class.'. No test instance configured for request caches.');
             } else {
index a8a3521..4a0e6cb 100644 (file)
@@ -259,8 +259,7 @@ class core_calendar_renderer extends plugin_renderer_base {
         if (calendar_user_can_add_event($calendar->course)) {
             $output .= $this->add_event_button($calendar->course->id, 0, 0, 0, $calendar->time);
         }
-        //$output .= html_writer::tag('label', get_string('dayview', 'calendar'), array('for'=>'cal_course_flt_jump'));
-        $output .= $this->course_filter_selector($returnurl, get_string('dayview', 'calendar'));
+        $output .= $this->course_filter_selector($returnurl, get_string('dayviewfor', 'calendar'));
         $output .= html_writer::end_tag('div');
         // Controls
         $output .= html_writer::tag('div', calendar_top_controls('day', array('id' => $calendar->courseid, 'time' => $calendar->time)), array('class'=>'controls'));
@@ -468,7 +467,7 @@ class core_calendar_renderer extends plugin_renderer_base {
         if (calendar_user_can_add_event($calendar->course)) {
             $output .= $this->add_event_button($calendar->course->id, 0, 0, 0, $calendar->time);
         }
-        $output .= get_string('detailedmonthview', 'calendar').': '.$this->course_filter_selector($returnurl);
+        $output .= $this->course_filter_selector($returnurl, get_string('detailedmonthviewfor', 'calendar'));
         $output .= html_writer::end_tag('div', array('class'=>'header'));
         // Controls
         $output .= html_writer::tag('div', calendar_top_controls('month', array('id' => $calendar->courseid, 'time' => $calendar->time)), array('class' => 'controls'));
@@ -618,8 +617,7 @@ class core_calendar_renderer extends plugin_renderer_base {
         if (calendar_user_can_add_event($calendar->course)) {
             $output .= $this->add_event_button($calendar->course->id);
         }
-        $output .= html_writer::tag('label', get_string('upcomingevents', 'calendar'), array('for'=>'cal_course_flt_jump'));
-        $output .= $this->course_filter_selector($returnurl);
+        $output .= $this->course_filter_selector($returnurl, get_string('upcomingeventsfor', 'calendar'));
         $output .= html_writer::end_tag('div');
 
         if ($events) {
index d9c932a..daa5f10 100644 (file)
Binary files a/calendar/yui/build/moodle-calendar-eventmanager/moodle-calendar-eventmanager-debug.js and b/calendar/yui/build/moodle-calendar-eventmanager/moodle-calendar-eventmanager-debug.js differ
index 3722351..04bd0a3 100644 (file)
Binary files a/calendar/yui/build/moodle-calendar-eventmanager/moodle-calendar-eventmanager-min.js and b/calendar/yui/build/moodle-calendar-eventmanager/moodle-calendar-eventmanager-min.js differ
index d9c932a..daa5f10 100644 (file)
Binary files a/calendar/yui/build/moodle-calendar-eventmanager/moodle-calendar-eventmanager.js and b/calendar/yui/build/moodle-calendar-eventmanager/moodle-calendar-eventmanager.js differ
index 42c3ca2..d1521d3 100644 (file)
@@ -60,6 +60,7 @@ Y.extend(EVENT, Y.Base, {
         }
     },
     startShow : function() {
+        this.cancelHide();
         if (this.get(SHOWTIMEOUT) !== null) {
             this.cancelShow();
         }
@@ -80,6 +81,7 @@ Y.extend(EVENT, Y.Base, {
         this.fire('showevent');
     },
     startHide : function() {
+        this.cancelShow();
         if (this.get(HIDETIMEOUT) !== null) {
             this.cancelHide();
         }
index 67b2329..c48158d 100644 (file)
@@ -69,12 +69,10 @@ if ($courseid) {
 } else {
     $course = null;
     $courseid = null;
-    $category = null;
-    $categoryid = null;
-    if ($viewmode === 'default') {
-        $viewmode = 'categories';
-    }
-    $context = $systemcontext;
+    $category = coursecat::get_default();
+    $categoryid = $category->id;
+    $context = context_coursecat::instance($category->id);
+    $url->param('categoryid', $category->id);
 }
 
 // Check if there is a selected category param, and if there is apply it.
index a306319..7698fe7 100644 (file)
@@ -1443,7 +1443,7 @@ class behat_course extends behat_base {
                 break;
             case "Course categories":
                 $return[] = new Given('"#category-listing" "css_element" should exist');
-                $return[] = new Given('"#course-listing" "css_element" should not exist');
+                $return[] = new Given('"#course-listing" "css_element" should exist');
                 break;
             case "Courses categories and courses":
             default:
index b4f13b2..ceccb9e 100644 (file)
@@ -15,7 +15,7 @@ Feature: We can change the visibility of categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And category in management listing should be visible "CAT1"
     And I toggle visibility of category "CAT1" in management listing
@@ -36,7 +36,7 @@ Feature: We can change the visibility of categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And category in management listing should be visible "CAT1"
     And I toggle visibility of category "CAT1" in management listing
@@ -55,7 +55,7 @@ Feature: We can change the visibility of categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And I should not see "Cat 2" in the "#category-listing ul.ml" "css_element"
     And category in management listing should be visible "CAT1"
@@ -91,7 +91,7 @@ Feature: We can change the visibility of categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And I should not see "Cat 2" in the "#category-listing ul.ml" "css_element"
     And category in management listing should be visible "CAT1"
@@ -124,7 +124,7 @@ Feature: We can change the visibility of categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -199,7 +199,7 @@ Feature: We can change the visibility of categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -264,7 +264,7 @@ Feature: We can change the visibility of categories in the management interface.
 
       And I log in as "admin"
       And I go to the courses management page
-      And I should see the "Course categories" management page
+      And I should see the "Course categories and courses" management page
       And I click on category "Cat 1" in the management interface
       # Redirect
       And I should see the "Course categories and courses" management page
index 63e00d4..b90a8f8 100644 (file)
@@ -21,7 +21,7 @@ Feature: Test category management actions
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "edit" action for "Cat 1" in management category listing
     # Redirect
     And I should see "Edit category settings"
@@ -55,7 +55,7 @@ Feature: Test category management actions
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And I should see "Cat 2" in the "#category-listing ul.ml" "css_element"
     And I should see "Cat 3" in the "#category-listing ul.ml" "css_element"
@@ -81,7 +81,7 @@ Feature: Test category management actions
     And I should see "Deleted course category Cat 2"
     And I press "Continue"
     # Redirect
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And I should not see "Cat 2" in the "#category-listing ul.ml" "css_element"
     And I should see "Cat 3" in the "#category-listing ul.ml" "css_element"
@@ -113,7 +113,7 @@ Feature: Test category management actions
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "assignroles" action for "Cat 1" in management category listing
     # Redirect
     And I should see "Assign roles in Category: Cat 1"
@@ -133,7 +133,7 @@ Feature: Test category management actions
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "permissions" action for "Cat 1" in management category listing
     # Redirect
     And I should see "Permissions in Category: Cat 1"
@@ -152,7 +152,7 @@ Feature: Test category management actions
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "cohorts" action for "Cat 1" in management category listing
     # Redirect
     And I should see "Category: Cat 1: available cohorts"
@@ -167,7 +167,7 @@ Feature: Test category management actions
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "filters" action for "Cat 1" in management category listing
     # Redirect
     And I should see "Filter settings in Category: Cat 1"
@@ -183,11 +183,12 @@ Feature: Test category management actions
       | Cat 1 | 0 | CAT1 |
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "Create new category" "link" in the ".category-listing-actions" "css_element"
     # Redirect.
     And I should see "Add new category"
     And I set the following fields to these values:
+      | Parent category | Top |
       | Category name | Test category 2 |
       | Category ID number | TC2 |
     And I press "Create category"
@@ -200,6 +201,7 @@ Feature: Test category management actions
     # Redirect
     And I should see "Add new category"
     And I set the following fields to these values:
+      | Parent category | Top |
       | Category name | Test category 3 |
       | Category ID number | TC3 |
     And I press "Create category"
@@ -219,7 +221,7 @@ Feature: Test category management actions
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And I should see "Cat 2" in the "#category-listing ul.ml" "css_element"
     And I should see "Cat 3" in the "#category-listing ul.ml" "css_element"
@@ -250,7 +252,7 @@ Feature: Test category management actions
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
     And I should see "Cat 2" in the "#category-listing ul.ml" "css_element"
     And I should see "Cat 3" in the "#category-listing ul.ml" "css_element"
index ddbb423..01d6857 100644 (file)
@@ -14,12 +14,12 @@ Feature: Test we can resort categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I set the field "menuselectsortby" to "All categories"
     And I set the field "menuresortcategoriesby" to <sortby>
     And I press "Sort"
     # Redirect.
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
 
@@ -38,7 +38,7 @@ Feature: Test we can resort categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "Test category" "link"
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -65,7 +65,7 @@ Feature: Test we can resort categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "Master cat" "link"
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -91,7 +91,7 @@ Feature: Test we can resort categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "Master cat" category in the management category listing
   # Redirect.
     And I should see the "Course categories and courses" management page
@@ -118,7 +118,7 @@ Feature: Test we can resort categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect. We should a 1, 1a, 1b, 1c, 2.
     And I should see the "Course categories and courses" management page
@@ -178,7 +178,7 @@ Feature: Test we can resort categories in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect.
     And I should see the "Course categories and courses" management page
index 295f4b3..818619b 100644 (file)
@@ -14,7 +14,7 @@ Feature: Course category management interface performs as expected
     And I should see "Course and category management" in the "h2" "css_element"
     And I should see "Course categories" in the ".view-mode-selector" "css_element"
     And I should see "Course categories" in the "h3" "css_element"
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
 
   @javascript
   Scenario: Test view mode functionality
@@ -27,7 +27,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Course categories" in the "#category-listing h3" "css_element"
     And I should see "Cat 1" in the "#category-listing" "css_element"
     And I should see "Course categories" in the ".view-mode-selector" "css_element"
@@ -42,9 +42,9 @@ Feature: Course category management interface performs as expected
     # Redirect.
     And I should see the "Course categories and courses" management page
     And I should see "Course categories" in the "#category-listing h3" "css_element"
-    And I should see "Courses" in the "#course-listing h3" "css_element"
+    And I should see "Miscellaneous" in the "#course-listing h3" "css_element"
     And I should see "Cat 1" in the "#category-listing" "css_element"
-    And I should see "Please select a category" in the "#course-listing" "css_element"
+    And I should see "No courses in this category" in the "#course-listing" "css_element"
     And I click on category "Cat 1" in the management interface
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -94,7 +94,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
@@ -164,7 +164,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
@@ -244,12 +244,12 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I set the field "menuselectsortby" to "All categories"
     And I set the field "menuresortcategoriesby" to <sortby>
     And I press "Sort"
     # Redirect.
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
 
@@ -269,7 +269,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "Master cat" category in the management category listing
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -297,7 +297,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "Cat 1" "link"
   # Redirect.
     And I should see the "Course categories and courses" management page
@@ -339,7 +339,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "Cat 1" "link"
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -522,7 +522,7 @@ Feature: Course category management interface performs as expected
       | CAT1 | Course 5 | Course 5 | C5 |
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "Cat 1" "link"
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -587,7 +587,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on "Cat 1" "link"
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -681,7 +681,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     And I click on "edit" action for "Course 1" in management course listing
     # Redirect
@@ -706,7 +706,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat 2" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat 1-1" in the "#course-category-listings ul.ml" "css_element"
@@ -764,7 +764,7 @@ Feature: Course category management interface performs as expected
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I should see "Cat A (1)" in the "#course-category-listings ul.ml" "css_element"
     And I should see "Cat B (2)" in the "#course-category-listings ul.ml" "css_element"
     And I should not see "Cat C (1-1)" in the "#course-category-listings ul.ml" "css_element"
index 2230dde..eedf984 100644 (file)
@@ -15,7 +15,7 @@ Feature: We can change the visibility of courses in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -62,7 +62,7 @@ Feature: We can change the visibility of courses in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect.
     And I should see the "Course categories and courses" management page
index 678bc4f..b530e5b 100644 (file)
@@ -17,7 +17,7 @@ Feature: Test we can resort course in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -50,7 +50,7 @@ Feature: Test we can resort course in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -86,7 +86,7 @@ Feature: Test we can resort course in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect.
     And I should see the "Course categories and courses" management page
@@ -123,7 +123,7 @@ Feature: Test we can resort course in the management interface.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect.
     And I should see the "Course categories and courses" management page
index c12ae95..2cff137 100644 (file)
@@ -11,7 +11,7 @@ Feature: Test we can both create and delete a course.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect
     And I should see the "Course categories and courses" management page
@@ -26,7 +26,7 @@ Feature: Test we can both create and delete a course.
     And I press "Save changes"
     # Redirect
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect
     And I should see the "Course categories and courses" management page
@@ -44,7 +44,7 @@ Feature: Test we can both create and delete a course.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect
     And I should see the "Course categories and courses" management page
@@ -76,7 +76,7 @@ Feature: Test we can both create and delete a course.
 
     And I log in as "admin"
     And I go to the courses management page
-    And I should see the "Course categories" management page
+    And I should see the "Course categories and courses" management page
     And I click on category "Cat 1" in the management interface
     # Redirect
     And I should see the "Course categories and courses" management page
index 3a9e038..63f1824 100644 (file)
@@ -443,7 +443,7 @@ class enrol_meta_plugin_testcase extends advanced_testcase {
     /**
      * Test user_enrolment_created event.
      */
-    public function test_user_enrolment_created_observer() {
+    public function test_user_enrolment_created_event() {
         global $DB;
 
         $this->resetAfterTest();
@@ -478,9 +478,9 @@ class enrol_meta_plugin_testcase extends advanced_testcase {
     }
 
     /**
-     * Test user_enrolment_deleted observer.
+     * Test user_enrolment_deleted event.
      */
-    public function test_user_enrolment_deleted_observer() {
+    public function test_user_enrolment_deleted_event() {
         global $DB;
 
         $this->resetAfterTest(true);
@@ -514,7 +514,7 @@ class enrol_meta_plugin_testcase extends advanced_testcase {
     /**
      * Test user_enrolment_updated event.
      */
-    public function test_user_enrolment_updated_observer() {
+    public function test_user_enrolment_updated_event() {
         global $DB;
 
         $this->resetAfterTest(true);
index 848cc4b..1f57544 100644 (file)
@@ -135,7 +135,7 @@ class edit_scale_form extends moodleform {
             $scalearray = array_map('trim', $scalearray);
             $scaleoptioncount = count($scalearray);
 
-            if (count($scalearray) < 2) {
+            if (count($scalearray) < 1) {
                 $errors['scale'] = get_string('badlyformattedscale', 'grades');
             } else {
                 $thescale = implode(',',$scalearray);
index ec2db5f..950e3bf 100644 (file)
@@ -86,19 +86,15 @@ class edit_category_form extends moodleform {
             $mform->setAdvanced('aggregatesubcats');
         }
 
-        $options = array(0 => get_string('none'));
-
-        for ($i=1; $i<=20; $i++) {
-            $options[$i] = $i;
-        }
-
-        $mform->addElement('select', 'keephigh', get_string('keephigh', 'grades'), $options);
+        $mform->addElement('text', 'keephigh', get_string('keephigh', 'grades'), 'size="3"');
+        $mform->setType('keephigh', PARAM_INT);
         $mform->addHelpButton('keephigh', 'keephigh', 'grades');
         if ((int)$CFG->grade_keephigh_flag & 2) {
             $mform->setAdvanced('keephigh');
         }
 
-        $mform->addElement('select', 'droplow', get_string('droplow', 'grades'), $options);
+        $mform->addElement('text', 'droplow', get_string('droplow', 'grades'), 'size="3"');
+        $mform->setType('droplow', PARAM_INT);
         $mform->addHelpButton('droplow', 'droplow', 'grades');
         $mform->disabledIf('droplow', 'keephigh', 'noteq', 0);
         if ((int)$CFG->grade_droplow_flag & 2) {
index aebb982..da430ec 100644 (file)
@@ -100,6 +100,7 @@ class moodlequickform_guideeditor extends HTML_QuickForm_input {
             $mode = gradingform_guide_controller::DISPLAY_EDIT_FULL;
             $module = array('name'=>'gradingform_guideeditor',
                 'fullpath'=>'/grade/grading/form/guide/js/guideeditor.js',
+                'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
                 'strings' => array(
                     array('confirmdeletecriterion', 'gradingform_guide'),
                     array('clicktoedit', 'gradingform_guide'),
index 3aeb83b..94c1e15 100644 (file)
@@ -111,9 +111,9 @@ M.gradingform_guideeditor.editmode = function(el, editmode) {
             value = M.str.gradingform_guide.clicktoedit
             taplain.addClass('empty')
         }
-        taplain.one('.textvalue').set('innerHTML', value)
+        taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value))
         if (tb) {
-            tbplain.one('.textvalue').set('innerHTML', tb.get('value'))
+            tbplain.one('.textvalue').set('innerHTML', Y.Escape.html(tb.get('value')))
         }
         // hide/display textarea, textbox and plaintexts
         taplain.removeClass('hiddenelement')
index 442d24b..87e94de 100644 (file)
@@ -93,20 +93,20 @@ class gradingform_guide_renderer extends plugin_renderer_base {
                 'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
 
             $shortname = html_writer::empty_tag('input', array('type'=> 'text',
-                'name' => '{NAME}[criteria][{CRITERION-id}][shortname]',  'value' => htmlspecialchars($criterion['shortname']),
+                'name' => '{NAME}[criteria][{CRITERION-id}][shortname]',  'value' => $criterion['shortname'],
                 'id ' => '{NAME}[criteria][{CRITERION-id}][shortname]'));
             $shortname = html_writer::tag('div', $shortname, array('class'=>'criterionname'));
-            $description = html_writer::tag('textarea', htmlspecialchars($criterion['description']),
+            $description = html_writer::tag('textarea', s($criterion['description']),
                 array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '65', 'rows' => '5'));
             $description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
 
-            $descriptionmarkers = html_writer::tag('textarea', htmlspecialchars($criterion['descriptionmarkers']),
+            $descriptionmarkers = html_writer::tag('textarea', s($criterion['descriptionmarkers']),
                 array('name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]', 'cols' => '65', 'rows' => '5'));
             $descriptionmarkers = html_writer::tag('div', $descriptionmarkers, array('class'=>'criteriondescmarkers'));
 
             $maxscore = html_writer::empty_tag('input', array('type'=> 'text',
                 'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]', 'size' => '3',
-                'value' => htmlspecialchars($criterion['maxscore']),
+                'value' => $criterion['maxscore'],
                 'id' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
             $maxscore = html_writer::tag('div', $maxscore, array('class'=>'criterionmaxscore'));
         } else {
@@ -125,7 +125,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
                        $mode == gradingform_guide_controller::DISPLAY_VIEW) {
                 $descriptionclass = 'descriptionreadonly';
             }
-            $shortname   = html_writer::tag('div', $criterion['shortname'],
+            $shortname   = html_writer::tag('div', s($criterion['shortname']),
                 array('class'=>'criterionshortname', 'name' => '{NAME}[criteria][{CRITERION-id}][shortname]'));
             $descmarkerclass = '';
             $descstudentclass = '';
@@ -137,13 +137,13 @@ class gradingform_guide_renderer extends plugin_renderer_base {
                     $descstudentclass = ' hide';
                 }
             }
-            $description = html_writer::tag('div', $criterion['description'],
+            $description = html_writer::tag('div', s($criterion['description']),
                 array('class'=>'criteriondescription'.$descstudentclass,
                       'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
-            $descriptionmarkers   = html_writer::tag('div', $criterion['descriptionmarkers'],
+            $descriptionmarkers   = html_writer::tag('div', s($criterion['descriptionmarkers']),
                 array('class'=>'criteriondescriptionmarkers'.$descmarkerclass,
                       'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
-            $maxscore   = html_writer::tag('div', $criterion['maxscore'],
+            $maxscore   = html_writer::tag('div', s($criterion['maxscore']),
                 array('class'=>'criteriondescriptionscore', 'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
         }
 
@@ -193,7 +193,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
                 $scoreclass = 'error';
                 $currentscore = $validationerrors[$criterion['id']]['score']; // Show invalid score in form.
             }
-            $input = html_writer::tag('textarea', htmlspecialchars($currentremark),
+            $input = html_writer::tag('textarea', s($currentremark),
                 array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '65', 'rows' => '5',
                       'class' => 'markingguideremark'));
             $criteriontemplate .= html_writer::tag('td', $input, array('class' => 'remark'));
@@ -202,7 +202,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
             $score .= html_writer::empty_tag('input', array('type'=> 'text',
                 'name' => '{NAME}[criteria][{CRITERION-id}][score]', 'class' => $scoreclass,
                 'id' => '{NAME}[criteria][{CRITERION-id}][score]',
-                'size' => '3', 'value' => htmlspecialchars($currentscore)));
+                'size' => '3', 'value' => $currentscore));
             $score .= '/'.$maxscore;
 
             $criteriontemplate .= html_writer::tag('td', $score, array('class' => 'score'));
@@ -211,9 +211,9 @@ class gradingform_guide_renderer extends plugin_renderer_base {
                 'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
         } else if ($mode == gradingform_guide_controller::DISPLAY_REVIEW ||
             $mode == gradingform_guide_controller::DISPLAY_VIEW) {
-            $criteriontemplate .= html_writer::tag('td', $currentremark, array('class' => 'remark'));
+            $criteriontemplate .= html_writer::tag('td', s($currentremark), array('class' => 'remark'));
             if (!empty($options['showmarkspercriterionstudents'])) {
-                $criteriontemplate .= html_writer::tag('td', htmlspecialchars($currentscore). ' / '.$maxscore,
+                $criteriontemplate .= html_writer::tag('td', s($currentscore). ' / '.$maxscore,
                     array('class' => 'score'));
             }
         }
@@ -272,7 +272,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
             $criteriontemplate .= html_writer::end_tag('td'); // Controls.
             $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
                 'name' => '{NAME}[comments][{COMMENT-id}][sortorder]', 'value' => $comment['sortorder']));
-            $description = html_writer::tag('textarea', htmlspecialchars($comment['description']),
+            $description = html_writer::tag('textarea', s($comment['description']),
                 array('name' => '{NAME}[comments][{COMMENT-id}][description]', 'cols' => '65', 'rows' => '5'));
             $description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
         } else {
@@ -283,12 +283,12 @@ class gradingform_guide_renderer extends plugin_renderer_base {
                     'name' => '{NAME}[comments][{COMMENT-id}][description]', 'value' => $comment['description']));
             }
             if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
-                $description = html_writer::tag('span', htmlspecialchars($comment['description']),
+                $description = html_writer::tag('span', s($comment['description']),
                     array('name' => '{NAME}[comments][{COMMENT-id}][description]',
                           'title' => get_string('clicktocopy', 'gradingform_guide'),
                           'id' => '{NAME}[comments][{COMMENT-id}]', 'class'=>'markingguidecomment'));
             } else {
-                $description = $comment['description'];
+                $description = s($comment['description']);
             }
         }
         $descriptionclass = 'description';
index bff4d09..f0dd3ae 100644 (file)
@@ -93,8 +93,8 @@ M.gradingform_rubriceditor.editmode = function(el, editmode, focustb) {
             value = (el.hasClass('level')) ? M.str.gradingform_rubric.levelempty : M.str.gradingform_rubric.criterionempty
             taplain.addClass('empty')
         }
-        taplain.one('.textvalue').set('innerHTML', value)
-        if (tb) tbplain.one('.textvalue').set('innerHTML', tb.get('value'))
+        taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value));
+        if (tb) tbplain.one('.textvalue').set('innerHTML', Y.Escape.html(tb.get('value')));
         // hide/display textarea, textbox and plaintexts
         taplain.removeClass('hiddenelement')
         ta.addClass('hiddenelement')
index 9132f43..b2ba8ff 100644 (file)
@@ -79,13 +79,13 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
             }
             $criteriontemplate .= html_writer::end_tag('td'); // .controls
             $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
-            $description = html_writer::tag('textarea', htmlspecialchars($criterion['description']), array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '10', 'rows' => '5'));
+            $description = html_writer::tag('textarea', s($criterion['description']), array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '10', 'rows' => '5'));
         } else {
             if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
                 $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
                 $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][description]', 'value' => $criterion['description']));
             }
-            $description = $criterion['description'];
+            $description = s($criterion['description']);
         }
         $descriptionclass = 'description';
         if (isset($criterion['error_description'])) {
@@ -111,12 +111,12 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
                 $currentremark = $value['remark'];
             }
             if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
-                $input = html_writer::tag('textarea', htmlspecialchars($currentremark), array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '10', 'rows' => '5'));
+                $input = html_writer::tag('textarea', s($currentremark), array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '10', 'rows' => '5'));
                 $criteriontemplate .= html_writer::tag('td', $input, array('class' => 'remark'));
             } else if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN) {
                 $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
             }else if ($mode == gradingform_rubric_controller::DISPLAY_REVIEW || $mode == gradingform_rubric_controller::DISPLAY_VIEW) {
-                $criteriontemplate .= html_writer::tag('td', $currentremark, array('class' => 'remark'));
+                $criteriontemplate .= html_writer::tag('td', s($currentremark), array('class' => 'remark'));
             }
         }
         $criteriontemplate .= html_writer::end_tag('tr'); // .criterion
@@ -169,7 +169,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
         $leveltemplate = html_writer::start_tag('td', $tdattributes);
         $leveltemplate .= html_writer::start_tag('div', array('class' => 'level-wrapper'));
         if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
-            $definition = html_writer::tag('textarea', htmlspecialchars($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
+            $definition = html_writer::tag('textarea', s($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
             $score = html_writer::label(get_string('criterionempty', 'gradingform_rubric'), '{NAME}criteria{CRITERION-id}levels{LEVEL-id}', false, array('class' => 'accesshide'));
             $score .= html_writer::empty_tag('input', array('type' => 'text','id' => '{NAME}criteria{CRITERION-id}levels{LEVEL-id}', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '3', 'value' => $level['score']));
         } else {
@@ -177,7 +177,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
                 $leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'value' => $level['definition']));
                 $leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'value' => $level['score']));
             }
-            $definition = $level['definition'];
+            $definition = s($level['definition']);
             $score = $level['score'];
         }
         if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
index bb9cee3..b3cc5bf 100644 (file)
@@ -106,6 +106,7 @@ class MoodleQuickForm_rubriceditor extends HTML_QuickForm_input {
         if (!$this->_flagFrozen) {
             $mode = gradingform_rubric_controller::DISPLAY_EDIT_FULL;
             $module = array('name'=>'gradingform_rubriceditor', 'fullpath'=>'/grade/grading/form/rubric/js/rubriceditor.js',
+                'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
                 'strings' => array(array('confirmdeletecriterion', 'gradingform_rubric'), array('confirmdeletelevel', 'gradingform_rubric'),
                     array('criterionempty', 'gradingform_rubric'), array('levelempty', 'gradingform_rubric')
                     ));
index dad616f..eaaccaf 100644 (file)
@@ -437,7 +437,6 @@ $string['docroot'] = 'Moodle Docs document root';
 $string['doctonewwindow'] = 'Open in new window';
 $string['download'] = 'Download';
 $string['edithelpdocs'] = 'Edit help documents';
-$string['editingnoncorelangfile'] = 'You are trying to modify translation of an add-on module/plugin. You can save translation of 3rd party modules in your _local folder only. You may want to move the file with translation into the module\'s lang directory and/or send it to the maintainer of the add-on module.';
 $string['editlang'] = '<b>Edit</b>';
 $string['editorbackgroundcolor'] = 'Background colour';
 $string['editordictionary'] = 'Editor dictionary';
@@ -734,10 +733,10 @@ $string['navadduserpostslinks'] = 'Add links to view user posts';
 $string['navadduserpostslinks_help'] = 'If enabled two links will be added to each user in the navigation to view discussions the user has started and posts the user has made in forums throughout the site or in specific courses.';
 $string['navigationupgrade'] = 'This upgrade introduces two new navigation blocks that will replace these blocks: Administration, Courses, Activities and Participants.  If you had set any special permissions on those blocks you should check to make sure everything is behaving as you want it.';
 $string['navcourselimit'] = 'Course limit';
-$string['navexpandmycourses'] = 'Expand My Courses initially on My Moodle page';
-$string['navexpandmycourses_desc'] = 'When enabled the My Courses branch will be expanded initially on the My Moodle page and any pages within the My Moodle area.';
+$string['navexpandmycourses'] = 'Show My courses expanded on My home';
+$string['navexpandmycourses_desc'] = 'If enabled, My courses is initially shown expanded in the navigation block on My home.';
 $string['navshowfullcoursenames'] = 'Show course full names';
-$string['navshowfullcoursenames_help'] = 'If enabled courses in the navigation will be shown with using their full name rather than their short name.';
+$string['navshowfullcoursenames_help'] = 'If enabled, course full names will be used in the navigation rather than short names.';
 $string['navshowfrontpagemods'] = 'Show front page activities in the navigation';
 $string['navshowfrontpagemods_help'] = 'If enabled, front page activities will be shown on the navigation under site pages.';
 $string['navshowallcourses'] = 'Show all courses';
@@ -970,7 +969,7 @@ $string['sitepolicyguest_help'] = 'If you have a site policy that all guests mus
 $string['sitesectionhelp'] = 'If selected, a topic section will be displayed on the site\'s front page.';
 $string['slasharguments'] = 'Use slash arguments';
 $string['smartpix'] = 'Smart pix search';
-$string['soaprecommended'] = 'Installing the optional soap extension is useful for web services and some add-ons.';
+$string['soaprecommended'] = 'Installing the optional SOAP extension is useful for web services and some plugins.';
 $string['sort_fullname'] = 'Course full name';
 $string['sort_idnumber'] = 'Course ID number';
 $string['sort_shortname'] = 'Course short name';
index 6037469..6feeadf 100644 (file)
@@ -46,6 +46,7 @@ $string['cachedef_coursemodinfo'] = 'Accumulated information about modules and s
 $string['cachedef_databasemeta'] = 'Database meta information';
 $string['cachedef_eventinvalidation'] = 'Event invalidation';
 $string['cachedef_externalbadges'] = 'External badges for particular user';
+$string['cachedef_suspended_userids'] = 'List of suspended users per course';
 $string['cachedef_gradecondition'] = 'User grades cached for evaluating conditional availability';
 $string['cachedef_groupdata'] = 'Course group information';
 $string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
index e29095c..8f5e675 100644 (file)
@@ -46,13 +46,13 @@ $string['courseevents'] = 'Course events';
 $string['courses'] = 'Courses';
 $string['customexport'] = 'Custom range ({$a->timestart} - {$a->timeend})';
 $string['daily'] = 'Daily';
-$string['dayview'] = 'Day view';
+$string['dayviewfor'] = 'Day view for:';
 $string['dayviewtitle'] = 'Day view: {$a}';
 $string['daywithnoevents'] = 'There are no events this day.';
 $string['default'] = 'Default';
 $string['deleteevent'] = 'Delete event';
 $string['deleteevents'] = 'Delete events';
-$string['detailedmonthview'] = 'Detailed month view';
+$string['detailedmonthviewfor'] = 'Detailed month view for:';
 $string['detailedmonthviewtitle'] = 'Detailed month view: {$a}';
 $string['durationminutes'] = 'Duration in minutes';
 $string['durationnone'] = 'Without duration';
@@ -205,6 +205,7 @@ $string['typegroup'] = 'Group event';
 $string['typesite'] = 'Site event';
 $string['typeuser'] = 'User event';
 $string['upcomingevents'] = 'Upcoming events';
+$string['upcomingeventsfor'] = 'Upcoming events for:';
 $string['urlforical'] = 'URL for iCalendar export, for subscribing to calendar';
 $string['user'] = 'User';
 $string['userevent'] = 'User event';
index ffe93e4..9dfcc56 100644 (file)
@@ -141,8 +141,6 @@ $string['unblockcontact'] = 'Unblock contact';
 $string['unreadmessages'] = 'Unread messages ({$a})';
 $string['unreadnewmessages'] = 'New messages ({$a})';
 $string['unreadnewmessage'] = 'New message from {$a}';
-$string['unreadnewnotification'] = 'New notification';
-$string['unreadnewnotifications'] = 'New notifications ({$a})';
 $string['userisblockingyou'] = 'This user has blocked you from sending messages to them';
 $string['userisblockingyounoncontact'] = '{$a} only accepts messages from their contacts.';
 $string['userssearchresults'] = 'Users found: {$a}';
index 7faba07..bceee51 100644 (file)
@@ -5,4 +5,4 @@ $string['enableplagiarism'] ='Enable plagiarism plugins';
 $string['configenableplagiarism'] = 'This will allow administrators to configure plagiarism plugins (if installed)';
 $string['manageplagiarism'] = 'Manage plagiarism plugins';
 $string['nopluginsinstalled'] = 'No plagiarism plugins are installed.';
-$string['plagiarism'] = 'Plagiarism prevention';
+$string['plagiarism'] = 'Plagiarism';
index a51bef2..ae687b2 100644 (file)
@@ -35,8 +35,8 @@ $string['err_response_curl'] = 'Unable to fetch available updates data - unexpec
 $string['err_response_format_version'] = 'Unexpected version of the response format. Please try to re-check for available updates.';
 $string['err_response_http_code'] = 'Unable to fetch available updates data - unexpected HTTP response code.';
 $string['filterall'] = 'Show all';
-$string['filtercontribonly'] = 'Show add-ons only';
-$string['filtercontribonlyactive'] = 'Showing add-ons only';
+$string['filtercontribonly'] = 'Show additional plugins only';
+$string['filtercontribonlyactive'] = 'Showing additional plugins only';
 $string['filterupdatesonly'] = 'Show updateable only';
 $string['filterupdatesonlyactive'] = 'Showing updateable only';
 $string['moodleversion'] = 'Moodle {$a}';
@@ -54,12 +54,12 @@ You need to make the plugin folder and all its contents writable to be able to i
 $string['notwritable_link'] = 'admin/mdeploy/notwritable';
 $string['numtotal'] = 'Installed: {$a}';
 $string['numdisabled'] = 'Disabled: {$a}';
-$string['numextension'] = 'Add-ons: {$a}';
+$string['numextension'] = 'Additional: {$a}';
 $string['numupdatable'] = 'Updates available: {$a}';
 $string['otherplugin'] = '{$a->component}';
 $string['otherpluginversion'] = '{$a->component} ({$a->version})';
 $string['showall'] = 'Reload and show all plugins';
-$string['pluginchecknotice'] = 'This page displays plugins that may require your attention during the upgrade. Highlighted items include new plugins that are about to be installed, updated plugins that are about to be upgraded and any missing plugins. Add-ons are highlighted if there is an available update for them. It is recommended that you check whether there are more recent versions of add-ons available and update their source code before continuing with this Moodle upgrade.';
+$string['pluginchecknotice'] = 'This page displays plugins that may require your attention during the upgrade. Highlighted items include new plugins that are about to be installed, updated plugins that are about to be upgraded and any missing plugins. Additional plugins are highlighted if there is an available update for them. It is recommended that you check whether there are more recent versions of plugins available and update their source code before continuing with this Moodle upgrade.';
 $string['plugindisable'] = 'Disable';
 $string['plugindisabled'] = 'Disabled';
 $string['pluginenable'] = 'Enable';
@@ -73,7 +73,7 @@ $string['somehighlighted'] = 'Number of plugins requiring your attention: {$a}';
 $string['somehighlightedinfo'] = 'Display the full list of installed plugins';
 $string['somehighlightedonly'] = 'Display only plugins requiring your attention';
 $string['source'] = 'Source';
-$string['sourceext'] = 'Add-on';
+$string['sourceext'] = 'Additional';
 $string['sourcestd'] = 'Standard';
 $string['status'] = 'Status';
 $string['status_delete'] = 'To be deleted';
@@ -122,8 +122,8 @@ $string['type_mnetservice'] = 'MNet service';
 $string['type_mnetservice_plural'] = 'MNet services';
 $string['type_mod'] = 'Activity module';
 $string['type_mod_plural'] = 'Activity modules';
-$string['type_plagiarism'] = 'Plagiarism prevention plugin';
-$string['type_plagiarism_plural'] = 'Plagiarism prevention plugins';
+$string['type_plagiarism'] = 'Plagiarism plugin';
+$string['type_plagiarism_plural'] = 'Plagiarism plugins';
 $string['type_portfolio'] = 'Portfolio';
 $string['type_portfolio_plural'] = 'Portfolios';
 $string['type_profilefield'] = 'Profile field type';
index cb99238..4b0b363 100644 (file)
@@ -45,8 +45,8 @@ $string['areauserbackup'] = 'User backup';
 $string['areauserpersonal'] = 'Private files';
 $string['areauserprofile'] = 'Profile';
 $string['attachedfiles'] = 'Attached files';
-$string['attachment'] = 'Attachment:';
-$string['author'] = 'Author:';
+$string['attachment'] = 'Attachment';
+$string['author'] = 'Author';
 $string['back'] = '&laquo; Back';
 $string['backtodraftfiles'] = '&laquo; Back to draft files manager';
 $string['cachecleared'] = 'Cached files are removed';
@@ -82,11 +82,11 @@ $string['createinstance'] = 'Create a repository instance';
 $string['createrepository'] = 'Create a repository instance';
 $string['createxxinstance'] = 'Create "{$a}" instance';
 $string['date'] = 'Date';
-$string['datecreated'] = 'Created:';
+$string['datecreated'] = 'Created';
 $string['deleted'] = 'Repository deleted';
 $string['deleterepository'] = 'Delete this repository';
 $string['detailview'] = 'View details';
-$string['dimensions'] = 'Dimensions:';
+$string['dimensions'] = 'Dimensions';
 $string['disabled'] = 'Disabled';
 $string['displaydetails'] = 'Display folder with file details';
 $string['displayicons'] = 'Display folder with file icons';
@@ -129,7 +129,7 @@ $string['getfiletimeout'] = 'Get file timeout';
 $string['hidden'] = 'Hidden';
 $string['help'] = 'Help';
 $string['choosealink'] = 'Choose a link...';
-$string['chooselicense'] = 'Choose license:';
+$string['chooselicense'] = 'Choose license';
 $string['iconview'] = 'View as icons';
 $string['imagesize'] = '{$a->width} x {$a->height} px';
 $string['instance'] = 'instance';
@@ -145,8 +145,8 @@ $string['invalidrepositoryid'] = 'Invalid repository ID';
 $string['invalidparams'] = 'Invalid parameters';
 $string['isactive'] = 'Active?';
 $string['keyword'] = 'Keyword';
-$string['lastmodified'] = 'Last modified:';
-$string['license'] = 'License:';
+$string['lastmodified'] = 'Last modified';
+$string['license'] = 'License';
 $string['linkexternal'] = 'Link external';
 $string['listview'] = 'View as list';
 $string['loading'] = 'Loading...';
@@ -160,9 +160,9 @@ $string['manage'] = 'Manage repositories';
 $string['manageurl'] = 'Manage';
 $string['manageuserrepository'] = 'Manage individual repository';
 $string['moving'] = 'Moving';
-$string['name'] = 'Name:';
+$string['name'] = 'Name';
 $string['newfolder'] = 'New folder';
-$string['newfoldername'] = 'New folder name:';
+$string['newfoldername'] = 'New folder name';
 $string['noenter'] = 'Nothing entered';
 $string['nofilesattached'] = 'No files attached';
 $string['nofilesavailable'] = 'No files available';
@@ -180,7 +180,7 @@ $string['operation'] = 'Operation';
 $string['on'] = 'Enabled and visible';
 $string['overwrite'] = 'Overwrite';
 $string['overwriteall'] = 'Overwrite all';
-$string['path'] = 'Path:';
+$string['path'] = 'Path';
 $string['personalrepositories'] = 'Available repository instances';
 $string['plugin'] = 'Repository plug-ins';
 $string['pluginerror'] = 'Errors in repository plugin.';
@@ -202,7 +202,7 @@ $string['repositorycourse'] = 'Course repositories';
 $string['repositoryicon'] = 'Repository icon';
 $string['repositoryerror'] = 'Remote repository returned error: {$a}';
 $string['save'] = 'Save';
-$string['saveas'] = 'Save as:';
+$string['saveas'] = 'Save as';
 $string['saved'] = 'Saved';
 $string['saving'] = 'Saving';
 $string['automatedbackup'] = 'Automated backups';
@@ -215,7 +215,7 @@ $string['setupdefaultplugins'] = 'Setting up default repository plugins';
 $string['setmainfile'] = 'Set main file';
 $string['setmainfile_help'] = 'If there are multiple files in the folder, the main file is the one that appears on the view page. Other files such as images or videos may be embedded in it. In filemanager the main file is indicated with a title in bold.';
 $string['siteinstances'] = 'Repositories instances of the site';
-$string['size'] = 'Size:';
+$string['size'] = 'Size';
 $string['submit'] = 'Submit';
 $string['sync'] = 'Sync';
 $string['syncfiletimeout'] = 'Sync file timeout';
index 7e8beca..bd58f9a 100644 (file)
@@ -7460,11 +7460,21 @@ function extract_suspended_users($context, &$users, $ignoreusers=array()) {
  * or enrolment has expired or not started.
  *
  * @param context $context context in which user enrolment is checked.
+ * @param bool $usecache Enable or disable (default) the request cache
  * @return array list of suspended user id's.
  */
-function get_suspended_userids($context){
+function get_suspended_userids(context $context, $usecache = false) {
     global $DB;
 
+    // Check the cache first for performance reasons if enabled.
+    if ($usecache) {
+        $cache = cache::make('core', 'suspended_userids');
+        $susers = $cache->get($context->id);
+        if ($susers !== false) {
+            return $susers;
+        }
+    }
+
     // Get all enrolled users.
     list($sql, $params) = get_enrolled_sql($context);
     $users = $DB->get_records_sql($sql, $params);
@@ -7481,5 +7491,12 @@ function get_suspended_userids($context){
             }
         }
     }
+
+    // Cache results for the remainder of this request.
+    if ($usecache) {
+        $cache->set($context->id, $susers);
+    }
+
+    // Return.
     return $susers;
 }
index cc1d1a2..8c8d20a 100644 (file)
@@ -68,7 +68,9 @@ class user_login_failed extends base {
      * @return string
      */
     public function get_description() {
-        return "Login failed for the username '{$this->other['username']}' for the reason with id '{$this->other['reason']}'.";
+        // Note that username could be any random user input.
+        $username = s($this->other['username']);
+        return "Login failed for the username '{$username}' for the reason with id '{$this->other['reason']}'.";
     }
 
     /**
index 8e730cb..b99b5d6 100644 (file)
@@ -378,6 +378,18 @@ class core_text {
         }
     }
 
+    /**
+     * Reverse UTF-8 multibytes character sets (used for RTL languages)
+     * (We only do this because there is no mb_strrev or iconv_strrev)
+     *
+     * @param string $str the multibyte string to reverse
+     * @return string the reversed multi byte string
+     */
+    public static function strrev($str) {
+        preg_match_all('/./us', $str, $ar);
+        return join('', array_reverse($ar[0]));
+    }
+
     /**
      * Try to convert upper unicode characters to plain ascii,
      * the returned string may contain unconverted unicode characters.
index c2f99cf..e71dc57 100644 (file)
@@ -221,5 +221,13 @@ $definitions = array(
         'mode' => cache_store::MODE_SESSION,
         'simplekeys' => true,
         'simpledata' => true
-    )
+    ),
+
+    // Caches suspended userids by course.
+    // The key is the courseid, the value is an array of user ids.
+    'suspended_userids' => array(
+        'mode' => cache_store::MODE_REQUEST,
+        'simplekeys' => true,
+        'simpledata' => true,
+    ),
 );
index acb4ade..fc405f7 100644 (file)
@@ -1583,6 +1583,8 @@ function xmldb_main_upgrade($oldversion) {
     }
 
     if ($oldversion < 2013021100.01) {
+        // Make sure there are no bogus nulls in old MySQL tables.
+        $DB->set_field_select('user', 'password', '', "password IS NULL");
 
         // Changing precision of field password on table user to (255).
         $table = new xmldb_table('user');
index ccd2d81..4faa2b2 100644 (file)
@@ -218,10 +218,10 @@ class mssql_sql_generator extends sql_generator {
                 if (empty($xmldb_length)) {
                     $xmldb_length='255';
                 }
-                $dbtype .= '(' . $xmldb_length . ')';
+                $dbtype .= '(' . $xmldb_length . ') COLLATE database_default';
                 break;
             case XMLDB_TYPE_TEXT:
-                $dbtype = 'NVARCHAR(MAX)';
+                $dbtype = 'NVARCHAR(MAX) COLLATE database_default';
                 break;
             case XMLDB_TYPE_BINARY:
                 $dbtype = 'VARBINARY(MAX)';
index 6fed8e1..9dac524 100644 (file)
@@ -1579,6 +1579,35 @@ class core_ddl_testcase extends database_driver_testcase {
         $dbman->drop_temp_table($table1);
         $this->assertFalse($dbman->table_exists('test_table1'));
         $this->assertDebuggingCalled();
+
+        // Try join with normal tables - MS SQL may use incompatible collation.
+        $table1 = new xmldb_table('test_table');
+        $table1->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table1->add_field('name', XMLDB_TYPE_CHAR, 255, null, XMLDB_NOTNULL, null);
+        $table1->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $dbman->create_table($table1);
+
+        $table2 = new xmldb_table('test_temp');
+        $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table2->add_field('name', XMLDB_TYPE_CHAR, 255, null, XMLDB_NOTNULL, null);
+        $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $dbman->create_temp_table($table2);
+
+        $record = array('name' => 'a');
+        $DB->insert_record('test_table', $record);
+        $DB->insert_record('test_temp', $record);
+
+        $record = array('name' => 'b');
+        $DB->insert_record('test_table', $record);
+
+        $record = array('name' => 'c');
+        $DB->insert_record('test_temp', $record);
+
+        $sql = "SELECT *
+                  FROM {test_table} n
+                  JOIN {test_temp} t ON t.name = n.name";
+        $records = $DB->get_records_sql($sql);
+        $this->assertCount(1, $records);
     }
 
     public function test_concurrent_temp_tables() {
index c3740bf..848c5f0 100644 (file)
Binary files a/lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button-debug.js and b/lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button-debug.js differ
index 5098939..f67c8b6 100644 (file)
Binary files a/lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button-min.js and b/lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button-min.js differ
index ee97f23..d4c0e9f 100644 (file)
Binary files a/lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button.js and b/lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button.js differ
index 0576996..c00b9a8 100644 (file)
@@ -53,7 +53,8 @@ Y.namespace('M.atto_collapse').Button = Y.Base.create('button', Y.M.editor_atto.
         }
 
         var button = this.addButton({
-            icon: M.util.image_url('icon', PLUGINNAME),
+            icon: 'icon',
+            iconComponent: PLUGINNAME,
             callback: this._toggle
         });
 
index 6ba6498..b65a36e 100644 (file)
@@ -1406,6 +1406,9 @@ abstract class enrol_plugin {
         $DB->update_record('user_enrolments', $ue);
         context_course::instance($instance->courseid)->mark_dirty(); // reset enrol caches
 
+        // Invalidate core_access cache for get_suspended_userids.
+        cache_helper::invalidate_by_definition('core', 'suspended_userids', array(), array($instance->courseid));
+
         // Trigger event.
         $event = \core\event\user_enrolment_updated::create(
                 array(
index 87d888d..a463d23 100644 (file)
@@ -97,6 +97,25 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
         editors_head_setup();
     }
 
+    /**
+     * Called by HTML_QuickForm whenever form event is made on this element
+     *
+     * @param string $event Name of event
+     * @param mixed $arg event arguments
+     * @param object $caller calling object
+     * @return bool
+     */
+    function onQuickFormEvent($event, $arg, &$caller)
+    {
+        switch ($event) {
+            case 'createElement':
+                $caller->setType($arg[0] . '[format]', PARAM_ALPHANUM);
+                $caller->setType($arg[0] . '[itemid]', PARAM_INT);
+                break;
+        }
+        return parent::onQuickFormEvent($event, $arg, $caller);
+    }
+
     /**
      * Sets name of editor
      *
@@ -388,7 +407,8 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
         if (!during_initial_install() && empty($CFG->adminsetuppending)) {
             // 0 means no files, -1 unlimited
             if ($maxfiles != 0 ) {
-                $str .= '<input type="hidden" name="'.$elname.'[itemid]" value="'.$draftitemid.'" />';
+                $str .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $elname.'[itemid]',
+                        'value' => $draftitemid));
 
                 // used by non js editor only
                 $editorurl = new moodle_url("$CFG->wwwroot/repository/draftfiles_manager.php", array(
index 77ee169..8140fec 100644 (file)
@@ -79,6 +79,24 @@ class MoodleQuickForm_filemanager extends HTML_QuickForm_element {
         parent::HTML_QuickForm_element($elementName, $elementLabel, $attributes);
     }
 
+    /**
+     * Called by HTML_QuickForm whenever form event is made on this element
+     *
+     * @param string $event Name of event
+     * @param mixed $arg event arguments
+     * @param object $caller calling object
+     * @return bool
+     */
+    function onQuickFormEvent($event, $arg, &$caller)
+    {
+        switch ($event) {
+            case 'createElement':
+                $caller->setType($arg[0], PARAM_INT);
+                break;
+        }
+        return parent::onQuickFormEvent($event, $arg, $caller);
+    }
+
     /**
      * Sets name of filemanager
      *
@@ -263,9 +281,9 @@ class MoodleQuickForm_filemanager extends HTML_QuickForm_element {
         $output = $PAGE->get_renderer('core', 'files');
         $html .= $output->render($fm);
 
-        $html .= '<input value="'.$draftitemid.'" name="'.$elname.'" type="hidden" />';
+        $html .= html_writer::empty_tag('input', array('value' => $draftitemid, 'name' => $elname, 'type' => 'hidden'));
         // label element needs 'for' attribute work
-        $html .= '<input value="" id="id_'.$elname.'" type="hidden" />';
+        $html .= html_writer::empty_tag('input', array('value' => '', 'id' => 'id_'.$elname, 'type' => 'hidden'));
 
         return $html;
     }
index 67b6da4..722f768 100644 (file)
@@ -36,6 +36,7 @@ class core_grade_scale_testcase extends grade_base_testcase {
         $this->sub_test_grade_scale_fetch();
         $this->sub_test_scale_load_items();
         $this->sub_test_scale_compact_items();
+        $this->sub_test_scale_one_item();
     }
 
     protected function sub_test_scale_construct() {
@@ -127,4 +128,44 @@ class core_grade_scale_testcase extends grade_base_testcase {
         // The original string and the new string may have differences in whitespace around the delimiter, and that's OK.
         $this->assertEquals(preg_replace('/\s*,\s*/', ',', $this->scale[0]->scale), $scale->scale);
     }
+
+    protected function sub_test_scale_one_item() {
+        $params = new stdClass();
+        $params->name         = 'unittestscale1i';
+        $params->courseid     = $this->course->id;
+        $params->userid       = $this->userid;
+        $params->scale        = 'Like';
+        $params->description  = 'This scale is used to like something.';
+        $params->timemodified = time();
+
+        $scale = new grade_scale($params, false);
+        $scale->load_items();
+
+        $this->assertCount(1, $scale->scale_items);
+        $this->assertSame(array('Like'), $scale->scale_items);
+        $this->assertSame('Like', $scale->compact_items());
+
+        $scale->insert();
+
+        // Manual grade item with 1 item scale.
+        $grade_item = new stdClass();
+        $grade_item->courseid = $this->course->id;
+        $grade_item->categoryid = $this->grade_categories[0]->id;
+        $grade_item->itemname = 'manual grade_item scale_1';
+        $grade_item->itemtype = 'manual';
+        $grade_item->itemnumber = 0;
+        $grade_item->needsupdate = false;
+        $grade_item->gradetype = GRADE_TYPE_SCALE;
+        $grade_item->scaleid = $scale->id;
+        $grade_item->iteminfo = 'Manual grade item used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+
+        $grade_item = new grade_item($grade_item);
+        $grade_item->insert();
+
+        $this->assertNotEmpty($grade_item->id);
+        $this->assertEquals(1, $grade_item->grademin);
+        $this->assertEquals(1, $grade_item->grademax);
+    }
 }
index 0fa5eef..685835c 100644 (file)
@@ -1193,7 +1193,8 @@ class graph {
     function print_TTF($message) {
       $points    = $message['points'];
       $angle     = $message['angle'];
-      $text      = $message['text'];
+      // We have to manually reverse the label, since php GD cannot handle RTL characters properly in UTF8 strings.
+      $text      = (right_to_left()) ? core_text::strrev($message['text']) : $message['text'];
       $colour    = $this->colour[$message['colour']];
       $font      = $this->parameter['path_to_fonts'].$message['font'];
 
index eef42c9..17c85d9 100644 (file)
@@ -8778,43 +8778,7 @@ function message_popup_window() {
     // If we have new messages to notify the user about.
     if (!empty($messageusers)) {
 
-        $strmessages = '';
-        if (count($messageusers)>1) {
-            $strmessages = get_string('unreadnewmessages', 'message', count($messageusers));
-        } else {
-            $messageusers = reset($messageusers);
-
-            // Show who the message is from if its not a notification.
-            if (!$messageusers->notification) {
-                $strmessages = get_string('unreadnewmessage', 'message', fullname($messageusers) );
-            }
-
-            // Try to display the small version of the message.
-            $smallmessage = null;
-            if (!empty($messageusers->smallmessage)) {
-                // Display the first 200 chars of the message in the popup.
-                $smallmessage = null;
-                if (core_text::strlen($messageusers->smallmessage) > 200) {
-                    $smallmessage = core_text::substr($messageusers->smallmessage, 0, 200).'...';
-                } else {
-                    $smallmessage = $messageusers->smallmessage;
-                }
-
-                // Prevent html symbols being displayed.
-                if ($messageusers->fullmessageformat == FORMAT_HTML) {
-                    $smallmessage = html_to_text($smallmessage);
-                } else {
-                    $smallmessage = s($smallmessage);
-                }
-            } else if ($messageusers->notification) {
-                // Its a notification with no smallmessage so just say they have a notification.
-                $smallmessage = get_string('unreadnewnotification', 'message');
-            }
-            if (!empty($smallmessage)) {
-                $strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
-            }
-        }
-
+        $strmessages = get_string('unreadnewmessages', 'message', count($messageusers));
         $strgomessage = get_string('gotomessages', 'message');
         $strstaymessage = get_string('ignore', 'admin');
 
index 8b9ef8f..9da96cb 100644 (file)
@@ -705,8 +705,8 @@ class core_renderer extends renderer_base {
                         $a->attempts = $count;
                         $loggedinas .= get_string('failedloginattempts', '', $a);
                         if (file_exists("$CFG->dirroot/report/log/index.php") and has_capability('report/log:view', context_system::instance())) {
-                            $loggedinas .= html_writer::link(new moodle_url('/report/log/index.php', array('chooselog' => 1,
-                                    'id' => 0 , 'modid' => 'site_errors')), '(' . get_string('logs') . ')');
+                            $loggedinas .= ' ('.html_writer::link(new moodle_url('/report/log/index.php', array('chooselog' => 1,
+                                    'id' => 0 , 'modid' => 'site_errors')), get_string('logs')).')';
                         }
                         $loggedinas .= '</div>';
                     }
index 393cc30..7df1a89 100644 (file)
@@ -461,8 +461,13 @@ class phpunit_util extends testing_util {
                 $suites .= $suite;
             }
         }
+        // Start a sequence between 100000 and 199000 to ensure each call to init produces
+        // different ids in the database.  This reduces the risk that hard coded values will
+        // end up being placed in phpunit or behat test code.
+        $sequencestart = 100000 + mt_rand(0, 99) * 1000;
 
         $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
+        $data = preg_replace('|<!--@PHPUNIT_SEQUENCE_START@-->|s', $sequencestart, $data, 1);
 
         $result = false;
         if (is_writable($CFG->dirroot)) {
index 56bf482..1cc7e52 100644 (file)
@@ -458,7 +458,11 @@ abstract class testing_util {
         // To reduce the chance of the coding error, we start sequences at different values where possible.
         // In a attempt to avoid tables with existing id's we start at a high number.
         // Reset the value each time all database sequences are reset.
-        self::$sequencenextstartingid = 100000;
+        if (defined('PHPUNIT_SEQUENCE_START')) {
+            self::$sequencenextstartingid = PHPUNIT_SEQUENCE_START;
+        } else {
+            self::$sequencenextstartingid = 100000;
+        }
 
         $dbfamily = $DB->get_dbfamily();
         if ($dbfamily === 'postgres') {
index 460c5c9..46eb90d 100644 (file)
@@ -249,6 +249,26 @@ class core_text_testcase extends advanced_testcase {
         $this->assertSame($str, core_text::strtoupper($str, 'GB18030'));
     }
 
+    /**
+     * Test the strrev method.
+     */
+    public function test_strrev() {
+        $strings = array(
+            "Žluťoučký koníček" => "kečínok ýkčuoťulŽ",
+            'ŽLUŤOUČKÝ KONÍČEK' => "KEČÍNOK ÝKČUOŤULŽ",
+            '言語設定' => '定設語言',
+            '简体中文' => '文中体简',
+            "Der eine stößt den Speer zum Mann" => "nnaM muz reepS ned tßöts enie reD"
+        );
+        foreach ($strings as $before => $after) {
+            // Make sure we can reverse it both ways and that it comes out the same.
+            $this->assertSame($after, core_text::strrev($before));
+            $this->assertSame($before, core_text::strrev($after));
+            // Reverse it twice to be doubly sure.
+            $this->assertSame($after, core_text::strrev(core_text::strrev($after)));
+        }
+    }
+
     /**
      * Tests the static strpos method.
      */
index cc265b3..9c79b77 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-debug.js and b/lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-debug.js differ
index dbe7855..22a7bcc 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-min.js and b/lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-min.js differ
index cc265b3..9c79b77 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception.js and b/lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception.js differ
index c8da6ee..44d860f 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js differ
index 65762d5..171c5bc 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js differ
index 8387c3f..ea69475 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js differ
index 968109c..3f813af 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-debug.js and b/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-debug.js differ
index 2007b8f..4fb70e3 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-min.js and b/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-min.js differ
index 968109c..3f813af 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception.js and b/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception.js differ
index 74eb5dc..a28774a 100644 (file)
@@ -30,15 +30,16 @@ Y.extend(AJAXEXCEPTION, M.core.notification.info, {
             delay = this.get('hideTimeoutDelay');
         this.get(BASE).addClass('moodle-dialogue-exception');
         this.setStdModContent(Y.WidgetStdMod.HEADER,
-                '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">' + config.name + '</h1>', Y.WidgetStdMod.REPLACE);
+                '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">' + Y.Escape.html(config.name) + '</h1>',
+                Y.WidgetStdMod.REPLACE);
         content = Y.Node.create('<div class="moodle-ajaxexception"></div>')
-                .append(Y.Node.create('<div class="moodle-exception-message">'+this.get('error')+'</div>'))
+                .append(Y.Node.create('<div class="moodle-exception-message">'+Y.Escape.html(this.get('error'))+'</div>'))
                 .append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>URL:</label> ' +
                         this.get('reproductionlink')+'</div>'))
                 .append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>Debug info:</label> ' +
-                        this.get('debuginfo')+'</div>'))
+                        Y.Escape.html(this.get('debuginfo'))+'</div>'))
                 .append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>Stack trace:</label> <pre>' +
-                        this.get('stacktrace')+'</pre></div>'));
+                        Y.Escape.html(this.get('stacktrace'))+'</pre></div>'));
         if (M.cfg.developerdebug) {
             content.all('.moodle-exception-param').removeClass('hidden');
         }
@@ -111,6 +112,7 @@ Y.extend(AJAXEXCEPTION, M.core.notification.info, {
         reproductionlink : {
             setter : function(link) {
                 if (link !== null) {
+                    link = Y.Escape.html(link);
                     link = '<a href="'+link+'">'+link.replace(M.cfg.wwwroot, '')+'</a>';
                 }
                 return link;
index 78186ea..72f71e2 100644 (file)
@@ -46,13 +46,14 @@ Y.extend(EXCEPTION, M.core.notification.info, {
             delay = this.get('hideTimeoutDelay');
         this.get(BASE).addClass('moodle-dialogue-exception');
         this.setStdModContent(Y.WidgetStdMod.HEADER,
-                '<h1 id="moodle-dialogue-'+config.COUNT+'-header-text">' + config.name + '</h1>', Y.WidgetStdMod.REPLACE);
+                '<h1 id="moodle-dialogue-'+config.COUNT+'-header-text">' + Y.Escape.html(config.name) + '</h1>',
+                Y.WidgetStdMod.REPLACE);
         content = Y.Node.create('<div class="moodle-exception"></div>')
-                .append(Y.Node.create('<div class="moodle-exception-message">'+this.get('message')+'</div>'))
+                .append(Y.Node.create('<div class="moodle-exception-message">'+Y.Escape.html(this.get('message'))+'</div>'))
                 .append(Y.Node.create('<div class="moodle-exception-param hidden param-filename"><label>File:</label> ' +
-                        this.get('fileName')+'</div>'))
+                        Y.Escape.html(this.get('fileName'))+'</div>'))
                 .append(Y.Node.create('<div class="moodle-exception-param hidden param-linenumber"><label>Line:</label> ' +
-                        this.get('lineNumber')+'</div>'))
+                        Y.Escape.html(this.get('lineNumber'))+'</div>'))
                 .append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>Stack trace:</label> <pre>' +
                         this.get('stack')+'</pre></div>'));
         if (M.cfg.developerdebug) {
@@ -133,7 +134,7 @@ Y.extend(EXCEPTION, M.core.notification.info, {
          */
         stack : {
             setter : function(str) {
-                var lines = str.split("\n"),
+                var lines = Y.Escape.html(str).split("\n"),
                     pattern = new RegExp('^(.+)@('+M.cfg.wwwroot+')?(.{0,75}).*:(\\d+)$'),
                     i;
                 for (i in lines) {
index db9bd7a..2ba870f 100644 (file)
@@ -13,6 +13,7 @@
         "base",
         "node",
         "panel",
+        "escape",
         "event-key",
         "dd-plugin",
         "moodle-core-widget-focusafterclose",
index d3771cc..53413e9 100644 (file)
@@ -132,9 +132,6 @@ class assign {
     /** @var bool whether to exclude users with inactive enrolment */
     private $showonlyactiveenrol = null;
 
-    /** @var array list of suspended user IDs in form of ([id1] => id1) */
-    public $susers = null;
-
     /** @var array cached list of participants for this assignment. The cache key will be group, showactive and the context id */
     private $participants = array();
 
@@ -6965,10 +6962,7 @@ class assign {
      * @return bool true is user is active in course.
      */
     public function is_active_user($userid) {
-        if (is_null($this->susers) && !is_null($this->context)) {
-            $this->susers = get_suspended_userids($this->context);
-        }
-        return !in_array($userid, $this->susers);
+        return !in_array($userid, get_suspended_userids($this->context, true));
     }
 
     /**
index e5af00f..19d53ac 100644 (file)
@@ -77,7 +77,7 @@ class subscription_created extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/mod/forum/view.php', array('f' => $this->other['forumid']));
+        return new \moodle_url('/mod/forum/subscribers.php', array('id' => $this->other['forumid']));
     }
 
     /**
index 738a5c0..f011059 100644 (file)
@@ -77,7 +77,7 @@ class subscription_deleted extends \core\event\base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new \moodle_url('/mod/forum/view.php', array('f' => $this->other['forumid']));
+        return new \moodle_url('/mod/forum/subscribers.php', array('id' => $this->other['forumid']));
     }
 
     /**
index 4c284da..0c017bc 100644 (file)
@@ -662,7 +662,7 @@ class mod_forum_events_testcase extends advanced_testcase {
         $this->assertEquals($context, $event->get_context());
         $expected = array($course->id, 'forum', 'subscribe', "view.php?f={$forum->id}", $forum->id, $forum->cmid);
         $this->assertEventLegacyLogData($expected, $event);
-        $url = new \moodle_url('/mod/forum/view.php', array('f' => $forum->id));
+        $url = new \moodle_url('/mod/forum/subscribers.php', array('id' => $forum->id));
         $this->assertEquals($url, $event->get_url());
         $this->assertEventContextNotUsed($event);
 
@@ -759,7 +759,7 @@ class mod_forum_events_testcase extends advanced_testcase {
         $this->assertEquals($context, $event->get_context());
         $expected = array($course->id, 'forum', 'unsubscribe', "view.php?f={$forum->id}", $forum->id, $forum->cmid);
         $this->assertEventLegacyLogData($expected, $event);
-        $url = new \moodle_url('/mod/forum/view.php', array('f' => $forum->id));
+        $url = new \moodle_url('/mod/forum/subscribers.php', array('id' => $forum->id));
         $this->assertEquals($url, $event->get_url());
         $this->assertEventContextNotUsed($event);
 
@@ -2693,4 +2693,73 @@ class mod_forum_events_testcase extends advanced_testcase {
         $this->assertEquals($context, $event->get_context());
 
     }
+
+    /**
+     * Test mod_forum_observer methods.
+     */
+    public function test_observers() {
+        global $DB, $CFG;
+
+        require_once($CFG->dirroot . '/mod/forum/lib.php');
+
+        $forumgen = $this->getDataGenerator()->get_plugin_generator('mod_forum');
+
+        $course = $this->getDataGenerator()->create_course();
+        $trackedrecord = array('course' => $course->id, 'type' => 'general', 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
+        $untrackedrecord = array('course' => $course->id, 'type' => 'general');
+        $trackedforum = $this->getDataGenerator()->create_module('forum', $trackedrecord);
+        $untrackedforum = $this->getDataGenerator()->create_module('forum', $untrackedrecord);
+
+        // Used functions don't require these settings; adding
+        // them just in case there are APIs changes in future.
+        $user = $this->getDataGenerator()->create_user(array(
+            'maildigest' => 1,
+            'trackforums' => 1
+        ));
+
+        $manplugin = enrol_get_plugin('manual');
+        $manualenrol = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
+        $student = $DB->get_record('role', array('shortname' => 'student'));
+
+        // The role_assign observer does it's job adding the forum_subscriptions record.
+        $manplugin->enrol_user($manualenrol, $user->id, $student->id);
+
+        // They are not required, but in a real environment they are supposed to be required;
+        // adding them just in case there are APIs changes in future.
+        set_config('forum_trackingtype', 1);
+        set_config('forum_trackreadposts', 1);
+
+        $record = array();
+        $record['course'] = $course->id;
+        $record['forum'] = $trackedforum->id;
+        $record['userid'] = $user->id;
+        $discussion = $forumgen->create_discussion($record);
+
+        $record = array();
+        $record['discussion'] = $discussion->id;
+        $record['userid'] = $user->id;
+        $post = $forumgen->create_post($record);
+
+        forum_tp_add_read_record($user->id, $post->id);
+        forum_set_user_maildigest($trackedforum, 2, $user);
+        forum_tp_stop_tracking($untrackedforum->id, $user->id);
+
+        $this->assertEquals(1, $DB->count_records('forum_subscriptions'));
+        $this->assertEquals(1, $DB->count_records('forum_digests'));
+        $this->assertEquals(1, $DB->count_records('forum_track_prefs'));
+        $this->assertEquals(1, $DB->count_records('forum_read'));
+
+        // The course_module_created observer does it's job adding a subscription.
+        $forumrecord = array('course' => $course->id, 'type' => 'general', 'forcesubscribe' => FORUM_INITIALSUBSCRIBE);
+        $extraforum = $this->getDataGenerator()->create_module('forum', $forumrecord);
+        $this->assertEquals(2, $DB->count_records('forum_subscriptions'));
+
+        $manplugin->unenrol_user($manualenrol, $user->id);
+
+        $this->assertEquals(0, $DB->count_records('forum_digests'));
+        $this->assertEquals(0, $DB->count_records('forum_subscriptions'));
+        $this->assertEquals(0, $DB->count_records('forum_track_prefs'));
+        $this->assertEquals(0, $DB->count_records('forum_read'));
+    }
+
 }
index 9a02dc1..3ebd106 100644 (file)
@@ -104,9 +104,11 @@ function imscp_parse_structure($imscp, $context) {
  */
 function imscp_parse_manifestfile($manifestfilecontents, $imscp, $context) {
     $doc = new DOMDocument();
+    $oldentities = libxml_disable_entity_loader(true);
     if (!$doc->loadXML($manifestfilecontents, LIBXML_NONET)) {
         return null;
     }
+    libxml_disable_entity_loader($oldentities);
 
     // we put this fake URL as base in order to detect path changes caused by xml:base attributes
     $doc->documentURI = 'http://grrr/';
@@ -204,10 +206,14 @@ function imscp_recursive_href($manifestfilename, $imscp, $context) {
     if (!$manifestfile = $fs->get_file($context->id, 'mod_imscp', 'content', $imscp->revision, $dirname, $filename)) {
         return null;
     }
+
     $doc = new DOMDocument();
+    $oldentities = libxml_disable_entity_loader(true);
     if (!$doc->loadXML($manifestfile->get_content(), LIBXML_NONET)) {
         return null;
     }
+    libxml_disable_entity_loader($oldentities);
+
     $xmlresources = $doc->getElementsByTagName('resource');
     foreach ($xmlresources as $res) {
         if (!$href = $res->attributes->getNamedItem('href')) {
index ef9806c..cdc0bff 100644 (file)
@@ -22,7 +22,7 @@
         <FIELD NAME="instructorchoiceallowsetting" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false" COMMENT="Allow a tool to store a setting"/>
         <FIELD NAME="instructorcustomparameters" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Additional custom parameters provided by the instructor"/>
         <FIELD NAME="instructorchoiceacceptgrades" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false" COMMENT="Accept grades from tool"/>
-        <FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="100" SEQUENCE="false" DECIMALS="5" COMMENT="Grade scale"/>
+        <FIELD NAME="grade" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="100" SEQUENCE="false" COMMENT="Grade scale"/>
         <FIELD NAME="launchcontainer" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Launch external tool in a pop-up"/>
         <FIELD NAME="resourcekey" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
         <FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
index 3f18562..7ecc04e 100644 (file)
@@ -85,6 +85,20 @@ function xmldb_lti_upgrade($oldversion) {
     // Moodle v2.7.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2014060201) {
+
+        // Changing type of field grade on table lti to int.
+        $table = new xmldb_table('lti');
+        $field = new xmldb_field('grade', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '100',
+                'instructorchoiceacceptgrades');
+
+        // Launch change of type for field grade.
+        $dbman->change_field_type($table, $field);
+
+        // Lti savepoint reached.
+        upgrade_mod_savepoint(true, 2014060201, 'lti');
+    }
+
     return true;
 }
 
index 4c231fe..0446d40 100644 (file)
@@ -31,6 +31,7 @@ $courseid = required_param('course', PARAM_INT);
 $instanceid = optional_param('instanceid', 0, PARAM_INT);
 
 $errormsg = optional_param('lti_errormsg', '', PARAM_RAW);
+$msg = optional_param('lti_msg', '', PARAM_RAW);
 $unsigned = optional_param('unsigned', '0', PARAM_INT);
 
 $launchcontainer = optional_param('launch_container', LTI_LAUNCH_CONTAINER_WINDOW, PARAM_INT);
@@ -47,7 +48,7 @@ if (!empty($instanceid)) {
 
 require_login($course);
 
-if (!empty($errormsg)) {
+if (!empty($errormsg) || !empty($msg)) {
     $url = new moodle_url('/mod/lti/return.php', array('course' => $courseid));
     $PAGE->set_url($url);
 
@@ -66,7 +67,9 @@ if (!empty($errormsg)) {
     if (!empty($lti) and !empty($context)) {
         echo $OUTPUT->heading(format_string($lti->name, true, array('context' => $context)));
     }
+}
 
+if (!empty($errormsg)) {
     echo get_string('lti_launch_error', 'lti');
 
     echo htmlspecialchars($errormsg);
@@ -90,6 +93,12 @@ if (!empty($errormsg)) {
     }
 
     echo $OUTPUT->footer();
+} else if (!empty($msg)) {
+
+    echo htmlspecialchars($msg);
+
+    echo $OUTPUT->footer();
+
 } else {
     $courseurl = new moodle_url('/course/view.php', array('id' => $courseid));
     $url = $courseurl->out();
index 7cfb425..7202289 100644 (file)
@@ -58,7 +58,14 @@ if ($sharedsecret === false) {
     throw new Exception('Message signature not valid');
 }
 
-$xml = new SimpleXMLElement($rawbody);
+// TODO MDL-46023 Replace this code with a call to the new library.
+$origentity = libxml_disable_entity_loader(true);
+$xml = simplexml_load_string($rawbody);
+if (!$xml) {
+    libxml_disable_entity_loader($origentity);
+    throw new Exception('Invalid XML content');
+}
+libxml_disable_entity_loader($origentity);
 
 $body = $xml->imsx_POXBody;
 foreach ($body->children() as $child) {
index 9e1ca22..dda6f1f 100644 (file)
@@ -1,5 +1,10 @@
 This files describes API changes in the lti code.
 
+=== 2.8 ===
+
+* The field 'grade' in the table {lti} is now an integer rather than a numeric to bring it
+  in line with the 'grade' field in other activities.
+
 === 2.7 ===
 
 * mod_lti\event\unknown_service_api_called now has less data stored in 'other'
index b9db2d2..a20a0e6 100644 (file)
@@ -48,7 +48,7 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-$plugin->version   = 2014060200;    // The current module version (Date: YYYYMMDDXX)
+$plugin->version   = 2014060201;    // The current module version (Date: YYYYMMDDXX)
 $plugin->requires  = 2014050800;    // Requires this Moodle version
 $plugin->component = 'mod_lti';        // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 0;
index 18cc1e6..2ebae5f 100644 (file)
@@ -709,6 +709,7 @@ $string['reportregrade'] = 'Regrade attempts';
 $string['reportresponses'] = 'Detailed responses';
 $string['reports'] = 'Reports';
 $string['reportshowonly'] = 'Show only attempts';
+$string['reportshowonlyfinished'] = 'Show at most one finished attempt per user ({$a})';
 $string['reportsimplestat'] = 'Simple statistics';
 $string['reportusersall'] = 'all users who have attempted the quiz';
 $string['reportuserswith'] = 'enrolled users who have attempted the quiz';
index 6d89f9a..e1ef6fa 100644 (file)
@@ -972,6 +972,7 @@ class mod_quiz_renderer extends plugin_renderer_base {
                     // Highlight the highest grade if appropriate.
                     if ($viewobj->overallstats && !$attemptobj->is_preview()
                             && $viewobj->numattempts > 1 && !is_null($viewobj->mygrade)
+                            && $attemptobj->get_state() == quiz_attempt::FINISHED
                             && $attemptgrade == $viewobj->mygrade
                             && $quiz->grademethod == QUIZ_GRADEHIGHEST) {
                         $table->rowclasses[$attemptobj->get_attempt_number()] = 'bestrow';
index c62a706..c468f6a 100644 (file)
@@ -89,8 +89,8 @@ abstract class mod_quiz_attempts_report_form extends moodleform {
             $gm = html_writer::tag('span',
                     quiz_get_grading_option_name($this->_customdata['quiz']->grademethod),
                     array('class' => 'highlight'));
-            $mform->addElement('advcheckbox', 'onlygraded', get_string('reportshowonly', 'quiz'),
-                    get_string('optonlygradedattempts', 'quiz_overview', $gm));
+            $mform->addElement('advcheckbox', 'onlygraded', '',
+                    get_string('reportshowonlyfinished', 'quiz', $gm));
             $mform->disabledIf('onlygraded', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
             $mform->disabledIf('onlygraded', 'statefinished', 'notchecked');
         }
index 06fd29c..839872f 100644 (file)
@@ -272,12 +272,20 @@ class mod_quiz_attempts_report_options {
             $this->onlygraded = false;
         }
 
-        if ($this->onlygraded) {
-            $this->states = array(quiz_attempt::FINISHED);
+        if (!$this->is_showing_finished_attempts()) {
+            $this->onlygraded = false;
         }
 
         if ($this->pagesize < 1) {
             $this->pagesize = quiz_attempts_report::DEFAULT_PAGE_SIZE;
         }
     }
+
+    /**
+     * Whether the options are such that finished attempts are being shown.
+     * @return boolean
+     */
+    protected function is_showing_finished_attempts() {
+        return $this->states === null || in_array(quiz_attempt::FINISHED, $this->states);
+    }
 }
index 61f3247..ab4ecb5 100644 (file)
@@ -425,7 +425,8 @@ abstract class quiz_attempts_report_table extends table_sql {
         $params = array('quizid' => $this->quiz->id);
 
         if ($this->qmsubselect && $this->options->onlygraded) {
-            $from .= " AND $this->qmsubselect";
+            $from .= " AND (quiza.state <> :finishedstate OR $this->qmsubselect)";
+            $params['finishedstate'] = quiz_attempt::FINISHED;
         }
 
         switch ($this->options->attempts) {
index e8bb6dc..dc188c2 100644 (file)
@@ -41,7 +41,6 @@ $string['optallattempts'] = 'all attempts';
 $string['optallstudents'] = 'all {$a} who have or have not attempted the quiz';
 $string['optattemptsonly'] = '{$a} who have attempted the quiz';
 $string['optnoattemptsonly'] = '{$a} who have not attempted the quiz';
-$string['optonlygradedattempts'] = 'that are graded for each user ({$a})';
 $string['optonlyregradedattempts'] = 'that have been regraded / are marked as needing regrading';
 $string['overview'] = 'Grades';
 $string['overviewdownload'] = 'Overview download';
index e03bab9..c3aed74 100644 (file)
@@ -38,7 +38,7 @@ class quiz_overview_settings_form extends mod_quiz_attempts_report_form {
 
     protected function other_attempt_fields(MoodleQuickForm $mform) {
         if (has_capability('mod/quiz:regrade', $this->_customdata['context'])) {
-            $mform->addElement('advcheckbox', 'onlyregraded', '',
+            $mform->addElement('advcheckbox', 'onlyregraded', get_string('reportshowonly', 'quiz'),
                     get_string('optonlyregradedattempts', 'quiz_overview'));
             $mform->disabledIf('onlyregraded', 'attempts', 'eq', quiz_attempts_report::ENROLLED_WITHOUT);
         }
index 57c1240..57c995a 100644 (file)
@@ -88,6 +88,10 @@ class quiz_overview_options extends mod_quiz_attempts_report_options {
     public function resolve_dependencies() {
         parent::resolve_dependencies();
 
+        if ($this->attempts == quiz_attempts_report::ENROLLED_WITHOUT) {
+            $this->onlyregraded = false;
+        }
+
         if (!$this->usercanseegrades) {
             $this->slotmarks = false;
         }
index d60da0b..5cb1145 100644 (file)
@@ -121,13 +121,6 @@ class quiz_overview_report extends quiz_attempts_report {
             // Construct the SQL.
             $fields = $DB->sql_concat('u.id', "'#'", 'COALESCE(quiza.attempt, 0)') .
                     ' AS uniqueid, ';
-            if ($this->qmsubselect) {
-                $fields .=
-                    "(CASE " .
-                    "   WHEN {$this->qmsubselect} THEN 1" .
-                    "   ELSE 0 " .
-                    "END) AS gradedattempt, ";
-            }
 
             list($fields, $from, $where, $params) = $table->base_sql($allowed);
 
diff --git a/mod/quiz/report/overview/tests/report_test.php b/mod/quiz/report/overview/tests/report_test.php
new file mode 100644 (file)
index 0000000..094faa6
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Tests for the quiz overview report.
+ *
+ * @package   quiz_overview
+ * @copyright 2014 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
+require_once($CFG->dirroot . '/mod/quiz/report/default.php');
+require_once($CFG->dirroot . '/mod/quiz/report/overview/report.php');
+
+
+/**
+ * Tests for the quiz overview report.
+ *
+ * @copyright  2014 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class quiz_overview_report_testcase extends advanced_testcase {
+
+    public function test_report_sql() {
+        global $DB, $SITE;
+        $this->resetAfterTest(true);
+
+        $generator = $this->getDataGenerator();
+        $quizgenerator = $generator->get_plugin_generator('mod_quiz');
+        $quiz = $quizgenerator->create_instance(array('course' => $SITE->id,
+                'grademethod' => QUIZ_GRADEHIGHEST, 'grade' => 100.0, 'sumgrades' => 10.0,
+                'attempts' => 10));
+
+        $student1 = $generator->create_user();
+        $student2 = $generator->create_user();
+        $student3 = $generator->create_user();
+
+        $quizid = 123;
+        $timestamp = 1234567890;
+
+        // The test data.
+        $fields = array('quiz', 'userid', 'attempt', 'sumgrades', 'state');
+        $attempts = array(
+            array($quiz->id, $student1->id, 1, 0.0,  quiz_attempt::FINISHED),
+            array($quiz->id, $student1->id, 2, 5.0,  quiz_attempt::FINISHED),
+            array($quiz->id, $student1->id, 3, 8.0,  quiz_attempt::FINISHED),
+            array($quiz->id, $student1->id, 4, null, quiz_attempt::ABANDONED),
+            array($quiz->id, $student1->id, 5, null, quiz_attempt::IN_PROGRESS),
+            array($quiz->id, $student2->id, 1, null, quiz_attempt::ABANDONED),
+            array($quiz->id, $student2->id, 2, null, quiz_attempt::ABANDONED),
+            array($quiz->id, $student2->id, 3, 7.0,  quiz_attempt::FINISHED),
+            array($quiz->id, $student2->id, 4, null, quiz_attempt::ABANDONED),
+            array($quiz->id, $student2->id, 5, null, quiz_attempt::ABANDONED),
+        );
+
+        // Load it in to quiz attempts table.
+        $uniqueid = 1;
+        foreach ($attempts as $attempt) {
+            $data = array_combine($fields, $attempt);
+            $data['timestart'] = $timestamp + 3600 * $data['attempt'];
+            $data['timemodifed'] = $data['timestart'];
+            if ($data['state'] == quiz_attempt::FINISHED) {
+                $data['timefinish'] = $data['timestart'] + 600;
+                $data['timemodifed'] = $data['timefinish'];
+            }
+            $data['layout'] = ''; // Not used, but cannot be null.
+            $data['uniqueid'] = $uniqueid++;
+            $data['preview'] = 0;
+            $DB->insert_record('quiz_attempts', $data);
+        }
+
+        // Actually getting the SQL to run is quit hard. Do a minimal set up of
+        // some objects.
+        $context = context_module::instance($quiz->cmid);
+        $cm = get_coursemodule_from_id('quiz', $quiz->cmid);
+        $qmsubselect = quiz_report_qm_filter_select($quiz);
+        $reportstudents = array($student1->id, $student2->id, $student3->id);
+
+        // Set the options.
+        $reportoptions = new quiz_overview_options('overview', $quiz, $cm, null);
+        $reportoptions->attempts = quiz_attempts_report::ENROLLED_ALL;
+        $reportoptions->onlygraded = true;
+        $reportoptions->states = array(quiz_attempt::IN_PROGRESS, quiz_attempt::OVERDUE, quiz_attempt::FINISHED);
+
+        // Now do a minimal set-up of the table class.
+        $table = new quiz_overview_table($quiz, $context, $qmsubselect, $reportoptions,
+                array(), $reportstudents, array(1), null);
+        $table->define_columns(array('attempt'));
+        $table->sortable(true, 'uniqueid');
+        $table->define_baseurl(new moodle_url('/mod/quiz/report.php'));
+        $table->setup();
+
+        // Run the query.
+        list($fields, $from, $where, $params) = $table->base_sql($reportstudents);
+        $table->set_sql($fields, $from, $where, $params);
+        $table->query_db(30, false);
+
+        // Verify what was returned: Student 1's best and in progress attempts.
+        // Stuent 2's finshed attempt, and Student 3 with no attempt.
+        // The array key is {student id}#{attempt number}.
+        $this->assertEquals(4, count($table->rawdata));
+        $this->assertArrayHasKey($student1->id . '#3', $table->rawdata);
+        $this->assertEquals(1, $table->rawdata[$student1->id . '#3']->gradedattempt);
+        $this->assertArrayHasKey($student1->id . '#3', $table->rawdata);
+        $this->assertEquals(0, $table->rawdata[$student1->id . '#5']->gradedattempt);
+        $this->assertArrayHasKey($student2->id . '#3', $table->rawdata);
+        $this->assertEquals(1, $table->rawdata[$student2->id . '#3']->gradedattempt);
+        $this->assertArrayHasKey($student3->id . '#0', $table->rawdata);
+        $this->assertEquals(0, $table->rawdata[$student3->id . '#0']->gradedattempt);
+    }
+}
index a998634..0bb3b5b 100644 (file)
@@ -156,27 +156,33 @@ function quiz_report_qm_filter_select($quiz, $quizattemptsalias = 'quiza') {
 function quiz_report_grade_method_sql($grademethod, $quizattemptsalias = 'quiza') {
     switch ($grademethod) {
         case QUIZ_GRADEHIGHEST :
-            return "NOT EXISTS (SELECT 1 FROM {quiz_attempts} qa2
+            return "($quizattemptsalias.state = 'finished' AND NOT EXISTS (
+                           SELECT 1 FROM {quiz_attempts} qa2
                             WHERE qa2.quiz = $quizattemptsalias.quiz AND
-                                qa2.userid = $quizattemptsalias.userid AND (
+                                qa2.userid = $quizattemptsalias.userid AND
+                                 qa2.state = 'finished' AND (
                 COALESCE(qa2.sumgrades, 0) > COALESCE($quizattemptsalias.sumgrades, 0) OR
                (COALESCE(qa2.sumgrades, 0) = COALESCE($quizattemptsalias.sumgrades, 0) AND qa2.attempt < $quizattemptsalias.attempt)
-                                ))";
+                                )))";
 
         case QUIZ_GRADEAVERAGE :
             return '';
 
         case QUIZ_ATTEMPTFIRST :
-            return "NOT EXISTS (SELECT 1 FROM {quiz_attempts} qa2
+            return "($quizattemptsalias.state = 'finished' AND NOT EXISTS (
+                           SELECT 1 FROM {quiz_attempts} qa2
                             WHERE qa2.quiz = $quizattemptsalias.quiz AND
                                 qa2.userid = $quizattemptsalias.userid AND
-                               qa2.attempt < $quizattemptsalias.attempt)";
+                                 qa2.state = 'finished' AND
+                               qa2.attempt < $quizattemptsalias.attempt))";
 
         case QUIZ_ATTEMPTLAST :
-            return "NOT EXISTS (SELECT 1 FROM {quiz_attempts} qa2
+            return "($quizattemptsalias.state = 'finished' AND NOT EXISTS (
+                           SELECT 1 FROM {quiz_attempts} qa2
                             WHERE qa2.quiz = $quizattemptsalias.quiz AND
                                 qa2.userid = $quizattemptsalias.userid AND
-                               qa2.attempt > $quizattemptsalias.attempt)";
+                                 qa2.state = 'finished' AND
+                               qa2.attempt > $quizattemptsalias.attempt))";
     }
 }
 
index e3ed8a6..9b2b74f 100644 (file)
@@ -311,8 +311,9 @@ div.editq div.question div.content .singlequestion a .questiontext {
 #page-mod-quiz-view #page .quizattemptsummary td p {
     margin-top: 0;
 }
-table.quizattemptsummary .bestrow td {
-    background-color: #e8e8e8;
+#page-mod-quiz-view table.quizattemptsummary tr.bestrow td {
+    border-color: #bce8f1;
+    background-color: #d9edf7;
 }
 table.quizattemptsummary .noreviewmessage {
     color: gray;
@@ -347,9 +348,10 @@ table.quizattemptsummary .noreviewmessage {
     display: inline;
 }
 
-.mod-quiz .gradedattempt,
-.mod-quiz tr.gradedattempt td {
-    background-color: #e8e8e8;
+body.path-mod-quiz .gradedattempt,
+body.path-mod-quiz table tbody tr.gradedattempt > td {
+    border-color: #bce8f1;
+    background-color: #d9edf7;
 }
 
 .quizattemptcounts {
@@ -431,8 +433,8 @@ table.quizreviewsummary td.cell {
     background-color: #fcc;
 }
 #page-mod-quiz-report .highlight {
-    border : medium solid yellow;
-    background-color: lightYellow;
+    border: 1px solid #bce8f1;
+    background-color: #d9edf7;
 }
 #page-mod-quiz-report .negcovar {
     border : medium solid pink;
@@ -443,11 +445,8 @@ table.quizreviewsummary td.cell {
 #page-mod-quiz-report .gradetheselink {
     font-size: 0.8em;
 }
-#page-mod-quiz-report .mform fieldset {
-    margin: 0;
-}
-#page-mod-quiz-report fieldset.felement.fgroup {
-    margin: 0;
+#page-mod-quiz-report .mform fieldset.fgroup span label {
+    margin-right: 14px;
 }
 #page-mod-quiz-report table th {
     white-space: normal;
index de4b6a6..9986d30 100644 (file)
@@ -95,16 +95,17 @@ class mod_quiz_reportlib_testcase extends advanced_testcase {
         $fakeattempt->userid = 123;
         $fakeattempt->quiz = 456;
         $fakeattempt->layout = '1,2,0,3,4,0,5';
+        $fakeattempt->state = quiz_attempt::FINISHED;
 
         // We intentionally insert these in a funny order, to test the SQL better.
         // The test data is:
-        // id | quizid | user | attempt | sumgrades
-        // ----------------------------------------
-        // 4  | 456    | 123  | 1       | 30
-        // 2  | 456    | 123  | 2       | 50
-        // 1  | 456    | 123  | 3       | 50
-        // 3  | 456    | 123  | 4       | null
-        // 5  | 456    | 1    | 1       | 100
+        // id | quizid | user | attempt | sumgrades | state
+        // ---------------------------------------------------
+        // 4  | 456    | 123  | 1       | 30        | finished
+        // 2  | 456    | 123  | 2       | 50        | finished
+        // 1  | 456    | 123  | 3       | 50        | finished
+        // 3  | 456    | 123  | 4       | null      | inprogress
+        // 5  | 456    | 1    | 1       | 100       | finished
         // layout is only given because it has a not-null constraint.
         // uniqueid values are meaningless, but that column has a unique constraint.
 
@@ -121,11 +122,13 @@ class mod_quiz_reportlib_testcase extends advanced_testcase {
         $fakeattempt->attempt = 4;
         $fakeattempt->sumgrades = null;
         $fakeattempt->uniqueid = 39;
+        $fakeattempt->state = quiz_attempt::IN_PROGRESS;
         $DB->insert_record('quiz_attempts', $fakeattempt);
 
         $fakeattempt->attempt = 1;
         $fakeattempt->sumgrades = 30;
         $fakeattempt->uniqueid = 52;
+        $fakeattempt->state = quiz_attempt::FINISHED;
         $DB->insert_record('quiz_attempts', $fakeattempt);
 
         $fakeattempt->attempt = 1;
@@ -151,7 +154,7 @@ class mod_quiz_reportlib_testcase extends advanced_testcase {
                 . quiz_report_qm_filter_select($quiz), array(123, 456));
         $this->assertEquals(1, count($lastattempt));
         $lastattempt = reset($lastattempt);
-        $this->assertEquals(4, $lastattempt->attempt);
+        $this->assertEquals(3, $lastattempt->attempt);
 
         $quiz->attempts = 0;
         $quiz->grademethod = QUIZ_GRADEHIGHEST;
index c4d2ab7..d831b4b 100644 (file)
@@ -15,6 +15,10 @@ $userid       = optional_param('user', 0, PARAM_INT);
 $filtertype   = optional_param('filtertype', '', PARAM_ALPHA);
 $filterselect = optional_param('filterselect', 0, PARAM_INT);
 
+if (empty($CFG->enablenotes)) {
+    print_error('notesdisabled', 'notes');
+}
+
 $url = new moodle_url('/notes/index.php');
 if ($courseid != SITEID) {
     $url->param('course', $courseid);
@@ -67,6 +71,7 @@ if ($course->id == SITEID) {
 } else {
     $coursecontext = context_course::instance($course->id);   // Course context
 }
+require_capability('moodle/notes:view', $coursecontext);
 $systemcontext = context_system::instance();   // SYSTEM context
 
 // Trigger event.
@@ -76,10 +81,6 @@ $event = \core\event\notes_viewed::create(array(
 ));
 $event->trigger();
 
-if (empty($CFG->enablenotes)) {
-    print_error('notesdisabled', 'notes');
-}
-
 $strnotes = get_string('notes', 'notes');
 if ($userid) {
     $PAGE->set_context(context_user::instance($user->id));
index 8e680eb..853b4ee 100644 (file)
@@ -20,6 +20,7 @@
 
     <php>
         <!--<const name="PHPUNIT_LONGTEST" value="1"/> uncomment to execute also slow or otherwise expensive tests-->
+        <const name="PHPUNIT_SEQUENCE_START" value="<!--@PHPUNIT_SEQUENCE_START@-->"/>
 
         <!--Following constants instruct tests to fetch external test files from alternative location or skip tests if empty, clone https://github.com/moodlehq/moodle-exttests to local web server-->
         <!--<const name="TEST_EXTERNAL_FILES_HTTP_URL" value="http://download.moodle.org/unittest"/> uncomment and alter to fetch external test files from alternative location-->
index cd331b8..4684415 100644 (file)
@@ -290,7 +290,7 @@ class question_category_object {
         /// Interface for editing existing categories
         if ($category = $DB->get_record("question_categories", array("id" => $categoryid))) {
 
-            $category->parent = "$category->parent,$category->contextid";
+            $category->parent = "{$category->parent},{$category->contextid}";
             $category->submitbutton = get_string('savechanges');
             $category->categoryheader = $this->str->edit;
             $this->catform->set_data($category);
index d4018ac..579bd31 100644 (file)
@@ -724,7 +724,7 @@ class view {
                                               array('qperpage' => DEFAULT_QUESTIONS_PER_PAGE)));
                 $showall = '<a href="'.$url.'">'.get_string('showperpage', 'moodle', DEFAULT_QUESTIONS_PER_PAGE).'</a>';
             }
-            echo "<div class='paging'>$showall</div>";
+            echo "<div class='paging'>{$showall}</div>";
         }
         echo '</div>';
 
@@ -743,13 +743,13 @@ class view {
 
             if ($canmoveall && count($addcontexts)) {
                 echo '<input type="submit" name="move" value="'.get_string('moveto', 'question')."\" />\n";
-                question_category_select_menu($addcontexts, false, 0, "$category->id,$category->contextid");
+                question_category_select_menu($addcontexts, false, 0, "{$category->id},{$category->contextid}");
             }
 
             if (function_exists('module_specific_controls') && $canuseall) {
                 $modulespecific = module_specific_controls($totalnumber, $recurse, $category, $this->cm->id, $cmoptions);
                 if (!empty($modulespecific)) {
-                    echo "<hr />$modulespecific";
+                    echo "<hr />{$modulespecific}";
                 }
             }
         }
@@ -835,13 +835,13 @@ class view {
                         SELECT q.*, c.contextid
                         FROM {question} q
                         JOIN {question_categories} c ON c.id = q.category
-                        WHERE q.id $usql", $params);
+                        WHERE q.id {$usql}", $params);
                 foreach ($questions as $question) {
                     question_require_capability_on($question, 'move');
                 }
                 question_move_questions_to_category($questionids, $tocategory->id);
                 redirect($this->baseurl->out(false,
-                        array('category' => "$tocategoryid,$contextid")));
+                        array('category' => "{$tocategoryid},{$contextid}")));
             }
         }
 
index 2739700..cb7348e 100644 (file)
@@ -75,7 +75,7 @@ function get_questions_category( $category, $noparent=false, $recurse=true, $exp
 
     // Get the list of questions for the category
     list($usql, $params) = $DB->get_in_or_equal($categorylist);
-    $questions = $DB->get_records_select('question', "category $usql $npsql", $params, 'qtype, name');
+    $questions = $DB->get_records_select('question', "category {$usql} {$npsql}", $params, 'qtype, name');
 
     // Iterate through questions, getting stuff we need
     $qresults = array();
@@ -346,7 +346,7 @@ function question_edit_setup($edittab, $baseurl, $requirecmid = false, $requirec
 
     $contextlistarr = array();
     foreach ($contexts->having_one_edit_tab_cap($edittab) as $context){
-        $contextlistarr[] = "'$context->id'";
+        $contextlistarr[] = "'{$context->id}'";
     }
     $contextlist = join($contextlistarr, ' ,');
     if (!empty($pagevars['cat'])){
@@ -357,7 +357,7 @@ function question_edit_setup($edittab, $baseurl, $requirecmid = false, $requirec
         }
     } else {
         $category = $defaultcategory;
-        $pagevars['cat'] = "$category->id,$category->contextid";
+        $pagevars['cat'] = "{$category->id},{$category->contextid}";
     }
 
     // Display options.
index 5950af6..11e3ddc 100644 (file)
@@ -100,7 +100,7 @@ abstract class question_bank {
         include_once($file);
         $class = 'qtype_' . $qtypename;
         if (!class_exists($class)) {
-            throw new coding_exception("Class $class must be defined in $file");
+            throw new coding_exception("Class {$class} must be defined in {$file}.");
         }
         self::$questiontypes[$qtypename] = new $class();
         return self::$questiontypes[$qtypename];
@@ -381,8 +381,8 @@ abstract class question_bank {
         // The the positive grades in descending order.
         foreach ($rawfractions as $fraction) {
             $percentage = format_float(100 * $fraction, 5, true, true) . '%';
-            self::$fractionoptions["$fraction"] = $percentage;
-            self::$fractionoptionsfull["$fraction"] = $percentage;
+            self::$fractionoptions["{$fraction}"] = $percentage;
+            self::$fractionoptionsfull["{$fraction}"] = $percentage;
         }
 
         // The the negative grades in descending order.
@@ -499,10 +499,10 @@ class question_finder implements cache_data_source {
         }
 
         return $DB->get_records_select_menu('question',
-                "category $qcsql
+                "category {$qcsql}
                  AND parent = 0
                  AND hidden = 0
-                 $extraconditions", $qcparams + $extraparams, '', 'id,id AS id2');
+                 {$extraconditions}", $qcparams + $extraparams, '', 'id,id AS id2');
     }
 
     /* See cache_data_source::load_for_cache. */
index 7b0685c..daaa0f6 100644 (file)
@@ -651,7 +651,7 @@ $sqlorderby
         if (!empty($slots)) {
             list($slottest, $slotsparams) = $this->db->get_in_or_equal(
                     $slots, SQL_PARAMS_NAMED, 'slot');
-            $slotwhere = " AND qa.slot $slottest";
+            $slotwhere = " AND qa.slot {$slottest}";
         } else {
             $slotwhere = '';
             $slotsparams = array();
@@ -879,9 +879,9 @@ ORDER BY
         $this->delete_response_files($context->id, $test, $params);
 
         $this->db->delete_records_select('question_attempt_step_data',
-                "attemptstepid $test", $params);
+                "attemptstepid {$test}", $params);
         $this->db->delete_records_select('question_attempt_steps',
-                "id $test", $params);
+                "id {$test}", $params);
     }
 
     /**
@@ -942,7 +942,7 @@ ORDER BY
     protected function full_states_to_summary_state_sql() {
         $sql = '';
         foreach (question_state::get_all() as $state) {
-            $sql .= "WHEN '$state' THEN '{$state->get_summary_state()}'\n";
+            $sql .= "WHEN '{$state}' THEN '{$state->get_summary_state()}'\n";
         }
         return $sql;
     }
@@ -1043,7 +1043,7 @@ ORDER BY
                   JOIN {question_attempt_steps} {$alias}qas ON {$alias}qas.questionattemptid = {$alias}qa.id
                             AND {$alias}qas.sequencenumber = {$this->latest_step_for_qa_subquery($alias . 'qa.id')}
                  WHERE {$qubaids->where()}
-            ) $alias", $qubaids->from_where_params());
+            ) {$alias}", $qubaids->from_where_params());
     }
 
     protected function latest_step_for_qa_subquery($questionattemptid = 'qa.id') {
@@ -1658,7 +1658,7 @@ class qubaid_join extends qubaid_condition {
     }
 
     public function from_question_attempts($alias) {
-        return "$this->from
+        return "{$this->from}
                 JOIN {question_attempts} {$alias} ON " .
                         "{$alias}.questionusageid = $this->usageidcolumn";
     }
@@ -1672,7 +1672,7 @@ class qubaid_join extends qubaid_condition {
     }
 
     public function usage_id_in() {
-        return "IN (SELECT $this->usageidcolumn FROM $this->from WHERE $this->where)";
+        return "IN (SELECT {$this->usageidcolumn} FROM {$this->from} WHERE {$this->where})";
     }
 
     public function usage_id_in_params() {
index c96659b..ef06426 100644 (file)
@@ -656,7 +656,7 @@ abstract class question_flags {
         $qid = $qa->get_question()->id;
         $slot = $qa->get_slot();
         $checksum = self::get_toggle_checksum($qubaid, $qid, $qaid, $slot);
-        return "qaid=$qaid&qubaid=$qubaid&qid=$qid&slot=$slot&checksum=$checksum&sesskey=" .
+        return "qaid={$qaid}&qubaid={$qubaid}&qid={$qid}&slot={$slot}&checksum={$checksum}&sesskey=" .
                 sesskey() . '&newstate=';
     }
 
@@ -735,7 +735,7 @@ class question_out_of_sequence_exception extends moodle_exception {
             $postdata = data_submitted();
         }
         parent::__construct('submissionoutofsequence', 'question', '', null,
-                "QUBAid: $qubaid, slot: $slot, post data: " . print_r($postdata, true));
+                "QUBAid: {$qubaid}, slot: {$slot}, post data: " . print_r($postdata, true));
     }
 }
 
index bce0465..3d1c55b 100644 (file)
@@ -1351,7 +1351,7 @@ class question_attempt {
         while ($record->questionattemptid != $questionattemptid) {
             $record = $records->next();
             if (!$records->valid()) {
-                throw new coding_exception("Question attempt $questionattemptid not found in the database.");
+                throw new coding_exception("Question attempt {$questionattemptid} not found in the database.");
             }
             $record = $records->current();
         }
index 7a4d5ed..20d0a6a 100644 (file)
@@ -837,7 +837,7 @@ class question_usage_by_activity {
         while ($record->qubaid != $qubaid) {
             $records->next();
             if (!$records->valid()) {
-                throw new coding_exception("Question usage $qubaid not found in the database.");
+                throw new coding_exception("Question usage {$qubaid} not found in the database.");
             }
             $record = $records->current();
         }
index fc97d58..5516338 100644 (file)
@@ -545,10 +545,10 @@ abstract class question_testcase extends advanced_testcase {
             $compare = (array)$compare;
             foreach ($expect as $k=>$v) {
                 if (!array_key_exists($k, $compare)) {
-                    $this->fail("Property $k does not exist");
+                    $this->fail("Property {$k} does not exist");
                 }
                 if ($v != $compare[$k]) {
-                    $this->fail("Property $k is different");
+                    $this->fail("Property {$k} is different");
                 }
             }
             $this->assertTrue(true);
index 8f2396e..892cf75 100644 (file)
@@ -50,7 +50,7 @@ $export_form = new question_export_form($thispageurl,
 
 if ($from_form = $export_form->get_data()) {
     $thiscontext = $contexts->lowest();
-    if (!is_readable("format/$from_form->format/format.php")) {
+    if (!is_readable("format/{$from_form->format}/format.php")) {
         print_error('unknowformat', '', '', $from_form->format);
     }
     $withcategories = 'nocategories';
index 547abf2..006e334 100644 (file)
@@ -51,10 +51,10 @@ class question_export_form extends moodleform {
         foreach ($fileformatnames as $shortname => $fileformatname) {
             $currentgrp1 = array();
             $currentgrp1[] = $mform->createElement('radio', 'format', '', $fileformatname, $shortname);
-            $mform->addGroup($currentgrp1, "formathelp[$i]", '', array('<br />'), false);
+            $mform->addGroup($currentgrp1, "formathelp[{$i}]", '', array('<br />'), false);
 
             if (get_string_manager()->string_exists('pluginname_help', 'qformat_' . $shortname)) {
-                $mform->addHelpButton("formathelp[$i]", 'pluginname', 'qformat_' . $shortname);
+                $mform->addHelpButton("formathelp[{$i}]", 'pluginname', 'qformat_' . $shortname);
             }
 
             $i++ ;
index 51b4672..e284d8c 100644 (file)
@@ -222,12 +222,12 @@ class qformat_default {
         $importerrorquestion = get_string('importerrorquestion', 'question');
 
         echo "<div class=\"importerror\">\n";
-        echo "<strong>$importerrorquestion $questionname</strong>";
+        echo "<strong>{$importerrorquestion} {$questionname}</strong>";
         if (!empty($text)) {
             $text = s($text);
-            echo "<blockquote>$text</blockquote>\n";
+            echo "<blockquote>{$text}</blockquote>\n";
         }
-        echo "<strong>$message</strong>\n";
+        echo "<strong>{$message}</strong>\n";
         echo "</div>";
 
          $this->importerrors++;
@@ -247,7 +247,7 @@ class qformat_default {
 
         // work out what format we are using
         $formatname = substr(get_class($this), strlen('qformat_'));
-        $methodname = "import_from_$formatname";
+        $methodname = "import_from_{$formatname}";
 
         //first try importing using a hint from format
         if (!empty($qtypehint)) {
@@ -377,7 +377,7 @@ class qformat_default {
 
             $count++;
 
-            echo "<hr /><p><b>$count</b>. ".$this->format_question_text($question)."</p>";
+            echo "<hr /><p><b>{$count}</b>. ".$this->format_question_text($question)."</p>";
 
             $question->category = $this->category->id;
             $question->stamp = make_unique_id_code();  // Set the unique code (not to be changed)
@@ -691,7 +691,7 @@ class qformat_default {
     protected function readquestion($lines) {
 
         $formatnotimplemented = get_string('formatnotimplemented', 'question');
-        echo "<p>$formatnotimplemented</p>";
+        echo "<p>{$formatnotimplemented}</p>";
 
         return null;
     }
@@ -719,7 +719,7 @@ class qformat_default {
     protected function try_exporting_using_qtypes($name, $question, $extra=null) {
         // work out the name of format in use
         $formatname = substr(get_class($this), strlen('qformat_'));
-        $methodname = "export_to_$formatname";
+        $methodname = "export_to_{$formatname}";
 
         $qtype = question_bank::get_qtype($name, false);
         if (method_exists($qtype, $methodname)) {
@@ -819,7 +819,7 @@ class qformat_default {
 
         // continue path for following error checks
         $course = $this->course;
-        $continuepath = "$CFG->wwwroot/question/export.php?courseid=$course->id";
+        $continuepath = "{$CFG->wwwroot}/question/export.php?courseid={$course->id}";
 
         // did we actually process anything
         if ($count==0) {
@@ -924,7 +924,7 @@ class qformat_default {
     protected function writequestion($question) {
         // if not overidden, then this is an error.
         $formatnotimplemented = get_string('formatnotimplemented', 'question');
-        echo "<p>$formatnotimplemented</p>";
+        echo "<p>{$formatnotimplemented}</p>";
         return null;
     }
 
index 75f6368..5033827 100644 (file)
@@ -134,7 +134,7 @@ class qformat_blackboard_six_base extends qformat_based_on_xml {
                 $dirpath = dirname($path);
                 $filename = basename($path);
                 $newfilename = $this->store_file_for_text_field($data, $this->filebase, $dirpath, $filename);
-                $text = preg_replace("|$path|", "@@PLUGINFILE@@/" . $newfilename, $text);
+                $text = preg_replace("|{$path}|", "@@PLUGINFILE@@/" . $newfilename, $text);
                 $filepaths[] = $path;
             }
 
index 156d4a8..5b3ca64 100644 (file)
@@ -416,7 +416,7 @@ class qformat_gift extends qformat_default {
                 list($answer, $wrongfeedback, $rightfeedback) =
                         $this->split_truefalse_comment($answertext, $question->questiontextformat);
 
-                if ($answer['text'] == "T" OR $answer['text'] == "TRUE") {
+                if ($answer['text'] == "T" || $answer['text'] == "TRUE") {
                     $question->correctanswer = 1;
                     $question->feedbacktrue = $rightfeedback;
                     $question->feedbackfalse = $wrongfeedback;
@@ -634,7 +634,7 @@ class qformat_gift extends qformat_default {
         global $OUTPUT;
 
         // Start with a comment.
-        $expout = "// question: $question->id  name: $question->name\n";
+        $expout = "// question: {$question->id}  name: {$question->name}\n";
 
         // Output depends on question type.
         switch($question->qtype) {
index 4e184bc..57c3175 100644 (file)
@@ -150,7 +150,7 @@ function qformat_webct_convert_formula($formula) {
         }
 
         // Replace it!
-        $formula = "$splits[0]pow($base,$exp)$splits[1]";
+        $formula = "{$splits[0]}pow({$base},{$exp}){$splits[1]}";
     }
 
     // Nothing more is known to need to be converted.
@@ -239,7 +239,7 @@ class qformat_webct extends qformat_default {
                 $dirpath = dirname($path);
                 $filename = basename($path);
                 $newfilename = $this->store_file_for_text_field($data, $this->tempdir, $dirpath, $filename);
-                $text = preg_replace("|$path|", "@@PLUGINFILE@@/" . $newfilename, $text);
+                $text = preg_replace("|{$path}|", "@@PLUGINFILE@@/" . $newfilename, $text);
                 $filepaths[] = $path;
             }
 
@@ -497,7 +497,7 @@ class qformat_webct extends qformat_default {
                             case 'shortanswer':
                                 if ($maxfraction != 1) {
                                     $maxfraction = $maxfraction * 100;
-                                    $errors[] = "'$question->name': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
+                                    $errors[] = "'{$question->name}': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
                                             .' '.get_string('fractionsnomax', 'question', $maxfraction);
                                     $questionok = false;
                                 }
@@ -509,7 +509,7 @@ class qformat_webct extends qformat_default {
                                 if ($question->single) {
                                     if ($maxfraction != 1) {
                                         $maxfraction = $maxfraction * 100;
-                                        $errors[] = "'$question->name': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
+                                        $errors[] = "'{$question->name}': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
                                                 .' '.get_string('fractionsnomax', 'question', $maxfraction);
                                         $questionok = false;
                                     }
@@ -517,7 +517,7 @@ class qformat_webct extends qformat_default {
                                     $totalfraction = round($totalfraction, 2);
                                     if ($totalfraction != 1) {
                                         $totalfraction = $totalfraction * 100;
-                                        $errors[] = "'$question->name': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
+                                        $errors[] = "'{$question->name}': ".get_string('wronggrade', 'qformat_webct', $nlinecounter)
                                                 .' '.get_string('fractionsaddwrong', 'question', $totalfraction);
                                         $questionok = false;
                                     }
@@ -527,7 +527,7 @@ class qformat_webct extends qformat_default {
                             case 'calculated':
                                 foreach ($question->answers as $answer) {
                                     if ($formulaerror = qtype_calculated_find_formula_errors($answer)) {
-                                        $warnings[] = "'$question->name': ". $formulaerror;
+                                        $warnings[] = "'{$question->name}': ". $formulaerror;
                                         $questionok = false;
                                     }
                                 }
@@ -687,7 +687,7 @@ class qformat_webct extends qformat_default {
                 continue;
             }
             if (isset($question->qtype ) && 'calculated' == $question->qtype && preg_match(
-                    "~^:([[:lower:]].*|::.*)-(MIN|MAX|DEC|VAL([0-9]+))::?:?($webctnumberregex)~", $line, $webctoptions)) {
+                    "~^:([[:lower:]].*|::.*)-(MIN|MAX|DEC|VAL([0-9]+))::?:?({$webctnumberregex})~", $line, $webctoptions)) {
                 $datasetname = preg_replace('/^::/', '', $webctoptions[1]);
                 $datasetvalue = qformat_webct_convert_formula($webctoptions[4]);
                 switch ($webctoptions[2]) {
@@ -803,7 +803,7 @@ class qformat_webct extends qformat_default {
             }
 
             if (isset($question->qtype )&& 'calculated' == $question->qtype
-                    && preg_match("~^:TOL:($webctnumberregex)~i", $line, $webctoptions)) {
+                    && preg_match("~^:TOL:({$webctnumberregex})~i", $line, $webctoptions)) {
                 // We can but hope that this always appear before the TOL property.
                 $question->tolerance[$currentchoice] =
                         qformat_webct_convert_formula($webctoptions[1]);
@@ -858,7 +858,7 @@ class qformat_webct extends qformat_default {
         if (count($errors) > 0) {
             echo '<p>'.get_string('errorsdetected', 'qformat_webct', count($errors)).'</p><ul>';
             foreach ($errors as $error) {
-                echo "<li>$error</li>";
+                echo "<li>{$error}</li>";
             }
             echo '</ul>';
             unset($questions);     // No questions imported.
@@ -867,7 +867,7 @@ class qformat_webct extends qformat_default {
         if (count($warnings) > 0) {
             echo '<p>'.get_string('warningsdetected', 'qformat_webct', count($warnings)).'</p><ul>';
             foreach ($warnings as $warning) {
-                echo "<li>$warning</li>";
+                echo "<li>{$warning}</li>";
             }
             echo '</ul>';
         }
index 0c62588..bb54d80 100644 (file)
@@ -59,11 +59,11 @@ class qformat_xhtml extends qformat_default {
         $id = $question->id;
 
         // Add comment and div tags.
-        $expout .= "<!-- question: $id  name: $question->name -->\n";
+        $expout .= "<!-- question: {$id}  name: {$question->name} -->\n";
         $expout .= "<div class=\"question\">\n";
 
         // Add header.
-        $expout .= "<h3>$question->name</h3>\n";
+        $expout .= "<h3>{$question->name}</h3>\n";
 
         // Format and add the question text.
         $text = question_rewrite_question_preview_urls($question->questiontext, $question->id,
@@ -78,8 +78,8 @@ class qformat_xhtml extends qformat_default {
                 $sttrue = get_string('true', 'qtype_truefalse');
                 $stfalse = get_string('false', 'qtype_truefalse');
                 $expout .= "<ul class=\"truefalse\">\n";
-                $expout .= "  <li><input name=\"quest_$id\" type=\"radio\" value=\"$sttrue\" />$sttrue</li>\n";
-                $expout .= "  <li><input name=\"quest_$id\" type=\"radio\" value=\"$stfalse\" />$stfalse</li>\n";
+                $expout .= "  <li><input name=\"quest_{$id}\" type=\"radio\" value=\"{$sttrue}\" />{$sttrue}</li>\n";
+                $expout .= "  <li><input name=\"quest_{$id}\" type=\"radio\" value=\"{$stfalse}\" />{$stfalse}</li>\n";
                 $expout .= "</ul>\n";
                 break;
             case 'multichoice':
@@ -87,11 +87,11 @@ class qformat_xhtml extends qformat_default {
                 foreach ($question->options->answers as $answer) {
                     $answertext = $this->repchar( $answer->answer );
                     if ($question->options->single) {
-                        $expout .= "  <li><input name=\"quest_$id\" type=\"radio\" value=\""
-                                . s($answertext) . "\" />$answertext</li>\n";
+                        $expout .= "  <li><input name=\"quest_{$id}\" type=\"radio\" value=\""
+                                . s($answertext) . "\" />{$answertext}</li>\n";
                     } else {
-                        $expout .= "  <li><input name=\"quest_$id\" type=\"checkbox\" value=\""
-                                . s($answertext) . "\" />$answertext</li>\n";
+                        $expout .= "  <li><input name=\"quest_{$id}\" type=\"checkbox\" value=\""
+                                . s($answertext) . "\" />{$answertext}</li>\n";
                     }
                 }
                 $expout .= "</ul>\n";
@@ -100,7 +100,7 @@ class qformat_xhtml extends qformat_default {
                 $expout .= html_writer::start_tag('ul', array('class' => 'shortanswer'));
                 $expout .= html_writer::start_tag('li');
                 $expout .= html_writer::label(get_string('answer'), 'quest_'.$id, false, array('class' => 'accesshide'));
-                $expout .= html_writer::empty_tag('input', array('id' => "quest_$id", 'name' => "quest_$id", 'type' => 'text'));
+                $expout .= html_writer::empty_tag('input', array('id' => "quest_{$id}", 'name' => "quest_{$id}", 'type' => 'text'));
                 $expout .= html_writer::end_tag('li');
                 $expout .= html_writer::end_tag('ul');
                 break;
@@ -108,7 +108,7 @@ class qformat_xhtml extends qformat_default {
                 $expout .= html_writer::start_tag('ul', array('class' => 'numerical'));
                 $expout .= html_writer::start_tag('li');
                 $expout .= html_writer::label(get_string('answer'), 'quest_'.$id, false, array('class' => 'accesshide'));
-                $expout .= html_writer::empty_tag('input', array('id' => "quest_$id", 'name' => "quest_$id", 'type' => 'text'));
+                $expout .= html_writer::empty_tag('input', array('id' => "quest_{$id}", 'name' => "quest_{$id}", 'type' => 'text'));
                 $expout .= html_writer::end_tag('li');
                 $expout .= html_writer::end_tag('ul');
                 break;
@@ -149,7 +149,7 @@ class qformat_xhtml extends qformat_default {
                 break;
             case 'multianswer':
             default:
-                $expout .= "<!-- export of $question->qtype type is not supported  -->\n";
+                $expout .= "<!-- export of {$question->qtype} type is not supported  -->\n";
         }
         // Close off div.
         $expout .= "</div>\n\n\n";
@@ -163,7 +163,7 @@ class qformat_xhtml extends qformat_default {
         global $CFG;
 
         // Get css bit.
-        $csslines = file( "$CFG->dirroot/question/format/xhtml/xhtml.css" );
+        $csslines = file( "{$CFG->dirroot}/question/format/xhtml/xhtml.css" );
         $css = implode( ' ', $csslines );
 
         $xp =  "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n";
index 304d379..3fc9b13 100644 (file)
@@ -1072,9 +1072,9 @@ class qformat_xml extends qformat_default {
         $raw = $this->xml_escape($raw);
 
         if ($short) {
-            $xml = "$indent<text>$raw</text>\n";
+            $xml = "{$indent}<text>{$raw}</text>\n";
         } else {
-            $xml = "$indent<text>\n$raw\n$indent</text>\n";
+            $xml = "{$indent}<text>\n{$raw}\n{$indent}</text>\n";
         }
 
         return $xml;
@@ -1137,7 +1137,7 @@ class qformat_xml extends qformat_default {
         $expout = '';
 
         // Add a comment linking this to the original question id.
-        $expout .= "<!-- question: $question->id  -->\n";
+        $expout .= "<!-- question: {$question->id}  -->\n";
 
         // Check question type.
         $questiontype = $this->get_qtype($question->qtype);
@@ -1147,7 +1147,7 @@ class qformat_xml extends qformat_default {
             $categorypath = $this->writetext($question->category);
             $expout .= "  <question type=\"category\">\n";
             $expout .= "    <category>\n";
-            $expout .= "        $categorypath\n";
+            $expout .= "        {$categorypath}\n";
             $expout .= "    </category>\n";
             $expout .= "  </question>\n";
             return $expout;
@@ -1155,7 +1155,7 @@ class qformat_xml extends qformat_default {
 
         // Now we know we are are handing a real question.
         // Output the generic information.
-        $expout .= "  <question type=\"$questiontype\">\n";
+        $expout .= "  <question type=\"{$questiontype}\">\n";
         $expout .= "    <name>\n";
         $expout .= $this->writetext($question->name, 3);
         $expout .= "    </name>\n";
@@ -1209,7 +1209,7 @@ class qformat_xml extends qformat_default {
             case 'numerical':
                 foreach ($question->options->answers as $answer) {
                     $expout .= $this->write_answer($answer,
-                            "      <tolerance>$answer->tolerance</tolerance>\n");
+                            "      <tolerance>{$answer->tolerance}</tolerance>\n");
                 }
 
                 $units = $question->options->units;
@@ -1333,7 +1333,7 @@ class qformat_xml extends qformat_default {
 
                 foreach ($question->options->answers as $answer) {
                     $percent = 100 * $answer->fraction;
-                    $expout .= "<answer fraction=\"$percent\">\n";
+                    $expout .= "<answer fraction=\"{$percent}\">\n";
                     // The "<text/>" tags are an added feature, old files won't have them.
                     $expout .= "    <text>{$answer->answer}</text>\n";
                     $expout .= "    <tolerance>{$answer->tolerance}</tolerance>\n";
@@ -1412,7 +1412,7 @@ class qformat_xml extends qformat_default {
                                 "</maximum>\n";
                         $expout .= "    <decimals>" . $this->writetext($def->decimals) .
                                 "</decimals>\n";
-                        $expout .= "    <itemcount>$def->itemcount</itemcount>\n";
+                        $expout .= "    <itemcount>{$def->itemcount}</itemcount>\n";
                         if ($def->itemcount > 0) {
                             $expout .= "    <dataset_items>\n";
                             foreach ($def->items as $item) {
@@ -1479,7 +1479,7 @@ class qformat_xml extends qformat_default {
     public function write_answer($answer, $extra = '') {
         $percent = $answer->fraction * 100;
         $output = '';
-        $output .= "    <answer fraction=\"$percent\" {$this->format($answer->answerformat)}>\n";
+        $output .= "    <answer fraction=\"{$percent}\" {$this->format($answer->answerformat)}>\n";
         $output .= $this->writetext($answer->answer, 3);
         $output .= $this->write_files($answer->answerfiles);
         $output .= "      <feedback {$this->format($answer->feedbackformat)}>\n";
index 7112db5..323aea7 100644 (file)
@@ -53,10 +53,10 @@ class question_import_form extends moodleform {
         foreach ($fileformatnames as $shortname => $fileformatname) {
             $currentgrp1 = array();
             $currentgrp1[] = $mform->createElement('radio', 'format', '', $fileformatname, $shortname);
-            $mform->addGroup($currentgrp1, "formathelp[$i]", '', array('<br />'), false);
+            $mform->addGroup($currentgrp1, "formathelp[{$i}]", '', array('<br />'), false);
 
             if (get_string_manager()->string_exists('pluginname_help', 'qformat_' . $shortname)) {
-                $mform->addHelpButton("formathelp[$i]", 'pluginname', 'qformat_' . $shortname);
+                $mform->addHelpButton("formathelp[{$i}]", 'pluginname', 'qformat_' . $shortname);
             }
 
             $i++ ;
index c4aa54f..00c8d23 100644 (file)
@@ -259,7 +259,7 @@ function question_preview_question_pluginfile($course, $context, $component,
 
     $fs = get_file_storage();
     $relativepath = implode('/', $args);
-    $fullpath = "/$context->id/$component/$filearea/$relativepath";
+    $fullpath = "/{$context->id}/{$component}/{$filearea}/{$relativepath}";
     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
         send_file_not_found();
     }
index 45cf3e6..a11f971 100644 (file)
@@ -188,7 +188,7 @@ if ($wizardnow !== '') {
     $mform = $qtypeobj->create_editing_form('question.php', $question, $category, $contexts, $formeditable);
 }
 $toform = fullclone($question); // send the question object and a few more parameters to the form
-$toform->category = "$category->id,$category->contextid";
+$toform->category = "{$category->id},{$category->contextid}";
 $toform->scrollpos = $scrollpos;
 if ($formeditable && $id){
     $toform->categorymoveto = $toform->category;
@@ -317,7 +317,7 @@ if ($mform->is_cancelled()) {
         $streditingmodule = get_string('editinga', 'moodle', $strmodule);
         $PAGE->navbar->add(get_string('modulenameplural', $cm->modname), new moodle_url('/mod/'.$cm->modname.'/index.php', array('id'=>$cm->course)));
         $PAGE->navbar->add(format_string($module->name), new moodle_url('/mod/'.$cm->modname.'/view.php', array('id'=>$cm->id)));
-        if (stripos($returnurl, "$CFG->wwwroot/mod/{$cm->modname}/view.php")!== 0){
+        if (stripos($returnurl, "{$CFG->wwwroot}/mod/{$cm->modname}/view.php")!== 0){
             //don't need this link if returnurl returns to view.php
             $PAGE->navbar->add($streditingmodule, $returnurl);
         }
index 00e0e38..0214f51 100644 (file)
@@ -75,12 +75,12 @@ class behat_question extends behat_question_base {
 
         // Split in two checkings to give more feedback in case of exception.
         $exception = new ElementNotFoundException($this->getSession(), 'Question "' . $questiondescription . '" ');
-        $questionxpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' qtext ')][contains(., $questiondescriptionliteral)]";
+        $questionxpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' qtext ')][contains(., {$questiondescriptionliteral})]";
         $this->find('xpath', $questionxpath, $exception);
 
         $exception = new ExpectationException('Question "' . $questiondescription . '" state is not "' . $state . '"', $this->getSession());
         $xpath = $questionxpath . "/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' que ')]" .
-            "/descendant::div[@class='state'][contains(., $stateliteral)]";
+            "/descendant::div[@class='state'][contains(., {$stateliteral})]";
         $this->find('xpath', $xpath, $exception);
     }
 }
index a5fdac3..67334c3 100644 (file)
@@ -104,14 +104,14 @@ class question_dataset_dependent_definitions_form extends question_wizard_form {
                         $this->qtypeobj->dataset_options($this->question, $datasetname);
                 unset($options['0']); // Mandatory...
                 $label = get_string('wildcard', 'qtype_calculated', $datasetname);
-                $mform->addElement('select', "dataset[$key]", $label, $options);
+                $mform->addElement('select', "dataset[{$key}]", $label, $options);
                 if (isset($datadefscat[$datasetname])) {
                     $mform->addElement('static', "there is a category",
                             get_string('sharedwildcard', 'qtype_calculated', $datasetname),
                             get_string('dataitemdefined', 'qtype_calculated',
                             $datadefscat[$datasetname]));
                 }
-                $mform->setDefault("dataset[$key]", $selected);
+                $mform->setDefault("dataset[{$key}]", $selected);
                 $datasetmenus[$datasetname] = '';
                 $key++;
             }
@@ -123,7 +123,7 @@ class question_dataset_dependent_definitions_form extends question_wizard_form {
                 list($options, $selected) = $this->qtypeobj->dataset_options(
                         $this->question, $datasetname, false);
                 $label = get_string('wildcard', 'qtype_calculated', $datasetname);
-                $mform->addElement('select', "dataset[$key]", $label, $options);
+                $mform->addElement('select', "dataset[{$key}]", $label, $options);
                 if (isset($datadefscat[$datasetname])) {
                     $mform->addElement('static', "there is a category",
                             get_string('sharedwildcard', 'qtype_calculated', $datasetname),
@@ -131,7 +131,7 @@ class question_dataset_dependent_definitions_form extends question_wizard_form {
                                     $datadefscat[$datasetname]));
                 }
 
-                $mform->setDefault("dataset[$key]", $selected);
+                $mform->setDefault("dataset[{$key}]", $selected);
                 $datasetmenus[$datasetname] = '';
                 $key++;
             }
index 4fba8f3..3482894 100644 (file)
@@ -146,16 +146,16 @@ class question_dataset_dependent_items_form extends question_wizard_form {
             } else {
                 $name = get_string('wildcard', 'qtype_calculated', $datasetdef->name);
             }
-            $mform->addElement('text', "number[$j]", $name);
-            $mform->setType("number[$j]", PARAM_RAW); // This parameter will be validated in validation().
+            $mform->addElement('text', "number[{$j}]", $name);
+            $mform->setType("number[{$j}]", PARAM_RAW); // This parameter will be validated in validation().
             $this->qtypeobj->custom_generator_tools_part($mform, $idx, $j);
             $idx++;
-            $mform->addElement('hidden', "definition[$j]");
-            $mform->setType("definition[$j]", PARAM_RAW);
-            $mform->addElement('hidden', "itemid[$j]");
-            $mform->setType("itemid[$j]", PARAM_RAW);
-            $mform->addElement('static', "divider[$j]", '', '<hr />');
-            $mform->setType("divider[$j]", PARAM_RAW);
+            $mform->addElement('hidden', "definition[{$j}]");
+            $mform->setType("definition[{$j}]", PARAM_RAW);
+            $mform->addElement('hidden', "itemid[{$j}]");
+            $mform->setType("itemid[{$j}]", PARAM_RAW);
+            $mform->addElement('static', "divider[{$j}]", '', '<hr />');
+            $mform->setType("divider[{$j}]", PARAM_RAW);
             $j++;
         }
 
@@ -218,14 +218,14 @@ class question_dataset_dependent_items_form extends question_wizard_form {
         $addremoveoptions = array();
         $addremoveoptions['1']='1';
         for ($i=10; $i<=100; $i+=10) {
-             $addremoveoptions["$i"]="$i";
+             $addremoveoptions["{$i}"] = "{$i}";
         }
         $showoptions = Array();
         $showoptions['1']='1';
         $showoptions['2']='2';
         $showoptions['5']='5';
         for ($i=10; $i<=100; $i+=10) {
-             $showoptions["$i"]="$i";
+             $showoptions["{$i}"] = "{$i}";
         }
         $mform->addElement('header', 'addhdr', get_string('add', 'moodle'));
         $mform->closeHeaderBefore('addhdr');
@@ -287,35 +287,35 @@ class question_dataset_dependent_items_form extends question_wizard_form {
             foreach ($this->datasetdefs as $defkey => $datasetdef) {
                 if ($k > 0) {
                     if ($datasetdef->category == 0 ) {
-                        $mform->addElement('text', "number[$j]",
+                        $mform->addElement('text', "number[{$j}]",
                                 get_string('wildcard', 'qtype_calculated', $datasetdef->name));
                     } else {
-                        $mform->addElement('text', "number[$j]", get_string(
+                        $mform->addElement('text', "number[{$j}]", get_string(
                                 'sharedwildcard', 'qtype_calculated', $datasetdef->name));
                     }
 
                 } else {
-                    $mform->addElement('hidden', "number[$j]" , '');
+                    $mform->addElement('hidden', "number[{$j}]" , '');
                 }
-                $mform->setType("number[$j]", PARAM_RAW); // This parameter will be validated in validation().
-                $mform->addElement('hidden', "itemid[$j]");
-                $mform->setType("itemid[$j]", PARAM_INT);
+                $mform->setType("number[{$j}]", PARAM_RAW); // This parameter will be validated in validation().
+                $mform->addElement('hidden', "itemid[{$j}]");
+                $mform->setType("itemid[{$j}]", PARAM_INT);
 
-                $mform->addElement('hidden', "definition[$j]");
-                $mform->setType("definition[$j]", PARAM_NOTAGS);
+                $mform->addElement('hidden', "definition[{$j}]");
+                $mform->setType("definition[{$j}]", PARAM_NOTAGS);
                 $data[$datasetdef->name] =$datasetdef->items[$i]->value;
 
                 $j--;
             }
             if ('' != $strquestionlabel && ($k > 0 )) {
                 // ... $this->outsidelimit || !empty($this->numbererrors ).
-                $repeated[] = $mform->addElement('static', "answercomment[$i]", $strquestionlabel);
+                $repeated[] = $mform->addElement('static', "answercomment[{$i}]", $strquestionlabel);
                 // Decode equations in question text.
                 $qtext = $this->qtypeobj->substitute_variables(
                         $this->question->questiontext, $data);
                 $textequations = $this->qtypeobj->find_math_equations($qtext);
                 if ($textequations != '' && count($textequations) > 0 ) {
-                    $mform->addElement('static', "divider1[$j]", '',
+                    $mform->addElement('static', "divider1[{$j}]", '',
                             'Formulas {=..} in question text');
                     foreach ($textequations as $key => $equation) {
                         if ($formulaerrors = qtype_calculated_find_formula_errors($equation)) {
@@ -324,7 +324,7 @@ class question_dataset_dependent_items_form extends question_wizard_form {
                             eval('$str = '.$equation.';');
                         }
                         $equation = shorten_text($equation, 17, true);
-                        $mform->addElement('static', "textequation", "{=$equation}", "=".$str);
+                        $mform->addElement('static', "textequation", "{={$equation}}", "=".$str);
                     }
                 }
 
@@ -408,9 +408,9 @@ class question_dataset_dependent_items_form extends question_wizard_form {
             $data = array();
             foreach ($this->datasetdefs as $defid => $datasetdef) {
                 if (isset($datasetdef->items[$itemnumber])) {
-                    $formdata["number[$j]"] = $datasetdef->items[$itemnumber]->value;
-                    $formdata["definition[$j]"] = $defid;
-                    $formdata["itemid[$j]"] = $datasetdef->items[$itemnumber]->id;
+                    $formdata["number[{$j}]"] = $datasetdef->items[$itemnumber]->value;
+                    $formdata["definition[{$j}]"] = $defid;
+                    $formdata["itemid[{$j}]"] = $datasetdef->items[$itemnumber]->id;
                     $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value;
                 }
                 $j--;
@@ -438,15 +438,15 @@ class question_dataset_dependent_items_form extends question_wizard_form {
             foreach ($this->datasetdefs as $defid => $datasetdef) {
                 if (!optional_param('updatedatasets', false, PARAM_BOOL) &&
                         !optional_param('updateanswers', false, PARAM_BOOL)) {
-                    $formdata["number[$j]"] = $this->qtypeobj->generate_dataset_item(
+                    $formdata["number[{$j}]"] = $this->qtypeobj->generate_dataset_item(
                             $datasetdef->options);
                 } else {
-                    $formdata["number[$j]"] = $this->_form->getElementValue("number[$j]");
+                    $formdata["number[{$j}]"] = $this->_form->getElementValue("number[{$j}]");
                 }
-                $formdata["definition[$j]"] = $defid;
-                $formdata["itemid[$j]"] = isset($datasetdef->items[$itemnumber]) ?
+                $formdata["definition[{$j}]"] = $defid;
+                $formdata["itemid[{$j}]"] = isset($datasetdef->items[$itemnumber]) ?
                         $datasetdef->items[$itemnumber]->id : 0;
-                $data[$datasetdef->name] = $formdata["number[$j]"];
+                $data[$datasetdef->name] = $formdata["number[{$j}]"];
                 $j++;
             }
         }
@@ -459,9 +459,9 @@ class question_dataset_dependent_items_form extends question_wizard_form {
             $itemnumber = $this->noofitems + 1;
             foreach ($this->datasetdefs as $defid => $datasetdef) {
                 if (isset($datasetdef->items[$itemnumber])) {
-                    $formdata["number[$j]"] = $datasetdef->items[$itemnumber]->value;
-                    $formdata["definition[$j]"] = $defid;
-                    $formdata["itemid[$j]"] = $datasetdef->items[$itemnumber]->id;
+                    $formdata["number[{$j}]"] = $datasetdef->items[$itemnumber]->value;
+                    $formdata["definition[{$j}]"] = $defid;
+                    $formdata["itemid[{$j}]"] = $datasetdef->items[$itemnumber]->id;
                     $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value;
                 }
                 $j++;
index 853030d..dcc48a1 100644 (file)
@@ -199,9 +199,9 @@ class qtype_calculated_edit_form extends qtype_numerical_edit_form {
         $key = 0;
         foreach ($question->options->answers as $answer) {
             // See comment in the parent method about this hack.
-            unset($this->_form->_defaultValues["tolerancetype[$key]"]);
-            unset($this->_form->_defaultValues["correctanswerlength[$key]"]);
-            unset($this->_form->_defaultValues["correctanswerformat[$key]"]);
+            unset($this->_form->_defaultValues["tolerancetype[{$key}]"]);
+            unset($this->_form->_defaultValues["correctanswerlength[{$key}]"]);
+            unset($this->_form->_defaultValues["correctanswerformat[{$key}]"]);
 
             $question->tolerancetype[$key]       = $answer->tolerancetype;
             $question->correctanswerlength[$key] = $answer->correctanswerlength;
@@ -216,36 +216,39 @@ class qtype_calculated_edit_form extends qtype_numerical_edit_form {
         return 'calculated';
     }
 
-    public function validation($data, $files) {
-
-        // Verifying for errors in {=...} in question text.
-        $qtext = "";
-        $qtextremaining = $data['questiontext']['text'];
-        $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
-        foreach ($possibledatasets as $name => $value) {
-            $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
-        }
-        while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
-            $qtextsplits = explode($regs1[0], $qtextremaining, 2);
-            $qtext = $qtext.$qtextsplits[0];
-            $qtextremaining = $qtextsplits[1];
-            if (!empty($regs1[1]) && $formulaerrors =
-                    qtype_calculated_find_formula_errors($regs1[1])) {
-                if (!isset($errors['questiontext'])) {
-                    $errors['questiontext'] = $formulaerrors.':'.$regs1[1];
-                } else {
-                    $errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
-                }
-            }
+    /**
+     * Validate the equations in the some question content.
+     * @param array $errors where errors are being accumulated.
+     * @param string $field the field being validated.
+     * @param string $text the content of that field.
+     * @return array the updated $errors array.
+     */
+    protected function validate_text($errors, $field, $text) {
+        $problems = qtype_calculated_find_formula_errors_in_text($text);
+        if ($problems) {
+            $errors[$field] = $problems;
         }
+        return $errors;
+    }
 
+    public function validation($data, $files) {
         $errors = parent::validation($data, $files);
 
+        // Verifying for errors in {=...} in question text.
+        $errors = $this->validate_text($errors, 'questiontext', $data['questiontext']['text']);
+        $errors = $this->validate_text($errors, 'generalfeedback', $data['generalfeedback']['text']);
+
         // Check that the answers use datasets.
         $answers = $data['answer'];
         $mandatorydatasets = array();
         foreach ($answers as $key => $answer) {
+            $problems = qtype_calculated_find_formula_errors($answer);
+            if ($problems) {
+                $errors['answeroptions['.$key.']'] = $problems;
+            }
             $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
+            $errors = $this->validate_text($errors, 'feedback[' . $key . ']',
+                    $data['feedback'][$key]['text']);
         }
         if (empty($mandatorydatasets)) {
             foreach ($answers as $key => $answer) {
index 71b25a6..40eb9ad 100644 (file)
@@ -278,6 +278,7 @@ class qtype_calculated_dataset_loader {
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qtype_calculated_variable_substituter {
+
     /** @var array variable name => value */
     protected $values;
 
@@ -384,8 +385,8 @@ class qtype_calculated_variable_substituter {
                     }
                 } else {
                     // Stick to plain numeric format.
-                    $answer *= "1e$p10";
-                    if (0.1 <= $answer / "1e$length") {
+                    $answer *= "1e{$p10}";
+                    if (0.1 <= $answer / "1e{$length}") {
                         $x = $sign.$answer;
                     } else {
                         // Could be an idea to add some zeros here.
@@ -465,108 +466,11 @@ class qtype_calculated_variable_substituter {
      * @return string the text with values substituted.
      */
     public function replace_expressions_in_text($text, $length = null, $format = null) {
-        $vs = $this; // Can't see to use $this in a PHP closure.
-        $text = preg_replace_callback('~\{=([^{}]*(?:\{[^{}]+}[^{}]*)*)}~',
+        $vs = $this; // Can't use $this in a PHP closure.
+        $text = preg_replace_callback(qtype_calculated::FORMULAS_IN_TEXT_REGEX,
                 function ($matches) use ($vs, $format, $length) {
                     return $vs->format_float($vs->calculate($matches[1]), $length, $format);
                 }, $text);
         return $this->substitute_values_pretty($text);
     }
-
-    /**
-     * Return an array describing any problems there are with an expression.
-     * Returns false if the expression is fine.
-     * @param string $formula an expression.
-     * @return array|false list of problems, or false if the exression is OK.
-     */
-    public function get_formula_errors($formula) {
-        // Validates the formula submitted from the question edit page.
-        // Returns false if everything is alright
-        // otherwise it constructs an error message.
-        // Strip away dataset names.
-        while (preg_match('~\\{[[:alpha:]][^>} <{"\']*\\}~', $formula, $regs)) {
-            $formula = str_replace($regs[0], '1', $formula);
-        }
-
-        // Strip away empty space and lowercase it.
-        $formula = strtolower(str_replace(' ', '', $formula));
-
-        $safeoperatorchar = '-+/*%>:^\~<?=&|!'; /* */
-        $operatorornumber = "[$safeoperatorchar.0-9eE]";
-
-        while (preg_match("~(^|[$safeoperatorchar,(])([a-z0-9_]*)" .
-                "\\(($operatorornumber+(,$operatorornumber+((,$operatorornumber+)+)?)?)?\\)~",
-            $formula, $regs)) {
-            switch ($regs[2]) {
-                // Simple parenthesis.
-                case '':
-                    if ((isset($regs[4]) && $regs[4]) || strlen($regs[3]) == 0) {
-                        return get_string('illegalformulasyntax', 'qtype_calculated', $regs[0]);
-                    }
-                    break;
-
-                    // Zero argument functions.
-                case 'pi':
-                    if ($regs[3]) {
-                        return get_string('functiontakesnoargs', 'qtype_calculated', $regs[2]);
-                    }
-                    break;
-
-                    // Single argument functions (the most common case).
-                case 'abs': case 'acos': case 'acosh': case 'asin': case 'asinh':
-                case 'atan': case 'atanh': case 'bindec': case 'ceil': case 'cos':
-                case 'cosh': case 'decbin': case 'decoct': case 'deg2rad':
-                case 'exp': case 'expm1': case 'floor': case 'is_finite':
-                case 'is_infinite': case 'is_nan': case 'log10': case 'log1p':
-                case 'octdec': case 'rad2deg': case 'sin': case 'sinh': case 'sqrt':
-                case 'tan': case 'tanh':
-                    if (!empty($regs[4]) || empty($regs[3])) {
-                        return get_string('functiontakesonearg', 'qtype_calculated', $regs[2]);
-                    }
-                    break;
-
-                    // Functions that take one or two arguments.
-                case 'log': case 'round':
-                    if (!empty($regs[5]) || empty($regs[3])) {
-                        return get_string('functiontakesoneortwoargs', 'qtype_calculated',
-                                $regs[2]);
-                    }
-                    break;
-
-                    // Functions that must have two arguments.
-                case 'atan2': case 'fmod': case 'pow':
-                    if (!empty($regs[5]) || empty($regs[4])) {
-                        return get_string('functiontakestwoargs', 'qtype_calculated', $regs[2]);
-                    }
-                    break;
-
-                    // Functions that take two or more arguments.
-                case 'min': case 'max':
-                    if (empty($regs[4])) {
-                        return get_string('functiontakesatleasttwo', 'qtype_calculated', $regs[2]);
-                    }
-                    break;
-
-                default:
-                    return get_string('unsupportedformulafunction', 'qtype_calculated', $regs[2]);
-            }
-
-            // Exchange the function call with '1' and then check for another function call.
-
-            if ($regs[1]) {
-                // The function call is proceeded by an operator.
-                $formula = str_replace($regs[0], $regs[1] . '1', $formula);
-            } else {
-                // The function call starts the formula.
-                $formula = preg_replace("~^$regs[2]\\([^)]*\\)~", '1', $formula);
-            }
-        }
-
-        if (preg_match("~[^$safeoperatorchar.0-9eE]+~", $formula, $regs)) {
-            return get_string('illegalformulasyntax', 'qtype_calculated', $regs[0]);
-        } else {
-            // Formula just might be valid.
-            return false;
-        }
-    }
-}
\ No newline at end of file
+}
index b2e3b2a..9d42130 100644 (file)
@@ -37,6 +37,9 @@ require_once($CFG->dirroot . '/question/type/numerical/question.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qtype_calculated extends question_type {
+    /** Regular expression that finds the formulas in content. */
+    const FORMULAS_IN_TEXT_REGEX = '~\{=([^{}]*(?:\{[^{}]+}[^{}]*)*)\}~';
+
     const MAX_DATASET_ITEMS = 100;
 
     public $wizardpagesnumber = 3;
@@ -119,7 +122,7 @@ class qtype_calculated extends question_type {
                             $def->number_of_items = $n;
                         }
                     }
-                    $datasetdefs["1-$r->category-$r->name"] = $def;
+                    $datasetdefs["1-{$r->category}-{$r->name}"] = $def;
                 }
             }
         }
@@ -404,11 +407,11 @@ class qtype_calculated extends question_type {
         // See where we're coming from.
         switch($form->wizardpage) {
             case 'question':
-                require("$CFG->dirroot/question/type/calculated/datasetdefinitions.php");
+                require("{$CFG->dirroot}/question/type/calculated/datasetdefinitions.php");
                 break;
             case 'datasetdefinitions':
             case 'datasetitems':
-                require("$CFG->dirroot/question/type/calculated/datasetitems.php");
+                require("{$CFG->dirroot}/question/type/calculated/datasetitems.php");
                 break;
             default:
                 print_error('invalidwizardpage', 'question');
@@ -432,15 +435,15 @@ class qtype_calculated extends question_type {
         // See where we're coming from.
         switch($wizardnow) {
             case 'datasetdefinitions':
-                require("$CFG->dirroot/question/type/calculated/datasetdefinitions_form.php");
+                require("{$CFG->dirroot}/question/type/calculated/datasetdefinitions_form.php");
                 $mform = new question_dataset_dependent_definitions_form(
-                        "$submiturl?wizardnow=datasetdefinitions", $question);
+                        "{$submiturl}?wizardnow=datasetdefinitions", $question);
                 break;
             case 'datasetitems':
-                require("$CFG->dirroot/question/type/calculated/datasetitems_form.php");
+                require("{$CFG->dirroot}/question/type/calculated/datasetitems_form.php");
                 $regenerate = optional_param('forceregeneration', false, PARAM_BOOL);
                 $mform = new question_dataset_dependent_items_form(
-                        "$submiturl?wizardnow=datasetitems", $question, $regenerate);
+                        "{$submiturl}?wizardnow=datasetitems", $question, $regenerate);
                 break;
             default:
                 print_error('invalidwizardpage', 'question');
@@ -481,6 +484,36 @@ class qtype_calculated extends question_type {
         $mform->display();
     }
 
+    /**
+     * Verify that the equations in part of the question are OK.
+     * We throw an exception here because this should have already been validated
+     * by the form. This is just a last line of defence to prevent a question
+     * being stored in the database if it has bad formulas. This saves us from,
+     * for example, malicious imports.
+     * @param string $text containing equations.
+     */
+    protected function validate_text($text) {
+        $error = qtype_calculated_find_formula_errors_in_text($text);
+        if ($error) {
+            throw new coding_exception($error);
+        }
+    }
+
+    /**
+     * Verify that an answer is OK.
+     * We throw an exception here because this should have already been validated
+     * by the form. This is just a last line of defence to prevent a question
+     * being stored in the database if it has bad formulas. This saves us from,
+     * for example, malicious imports.
+     * @param string $text containing equations.
+     */
+    protected function validate_answer($answer) {
+        $error = qtype_calculated_find_formula_errors($answer);
+        if ($error) {
+            throw new coding_exception($error);
+        }
+    }
+
     /**
      * This method prepare the $datasets in a format similar to dadatesetdefinitions_form.php
      * so that they can be saved
@@ -494,11 +527,12 @@ class qtype_calculated extends question_type {
      * @param int $questionfromid default = '0'
      */
     public function preparedatasets($form , $questionfromid = '0') {
+
         // The dataset names present in the edit_question_form and edit_calculated_form
         // are retrieved.
         $possibledatasets = $this->find_dataset_names($form->questiontext);
         $mandatorydatasets = array();
-        foreach ($form->answers as $answer) {
+        foreach ($form->answers as $key => $answer) {
             $mandatorydatasets += $this->find_dataset_names($answer);
         }
         // If there are identical datasetdefs already saved in the original question
@@ -587,8 +621,15 @@ class qtype_calculated extends question_type {
      */
     public function save_question($question, $form) {
         global $DB;
+
+        if (isset($form->correctfeedback)) {
+            $this->validate_text($form->correctfeedback['text']);
+            $this->validate_text($form->partiallycorrectfeedback['text']);
+            $this->validate_text($form->incorrectfeedback['text']);
+        }
+
         if ($this->wizardpagesnumber() == 1 || $question->qtype == 'calculatedsimple') {
-                $question = parent::save_question($question, $form);
+            $question = parent::save_question($question, $form);
             return $question;
         }
 
@@ -607,6 +648,14 @@ class qtype_calculated extends question_type {
             case '' :
             case 'question': // Coming from the first page, creating the second.
                 if (empty($form->id)) { // or a new question $form->id is empty.
+                    // Make it impossible to save bad formulas anywhere.
+                    $this->validate_text($form->questiontext['text']);
+                    $this->validate_text($form->generalfeedback['text']);
+                    foreach ($form->answer as $key => $answer) {
+                        $this->validate_answer($answer);
+                        $this->validate_text($form->feedback[$key]['text']);
+                    }
+
                     $question = parent::save_question($question, $form);
                     // Prepare the datasets using default $questionfromid.
                     $this->preparedatasets($form);
@@ -706,22 +755,22 @@ class qtype_calculated extends question_type {
     public function custom_generator_tools_part($mform, $idx, $j) {
 
         $minmaxgrp = array();
-        $minmaxgrp[] = $mform->createElement('text', "calcmin[$idx]",
+        $minmaxgrp[] = $mform->createElement('text', "calcmin[{$idx}]",
                 get_string('calcmin', 'qtype_calculated'));
-        $minmaxgrp[] = $mform->createElement('text', "calcmax[$idx]",
+        $minmaxgrp[] = $mform->createElement('text', "calcmax[{$idx}]",
                 get_string('calcmax', 'qtype_calculated'));
         $mform->addGroup($minmaxgrp, 'minmaxgrp',
                 get_string('minmax', 'qtype_calculated'), ' - ', false);
-        $mform->setType("calcmin[$idx]", PARAM_FLOAT);
-        $mform->setType("calcmax[$idx]", PARAM_FLOAT);
+        $mform->setType("calcmin[{$idx}]", PARAM_FLOAT);
+        $mform->setType("calcmax[{$idx}]", PARAM_FLOAT);
 
         $precisionoptions = range(0, 10);
-        $mform->addElement('select', "calclength[$idx]",
+        $mform->addElement('select', "calclength[{$idx}]",
                 get_string('calclength', 'qtype_calculated'), $precisionoptions);
 
         $distriboptions = array('uniform' => get_string('uniform', 'qtype_calculated'),
                 'loguniform' => get_string('loguniform', 'qtype_calculated'));
-        $mform->addElement('select', "calcdistribution[$idx]",
+        $mform->addElement('select', "calcdistribution[{$idx}]",
                 get_string('calcdistribution', 'qtype_calculated'), $distriboptions);
     }
 
@@ -730,11 +779,11 @@ class qtype_calculated extends question_type {
         foreach ($datasetdefs as $datasetdef) {
             if (preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~',
                     $datasetdef->options, $regs)) {
-                $defid = "$datasetdef->type-$datasetdef->category-$datasetdef->name";
-                $formdata["calcdistribution[$idx]"] = $regs[1];
-                $formdata["calcmin[$idx]"] = $regs[2];
-                $formdata["calcmax[$idx]"] = $regs[3];
-                $formdata["calclength[$idx]"] = $regs[4];
+                $defid = "{$datasetdef->type}-{$datasetdef->category}-{$datasetdef->name}";
+                $formdata["calcdistribution[{$idx}]"] = $regs[1];
+                $formdata["calcmin[{$idx}]"] = $regs[2];
+                $formdata["calcmax[{$idx}]"] = $regs[3];
+                $formdata["calclength[{$idx}]"] = $regs[4];
             }
             $idx++;
         }
@@ -745,7 +794,7 @@ class qtype_calculated extends question_type {
         global $OUTPUT;
         if (preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~',
                 $datasetdef->options, $regs)) {
-            $defid = "$datasetdef->type-$datasetdef->category-$datasetdef->name";
+            $defid = "{$datasetdef->type}-{$datasetdef->category}-{$datasetdef->name}";
             for ($i = 0; $i<10; ++$i) {
                 $lengthoptions[$i] = get_string(($regs[1] == 'uniform'
                     ? 'decimals'
@@ -761,10 +810,10 @@ class qtype_calculated extends question_type {
                 'menucalcdistribution', false, array('class' => 'accesshide'));
             $menu2 .= html_writer::select($options, 'calcdistribution[]', $regs[1], null);
             return '<input type="submit" onclick="'
-                . "getElementById('addform').regenerateddefid.value='$defid'; return true;"
+                . "getElementById('addform').regenerateddefid.value='{$defid}'; return true;"
                 .'" value="'. get_string('generatevalue', 'qtype_calculated') . '"/><br/>'
                 . '<input type="text" size="3" name="calcmin[]" '
-                . " value=\"$regs[2]\"/> &amp; <input name=\"calcmax[]\" "
+                . " value=\"{$regs[2]}\"/> &amp; <input name=\"calcmax[]\" "
                 . ' type="text" size="3" value="' . $regs[3] .'"/> '
                 . $menu1 . '<br/>'
                 . $menu2;
@@ -940,7 +989,7 @@ class qtype_calculated extends question_type {
                 // Fix regenerate for this datadefs.
                 $defregenerate = 0;
                 if ($synchronize &&
-                        !empty ($fromform->nextpageparam["datasetregenerate[$datasetdef->name"])) {
+                        !empty ($fromform->nextpageparam["datasetregenerate[{$datasetdef->name}"])) {
                     $defregenerate = 1;
                 } else if (!$synchronize &&
                         (($regenerate == 1 && $datasetdef->category == 0) ||$regenerate == 2)) {
@@ -1075,7 +1124,7 @@ class qtype_calculated extends question_type {
             }
             if ($answer->min === '') {
                 // This should mean that something is wrong.
-                $comment->stranswers[$key] = " $formattedanswer->answer".'<br/><br/>';
+                $comment->stranswers[$key] = " {$formattedanswer->answer}".'<br/><br/>';
             } else if ($formula === '*') {
                 $comment->stranswers[$key] = $formula . ' = ' .
                         get_string('anyvalue', 'qtype_calculated') . '<br/><br/><br/>';
@@ -1130,7 +1179,7 @@ class qtype_calculated extends question_type {
         }
         if (!$selected) {
             if ($mandatory) {
-                $selected =  "1-0-$name"; // Default.
+                $selected =  "1-0-{$name}"; // Default.
             } else {
                 $selected = '0'; // Default.
             }
@@ -1222,7 +1271,7 @@ class qtype_calculated extends question_type {
                   ORDER BY i.id";
             if ($records = $DB->get_records_sql($sql, array($questionid))) {
                 foreach ($records as $r) {
-                    $datasetdefs["$r->type-$r->category-$r->name"] = $r;
+                    $datasetdefs["{$r->type}-{$r->category}-{$r->name}"] = $r;
                 }
             }
         }
@@ -1465,8 +1514,8 @@ class qtype_calculated extends question_type {
         $options['0'] = get_string($prefix.'nodataset', $langfile);
         // New question no local.
         if (!isset($form->id) || $form->id == 0) {
-            $key = "$type-0-$name";
-            $options[$key] = get_string($prefix."newlocal$type", $langfile);
+            $key = "{$type}-0-{$name}";
+            $options[$key] = get_string($prefix."newlocal{$type}", $langfile);
             $currentdatasetdef = new stdClass();
             $currentdatasetdef->type = '0';
         } else {
@@ -1479,12 +1528,12 @@ class qtype_calculated extends question_type {
                 $currentdatasetdef = new stdClass();
                 $currentdatasetdef->type = '0';
             }
-            $key = "$type-0-$name";
+            $key = "{$type}-0-{$name}";
             if ($currentdatasetdef->type == $type
                     and $currentdatasetdef->category == 0) {
-                $options[$key] = get_string($prefix."keptlocal$type", $langfile);
+                $options[$key] = get_string($prefix."keptlocal{$type}", $langfile);
             } else {
-                $options[$key] = get_string($prefix."newlocal$type", $langfile);
+                $options[$key] = get_string($prefix."newlocal{$type}", $langfile);
             }
         }
         // Construct question category options.
@@ -1497,21 +1546,21 @@ class qtype_calculated extends question_type {
             AND a.category = ?
             AND a.name = ?", array($form->category, $name));
         $type = 1;
-        $key = "$type-$form->category-$name";
+        $key = "{$type}-{$form->category}-{$name}";
         if (!empty($categorydatasetdefs)) {
             // There is at least one with the same name.
             if (isset($form->id) && isset($categorydatasetdefs[$form->id])) {
                 // It is already used by this question.
-                $options[$key] = get_string($prefix."keptcategory$type", $langfile);
+                $options[$key] = get_string($prefix."keptcategory{$type}", $langfile);
             } else {
-                $options[$key] = get_string($prefix."existingcategory$type", $langfile);
+                $options[$key] = get_string($prefix."existingcategory{$type}", $langfile);
             }
         } else {
-            $options[$key] = get_string($prefix."newcategory$type", $langfile);
+            $options[$key] = get_string($prefix."newcategory{$type}", $langfile);
         }
         // All done!
         return array($options, $currentdatasetdef->type
-            ? "$currentdatasetdef->type-$currentdatasetdef->category-$name"
+            ? "{$currentdatasetdef->type}-{$currentdatasetdef->category}-{$name}"
             : '');
     }
 
@@ -1541,8 +1590,8 @@ class qtype_calculated extends question_type {
                      WHERE i.id = d.datasetdefinition AND i.category = ?";
             if ($records = $DB->get_records_sql($sql, array($form->category))) {
                 foreach ($records as $r) {
-                    if (!isset ($datasetdefs["$r->name"])) {
-                        $datasetdefs["$r->name"] = $r->itemcount;
+                    if (!isset ($datasetdefs["{$r->name}"])) {
+                        $datasetdefs["{$r->name}"] = $r->itemcount;
               &