Merge branch 'MDL-64484' of https://github.com/paulholden/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 12 Jun 2019 17:43:52 +0000 (19:43 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 12 Jun 2019 17:43:52 +0000 (19:43 +0200)
82 files changed:
.eslintignore
.stylelintignore
admin/cli/upgrade.php
admin/settings/frontpage.php
admin/settings/security.php
admin/tool/analytics/classes/output/models_list.php
admin/tool/analytics/classes/output/renderer.php
admin/tool/analytics/cli/enable_model.php
admin/tool/analytics/cli/evaluate_model.php
admin/tool/analytics/lang/en/deprecated.txt [new file with mode: 0644]
admin/tool/analytics/lang/en/tool_analytics.php
admin/tool/analytics/model.php
admin/tool/lp/tests/externallib_test.php
admin/upgrade.txt
analytics/upgrade.txt
auth/email/classes/external.php
blocks/myoverview/templates/course-action-menu.mustache
blocks/recentlyaccesseditems/db/upgrade.php [new file with mode: 0644]
blocks/recentlyaccesseditems/lib.php [new file with mode: 0644]
blocks/recentlyaccesseditems/tests/externallib_test.php
blocks/recentlyaccesseditems/tests/privacy_test.php
blocks/recentlyaccesseditems/version.php
blocks/timeline/templates/event-list-item.mustache
calendar/classes/external/footer_options_exporter.php
calendar/managesubscriptions.php
cohort/tests/externallib_test.php
competency/tests/external_test.php
competency/tests/privacy_test.php
composer.json
composer.lock
course/classes/analytics/target/course_competencies.php
course/classes/analytics/target/course_completion.php
course/classes/analytics/target/course_dropout.php
course/classes/analytics/target/course_enrolments.php
course/classes/analytics/target/course_gradetopass.php
enrol/ldap/cli/sync.php [deleted file]
enrol/ldap/lang/en/enrol_ldap.php
enrol/ldap/upgrade.txt
lang/en/admin.php
lang/en/analytics.php
lang/en/auth.php
lang/en/debug.php
lang/en/moodle.php
lib/amd/build/templates.min.js
lib/amd/src/templates.js
lib/behat/classes/partial_named_selector.php
lib/classes/lock/db_record_lock_factory.php
lib/classes/session/manager.php
lib/classes/uuid.php [new file with mode: 0644]
lib/db/upgrade.php
lib/mlbackend/php/lang/en/mlbackend_php.php
lib/outputlib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/pear/Auth/RADIUS.php [deleted file]
lib/pear/README.txt
lib/pear/README_MOODLE.txt
lib/recaptchalib.php [deleted file]
lib/setuplib.php
lib/tests/behat/behat_data_generators.php
lib/tests/setuplib_test.php
lib/tests/targets_test.php
lib/thirdpartylibs.xml
lib/upgrade.txt
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-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
message/tests/behat/mute_conversations.feature [new file with mode: 0644]
mod/assign/feedback/editpdf/locallib.php
mod/feedback/item/captcha/lib.php
mod/forum/backup/moodle2/backup_forum_stepslib.php
mod/lti/amd/build/tool_configure_controller.min.js
mod/lti/amd/src/tool_configure_controller.js
report/insights/lang/en/report_insights.php
report/security/lang/en/report_security.php
report/security/locallib.php
version.php

index a262404..5e12643 100644 (file)
@@ -27,7 +27,6 @@ lib/htmlpurifier/
 lib/jabber/
 lib/minify/matthiasmullie-minify/
 lib/minify/matthiasmullie-pathconverter/
-lib/pear/Auth/RADIUS.php
 lib/pear/Crypt/CHAP.php
 lib/pear/HTML/Common.php
 lib/pear/HTML/QuickForm.php
@@ -42,7 +41,6 @@ lib/yuilib/gallery/
 lib/jquery/
 lib/html2text/
 lib/markdown/
-lib/recaptchalib.php
 lib/xhprof/
 lib/horde/
 lib/requirejs/
index 6bcaefe..a22b9cc 100644 (file)
@@ -28,7 +28,6 @@ lib/htmlpurifier/
 lib/jabber/
 lib/minify/matthiasmullie-minify/
 lib/minify/matthiasmullie-pathconverter/
-lib/pear/Auth/RADIUS.php
 lib/pear/Crypt/CHAP.php
 lib/pear/HTML/Common.php
 lib/pear/HTML/QuickForm.php
@@ -43,7 +42,6 @@ lib/yuilib/gallery/
 lib/jquery/
 lib/html2text/
 lib/markdown/
-lib/recaptchalib.php
 lib/xhprof/
 lib/horde/
 lib/requirejs/
index 8e439c5..d827e6d 100644 (file)
@@ -52,7 +52,8 @@ list($options, $unrecognized) = cli_get_params(
         'allow-unstable'    => false,
         'help'              => false,
         'lang'              => $lang,
-        'verbose-settings'  => false
+        'verbose-settings'  => false,
+        'is-pending'        => false,
     ),
     array(
         'h' => 'help'
@@ -88,6 +89,8 @@ Options:
 --verbose-settings    Show new settings values. By default only the name of
                       new core or plugin settings are displayed. This option
                       outputs the new values as well as the setting name.
+--is-pending          If an upgrade is needed it exits with an error code of
+                      2 so it distinct from other types of errors.
 -h, --help            Print out this help
 
 Example:
@@ -116,6 +119,10 @@ if (!moodle_needs_upgrading()) {
     cli_error(get_string('cliupgradenoneed', 'core_admin', $newversion), 0);
 }
 
+if ($options['is-pending']) {
+    cli_error(get_string('cliupgradepending', 'core_admin'), 2);
+}
+
 // Test environment first.
 list($envstatus, $environment_results) = check_moodle_environment(normalize_version($release), ENV_SELECT_RELEASE);
 if (!$envstatus) {
index d435022..3eed171 100644 (file)
@@ -58,7 +58,7 @@ if (!during_initial_install()) { //do not use during installation
         foreach ($roles as $role) {
             if (empty($role->archetype) or $role->archetype === 'guest' or $role->archetype === 'frontpage' or $role->archetype === 'student') {
                 $options[$role->id] = $role->localname;
-                if ($role->archetype === 'frontpage') {
+                if ($role->archetype === 'frontpage' && !$defaultfrontpageroleid) {
                     $defaultfrontpageroleid = $role->id;
                 }
             }
index a6ac61f..564845b 100644 (file)
@@ -17,7 +17,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configcheckbox('forcelogin', new lang_string('forcelogin', 'admin'), new lang_string('configforcelogin', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('forceloginforprofiles', new lang_string('forceloginforprofiles', 'admin'), new lang_string('configforceloginforprofiles', 'admin'), 1));
     $temp->add(new admin_setting_configcheckbox('forceloginforprofileimage', new lang_string('forceloginforprofileimage', 'admin'), new lang_string('forceloginforprofileimage_help', 'admin'), 0));
-    $temp->add(new admin_setting_configcheckbox('opentogoogle', new lang_string('opentogoogle', 'admin'), new lang_string('configopentogoogle', 'admin'), 0));
+    $temp->add(new admin_setting_configcheckbox('opentowebcrawlers', new lang_string('opentowebcrawlers', 'admin'), new lang_string('configopentowebcrawlers', 'admin'), 0));
     $temp->add(new admin_setting_configselect('allowindexing', new lang_string('allowindexing', 'admin'), new lang_string('allowindexing_desc', 'admin'),
         0,
         array(0 => new lang_string('allowindexingexceptlogin', 'admin'),
index 9c918ce..fbbc227 100644 (file)
@@ -156,7 +156,7 @@ class models_list implements \renderable, \templatable {
                     $modeldata->timesplittinghelp = $helpicon->export_for_template($output);
                 } else {
                     // We really want to encourage developers to add help to their time splitting methods.
-                    debugging("The time splitting method '{$modeldata->timesplitting}' should include a '{$identifier}_help'
+                    debugging("The analysis interval '{$modeldata->timesplitting}' should include a '{$identifier}_help'
                         string to describe its purpose.", DEBUG_DEVELOPER);
                 }
             } else {
@@ -216,10 +216,11 @@ class models_list implements \renderable, \templatable {
 
             // Get predictions.
             if (!$onlycli && $modeldata->enabled && !empty($modeldata->timesplitting)) {
-                $urlparams['action'] = 'getpredictions';
+                $urlparams['action'] = 'scheduledanalysis';
                 $url = new \moodle_url('model.php', $urlparams);
-                $icon = new \action_menu_link_secondary($url, new \pix_icon('i/notifications',
-                    get_string('getpredictions', 'tool_analytics')), get_string('getpredictions', 'tool_analytics'));
+                $icon = new \action_menu_link_secondary($url,
+                    new \pix_icon('i/notifications', get_string('executescheduledanalysis', 'tool_analytics')),
+                    get_string('executescheduledanalysis', 'tool_analytics'));
                 $actionsmenu->add($icon);
             }
 
index 67c4406..743844e 100644 (file)
@@ -90,9 +90,9 @@ class renderer extends plugin_renderer_base {
                 $langstrdata = (object)array('name' => $timesplitting->get_name(), 'id' => $timesplittingid);
 
                 if (CLI_SCRIPT) {
-                    $output .= $OUTPUT->heading(get_string('getpredictionsresultscli', 'tool_analytics', $langstrdata), 3);
+                    $output .= $OUTPUT->heading(get_string('scheduledanalysisresultscli', 'tool_analytics', $langstrdata), 3);
                 } else {
-                    $output .= $OUTPUT->heading(get_string('getpredictionsresults', 'tool_analytics', $langstrdata), 3);
+                    $output .= $OUTPUT->heading(get_string('scheduledanalysisresults', 'tool_analytics', $langstrdata), 3);
                 }
             }
 
index e31dfec..ed8e3fc 100644 (file)
@@ -32,20 +32,20 @@ $help = "Enables the provided model.
 Options:
 --modelid           Model id
 --list              List models
---timesplitting     Time splitting method full class name
+--analysisinterval  Time splitting method full class name
 -h, --help          Print out this help
 
 Example:
-\$ php admin/tool/analytics/cli/enable_model.php --modelid=1 --timesplitting=\"\\core\\analytics\\time_splitting\\quarters\"
+\$ php admin/tool/analytics/cli/enable_model.php --modelid=1 --analysisinterval=\"\\core\\analytics\\time_splitting\\quarters\"
 ";
 
 // Now get cli options.
 list($options, $unrecognized) = cli_get_params(
     array(
-        'help'            => false,
-        'list'            => false,
-        'modelid'         => false,
-        'timesplitting'   => false
+        'help'             => false,
+        'list'             => false,
+        'modelid'          => false,
+        'analysisinterval' => false
     ),
     array(
         'h' => 'help',
@@ -62,7 +62,7 @@ if ($options['list'] || $options['modelid'] === false) {
     exit(0);
 }
 
-if ($options['timesplitting'] === false) {
+if ($options['analysisinterval'] === false) {
     echo $help;
     exit(0);
 }
@@ -73,7 +73,7 @@ if ($options['timesplitting'] === false) {
 $model = new \core_analytics\model($options['modelid']);
 
 // Evaluate its suitability to predict accurately.
-$model->enable($options['timesplitting']);
+$model->enable($options['analysisinterval']);
 
 cli_heading(get_string('success'));
 exit(0);
index 8473aba..067320e 100644 (file)
@@ -33,7 +33,7 @@ Options:
 --modelid              Model id
 --list                 List models
 --non-interactive      Not interactive questions
---timesplitting        Restrict the evaluation to 1 single time splitting method (Optional)
+--analysisinterval     Restrict the evaluation to 1 single analysis interval (Optional)
 --filter               Analyser dependant. e.g. A courseid would evaluate the model using a single course (Optional)
 --mode                 'configuration' or 'trainedmodel'. You can only use mode=trainedmodel when the trained" .
     " model was imported" . "
@@ -42,7 +42,7 @@ Options:
 -h, --help             Print out this help
 
 Example:
-\$ php admin/tool/analytics/cli/evaluate_model.php --modelid=1 --timesplitting='\\core\\analytics\\time_splitting\\quarters' --filter=123,321
+\$ php admin/tool/analytics/cli/evaluate_model.php --modelid=1 --analysisinterval='\\core\\analytics\\time_splitting\\quarters' --filter=123,321
 ";
 
 // Now get cli options.
@@ -51,7 +51,7 @@ list($options, $unrecognized) = cli_get_params(
         'help'                  => false,
         'modelid'               => false,
         'list'                  => false,
-        'timesplitting'         => false,
+        'analysisinterval'      => false,
         'mode'                  => 'configuration',
         'reuse-prev-analysed'   => true,
         'non-interactive'       => false,
@@ -87,8 +87,8 @@ if ($options['mode'] !== 'configuration' && $options['mode'] !== 'trainedmodel')
     cli_error('Error: The provided mode is not supported');
 }
 
-if ($options['mode'] == 'trainedmodel' && $options['timesplitting']) {
-    cli_error('Sorry, no time splitting method can be specified when using \'trainedmodel\' mode.');
+if ($options['mode'] == 'trainedmodel' && $options['analysisinterval']) {
+    cli_error('Sorry, no analysis interval can be specified when using \'trainedmodel\' mode.');
 }
 
 // We need admin permissions.
@@ -104,7 +104,7 @@ if ($options['reuse-prev-analysed']) {
 
 $analyseroptions = array(
     'filter' => $options['filter'],
-    'timesplitting' => $options['timesplitting'],
+    'timesplitting' => $options['analysisinterval'],
     'reuseprevanalysed' => $options['reuse-prev-analysed'],
     'mode' => $options['mode'],
 );
diff --git a/admin/tool/analytics/lang/en/deprecated.txt b/admin/tool/analytics/lang/en/deprecated.txt
new file mode 100644 (file)
index 0000000..f308e25
--- /dev/null
@@ -0,0 +1 @@
+getpredictions,tool_analytics
\ No newline at end of file
index 9e7f30d..0979652 100644 (file)
@@ -24,8 +24,9 @@
 
 $string['accuracy'] = 'Accuracy';
 $string['allpredictions'] = 'All predictions';
-$string['alltimesplittingmethods'] = 'All time-splitting methods';
+$string['alltimesplittingmethods'] = 'All analysis intervals';
 $string['analysingsitedata'] = 'Analysing the site';
+$string['analysis'] = 'Analysis';
 $string['analyticmodels'] = 'Analytics models';
 $string['bettercli'] = 'Evaluating models and generating predictions may involve heavy processing. It is recommended to run these actions from the command line.';
 $string['cantguessstartdate'] = 'Can\'t guess the start date';
@@ -33,7 +34,7 @@ $string['cantguessenddate'] = 'Can\'t guess the end date';
 $string['classdoesnotexist'] = 'Class {$a} does not exist';
 $string['clearpredictions'] = 'Clear predictions';
 $string['clearmodelpredictions'] = 'Are you sure you want to clear all "{$a}" predictions?';
-$string['clienablemodel'] = 'You can enable the model by selecting a time-splitting method by its ID. Note that you can also enable it later using the web interface (\'none\' to exit).';
+$string['clienablemodel'] = 'You can enable the model by selecting an analysis interval by its ID. Note that you can also enable it later using the web interface (\'none\' to exit).';
 $string['clievaluationandpredictions'] = 'A scheduled task iterates through enabled models and gets predictions. Models evaluation via the web interface is disabled. You can allow these processes to be executed manually via the web interface by disabling the <a href="{$a}">\'onlycli\'</a> analytics setting.';
 $string['clievaluationandpredictionsnoadmin'] = 'A scheduled task iterates through enabled models and gets predictions. Models evaluation via the web interface is disabled. It may be enabled by a site administrator.';
 $string['component'] = 'Component';
@@ -41,14 +42,14 @@ $string['componentcore'] = 'Core';
 $string['componentselect'] = 'Select all models provided by the component \'{$a}\'';
 $string['componentselectnone'] = 'Unselect all';
 $string['createmodel'] = 'Create model';
-$string['currenttimesplitting'] = 'Current time-splitting method';
+$string['currenttimesplitting'] = 'Current analysis interval';
 $string['delete'] = 'Delete';
 $string['deletemodelconfirmation'] = 'Are you sure you want to delete "{$a}"? These changes cannot be reverted.';
 $string['disabled'] = 'Disabled';
 $string['editmodel'] = 'Edit "{$a}" model';
-$string['edittrainedwarning'] = 'This model has already been trained. Note that changing its indicators or its time-splitting method will delete its previous predictions and start generating new predictions.';
+$string['edittrainedwarning'] = 'This model has already been trained. Note that changing its indicators or its analysis interval will delete its previous predictions and start generating new predictions.';
 $string['enabled'] = 'Enabled';
-$string['errorcantenablenotimesplitting'] = 'You need to select a time-splitting method before enabling the model';
+$string['errorcantenablenotimesplitting'] = 'You need to select an analysis interval before enabling the model';
 $string['errornoenabledandtrainedmodels'] = 'There are no enabled and trained models to predict.';
 $string['errornoenabledmodels'] = 'There are no enabled models to train.';
 $string['errornoexport'] = 'Only trained models can be exported';
@@ -71,15 +72,13 @@ $string['evaluationmodecoltrainedmodel'] = 'Trained model';
 $string['evaluationmodecolconfiguration'] = 'Configuration';
 $string['evaluationmodeconfiguration'] = 'Evaluate the model configuration';
 $string['evaluationinbatches'] = 'The site contents are calculated and stored in batches. The evaluation process may be stopped at any time. The next time it is run, it will continue from the point when it was stopped.';
+$string['executescheduledanalysis'] = 'Execute scheduled analysis';
 $string['export'] = 'Export';
 $string['exportincludeweights'] = 'Include the weights of the trained model';
 $string['exportmodel'] = 'Export configuration';
 $string['exporttrainingdata'] = 'Export training data';
-$string['getpredictionsresultscli'] = 'Results using {$a->name} (id: {$a->id}) time-splitting method';
-$string['getpredictionsresults'] = 'Results using {$a->name} time-splitting method';
 $string['extrainfo'] = 'Info';
 $string['generalerror'] = 'Evaluation error. Status code {$a}';
-$string['getpredictions'] = 'Get predictions';
 $string['goodmodel'] = 'This is a good model for using to obtain predictions. Enable it to start obtaining predictions.';
 $string['importmodel'] = 'Import model';
 $string['indicators'] = 'Indicators';
@@ -103,12 +102,12 @@ $string['modelid'] = 'Model ID';
 $string['modelinvalidanalysables'] = 'Invalid analysable elements for "{$a}" model';
 $string['modelname'] = 'Model name';
 $string['modelresults'] = '{$a} results';
-$string['modeltimesplitting'] = 'Time splitting';
+$string['modeltimesplitting'] = 'Analysis interval';
 $string['newmodel'] = 'New model';
 $string['nextpage'] = 'Next page';
 $string['nodatatoevaluate'] = 'There is no data to evaluate the model';
-$string['nodatatopredict'] = 'No new elements to get predictions for';
-$string['nodatatotrain'] = 'There is no new data that can be used for training';
+$string['nodatatopredict'] = 'No new elements to get predictions for.';
+$string['nodatatotrain'] = 'There is no new data that can be used for training.';
 $string['noinvalidanalysables'] = 'This site does not contain any invalid analysable element.';
 $string['notdefined'] = 'Not yet defined';
 $string['pluginname'] = 'Analytic models';
@@ -125,12 +124,14 @@ $string['restoredefaultsome'] = 'Succesfully re-created {$a->count} new model(s)
 $string['restoredefaultsubmit'] = 'Restore selected';
 $string['samestartdate'] = 'Current start date is good';
 $string['sameenddate'] = 'Current end date is good';
-$string['selecttimesplittingforevaluation'] = 'Select the time-splitting method you want to use to evaluate the model configuration.';
+$string['scheduledanalysisresults'] = 'Results using {$a->name} analysis interval';
+$string['scheduledanalysisresultscli'] = 'Results using {$a->name} (id: {$a->id}) analysis interval';
+$string['selecttimesplittingforevaluation'] = 'Select the analysis interval you want to use to evaluate the model configuration.';
 $string['target'] = 'Target';
 $string['target_help'] = 'The target is what the model will predict.';
 $string['target_link'] = 'Targets';
-$string['timesplittingnotdefined'] = 'Time splitting is not defined.';
-$string['timesplittingnotdefined_help'] = 'You need to select a time-splitting method before enabling the model.';
+$string['timesplittingnotdefined'] = 'No analysis interval is defined.';
+$string['timesplittingnotdefined_help'] = 'You need to select an analysis interval before enabling the model.';
 $string['trainandpredictmodel'] = 'Training model and calculating predictions';
 $string['trainingprocessfinished'] = 'Training process finished';
 $string['trainingresults'] = 'Training results';
@@ -140,3 +141,6 @@ $string['viewlog'] = 'Evaluation log';
 $string['weeksenddateautomaticallyset'] = 'End date automatically set based on start date and the number of sections';
 $string['weeksenddatedefault'] = 'End date automatically calculated from the course start date.';
 $string['privacy:metadata'] = 'The Analytic models plugin does not store any personal data.';
+
+// Deprecated since Moodle 3.8.
+$string['getpredictions'] = 'Get predictions';
\ No newline at end of file
index c6cc950..ae2c8dc 100644 (file)
@@ -45,8 +45,8 @@ switch ($action) {
     case 'evaluate':
         $title = get_string('evaluatemodel', 'tool_analytics');
         break;
-    case 'getpredictions':
-        $title = get_string('getpredictions', 'tool_analytics');
+    case 'scheduledanalysis':
+        $title = get_string('analysis', 'tool_analytics');
         break;
     case 'log':
         $title = get_string('viewlog', 'tool_analytics');
@@ -200,7 +200,7 @@ switch ($action) {
         echo $renderer->render_evaluate_results($results, $model->get_analyser()->get_logs());
         break;
 
-    case 'getpredictions':
+    case 'scheduledanalysis':
         confirm_sesskey();
 
         if ($onlycli) {
index 450570a..8511c84 100644 (file)
@@ -76,7 +76,7 @@ class tool_lp_external_testcase extends externallib_advanced_testcase {
      * Setup function- we will create a course and add an assign instance to it.
      */
     protected function setUp() {
-        global $DB;
+        global $DB, $CFG;
 
         $this->resetAfterTest(true);
 
@@ -91,9 +91,7 @@ class tool_lp_external_testcase extends externallib_advanced_testcase {
         $catcontext = context_coursecat::instance($category->id);
 
         // Fetching default authenticated user role.
-        $userroles = get_archetype_roles('user');
-        $this->assertCount(1, $userroles);
-        $authrole = array_pop($userroles);
+        $authrole = $DB->get_record('role', array('id' => $CFG->defaultuserroleid));
 
         // Reset all default authenticated users permissions.
         unassign_capability('moodle/competency:competencygrade', $authrole->id);
index 638a30c..68f18eb 100644 (file)
@@ -1,5 +1,10 @@
 This files describes API changes in /admin/*.
 
+=== 3.8 ===
+
+* Admin setting "Open to Google" (opentogoogle) has been renamed to the more generic "Open to search engines" (opentowebcrawlers).
+  This is a more accurate representation of what is being set and the config string has also been moved and updated to reflect this.
+
 === 3.7 ===
 
 * Admin setting "Allow blocks to use the dock" (allowblockstodock) has been removed & stings deprecated.
index 2ae0869..692c594 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in analytics sub system,
 information provided here is intended especially for developers.
 
+=== 3.8 ===
+
+* "Time-splitting method" have been replaced by "Analysis interval" for the language strings that are
+  displayed in the Moodle UI. The name of several time-splitting methods have been updated according
+  to the new description of the field.
+
 === 3.7 ===
 
 * \core_analytics\regressor::evaluate_regression and \core_analytics\classifier::evaluate_classification
index a7b9bf2..34e9540 100644 (file)
@@ -117,10 +117,7 @@ class auth_email_external extends external_api {
 
         if (signup_captcha_enabled()) {
             // With reCAPTCHA v2 the captcha will be rendered by the mobile client using just the publickey.
-            // For now include placeholders for the v1 paramaters to support older mobile app versions.
             $result['recaptchapublickey'] = $CFG->recaptchapublickey;
-            list($result['recaptchachallengehash'], $result['recaptchachallengeimage'], $result['recaptchachallengejs']) =
-                array('', '', '');
         }
 
         $result['warnings'] = array();
index e1f70d2..2631ea9 100644 (file)
@@ -41,7 +41,6 @@
             data-course-id="{{id}}"
             aria-controls="favorite-icon-{{ id }}-{{ uniqid }}"
             >
-            <span class="text-primary">{{#pix}} i/star, core, {{#str}} favourites, block_myoverview {{/str}} {{/pix}}</span>
             {{#str}} addtofavourites, block_myoverview {{/str}}
             <div class="sr-only">
                 {{#str}} aria:addtofavourites, block_myoverview {{/str}} {{{fullname}}}
@@ -62,7 +61,6 @@
             data-course-id="{{id}}"
             aria-controls="favorite-icon-{{ id }}-{{ uniqid }}"
             >
-            {{#pix}} i/show, core, {{#str}} hidden, block_myoverview {{/str}} {{/pix}}
             {{#str}} show, block_myoverview {{/str}}
             <div class="sr-only">
                 {{#str}} aria:showcourse, block_myoverview, {{fullname}} {{/str}}
@@ -73,7 +71,6 @@
             data-course-id="{{id}}"
             aria-controls="favorite-icon-{{ id }}-{{ uniqid }}"
             >
-            {{#pix}} i/hide, core, {{#str}} hidden, block_myoverview {{/str}} {{/pix}}
             {{#str}} hidecourse, block_myoverview {{/str}}
             <div class="sr-only">
                 {{#str}} aria:hidecourse, block_myoverview, {{fullname}} {{/str}}
diff --git a/blocks/recentlyaccesseditems/db/upgrade.php b/blocks/recentlyaccesseditems/db/upgrade.php
new file mode 100644 (file)
index 0000000..cc895b3
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file keeps track of upgrades to the recentlyaccesseditems block
+ *
+ * Sometimes, changes between versions involve alterations to database structures
+ * and other major things that may break installations.
+ *
+ * The upgrade function in this file will attempt to perform all the necessary
+ * actions to upgrade your older installation to the current version.
+ *
+ * If there's something it cannot do itself, it will tell you what you need to do.
+ *
+ * The commands in here will all be database-neutral, using the methods of
+ * database_manager class
+ *
+ * Please do not forget to use upgrade_set_timeout()
+ * before any action that may take longer time to finish.
+ *
+ * @package block_recentlyaccesseditems
+ * @copyright 2019 Peter Dias
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Upgrade the recentlyaccesseditems db table.
+ *
+ * @param $oldversion
+ * @return bool
+ */
+function xmldb_block_recentlyaccesseditems_upgrade($oldversion, $block) {
+    global $DB;
+
+    // Automatically generated Moodle v3.7.0 release upgrade line.
+    // Put any upgrade step following this.
+    if ($oldversion < 2019052001) {
+        $sql = "courseid NOT IN (SELECT c.id from {course} c) OR cmid NOT IN (SELECT cm.id from {course_modules} cm)";
+        $DB->delete_records_select("block_recentlyaccesseditems", $sql);
+        upgrade_block_savepoint(true, 2019052001, 'recentlyaccesseditems', false);
+    }
+
+    return true;
+}
diff --git a/blocks/recentlyaccesseditems/lib.php b/blocks/recentlyaccesseditems/lib.php
new file mode 100644 (file)
index 0000000..eedf29d
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+// This file is part of Moodle - https://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/>.
+
+/**
+ * The interface library between the core and the subsystem.
+ *
+ * @package     block_recentlyaccesseditems
+ * @copyright   2019 Peter Dias <peter@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Pre-delete course module hook to cleanup any records with references to the deleted module.
+ *
+ * @param stdClass $cm The deleted course module
+ */
+function block_recentlyaccesseditems_pre_course_module_delete($cm) {
+    global $DB;
+
+    $DB->delete_records('block_recentlyaccesseditems', ['cmid' => $cm->id]);
+}
+
+/**
+ * Pre-delete course hook to cleanup any records with references to the deleted course.
+ *
+ * @param stdClass $course The deleted course
+ */
+function block_recentlyaccesseditems_pre_course_delete($course) {
+    global $DB;
+
+    $DB->delete_records('block_recentlyaccesseditems', ['courseid' => $course->id]);
+}
index 62de36a..b6d6b5e 100644 (file)
@@ -103,5 +103,15 @@ class block_recentlyaccesseditems_externallib_testcase extends externallib_advan
             }
             $this->assertTrue($record->timeaccess < $result[$key - 1]->timeaccess);
         }
+
+        // Delete a course and confirm it's activities don't get returned.
+        delete_course($courses[0], false);
+        $result = \block_recentlyaccesseditems\external::get_recent_items();
+        $this->assertCount((count($forum) + count($chat)) - 2, $result);
+
+        // Delete a single course module should still return.
+        course_delete_module($forum[1]->cmid);
+        $result = \block_recentlyaccesseditems\external::get_recent_items();
+        $this->assertCount((count($forum) + count($chat)) - 3, $result);
     }
 }
\ No newline at end of file
index 8c5ab69..92152c5 100644 (file)
@@ -207,6 +207,60 @@ class block_recentlyaccesseditems_privacy_testcase extends \core_privacy\tests\p
         // Confirm student's data is exported.
         $writer = \core_privacy\local\request\writer::with_context($studentcontext);
         $this->assertTrue($writer->has_any_data());
+
+        delete_course($course, false);
+        $sc = context_user::instance($student->id);
+        $approvedlist = new approved_contextlist($student, $component, [$sc->id]);
+        provider::export_user_data($approvedlist);
+        $writer = \core_privacy\local\request\writer::with_context($sc);
+        $this->assertTrue($writer->has_any_data());
+    }
+
+    /**
+     * Test exporting data for an approved contextlist with a deleted course
+     */
+    public function test_export_user_data_with_deleted_course() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $generator = $this->getDataGenerator();
+        $component = 'block_recentlyaccesseditems';
+
+        $student = $generator->create_user();
+        $studentcontext = context_user::instance($student->id);
+
+        // Enrol user in course and add course items.
+        $course = $generator->create_course();
+        $generator->enrol_user($student->id, $course->id, 'student');
+        $forum = $generator->create_module('forum', ['course' => $course]);
+        $chat = $generator->create_module('chat', ['course' => $course]);
+
+        // Generate some recent activity.
+        $this->setUser($student);
+        $event = \mod_forum\event\course_module_viewed::create(['context' => context_module::instance($forum->cmid),
+                'objectid' => $forum->id]);
+        $event->trigger();
+        $event = \mod_chat\event\course_module_viewed::create(['context' => context_module::instance($chat->cmid),
+                'objectid' => $chat->id]);
+        $event->trigger();
+
+        // Confirm data is present.
+        $params = [
+            'courseid' => $course->id,
+            'userid' => $student->id,
+        ];
+
+        $result = $DB->count_records('block_recentlyaccesseditems', $params);
+        $this->assertEquals(2, $result);
+        delete_course($course, false);
+
+        // Export data for student.
+        $approvedlist = new approved_contextlist($student, $component, [$studentcontext->id]);
+        provider::export_user_data($approvedlist);
+
+        // Confirm student's data is exported.
+        $writer = \core_privacy\local\request\writer::with_context($studentcontext);
+        $this->assertFalse($writer->has_any_data());
     }
 
     /**
index c96794f..d60ba3c 100644 (file)
@@ -22,6 +22,6 @@
  */
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2019052000;            // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2019052001;            // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2019051100;            // Requires this Moodle version.
 $plugin->component = 'block_recentlyaccesseditems'; // Full name of the plugin (used for diagnostics).
index b89e808..73b275e 100644 (file)
         </div>
         <div class="w-100 event-name-container text-truncate line-height-3">
             <a href="{{url}}"
-               title="{{name}}"
-               aria-label='{{#str}} ariaeventlistitem, block_timeline, { "name": "{{name}}", "course": "{{course.fullnamedisplay}}", "date": "{{#userdate}} {{timesort}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}" } {{/str}}'
-            ><h6 class="event-name text-truncate mb-0">{{{name}}}</h6></a>
-            <small class="text-muted text-truncate mb-0">{{{course.fullnamedisplay}}}</small>
+               title={{#quote}}{{{name}}}{{/quote}}
+               aria-label='{{#str}} ariaeventlistitem, block_timeline, { "name": {{#quote}}{{{name}}}{{/quote}}, "course": {{#quote}}{{{course.fullnamedisplay}}}{{/quote}}, "date": "{{#userdate}} {{timesort}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}" } {{/str}}'
+            ><h6 class="event-name text-truncate mb-0">{{#quote}}{{{name}}}{{/quote}}</h6></a>
+            <small class="text-muted text-truncate mb-0">{{#quote}}{{{course.fullnamedisplay}}}{{/quote}}</small>
             {{#action.actionable}}
             <h6 class="mb-0 pt-2">
                 <a href="{{{action.url}}}" aria-label="{{{action.name}}}" title="{{{action.name}}}" class="list-group-item-action">{{{action.name}}}</a>
index 5d7f198..da7b69d 100644 (file)
@@ -100,8 +100,6 @@ class footer_options_exporter extends exporter {
             $params['course'] = $this->calendar->course->id;
         } else if (null !== $this->calendar->categoryid && $this->calendar->categoryid > 0) {
             $params['category'] = $this->calendar->categoryid;
-        } else {
-            $params['course'] = SITEID;
         }
 
         return $params;
index 4837733..2bc8542 100644 (file)
@@ -129,6 +129,10 @@ $params = [];
 $usedefaultfilters = true;
 if (!empty($courseid) && $courseid == SITEID && !empty($types['site'])) {
     $searches[] = "(eventtype = 'site')";
+    $usedefaultfilters = false;
+}
+
+if (!empty($types['user'])) {
     $searches[] = "(eventtype = 'user' AND userid = :userid)";
     $params['userid'] = $USER->id;
     $usedefaultfilters = false;
index b8fd082..f5cdd79 100644 (file)
@@ -522,6 +522,7 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
      * Search cohorts.
      */
     public function test_search_cohorts() {
+        global $DB, $CFG;
         $this->resetAfterTest(true);
 
         $creator = $this->getDataGenerator()->create_user();
@@ -537,9 +538,7 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
         $coursecontext = context_course::instance($course->id);
 
         // Fetching default authenticated user role.
-        $userroles = get_archetype_roles('user');
-        $this->assertCount(1, $userroles);
-        $authrole = array_pop($userroles);
+        $authrole = $DB->get_record('role', array('id' => $CFG->defaultuserroleid));
 
         // Reset all default authenticated users permissions.
         unassign_capability('moodle/cohort:manage', $authrole->id);
index 4c83541..1d72b05 100644 (file)
@@ -97,7 +97,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
      * Setup function- we will create a course and add an assign instance to it.
      */
     protected function setUp() {
-        global $DB;
+        global $DB, $CFG;
 
         $this->resetAfterTest(true);
 
@@ -114,9 +114,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $othercatcontext = context_coursecat::instance($othercategory->id);
 
         // Fetching default authenticated user role.
-        $userroles = get_archetype_roles('user');
-        $this->assertCount(1, $userroles);
-        $authrole = array_pop($userroles);
+        $authrole = $DB->get_record('role', array('id' => $CFG->defaultuserroleid));
 
         // Reset all default authenticated users permissions.
         unassign_capability('moodle/competency:competencygrade', $authrole->id);
index 05c463e..77c5a5a 100644 (file)
@@ -2576,7 +2576,7 @@ class core_competency_privacy_testcase extends provider_testcase {
      */
     protected function allow_anyone_to_comment_anywhere() {
         global $DB;
-        $roleid = $DB->get_field('role', 'id', ['archetype' => 'user'], MUST_EXIST);
+        $roleid = $DB->get_field('role', 'id', ['shortname' => 'user'], MUST_EXIST);
         assign_capability('moodle/competency:plancomment', CAP_ALLOW, $roleid, SYSCONTEXTID, true);
         assign_capability('moodle/competency:planmanage', CAP_ALLOW, $roleid, SYSCONTEXTID, true);
         assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $roleid, SYSCONTEXTID, true);
index 02de25a..ced40fc 100644 (file)
@@ -4,10 +4,17 @@
     "description": "Moodle - the world's open source learning platform",
     "type": "project",
     "homepage": "https://moodle.org",
+    "repositories": [
+        {
+            "type": "vcs",
+            "url": "https://github.com/moodlehq/php-webdriver.git"
+        }
+    ],
     "require-dev": {
         "phpunit/phpunit": "7.5.*",
         "phpunit/dbunit": "4.0.*",
         "moodlehq/behat-extension": "3.38.0",
-        "mikey179/vfsstream": "^1.6"
+        "mikey179/vfsstream": "^1.6",
+        "instaclick/php-webdriver": "dev-local as 1.x-dev"
     }
 }
index 69a3ccd..258d0fb 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "c6d083d8c30f80b245ed03f8f064d4ec",
+    "content-hash": "26075c0bf968fde4986dd492a3b8889f",
     "packages": [],
     "packages-dev": [
         {
         },
         {
             "name": "instaclick/php-webdriver",
-            "version": "1.4.5",
+            "version": "dev-local",
             "source": {
                 "type": "git",
-                "url": "https://github.com/instaclick/php-webdriver.git",
-                "reference": "6fa959452e774dcaed543faad3a9d1a37d803327"
+                "url": "https://github.com/moodlehq/php-webdriver.git",
+                "reference": "e311e55bf2c4746db9df72707f3cf1a731ad98aa"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/6fa959452e774dcaed543faad3a9d1a37d803327",
-                "reference": "6fa959452e774dcaed543faad3a9d1a37d803327",
+                "url": "https://api.github.com/repos/moodlehq/php-webdriver/zipball/e311e55bf2c4746db9df72707f3cf1a731ad98aa",
+                "reference": "e311e55bf2c4746db9df72707f3cf1a731ad98aa",
                 "shasum": ""
             },
             "require": {
                     "WebDriver": "lib/"
                 }
             },
-            "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "Apache-2.0"
             ],
                 "webdriver",
                 "webtest"
             ],
-            "time": "2017-06-30T04:02:48+00:00"
+            "support": {
+                "source": "https://github.com/moodlehq/php-webdriver/tree/local"
+            },
+            "time": "2019-06-03T22:55:37+00:00"
         },
         {
             "name": "mikey179/vfsStream",
             "time": "2018-12-25T11:19:39+00:00"
         }
     ],
-    "aliases": [],
+    "aliases": [
+        {
+            "alias": "1.x-dev",
+            "alias_normalized": "1.9999999.9999999.9999999-dev",
+            "version": "dev-local",
+            "package": "instaclick/php-webdriver"
+        }
+    ],
     "minimum-stability": "stable",
-    "stability-flags": [],
+    "stability-flags": {
+        "instaclick/php-webdriver": 20
+    },
     "prefer-stable": false,
     "prefer-lowest": false,
     "platform": [],
index 046013b..0aea626 100644 (file)
@@ -118,11 +118,6 @@ class course_competencies extends course_enrolments {
      */
     protected function calculate_sample($sampleid, \core_analytics\analysable $course, $starttime = false, $endtime = false) {
 
-        if ($this->enrolment_starts_after_calculation_start($sampleid, $starttime)) {
-            // Discard user enrolments whose start date is after $starttime.
-            return null;
-        }
-
         $userenrol = $this->retrieve('user_enrolments', $sampleid);
 
         $key = $course->get_id();
index 75f0d82..9c187ca 100644 (file)
@@ -96,10 +96,6 @@ class course_completion extends course_enrolments {
      */
     protected function calculate_sample($sampleid, \core_analytics\analysable $course, $starttime = false, $endtime = false) {
 
-        if ($this->enrolment_starts_after_calculation_start($sampleid, $starttime)) {
-            // Discard user enrolments whose start date is after $starttime.
-            return null;
-        }
         $userenrol = $this->retrieve('user_enrolments', $sampleid);
 
         // We use completion as a success metric.
index 4cb43c7..694b058 100644 (file)
@@ -118,11 +118,6 @@ class course_dropout extends course_enrolments {
      */
     protected function calculate_sample($sampleid, \core_analytics\analysable $course, $starttime = false, $endtime = false) {
 
-        if ($this->enrolment_starts_after_calculation_start($sampleid, $starttime)) {
-            // Discard user enrolments whose start date is after $starttime.
-            return null;
-        }
-
         $userenrol = $this->retrieve('user_enrolments', $sampleid);
 
         // We use completion as a success metric only when it is enabled.
index e338413..57b8ee6 100644 (file)
@@ -197,27 +197,4 @@ abstract class course_enrolments extends \core_analytics\local\target\binary {
 
         return array_merge($actions, parent::prediction_actions($prediction, $includedetailsaction));
     }
-
-    /**
-     * Does the user enrolment created after this time range start time or starts after it?
-     *
-     * We need to identify these enrolments because the indicators can not be calculated properly
-     * if the student enrolment started half way through this time range.
-     *
-     * User enrolments whose end date is before time() have already been discarded in
-     * course_enrolments::is_valid_sample.
-     *
-     * @param  int    $sampleid
-     * @param  int    $starttime
-     * @return bool
-     */
-    protected function enrolment_starts_after_calculation_start(int $sampleid, int $starttime) {
-
-        $userenrol = $this->retrieve('user_enrolments', $sampleid);
-        if ($userenrol->timestart && $userenrol->timestart > $starttime) {
-            return true;
-        }
-
-        return false;
-    }
 }
index 84152eb..cb5727c 100644 (file)
@@ -164,11 +164,6 @@ class course_gradetopass extends course_enrolments {
      */
     protected function calculate_sample($sampleid, \core_analytics\analysable $course, $starttime = false, $endtime = false) {
 
-        if ($this->enrolment_starts_after_calculation_start($sampleid, $starttime)) {
-            // Discard user enrolments whose start date is after $starttime.
-            return null;
-        }
-
         $userenrol = $this->retrieve('user_enrolments', $sampleid);
 
         // Get course grade to pass.
diff --git a/enrol/ldap/cli/sync.php b/enrol/ldap/cli/sync.php
deleted file mode 100644 (file)
index 8aedc9f..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * CLI sync for full LDAP synchronisation.
- *
- * This script is meant to be called from a cronjob to sync moodle with the LDAP
- * backend in those setups where the LDAP backend acts as 'master' for enrolment.
- *
- * Sample cron entry:
- * # 5 minutes past 4am
- * 5 4 * * * $sudo -u www-data /usr/bin/php /var/www/moodle/enrol/ldap/cli/sync.php
- *
- * Notes:
- *   - it is required to use the web server account when executing PHP CLI scripts
- *   - you need to change the "www-data" to match the apache user account
- *   - use "su" if "sudo" not available
- *   - If you have a large number of users, you may want to raise the memory limits
- *     by passing -d momory_limit=256M
- *   - For debugging & better logging, you are encouraged to use in the command line:
- *     -d log_errors=1 -d error_reporting=E_ALL -d display_errors=0 -d html_errors=0
- *
- * @deprecated since Moodle 3.3 MDL-57631 - please do not use this CLI script any more, use scheduled task instead.
- * @todo       MDL-58268 This will be deleted in Moodle 3.7.
- * @package    enrol_ldap
- * @author     Iñaki Arenaza - based on code by Martin Dougiamas, Martin Langhoff and others
- * @copyright  1999 onwards Martin Dougiamas {@link http://moodle.com}
- * @copyright  2010 Iñaki Arenaza <iarenaza@eps.mondragon.edu>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-define('CLI_SCRIPT', true);
-
-require(__DIR__.'/../../../config.php');
-require_once("$CFG->libdir/clilib.php");
-
-// Ensure errors are well explained.
-set_debugging(DEBUG_DEVELOPER, true);
-
-cli_problem('[ENROL LDAP] The sync enrolments cron script has been deprecated. Please use the scheduled task instead.');
-
-// Abort execution of the CLI script if the enrol_ldap\task\sync_enrolments is enabled.
-$task = \core\task\manager::get_scheduled_task('enrol_ldap\task\sync_enrolments');
-if (!$task->get_disabled()) {
-    cli_error('[ENROL LDAP] The scheduled task sync_enrolments is enabled, the cron execution has been aborted.');
-}
-
-if (!enrol_is_enabled('ldap')) {
-    cli_error(get_string('pluginnotenabled', 'enrol_ldap'), 2);
-}
-
-/** @var enrol_ldap_plugin $enrol */
-$enrol = enrol_get_plugin('ldap');
-
-$trace = new text_progress_trace();
-
-// Update enrolments -- these handlers should autocreate courses if required.
-$enrol->sync_enrolments($trace);
-
-exit(0);
index 2a95489..89841c9 100644 (file)
@@ -29,7 +29,7 @@ $string['autocreate'] = '<p>Courses can be created automatically if there are en
 $string['autocreate_key'] = 'Auto create';
 $string['autocreation_settings'] = 'Automatic course creation settings';
 $string['autoupdate_settings'] = 'Automatic course update settings';
-$string['autoupdate_settings_desc'] = '<p>Select fields to update when synchronisation script is running (enrol/ldap/cli/sync.php).</p><p>When at least one field is selected an update will occur.</p>';
+$string['autoupdate_settings_desc'] = '<p>Select fields to update when synchronisation scheduled task for LDAP Enrolments is running (enrol_ldap\task\sync_enrolments).</p><p>When at least one field is selected an update will occur.</p>';
 $string['bind_dn'] = 'If you want to use a bind user to search users, specify it here. Someting like \'cn=ldapuser,ou=public,o=org\'';
 $string['bind_dn_key'] = 'Bind user distinguished name';
 $string['bind_pw'] = 'Password for the bind user';
index d00991d..4607ec0 100644 (file)
@@ -1,4 +1,8 @@
 This files describes API changes in the enrol_ldap code.
 
+=== 3.8 ===
+
+* enrol/ldap/cli/sync.php script has been removed. You should use enrol_ldap\task\sync_enrolments task instead.
+
 === 3.3 ===
 * enrol/ldap/cli/sync.php script has been deprecated in favour of enrol_ldap\task\sync_enrolments task.
index 26c8961..06cbc2b 100644 (file)
@@ -132,6 +132,7 @@ $string['cliupgradedefaultheading'] = 'Setting new default values';
 $string['cliupgradedefaultverbose'] = 'New setting: {$a->name}, Default value: {$a->defaultsetting}';
 $string['cliupgradefinished'] = 'Command line upgrade from {$a->oldversion} to {$a->newversion} completed successfully.';
 $string['cliupgradenoneed'] = 'No upgrade needed for the installed version {$a}. Thanks for coming anyway!';
+$string['cliupgradepending'] = 'An upgrade is pending';
 $string['cliyesnoprompt'] = 'type y (means yes) or n (means no)';
 $string['commentsperpage'] = 'Comments displayed per page';
 $string['commonactivitysettings'] = 'Common activity settings';
@@ -298,7 +299,7 @@ $string['confignoreplyaddress'] = 'Emails are sometimes sent out on behalf of a
 $string['confignotifyloginfailures'] = 'Send login failure notification messages to these selected users. This requires an internal logstore (eg Standard Logstore) to be enabled.';
 $string['confignotifyloginthreshold'] = 'If notifications about failed logins are active, how many failed login attempts by one user or one IP address is it worth notifying about?';
 $string['confignotloggedinroleid'] = 'Users who are not logged in to the site will be treated as if they have this role granted to them at the site context.  Guest is almost always what you want here, but you might want to create roles that are less or more restrictive.  Things like creating posts still require the user to log in properly.';
-$string['configopentogoogle'] = 'If you enable this setting, then Google will be allowed to enter your site as a Guest.  In addition, people coming in to your site via a Google search will automatically be logged in as a Guest.  Note that this only provides transparent access to courses that already allow guest access.';
+$string['configopentowebcrawlers'] = 'If you enable this setting, then search engines will be allowed to enter your site as a guest.  In addition, people coming in to your site via a search engine will automatically be logged in as a guest.  Note that this only provides transparent access to courses that already allow guest access.';
 $string['configoverride'] = 'Defined in config.php';
 $string['configpasswordpolicy'] = 'If enabled, user passwords will be checked against the password policy as specified in the settings below. Enabling the password policy will not affect existing users until they decide to, or are required to, change their password.';
 $string['configpasswordresettime'] = 'This specifies the amount of time people have to validate a password reset request before it expires. Usually 30 minutes is a good value.';
@@ -860,7 +861,7 @@ $string['onlynoreply'] = 'Only when from a no-reply address';
 $string['opcacherecommended'] = 'PHP opcode caching improves performance and lowers memory requirements, OPcache extension is recommended and fully supported.';
 $string['opensslrecommended'] = 'Installing the optional OpenSSL library is highly recommended -- it enables Moodle Networking functionality.';
 $string['opensslrequired'] = 'The OpenSSL PHP extension is now required by Moodle to provide stronger cryptographic services.';
-$string['opentogoogle'] = 'Open to Google';
+$string['opentowebcrawlers'] = 'Open to search engines';
 $string['optionalmaintenancemessage'] = 'Optional maintenance message';
 $string['order1'] = 'First';
 $string['order2'] = 'Second';
index 5ba4125..9962737 100644 (file)
@@ -30,8 +30,8 @@ $string['analyticslogstore'] = 'Log store used for analytics';
 $string['analyticslogstore_help'] = 'The log store that will be used by the analytics API to read users\' activity.';
 $string['analyticssettings'] = 'Analytics settings';
 $string['analyticssiteinfo'] = 'Site information';
-$string['defaulttimesplittingmethods'] = 'Default time-splitting methods for model\'s evaluation';
-$string['defaulttimesplittingmethods_help'] = 'The time-splitting method divides the course duration into parts; the predictions engine will run at the end of these parts. The model evaluation process will iterate through these time-splitting methods unless a specific time-splitting method is specified.  (The ability to specify a time-splitting method is only available when evaluating models using the command line script.)';
+$string['defaulttimesplittingmethods'] = 'Default analysis intervals for model\'s evaluation';
+$string['defaulttimesplittingmethods_help'] = 'The analysis interval defines when the system will calculate predictions and the portion of activity logs that will be considered for those predictions.The model evaluation process will iterate through these analysis intervals unless a specific analysis interval is specified.';
 $string['defaultpredictionsprocessor'] = 'Default predictions processor';
 $string['defaultpredictoroption'] = 'Default processor ({$a})';
 $string['disabledmodel'] = 'Disabled model';
@@ -45,12 +45,12 @@ $string['errorimportversionmismatches'] = 'The version of the following componen
 $string['errorimportmissingclasses'] = 'The following analytics components are not available on this site: {$a->missingclasses}.';
 $string['errorinvalidindicator'] = 'Invalid {$a} indicator';
 $string['errorinvalidtarget'] = 'Invalid {$a} target';
-$string['errorinvalidtimesplitting'] = 'Invalid time splitting; please ensure you add the fully qualified class name.';
+$string['errorinvalidtimesplitting'] = 'Invalid analysis interval; please ensure you add the fully qualified class name.';
 $string['errornoexportconfig'] = 'There was a problem exporting the model configuration.';
-$string['errornoexportconfigrequirements'] = 'Only non-static models with time-splitting methods can be exported.';
+$string['errornoexportconfigrequirements'] = 'Only non-static models with an analysis interval can be exported.';
 $string['errornoindicators'] = 'This model does not have any indicators.';
 $string['errornopredictresults'] = 'No results returned from the predictions processor. Check the output directory contents for more information.';
-$string['errornotimesplittings'] = 'This model does not have any time-splitting method.';
+$string['errornotimesplittings'] = 'This model does not have an analysis interval.';
 $string['errornoroles'] = 'Student or teacher roles have not been defined. Define them in the analytics settings page.';
 $string['errornotarget'] = 'This model does not have any target.';
 $string['errorpredictioncontextnotavailable'] = 'This prediction context is no longer available.';
@@ -60,7 +60,7 @@ $string['errorpredictionsprocessor'] = 'Predictions processor error: {$a}';
 $string['errorpredictwrongformat'] = 'The predictions processor return cannot be decoded: "{$a}"';
 $string['errorprocessornotready'] = 'The selected predictions processor is not ready: {$a}';
 $string['errorsamplenotavailable'] = 'The predicted sample is no longer available.';
-$string['errorunexistingtimesplitting'] = 'The selected time-splitting method is not available.';
+$string['errorunexistingtimesplitting'] = 'The selected analysis interval is not available.';
 $string['errorunexistingmodel'] = 'Non-existing model {$a}';
 $string['errorunknownaction'] = 'Unknown action';
 $string['eventpredictionactionstarted'] = 'Prediction process started';
@@ -70,8 +70,8 @@ $string['insightmessagesubject'] = 'New insight for "{$a}"';
 $string['insightinfomessage'] = 'The system generated an insight for you: {$a}';
 $string['insightinfomessagehtml'] = 'The system generated an insight for you.';
 $string['insightinfomessageaction'] = '{$a->text}: {$a->url}';
-$string['invalidtimesplitting'] = 'Model with ID {$a} needs a time-splitting method before it can be used for training.';
-$string['invalidanalysablefortimesplitting'] = 'It cannot be analysed using {$a} time-splitting method.';
+$string['invalidtimesplitting'] = 'Model with ID {$a} needs an analysis interval before it can be used for training.';
+$string['invalidanalysablefortimesplitting'] = 'It cannot be analysed using {$a} analysis interval.';
 $string['levelinstitution'] = 'Level of education';
 $string['levelinstitutionisced0'] = 'Early childhood education (‘less than primary’ for educational attainment)';
 $string['levelinstitutionisced1'] = 'Primary education';
@@ -95,9 +95,8 @@ $string['noevaluationbasedassumptions'] = 'Models based on assumptions cannot be
 $string['nodata'] = 'No data to analyse';
 $string['noinsightsmodel'] = 'This model does not generate insights';
 $string['noinsights'] = 'No insights reported';
-$string['nonewdata'] = 'No new data available';
-$string['nonewranges'] = 'No new predictions yet';
-$string['nonewtimeranges'] = 'No new time ranges; nothing to predict.';
+$string['nonewdata'] = 'No new data available. It will be analysed after the next analysis interval.';
+$string['nonewranges'] = 'No new predictions yet. It will be analysed after the next analysis interval.';
 $string['nopredictionsyet'] = 'No predictions available yet';
 $string['noranges'] = 'No predictions yet';
 $string['notrainingbasedassumptions'] = 'Models based on assumptions do not need training';
@@ -123,7 +122,7 @@ $string['privacy:metadata:analytics:predictions'] = 'Predictions';
 $string['privacy:metadata:analytics:predictions:modelid'] = 'The model ID';
 $string['privacy:metadata:analytics:predictions:contextid'] = 'The context';
 $string['privacy:metadata:analytics:predictions:sampleid'] = 'The sample ID';
-$string['privacy:metadata:analytics:predictions:rangeindex'] = 'The index of the time-splitting method';
+$string['privacy:metadata:analytics:predictions:rangeindex'] = 'The index of the analysis interval';
 $string['privacy:metadata:analytics:predictions:prediction'] = 'The prediction';
 $string['privacy:metadata:analytics:predictions:predictionscore'] = 'The prediction score';
 $string['privacy:metadata:analytics:predictions:calculations'] = 'Indicator calculations';
@@ -137,8 +136,8 @@ $string['privacy:metadata:analytics:predictionactions:actionname'] = 'The action
 $string['privacy:metadata:analytics:predictionactions:timecreated'] = 'When the prediction action was performed';
 $string['processingsitecontents'] = 'Processing site contents';
 $string['successfullyanalysed'] = 'Successfully analysed';
-$string['timesplittingmethod'] = 'Time-splitting method';
-$string['timesplittingmethod_help'] = 'The time-splitting method defines when the system will calculate predictions and the portion of activity logs that will be considered for those predictions. For example, the course duration may be divided into parts, with a prediction generated at the end of each part.';
+$string['timesplittingmethod'] = 'Analysis interval';
+$string['timesplittingmethod_help'] = 'The analysis interval defines when the system will calculate predictions and the portion of activity logs that will be considered for those predictions. For example, the course duration may be divided into parts, with a prediction generated at the end of each part.';
 $string['timesplittingmethod_link'] = 'Time_splitting_methods';
 $string['typeinstitution'] = 'Type of institution';
 $string['typeinstitutionacademic'] = 'Academic';
index 1233c20..0b395a7 100644 (file)
@@ -82,8 +82,6 @@ If you have any questions please contact support on: {$a->supportemail}
 {$a->url}';
 $string['emailupdatesuccess'] = 'Email address of user <em>{$a->fullname}</em> was successfully updated to <em>{$a->email}</em>.';
 $string['emailupdatetitle'] = 'Confirmation of email update at {$a->site}';
-$string['enterthenumbersyouhear'] = 'Enter the numbers you hear';
-$string['enterthewordsabove'] = 'Enter the words above';
 $string['errormaxconsecutiveidentchars'] = 'Passwords must have at most {$a} consecutive identical characters.';
 $string['errorminpassworddigits'] = 'Passwords must have at least {$a} digit(s).';
 $string['errorminpasswordlength'] = 'Passwords must be at least {$a} characters long.';
@@ -100,9 +98,6 @@ $string['forcechangepasswordfirst_help'] = 'Force users to change password on th
 $string['forcechangepassword_help'] = 'Force users to change password on their next login to Moodle.';
 $string['forgottenpassword'] = 'If you enter a URL here, it will be used as the lost password recovery page for this site. This is intended for sites where passwords are handled entirely outside of Moodle. Leave this blank to use the default password recovery.';
 $string['forgottenpasswordurl'] = 'Forgotten password URL';
-$string['getanaudiocaptcha'] = 'Get an audio CAPTCHA';
-$string['getanimagecaptcha'] = 'Get an image CAPTCHA';
-$string['getanothercaptcha'] = 'Get another CAPTCHA';
 $string['getrecaptchaapi'] = 'To use reCAPTCHA you must get an API key from <a href=\'https://www.google.com/recaptcha/admin\'>https://www.google.com/recaptcha/admin</a>';
 $string['guestloginbutton'] = 'Guest login button';
 $string['changepassword'] = 'Change password URL';
index 86d813c..b03181e 100644 (file)
@@ -31,11 +31,13 @@ $string['cannotinitpage'] = 'Cannot fully initialize page: invalid {$a->name} id
 $string['cannotsetuptable'] = '{$a} tables could NOT be set up successfully!';
 $string['codingerror'] = 'Coding error detected, it must be fixed by a programmer: {$a}';
 $string['configmoodle'] = 'Moodle has not been configured yet. You need to edit config.php first.';
+$string['debuginfo'] = 'Debug info';
 $string['erroroccur'] = 'An error has occurred during this process';
 $string['invalidarraysize'] = 'Incorrect size of arrays in params of {$a}';
 $string['invalideventdata'] = 'Incorrect event data submitted: {$a}';
 $string['invalidparameter'] = 'Invalid parameter value detected';
 $string['invalidresponse'] = 'Invalid response value detected';
+$string['line'] = 'Line';
 $string['missingconfigversion'] = 'Config table does not contain version, can not continue, sorry.';
 $string['modulenotexist'] = '{$a} module doesn\'t exist';
 $string['morethanonerecordinfetch'] = 'Found more than one record in fetch() !';
@@ -47,9 +49,11 @@ $string['nomodules'] = 'No modules found!!';
 $string['nopageclass'] = 'Imported {$a} but found no page classes';
 $string['noreports'] = 'No reports accessible';
 $string['notables'] = 'No tables!';
+$string['outputbuffer'] = 'Output buffer';
 $string['phpvaroff'] = 'The PHP server variable \'{$a->name}\' should be Off - {$a->link}';
 $string['phpvaron'] = 'The PHP server variable \'{$a->name}\' is not turned On - {$a->link}';
 $string['sessionmissing'] = '{$a} object missing from session';
 $string['sqlrelyonobsoletetable'] = 'This SQL relies on obsolete table(s): {$a}!  Your code must be fixed by a developer.';
+$string['stacktrace'] = 'Stack trace';
 $string['withoutversion'] = 'Main version.php file is missing, not readable or broken.';
 $string['xmlizeunavailable'] = 'xmlize functions are not available';
index 6c3294b..6c37eea 100644 (file)
@@ -1983,24 +1983,24 @@ If \'plain text area\' is selected, a format for text input areas such as HTML o
 The list of available text editors is determined by the site administrator.';
 $string['texteditor'] = 'Use standard web forms';
 $string['textformat'] = 'Plain text format';
-$string['timesplitting:deciles'] = 'Tenths';
-$string['timesplitting:deciles_help'] = 'This time-splitting method divides the course into tenths (10 equal parts), with each prediction being based on the data of only the most recent previous tenth.';
-$string['timesplitting:decilesaccum'] = 'Tenths accumulative';
-$string['timesplitting:decilesaccum_help'] = 'This time-splitting method divides the course into tenths (10 equal parts), with each prediction being based on the data of all previous tenths.';
-$string['timesplitting:nosplitting'] = 'No time splitting';
-$string['timesplitting:nosplitting_help'] = 'No time-splitting method is defined for this model.';
-$string['timesplitting:quarters'] = 'Quarters';
-$string['timesplitting:quarters_help'] = 'This time-splitting method divides the course into quarters (4 equal parts), with each prediction being based on the data of only the most recent previous quarter.';
-$string['timesplitting:quartersaccum'] = 'Quarters accumulative';
-$string['timesplitting:quartersaccum_help'] = 'This time-splitting method divides the course into quarters (4 equal parts), with each prediction being based on the data of all previous quarters.';
-$string['timesplitting:singlerange'] = 'Single range';
-$string['timesplitting:singlerange_help'] = 'This time-splitting method considers the entire course as a single span.';
+$string['timesplitting:deciles'] = 'Last tenth';
+$string['timesplitting:deciles_help'] = 'This analysis interval divides the course into tenths (10 equal parts), with each prediction being based on the data of only the most recent previous tenth.';
+$string['timesplitting:decilesaccum'] = 'All previous tenths';
+$string['timesplitting:decilesaccum_help'] = 'This analysis interval divides the course into tenths (10 equal parts), with each prediction being based on the accumulated data of all previous tenths.';
+$string['timesplitting:nosplitting'] = 'No time limits';
+$string['timesplitting:nosplitting_help'] = 'No analysis interval is defined for this model.';
+$string['timesplitting:quarters'] = 'Last quarter';
+$string['timesplitting:quarters_help'] = 'This analysis interval divides the course into quarters (4 equal parts), with each prediction being based on the data of only the most recent previous quarter.';
+$string['timesplitting:quartersaccum'] = 'All previous quarters';
+$string['timesplitting:quartersaccum_help'] = 'This analysis interval divides the course into quarters (4 equal parts), with each prediction being based on the accumulated data of all previous quarters.';
+$string['timesplitting:singlerange'] = 'From start to end';
+$string['timesplitting:singlerange_help'] = 'This analysis interval considers the entire course as a single span.';
 $string['timesplitting:upcoming3days'] = 'Upcoming 3 days';
-$string['timesplitting:upcoming3days_help'] = 'This time-splitting method generates predictions every 3 days. The indicators calculations will be based on the upcoming 3 days.';
+$string['timesplitting:upcoming3days_help'] = 'This analysis interval generates predictions every 3 days. The indicators calculations will be based on the upcoming 3 days.';
 $string['timesplitting:upcomingfortnight'] = 'Upcoming fortnight';
-$string['timesplitting:upcomingfortnight_help'] = 'This time-splitting method generates predictions every fortnight. The indicators calculations will be based on the upcoming fortnight.';
+$string['timesplitting:upcomingfortnight_help'] = 'This analysis interval generates predictions every fortnight. The indicators calculations will be based on the upcoming fortnight.';
 $string['timesplitting:upcomingweek'] = 'Upcoming week';
-$string['timesplitting:upcomingweek_help'] = 'This time-splitting method generates predictions every week. The indicators calculations will be based on the upcoming week.';
+$string['timesplitting:upcomingweek_help'] = 'This analysis interval generates predictions every week. The indicators calculations will be based on the upcoming week.';
 $string['thanks'] = 'Thanks';
 $string['theme'] = 'Theme';
 $string['themes'] = 'Themes';
index 606dc85..1293f1a 100644 (file)
Binary files a/lib/amd/build/templates.min.js and b/lib/amd/build/templates.min.js differ
index 89212f0..70140ee 100644 (file)
@@ -486,6 +486,7 @@ define([
         content = content
             .replace('"', '\\"')
             .replace(/([\{\}]{2,3})/g, '{{=<% %>=}}$1<%={{ }}=%>')
+            .replace(/(\r\n|\r|\n)/g, '&#x0a;')
             ;
         return '"' + content + '"';
     };
index b632895..3f8c053 100644 (file)
@@ -73,6 +73,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
         'xpath_element' => 'xpath_element',
         'form_row' => 'form_row',
         'group_message_header' => 'group_message_header',
+        'group_message' => 'group_message',
     );
 
     /**
@@ -96,6 +97,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
         'group_message_tab' => 'group_message_tab',
         'group_message_list_area' => 'group_message_list_area',
         'group_message_message_content' => 'group_message_message_content',
+        'icon_container' => 'icon_container',
         'icon' => 'icon',
         'link' => 'link',
         'link_or_button' => 'link_or_button',
@@ -162,7 +164,7 @@ XPATH
             .//*[@data-region='message-drawer' and contains(., %locator%)]//div[@data-region='content-message-container']
 XPATH
     , 'group_message_header' => <<<XPATH
-        .//*[@data-region='message-drawer']//div[@data-region='header-container' and contains(., %locator%)]
+        .//*[@data-region='message-drawer']//div[@data-region='header-content' and contains(., %locator%)]
 XPATH
     , 'group_message_member' => <<<XPATH
         .//*[@data-region='message-drawer']//div[@data-region='group-info-content-container']
@@ -178,6 +180,9 @@ XPATH
 XPATH
     , 'group_message_message_content' => <<<XPATH
         .//*[@data-region='message-drawer']//*[@data-region='message' and @data-message-id and contains(., %locator%)]
+XPATH
+    , 'icon_container' => <<<XPATH
+        .//span[contains(@data-region, concat(%locator%,'-icon-container'))]
 XPATH
         , 'icon' => <<<XPATH
 .//*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') and ( contains(normalize-space(@title), %locator%))]
index dd2aa75..83321d2 100644 (file)
@@ -103,7 +103,7 @@ class db_record_lock_factory implements lock_factory {
      * to duplicates in a clustered environment (especially on VMs due to poor time precision).
      */
     protected function generate_unique_token() {
-        return generate_uuid();
+        return \core\uuid::generate();
     }
 
     /**
index 47c5df9..8a6f101 100644 (file)
@@ -428,7 +428,7 @@ class manager {
 
         $user = null;
 
-        if (!empty($CFG->opentogoogle)) {
+        if (!empty($CFG->opentowebcrawlers)) {
             if (\core_useragent::is_web_crawler()) {
                 $user = guest_user();
             }
diff --git a/lib/classes/uuid.php b/lib/classes/uuid.php
new file mode 100644 (file)
index 0000000..b0984fa
--- /dev/null
@@ -0,0 +1,144 @@
+<?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/>.
+
+/**
+ * V4 UUID generator.
+ *
+ * @package    core
+ * @copyright  2019 Matteo Scaramuccia <moodle@matteoscaramuccia.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core;
+
+use Exception;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * V4 UUID generator class.
+ *
+ * @package    core
+ * @copyright  2019 Matteo Scaramuccia <moodle@matteoscaramuccia.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class uuid {
+    /**
+     * Generate a V4 UUID using PECL UUID extension.
+     * @see https://github.com/php/pecl-networking-uuid PECL uuid
+     * @see https://tools.ietf.org/html/rfc4122
+     *
+     * @return string|bool The UUID when PECL UUID extension is available;
+     *                     otherwise, false.
+     */
+    protected static function generate_uuid_via_pecl_uuid_extension() {
+        $uuid = false;
+
+        // Check if PECL uuid extension has been actually installed.
+        if (function_exists('\uuid_time')) {
+            // Set V4 version.
+            $uuid = \uuid_create(UUID_TYPE_RANDOM);
+        }
+
+        return $uuid;
+    }
+
+    /**
+     * Generate a V4 UUID using PHP 7+ features.
+     *
+     * @see https://www.php.net/manual/en/function.random-bytes.php
+     * @see https://tools.ietf.org/html/rfc4122
+     *
+     * @return string|bool The UUID when random_bytes() function is available;
+     *                     otherwise, false when missing the sources of randomness used by random_bytes().
+     */
+    protected static function generate_uuid_via_random_bytes() {
+        $uuid = false;
+
+        // If none of the sources of randomness are available,
+        // then an Exception will be thrown.
+        try {
+            $data = random_bytes(16);
+            $data[6] = chr((ord($data[6]) & 0x0f) | 0x40); // Set version to 0100.
+            $data[8] = chr((ord($data[8]) & 0x3f) | 0x80); // Set bits 6-7 to 10.
+            $uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
+        } catch (Exception $e) {
+            // Could not generate a random string. Is this OS secure?
+            $uuid = false;
+        }
+
+        return $uuid;
+    }
+
+    /**
+     * Generate a V4 UUID.
+     *
+     * Unique is hard. Very hard. Attempt to use the PECL UUID function if available, and if not then revert to
+     * constructing the uuid using random_bytes or mt_rand.
+     *
+     * It is important that this token is not solely based on time as this could lead
+     * to duplicates in a clustered environment (especially on VMs due to poor time precision).
+     *
+     * UUIDs are just 128 bits long but with different supported versions (RFC 4122), mainly two:
+     * - V1: the goal is uniqueness, at the cost of anonymity since it is coupled to the host generating it, via its MAC address.
+     * - V4: the goal is randomness, at the cost of (rare) collisions.
+     * Here, the V4 type is the preferred choice.
+     *
+     * The format is:
+     * xxxxxxxx-xxxx-4xxx-Yxxx-xxxxxxxxxxxx
+     * where x is any hexadecimal digit and Y is a random choice from 8, 9, a, or b.
+     *
+     * @see https://tools.ietf.org/html/rfc4122
+     *
+     * @return string The V4 UUID.
+     */
+    public static function generate() {
+        // Try PHP UUID extensions first.
+        $uuid = self::generate_uuid_via_pecl_uuid_extension();
+
+        // Fall back to better random features, when possible.
+        if (empty($uuid)) {
+            $uuid = self::generate_uuid_via_random_bytes();
+        }
+
+        // Finally, create it with the available randomness.
+        if (empty($uuid)) {
+            // Fallback uuid generation based on:
+            // "http://www.php.net/manual/en/function.uniqid.php#94959".
+            $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+
+                // 32 bits for "time_low".
+                mt_rand(0, 0xffff), mt_rand(0, 0xffff),
+
+                // 16 bits for "time_mid".
+                mt_rand(0, 0xffff),
+
+                // 16 bits for "time_hi_and_version",
+                // four most significant bits holds version number 4.
+                mt_rand(0, 0x0fff) | 0x4000,
+
+                // 16 bits, 8 bits for "clk_seq_hi_res",
+                // 8 bits for "clk_seq_low",
+                // two most significant bits holds zero and one for variant DCE1.1.
+                mt_rand(0, 0x3fff) | 0x8000,
+
+                // 48 bits for "node".
+                mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
+        }
+
+        return trim($uuid);
+    }
+}
index f304bab..62a7d44 100644 (file)
@@ -3378,5 +3378,21 @@ function xmldb_main_upgrade($oldversion) {
     // Automatically generated Moodle v3.7.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2019060600.02) {
+        // Renaming 'opentogoogle' config to 'opentowebcrawlers'.
+        $opentogooglevalue = get_config('core', 'opentogoogle');
+
+        // Move the value over if it was previously configured.
+        if ($opentogooglevalue !== false) {
+            set_config('opentowebcrawlers', $opentogooglevalue);
+        }
+
+        // Remove the now unused value.
+        unset_config('opentogoogle');
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2019060600.02);
+    }
+
     return true;
 }
index ab85441..45ed645 100644 (file)
@@ -25,7 +25,7 @@
 $string['datasetsizelimited'] = 'Only part of the dataset has been evaluated due to its size. Set $CFG->mlbackend_php_no_memory_limit if you are confident that your system can cope with a {$a} dataset.';
 $string['errorcantloadmodel'] = 'Model file {$a} does not exist. The model should been trained before using it to predict.';
 $string['errorlowscore'] = 'The evaluated model prediction accuracy is not very high, so some predictions may not be accurate. Model score = {$a->score}, minimum score = {$a->minscore}';
-$string['errornotenoughdata'] = 'There is not enough data to evaluate this model using the time-splitting method.';
+$string['errornotenoughdata'] = 'There is not enough data to evaluate this model using the provided analysis interval.';
 $string['errornotenoughdatadev'] = 'The evaluation results varied too much. It is recommended that more data is gathered to ensure the model is valid. Evaluation results standard deviation = {$a->deviation}, maximum recommended standard deviation = {$a->accepteddeviation}';
 $string['errorphp7required'] = 'The PHP machine learning backend requires PHP 7';
 $string['pluginname'] = 'PHP machine learning backend';
index 5a2c831..b568f28 100644 (file)
@@ -1107,6 +1107,8 @@ class theme_config {
             }
         }
 
+        // Allow themes to change the css url to something like theme/mytheme/mycss.php.
+        component_callback('theme_' . $this->name, 'alter_css_urls', [&$urls]);
         return $urls;
     }
 
index 3901907..0f309bf 100644 (file)
@@ -2809,16 +2809,20 @@ EOD;
         $output .= $this->box($message, 'errorbox alert alert-danger', null, array('data-rel' => 'fatalerror'));
 
         if ($CFG->debugdeveloper) {
+            $labelsep = get_string('labelsep', 'langconfig');
             if (!empty($debuginfo)) {
                 $debuginfo = s($debuginfo); // removes all nasty JS
                 $debuginfo = str_replace("\n", '<br />', $debuginfo); // keep newlines
-                $output .= $this->notification('<strong>Debug info:</strong> '.$debuginfo, 'notifytiny');
+                $label = get_string('debuginfo', 'debug') . $labelsep;
+                $output .= $this->notification("<strong>$label</strong> " . $debuginfo, 'notifytiny');
             }
             if (!empty($backtrace)) {
-                $output .= $this->notification('<strong>Stack trace:</strong> '.format_backtrace($backtrace), 'notifytiny');
+                $label = get_string('stacktrace', 'debug') . $labelsep;
+                $output .= $this->notification("<strong>$label</strong> " . format_backtrace($backtrace), 'notifytiny');
             }
             if ($obbuffer !== '' ) {
-                $output .= $this->notification('<strong>Output buffer:</strong> '.s($obbuffer), 'notifytiny');
+                $label = get_string('outputbuffer', 'debug') . $labelsep;
+                $output .= $this->notification("<strong>$label</strong> " . s($obbuffer), 'notifytiny');
             }
         }
 
index f299050..2ab030b 100644 (file)
@@ -1639,7 +1639,16 @@ class page_requirements_manager {
             'areyousure',
             'closebuttontitle',
             'unknownerror',
+            'error',
+            'file',
+            'url',
         ), 'moodle');
+        $this->strings_for_js(array(
+            'debuginfo',
+            'line',
+            'stacktrace',
+        ), 'debug');
+        $this->string_for_js('labelsep', 'langconfig');
         if (!empty($this->stringsforjs)) {
             $strings = array();
             foreach ($this->stringsforjs as $component=>$v) {
diff --git a/lib/pear/Auth/RADIUS.php b/lib/pear/Auth/RADIUS.php
deleted file mode 100644 (file)
index 297b60d..0000000
+++ /dev/null
@@ -1,1008 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4: */
-/*
-Copyright (c) 2003, Michael Bretterklieber <michael@bretterklieber.com>
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
-1. Redistributions of source code must retain the above copyright
-   notice, this list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright
-   notice, this list of conditions and the following disclaimer in the
-   documentation and/or other materials provided with the distribution.
-3. The names of the authors may not be used to endorse or promote products
-   derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
-INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
-OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
-EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-This code cannot simply be copied and put under the GNU Public License or
-any other GPL-like (LGPL, GPL2) License.
-
-    $Id$
-*/
-
-require_once('PEAR.php');
-
-/**
- * Client implementation of RADIUS. This are wrapper classes for
- * the RADIUS PECL.
- * Provides RADIUS Authentication (RFC2865) and RADIUS Accounting (RFC2866).
- *
- * @package Auth_RADIUS
- * @author  Michael Bretterklieber <michael@bretterklieber.com>
- * @access  public
- * @version $Revision$
- */
-
-
-/**
- * class Auth_RADIUS
- *
- * Abstract base class for RADIUS
- *
- * @package Auth_RADIUS
- */
-class Auth_RADIUS extends PEAR {
-
-    /**
-     * List of RADIUS servers.
-     * @var  array
-     * @see  addServer(), putServer()
-     */
-    var $_servers  = array();
-
-    /**
-     * Path to the configuration-file.
-     * @var  string
-     * @see  setConfigFile()
-     */
-    var $_configfile = null;
-
-    /**
-     * Resource.
-     * @var  resource
-     * @see  open(), close()
-     */
-    var $res = null;
-
-    /**
-     * Username for authentication and accounting requests.
-     * @var  string
-     */
-    var $username = null;
-
-    /**
-     * Password for plaintext-authentication (PAP).
-     * @var  string
-     */
-    var $password = null;
-
-    /**
-     * List of known attributes.
-     * @var  array
-     * @see  dumpAttributes(), getAttributes()
-     */
-    var $attributes = array();
-
-    /**
-     * List of raw attributes.
-     * @var  array
-     * @see  dumpAttributes(), getAttributes()
-     */
-    var $rawAttributes = array();
-
-    /**
-     * List of raw vendor specific attributes.
-     * @var  array
-     * @see  dumpAttributes(), getAttributes()
-     */
-    var $rawVendorAttributes = array();
-
-    /**
-     * Switch whether we should put standard attributes or not
-     * @var  bool
-     * @see  putStandardAttributes()
-     */
-    var $useStandardAttributes = true;
-
-    /**
-     * Constructor
-     *
-     * Loads the RADIUS PECL/extension
-     *
-     * @return void
-     */
-    public function __construct()
-    {
-        $this->loadExtension('radius');
-    }
-
-    /**
-     */
-    public function loadExtension($ext) {
-        if (extension_loaded($ext)) {
-            return true;
-        }
-        // if either returns true dl() will produce a FATAL error, stop that
-        if (
-            function_exists('dl') === false ||
-            ini_get('enable_dl') != 1 ||
-            ini_get('safe_mode') == 1
-        ) {
-            return false;
-        }
-        if (OS_WINDOWS) {
-            $suffix = '.dll';
-        } elseif (PHP_OS == 'HP-UX') {
-            $suffix = '.sl';
-        } elseif (PHP_OS == 'AIX') {
-            $suffix = '.a';
-        } elseif (PHP_OS == 'OSX') {
-            $suffix = '.bundle';
-        } else {
-            $suffix = '.so';
-        }
-        return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
-    }
-
-    /**
-     * Adds a RADIUS server to the list of servers for requests.
-     *
-     * At most 10 servers may be specified.    When multiple servers
-     * are given, they are tried in round-robin fashion until a
-     * valid response is received
-     *
-     * @param  string  $servername   Servername or IP-Address
-     * @param  integer $port         Portnumber
-     * @param  string  $sharedSecret Shared secret
-     * @param  integer $timeout      Timeout for each request
-     * @param  integer $maxtries     Max. retries for each request
-     * @return void
-     */
-    public function addServer($servername = 'localhost', $port = 0, $sharedSecret = 'testing123', $timeout = 3, $maxtries = 3)
-    {
-        $this->_servers[] = array($servername, $port, $sharedSecret, $timeout, $maxtries);
-    }
-
-    /**
-     * Returns an error message, if an error occurred.
-     *
-     * @return string
-     */
-    public function getError()
-    {
-        return radius_strerror($this->res);
-    }
-
-    /**
-     * Sets the configuration-file.
-     *
-     * @param  string  $file Path to the configuration file
-     * @return void
-     */
-    public function setConfigfile($file)
-    {
-        $this->_configfile = $file;
-    }
-
-    /**
-     * Puts an attribute.
-     *
-     * @param  integer $attrib       Attribute-number
-     * @param  mixed   $port         Attribute-value
-     * @param  type    $type         Attribute-type
-     * @return bool  true on success, false on error
-     */
-    public function putAttribute($attrib, $value, $type = null)
-    {
-        if ($type == null) {
-            $type = gettype($value);
-        }
-
-        switch ($type) {
-            case 'integer':
-            case 'double':
-                return radius_put_int($this->res, $attrib, $value);
-
-            case 'addr':
-                return radius_put_addr($this->res, $attrib, $value);
-
-            case 'string':
-            default:
-                return radius_put_attr($this->res, $attrib, $value);
-        }
-
-    }
-
-    /**
-     * Puts a vendor-specific attribute.
-     *
-     * @param  integer $vendor       Vendor (MSoft, Cisco, ...)
-     * @param  integer $attrib       Attribute-number
-     * @param  mixed   $port         Attribute-value
-     * @param  type    $type         Attribute-type
-     * @return bool  true on success, false on error
-     */
-    public function putVendorAttribute($vendor, $attrib, $value, $type = null)
-    {
-
-        if ($type == null) {
-            $type = gettype($value);
-        }
-
-        switch ($type) {
-            case 'integer':
-            case 'double':
-                return radius_put_vendor_int($this->res, $vendor, $attrib, $value);
-
-            case 'addr':
-                return radius_put_vendor_addr($this->res, $vendor,$attrib, $value);
-
-            case 'string':
-            default:
-                return radius_put_vendor_attr($this->res, $vendor, $attrib, $value);
-        }
-
-    }
-
-    /**
-     * Prints known attributes received from the server.
-     *
-     */
-    public function dumpAttributes()
-    {
-        foreach ($this->attributes as $name => $data) {
-            echo "$name:$data<br>\n";
-        }
-    }
-
-    /**
-     * Overwrite this.
-     */
-    public function open()
-    {
-    }
-
-    /**
-     * Overwrite this.
-     */
-    public function createRequest()
-    {
-    }
-
-    /**
-     * Puts standard attributes.
-     */
-    public function putStandardAttributes()
-    {
-        if (!$this->useStandardAttributes) {
-            return;
-        }
-
-        if (isset($_SERVER)) {
-            $var = $_SERVER;
-        } else {
-            $var = $GLOBALS['HTTP_SERVER_VARS'];
-        }
-
-        $this->putAttribute(RADIUS_NAS_IDENTIFIER, isset($var['HTTP_HOST']) ? $var['HTTP_HOST'] : 'localhost');
-        $this->putAttribute(RADIUS_NAS_PORT_TYPE, RADIUS_VIRTUAL);
-        $this->putAttribute(RADIUS_SERVICE_TYPE, RADIUS_FRAMED);
-        $this->putAttribute(RADIUS_FRAMED_PROTOCOL, RADIUS_PPP);
-        $this->putAttribute(RADIUS_CALLING_STATION_ID, isset($var['REMOTE_HOST']) ? $var['REMOTE_HOST'] : '127.0.0.1');
-    }
-
-    /**
-     * Puts custom attributes.
-     */
-    public function putAuthAttributes()
-    {
-        if (isset($this->username)) {
-            $this->putAttribute(RADIUS_USER_NAME, $this->username);
-        }
-    }
-
-    /**
-     * Configures the radius library.
-     *
-     * @param  string  $servername   Servername or IP-Address
-     * @param  integer $port         Portnumber
-     * @param  string  $sharedSecret Shared secret
-     * @param  integer $timeout      Timeout for each request
-     * @param  integer $maxtries     Max. retries for each request
-     * @return bool  true on success, false on error
-     * @see addServer()
-     */
-    public function putServer($servername, $port = 0, $sharedsecret = 'testing123', $timeout = 3, $maxtries = 3)
-    {
-        if (!radius_add_server($this->res, $servername, $port, $sharedsecret, $timeout, $maxtries)) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Configures the radius library via external configurationfile
-     *
-     * @param  string  $servername   Servername or IP-Address
-     * @return bool  true on success, false on error
-     */
-    public function putConfigfile($file)
-    {
-        if (!radius_config($this->res, $file)) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Initiates a RADIUS request.
-     *
-     * @return bool  true on success, false on errors
-     */
-    public function start()
-    {
-        if (!$this->open()) {
-            return false;
-        }
-
-        foreach ($this->_servers as $s) {
-            // Servername, port, sharedsecret, timeout, retries
-            if (!$this->putServer($s[0], $s[1], $s[2], $s[3], $s[4])) {
-                return false;
-            }
-        }
-
-        if (!empty($this->_configfile)) {
-            if (!$this->putConfigfile($this->_configfile)) {
-                return false;
-            }
-        }
-
-        $this->createRequest();
-        $this->putStandardAttributes();
-        $this->putAuthAttributes();
-        return true;
-    }
-
-    /**
-     * Sends a prepared RADIUS request and waits for a response
-     *
-     * @return mixed  true on success, false on reject, PEAR_Error on error
-     */
-    public function send()
-    {
-        $req = radius_send_request($this->res);
-        if (!$req) {
-            throw new Auth_RADIUS_Exception('Error sending request: ' . $this->getError());
-        }
-
-        switch($req) {
-            case RADIUS_ACCESS_ACCEPT:
-                if (is_subclass_of($this, 'auth_radius_acct')) {
-                    throw new Auth_RADIUS_Exception('RADIUS_ACCESS_ACCEPT is unexpected for accounting');
-                }
-                return true;
-
-            case RADIUS_ACCESS_REJECT:
-                return false;
-
-            case RADIUS_ACCOUNTING_RESPONSE:
-                if (is_subclass_of($this, 'auth_radius_pap')) {
-                    throw new Auth_RADIUS_Exception('RADIUS_ACCOUNTING_RESPONSE is unexpected for authentication');
-                }
-                return true;
-
-            default:
-                throw new Auth_RADIUS_Exception("Unexpected return value: $req");
-        }
-
-    }
-
-    /**
-     * Reads all received attributes after sending the request.
-     *
-     * This methods stores known attributes in the property attributes,
-     * all attributes (including known attibutes) are stored in rawAttributes
-     * or rawVendorAttributes.
-     * NOTE: call this function also even if the request was rejected, because the
-     * Server returns usualy an errormessage
-     *
-     * @return bool   true on success, false on error
-     */
-    public function getAttributes()
-    {
-
-        while ($attrib = radius_get_attr($this->res)) {
-
-            if (!is_array($attrib)) {
-                return false;
-            }
-
-            $attr = $attrib['attr'];
-            $data = $attrib['data'];
-
-            $this->rawAttributes[$attr] = $data;
-
-            switch ($attr) {
-                case RADIUS_FRAMED_IP_ADDRESS:
-                    $this->attributes['framed_ip'] = radius_cvt_addr($data);
-                    break;
-
-                case RADIUS_FRAMED_IP_NETMASK:
-                    $this->attributes['framed_mask'] = radius_cvt_addr($data);
-                    break;
-
-                case RADIUS_FRAMED_MTU:
-                    $this->attributes['framed_mtu'] = radius_cvt_int($data);
-                    break;
-
-                case RADIUS_FRAMED_COMPRESSION:
-                    $this->attributes['framed_compression'] = radius_cvt_int($data);
-                    break;
-
-                case RADIUS_SESSION_TIMEOUT:
-                    $this->attributes['session_timeout'] = radius_cvt_int($data);
-                    break;
-
-                case RADIUS_IDLE_TIMEOUT:
-                    $this->attributes['idle_timeout'] = radius_cvt_int($data);
-                    break;
-
-                case RADIUS_SERVICE_TYPE:
-                    $this->attributes['service_type'] = radius_cvt_int($data);
-                    break;
-
-                case RADIUS_CLASS:
-                    $this->attributes['class'] = radius_cvt_string($data);
-                    break;
-
-                case RADIUS_FRAMED_PROTOCOL:
-                    $this->attributes['framed_protocol'] = radius_cvt_int($data);
-                    break;
-
-                case RADIUS_FRAMED_ROUTING:
-                    $this->attributes['framed_routing'] = radius_cvt_int($data);
-                    break;
-
-                case RADIUS_FILTER_ID:
-                    $this->attributes['filter_id'] = radius_cvt_string($data);
-                    break;
-
-                case RADIUS_REPLY_MESSAGE:
-                    $this->attributes['reply_message'] = radius_cvt_string($data);
-                    break;
-
-                case RADIUS_VENDOR_SPECIFIC:
-                    $attribv = radius_get_vendor_attr($data);
-                    if (!is_array($attribv)) {
-                        return false;
-                    }
-
-                    $vendor = $attribv['vendor'];
-                    $attrv = $attribv['attr'];
-                    $datav = $attribv['data'];
-
-                    $this->rawVendorAttributes[$vendor][$attrv] = $datav;
-
-                    if ($vendor == RADIUS_VENDOR_MICROSOFT) {
-
-                        switch ($attrv) {
-                            case RADIUS_MICROSOFT_MS_CHAP2_SUCCESS:
-                                $this->attributes['ms_chap2_success'] = radius_cvt_string($datav);
-                                break;
-
-                            case RADIUS_MICROSOFT_MS_CHAP_ERROR:
-                                $this->attributes['ms_chap_error'] = radius_cvt_string(substr($datav,1));
-                                break;
-
-                            case RADIUS_MICROSOFT_MS_CHAP_DOMAIN:
-                                $this->attributes['ms_chap_domain'] = radius_cvt_string($datav);
-                                break;
-
-                            case RADIUS_MICROSOFT_MS_MPPE_ENCRYPTION_POLICY:
-                                $this->attributes['ms_mppe_encryption_policy'] = radius_cvt_int($datav);
-                                break;
-
-                            case RADIUS_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES:
-                                $this->attributes['ms_mppe_encryption_types'] = radius_cvt_int($datav);
-                                break;
-
-                            case RADIUS_MICROSOFT_MS_CHAP_MPPE_KEYS:
-                                $demangled = radius_demangle($this->res, $datav);
-                                $this->attributes['ms_chap_mppe_lm_key'] = substr($demangled, 0, 8);
-                                $this->attributes['ms_chap_mppe_nt_key'] = substr($demangled, 8, RADIUS_MPPE_KEY_LEN);
-                                break;
-
-                            case RADIUS_MICROSOFT_MS_MPPE_SEND_KEY:
-                                $this->attributes['ms_chap_mppe_send_key'] = radius_demangle_mppe_key($this->res, $datav);
-                                break;
-
-                            case RADIUS_MICROSOFT_MS_MPPE_RECV_KEY:
-                                $this->attributes['ms_chap_mppe_recv_key'] = radius_demangle_mppe_key($this->res, $datav);
-                                break;
-
-                            case RADIUS_MICROSOFT_MS_PRIMARY_DNS_SERVER:
-                                $this->attributes['ms_primary_dns_server'] = radius_cvt_string($datav);
-                                break;
-                        }
-                    }
-                    break;
-
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Frees resources.
-     *
-     * Calling this method is always a good idea, because all security relevant
-     * attributes are filled with Nullbytes to leave nothing in the mem.
-     *
-     */
-    public function close()
-    {
-        if ($this->res != null) {
-            radius_close($this->res);
-            $this->res = null;
-        }
-        $this->username = str_repeat("\0", strlen($this->username));
-        $this->password = str_repeat("\0", strlen($this->password));
-    }
-
-}
-
-/**
- * class Auth_RADIUS_PAP
- *
- * Class for authenticating using PAP (Plaintext)
- *
- * @package Auth_RADIUS
- */
-class Auth_RADIUS_PAP extends Auth_RADIUS
-{
-
-    /**
-     * Constructor
-     *
-     * @param  string  $username   Username
-     * @param  string  $password   Password
-     * @return void
-     */
-    public function __construct($username = null, $password = null)
-    {
-        parent::__construct();
-        $this->username = $username;
-        $this->password = $password;
-    }
-
-    /**
-     * Creates a RADIUS resource
-     *
-     * Creates a RADIUS resource for authentication. This should be the first
-     * call before you make any other things with the library.
-     *
-     * @return bool   true on success, false on error
-     */
-    function open()
-    {
-        $this->res = radius_auth_open();
-        if (!$this->res) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Creates an authentication request
-     *
-     * Creates an authentication request.
-     * You MUST call this method before you can put any attribute
-     *
-     * @return bool   true on success, false on error
-     */
-    function createRequest()
-    {
-        if (!radius_create_request($this->res, RADIUS_ACCESS_REQUEST)) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Put authentication specific attributes
-     *
-     * @return void
-     */
-    function putAuthAttributes()
-    {
-        if (isset($this->username)) {
-            $this->putAttribute(RADIUS_USER_NAME, $this->username);
-        }
-        if (isset($this->password)) {
-            $this->putAttribute(RADIUS_USER_PASSWORD, $this->password);
-        }
-    }
-
-}
-
-/**
- * class Auth_RADIUS_CHAP_MD5
- *
- * Class for authenticating using CHAP-MD5 see RFC1994.
- * Instead og the plaintext password the challenge and
- * the response are needed.
- *
- * @package Auth_RADIUS
- */
-class Auth_RADIUS_CHAP_MD5 extends Auth_RADIUS_PAP
-{
-    /**
-     * 8 Bytes binary challenge
-     * @var  string
-     */
-    var $challenge = null;
-
-    /**
-     * 16 Bytes MD5 response binary
-     * @var  string
-     */
-    var $response = null;
-
-    /**
-     * Id of the authentication request. Should incremented after every request.
-     * @var  integer
-     */
-    var $chapid = 1;
-
-    /**
-     * Constructor
-     *
-     * @param  string  $username   Username
-     * @param  string  $challenge  8 Bytes Challenge (binary)
-     * @param  integer $chapid     Requestnumber
-     * @return void
-     */
-    function __construct($username = null, $challenge = null, $chapid = 1)
-    {
-        parent::__construct();
-        $this->username = $username;
-        $this->challenge = $challenge;
-        $this->chapid = $chapid;
-    }
-
-    /**
-     * Put CHAP-MD5 specific attributes
-     *
-     * For authenticating using CHAP-MD5 via RADIUS you have to put the challenge
-     * and the response. The chapid is inserted in the first byte of the response.
-     *
-     * @return void
-     */
-    function putAuthAttributes()
-    {
-        if (isset($this->username)) {
-            $this->putAttribute(RADIUS_USER_NAME, $this->username);
-        }
-        if (isset($this->response)) {
-            $response = pack('C', $this->chapid) . $this->response;
-            $this->putAttribute(RADIUS_CHAP_PASSWORD, $response);
-        }
-        if (isset($this->challenge)) {
-            $this->putAttribute(RADIUS_CHAP_CHALLENGE, $this->challenge);
-        }
-    }
-
-    /**
-     * Frees resources.
-     *
-     * Calling this method is always a good idea, because all security relevant
-     * attributes are filled with Nullbytes to leave nothing in the mem.
-     */
-    public function close()
-    {
-        parent::close();
-        $this->challenge =  str_repeat("\0", strlen($this->challenge));
-        $this->response =  str_repeat("\0", strlen($this->response));
-    }
-
-}
-
-/**
- * class Auth_RADIUS_MSCHAPv1
- *
- * Class for authenticating using MS-CHAPv1 see RFC2433
- *
- * @package Auth_RADIUS
- */
-class Auth_RADIUS_MSCHAPv1 extends Auth_RADIUS_CHAP_MD5
-{
-    /**
-     * LAN-Manager-Response
-     * @var  string
-     */
-    var $lmResponse = null;
-
-    /**
-     * Wether using deprecated LM-Responses or not.
-     * 0 = use LM-Response, 1 = use NT-Response
-     * @var  bool
-     */
-    var $flags = 1;
-
-    /**
-     * Put MS-CHAPv1 specific attributes
-     *
-     * For authenticating using MS-CHAPv1 via RADIUS you have to put the challenge
-     * and the response. The response has this structure:
-     * struct rad_mschapvalue {
-     *   u_char ident;
-     *   u_char flags;
-     *   u_char lm_response[24];
-     *   u_char response[24];
-     * };
-     *
-     * @return void
-     */
-    function putAuthAttributes()
-    {
-        if (isset($this->username)) {
-            $this->putAttribute(RADIUS_USER_NAME, $this->username);
-        }
-        if (isset($this->response) || isset($this->lmResponse)) {
-            $lmResp = isset($this->lmResponse) ? $this->lmResponse : str_repeat ("\0", 24);
-            $ntResp = isset($this->response)   ? $this->response :   str_repeat ("\0", 24);
-            $resp = pack('CC', $this->chapid, $this->flags) . $lmResp . $ntResp;
-            $this->putVendorAttribute(RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP_RESPONSE, $resp);
-        }
-        if (isset($this->challenge)) {
-            $this->putVendorAttribute(RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP_CHALLENGE, $this->challenge);
-        }
-    }
-}
-
-/**
- * class Auth_RADIUS_MSCHAPv2
- *
- * Class for authenticating using MS-CHAPv2 see RFC2759
- *
- * @package Auth_RADIUS
- */
-class Auth_RADIUS_MSCHAPv2 extends Auth_RADIUS_MSCHAPv1
-{
-    /**
-     * 16 Bytes binary challenge
-     * @var  string
-     */
-    var $challenge = null;
-
-    /**
-     * 16 Bytes binary Peer Challenge
-     * @var  string
-     */
-    var $peerChallenge = null;
-
-    /**
-     * Put MS-CHAPv2 specific attributes
-     *
-     * For authenticating using MS-CHAPv1 via RADIUS you have to put the challenge
-     * and the response. The response has this structure:
-     * struct rad_mschapv2value {
-     *   u_char ident;
-     *   u_char flags;
-     *   u_char pchallenge[16];
-     *   u_char reserved[8];
-     *   u_char response[24];
-     * };
-     * where pchallenge is the peer challenge. Like for MS-CHAPv1 we set the flags field to 1.
-     * @return void
-     */
-    function putAuthAttributes()
-    {
-        if (isset($this->username)) {
-            $this->putAttribute(RADIUS_USER_NAME, $this->username);
-        }
-        if (isset($this->response) && isset($this->peerChallenge)) {
-            // Response: chapid, flags (1 = use NT Response), Peer challenge, reserved, Response
-            $resp = pack('CCa16a8a24',$this->chapid , 1, $this->peerChallenge, str_repeat("\0", 8), $this->response);
-            $this->putVendorAttribute(RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP2_RESPONSE, $resp);
-        }
-        if (isset($this->challenge)) {
-            $this->putVendorAttribute(RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP_CHALLENGE, $this->challenge);
-        }
-    }
-
-    /**
-     * Frees resources.
-     *
-     * Calling this method is always a good idea, because all security relevant
-     * attributes are filled with Nullbytes to leave nothing in the mem.
-     *
-     * @access public
-     */
-    function close()
-    {
-        parent::close();
-        $this->peerChallenge = str_repeat("\0", strlen($this->peerChallenge));
-    }
-}
-
-/**
- * class Auth_RADIUS_Acct
- *
- * Class for RADIUS accounting
- *
- * @package Auth_RADIUS
- */
-class Auth_RADIUS_Acct extends Auth_RADIUS
-{
-    /**
-     * Defines where the Authentication was made, possible values are:
-     * RADIUS_AUTH_RADIUS, RADIUS_AUTH_LOCAL, RADIUS_AUTH_REMOTE
-     * @var  integer
-     */
-    var $authentic = null;
-
-    /**
-     * Defines the type of the accounting request, on of:
-     * RADIUS_START, RADIUS_STOP, RADIUS_ACCOUNTING_ON, RADIUS_ACCOUNTING_OFF
-     * @var  integer
-     */
-    var $status_type = null;
-
-    /**
-     * The time the user was logged in in seconds
-     * @var  integer
-     */
-    var $session_time = null;
-
-    /**
-     * A uniq identifier for the session of the user, maybe the PHP-Session-Id
-     * @var  string
-     */
-    var $session_id = null;
-
-    /**
-     * Constructor
-     *
-     * Generates a predefined session_id. We use the Remote-Address, the PID, and the Current user.
-     * @return void
-     */
-    function __construct()
-    {
-        parent::__construct();
-
-        if (isset($_SERVER)) {
-            $var = $_SERVER;
-        } else {
-            $var = $GLOBALS['HTTP_SERVER_VARS'];
-        }
-
-        $this->session_id = sprintf("%s:%d-%s", isset($var['REMOTE_ADDR']) ? $var['REMOTE_ADDR'] : '127.0.0.1' , getmypid(), get_current_user());
-    }
-
-    /**
-     * Creates a RADIUS resource
-     *
-     * Creates a RADIUS resource for accounting. This should be the first
-     * call before you make any other things with the library.
-     *
-     * @return bool   true on success, false on error
-     */
-    function open()
-    {
-        $this->res = radius_acct_open();
-        if (!$this->res) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Creates an accounting request
-     *
-     * Creates an accounting request.
-     * You MUST call this method before you can put any attribute.
-     *
-     * @return bool   true on success, false on error
-     */
-    function createRequest()
-    {
-        if (!radius_create_request($this->res, RADIUS_ACCOUNTING_REQUEST)) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Put attributes for accounting.
-     *
-     * Here we put some accounting values. There many more attributes for accounting,
-     * but for web-applications only certain attributes make sense.
-     * @return void
-     */
-    function putAuthAttributes()
-    {
-        $this->putAttribute(RADIUS_ACCT_SESSION_ID, $this->session_id);
-        $this->putAttribute(RADIUS_ACCT_STATUS_TYPE, $this->status_type);
-        if (isset($this->session_time) && $this->status_type == RADIUS_STOP) {
-            $this->putAttribute(RADIUS_ACCT_SESSION_TIME, $this->session_time);
-        }
-        if (isset($this->authentic)) {
-            $this->putAttribute(RADIUS_ACCT_AUTHENTIC, $this->authentic);
-        }
-
-    }
-
-}
-
-/**
- * class Auth_RADIUS_Acct_Start
- *
- * Class for RADIUS accounting. Its usualy used, after the user has logged in.
- *
- * @package Auth_RADIUS
- */
-class Auth_RADIUS_Acct_Start extends Auth_RADIUS_Acct
-{
-    /**
-     * Defines the type of the accounting request.
-     * It is set to RADIUS_START by default in this class.
-     * @var  integer
-     */
-    var $status_type = RADIUS_START;
-}
-
-/**
- * class Auth_RADIUS_Acct_Start
- *
- * Class for RADIUS accounting. Its usualy used, after the user has logged out.
- *
- * @package Auth_RADIUS
- */
-class Auth_RADIUS_Acct_Stop extends Auth_RADIUS_Acct
-{
-    /**
-     * Defines the type of the accounting request.
-     * It is set to RADIUS_STOP by default in this class.
-     * @var  integer
-     */
-    var $status_type = RADIUS_STOP;
-}
-
-if (!defined('RADIUS_UPDATE')) {
-    define('RADIUS_UPDATE', 3);
-}
-
-/**
- * class Auth_RADIUS_Acct_Update
- *
- * Class for interim RADIUS accounting updates.
- *
- * @package Auth_RADIUS
- */
-class Auth_RADIUS_Acct_Update extends Auth_RADIUS_Acct
-{
-    /**
-     * Defines the type of the accounting request.
-     * It is set to RADIUS_UPDATE by default in this class.
-     * @var  integer
-     */
-    var $status_type = RADIUS_UPDATE;
-}
-
-class Auth_RADIUS_Exception extends Exception {}
\ No newline at end of file
index de71c62..42af0e9 100644 (file)
@@ -45,11 +45,6 @@ In detail, the libraries added here are:
     - by Elizabeth Smith, Arpad Ray, Joshua Eichorn, David Coallier and Laurent Yaish
     - License: LGPL
     - http://pear.php.net/package/HTML_AJAX/
-- PEAR Auth_RADIUS:
-    - Current version: 1.1.0 (2015-02-10)
-    - by Michael Bretterklieber
-    - License: BSD
-    - http://pear.php.net/package/Auth_RADIUS
 - PEAR Crypt_CHAP:
     - Current Version: 1.0.1 (2007-03-14)
     - by Michael Bretterklieber
index 05043ac..fb38615 100644 (file)
@@ -1,18 +1,6 @@
 MOODLE-SPECIFIC PEAR MODIFICATIONS
 ==================================
 
-Auth/RADIUS
-===========
-
-1/ Changed static call to correct alternative (MDL-38373):
-    - From: PEAR::loadExtension('radius'); (in global scope)
-    - To: $this->loadExtension('radius'); (in constructor)
-2/ Upgraded to version 1.1.0 (see MDL-51523).
-   Changes made to the lib/pear/Auth/RADIUS.php file that was downloaded.
-    - Added "require_once('PEAR.php')".
-    - Changed the 'Auth_RADIUS' class so that it extends the 'PEAR' class.
-    - Changed the function 'loadExtension' to public.
-
 XML/Parser
 =================
 1/ changed ereg_ to preg_
diff --git a/lib/recaptchalib.php b/lib/recaptchalib.php
deleted file mode 100644 (file)
index 79eb4a3..0000000
+++ /dev/null
@@ -1,343 +0,0 @@
-<?php
-
-/**
- * This is a PHP library that handles calling reCAPTCHA.
- *    - Documentation and latest version
- *          {@link http://code.google.com/apis/recaptcha/docs/php.html}
- *    - Get a reCAPTCHA API Key
- *          {@link https://www.google.com/recaptcha/admin/create}
- *    - Discussion group
- *          {@link http://groups.google.com/group/recaptcha}
- *
- * Copyright (c) 2007 reCAPTCHA -- {@link http://www.google.com/recaptcha}
- * AUTHORS:
- *   Mike Crawford
- *   Ben Maurer
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * @package moodlecore
- * @copyright (c) 2007 reCAPTCHA -- {@link http://www.google.com/recaptcha}
- */
-
-/**
- * The reCAPTCHA server URL's
- */
-define("RECAPTCHA_API_SERVER", "http://www.google.com/recaptcha/api");
-define("RECAPTCHA_API_SECURE_SERVER", "https://www.google.com/recaptcha/api");
-define("RECAPTCHA_VERIFY_SERVER", "www.google.com");
-
-/**
- * Encodes the given data into a query string format
- * @param $data - array of string elements to be encoded
- * @return string - encoded request
- */
-function _recaptcha_qsencode ($data) {
-        $req = "";
-        foreach ( $data as $key => $value )
-                $req .= $key . '=' . urlencode( $value ) . '&';
-
-        // Cut the last '&'
-        $req=substr($req,0,strlen($req)-1);
-        return $req;
-}
-
-
-
-/**
- * Submits an HTTP POST to a reCAPTCHA server
- *
- * @global object
- * @param string $host
- * @param string $path
- * @param array $data
- * @param int port
- * @return array response
- */
-function _recaptcha_http_post($host, $path, $data, $port = 80, $https=false) {
-        global $CFG;
-        $protocol = 'http';
-        if ($https) {
-            $protocol = 'https';
-        }
-
-        require_once $CFG->libdir . '/filelib.php';
-
-        $req = _recaptcha_qsencode ($data);
-
-        $headers = array();
-        $headers['Host'] = $host;
-        $headers['Content-Type'] = 'application/x-www-form-urlencoded';
-        $headers['Content-Length'] = strlen($req);
-        $headers['User-Agent'] = 'reCAPTCHA/PHP';
-
-        $results = download_file_content("$protocol://" . $host . $path, $headers, $data, false, 300, 20, true);
-
-        if ($results) {
-            return array(1 => $results);
-        } else {
-            return false;
-        }
-}
-
-/**
- * Return the recaptcha challenge and image and javascript urls
- *
- * @param  string $server    server url
- * @param  string $pubkey    public key
- * @param  string $errorpart error part to append
- * @return array the challenge hash, image and javascript url
- * @since  Moodle 3.2
- */
-function recaptcha_get_challenge_hash_and_urls($server, $pubkey, $errorpart = '') {
-    global $CFG;
-
-    require_once($CFG->libdir . '/filelib.php');
-    $html = download_file_content($server . '/noscript?k=' . $pubkey . $errorpart, null, null, false, 300, 20, true);
-    preg_match('/image\?c\=([A-Za-z0-9\-\_]*)\"/', $html, $matches);
-    $challengehash = $matches[1];
-    $imageurl = $server . '/image?c=' . $challengehash;
-
-    $jsurl = $server . '/challenge?k=' . $pubkey . $errorpart;
-
-    return array($challengehash, $imageurl, $jsurl);
-}
-
-
-/**
- * Gets the challenge HTML (javascript and non-javascript version).
- * This is called from the browser, and the resulting reCAPTCHA HTML widget
- * is embedded within the HTML form it was called from.
- *
- * @global object
- * @param string $pubkey A public key for reCAPTCHA
- * @param string $error The error given by reCAPTCHA (optional, default is null)
- * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
-
- * @return string - The HTML to be embedded in the user's form.
- */
-function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) {
-    global $PAGE;
-
-    $recaptchatype = optional_param('recaptcha', 'image', PARAM_TEXT);
-
-    if ($pubkey == null || $pubkey == '') {
-               die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
-    }
-
-    if ($use_ssl) {
-        $server = RECAPTCHA_API_SECURE_SERVER;
-    } else {
-        $server = RECAPTCHA_API_SERVER;
-    }
-
-    $errorpart = "";
-    if ($error) {
-        $errorpart = "&amp;error=" . $error;
-    }
-
-    list($challengehash, $imageurl, $jsurl) = recaptcha_get_challenge_hash_and_urls($server, $pubkey, $errorpart);
-
-    $strincorrectpleasetryagain = get_string('incorrectpleasetryagain', 'auth');
-    $strenterthewordsabove = get_string('enterthewordsabove', 'auth');
-    $strenterthenumbersyouhear = get_string('enterthenumbersyouhear', 'auth');
-    $strgetanothercaptcha = get_string('getanothercaptcha', 'auth');
-    $strgetanaudiocaptcha = get_string('getanaudiocaptcha', 'auth');
-    $strgetanimagecaptcha = get_string('getanimagecaptcha', 'auth');
-
-    $return = html_writer::script('', $jsurl);
-    $return .= '<noscript>
-        <div id="recaptcha_widget_noscript">
-        <div id="recaptcha_image_noscript"><img src="' . $imageurl . '" alt="reCAPTCHA"/></div>';
-
-    if ($error == 'incorrect-captcha-sol') {
-        $return .= '<div class="recaptcha_only_if_incorrect_sol" style="color:red">' . $strincorrectpleasetryagain . '</div>';
-    }
-
-    if ($recaptchatype == 'image') {
-        $return .= '<span class="recaptcha_only_if_image">' . $strenterthewordsabove . '</span>';
-    } elseif ($recaptchatype == 'audio') {
-        $return .= '<span class="recaptcha_only_if_audio">' . $strenterthenumbersyouhear . '</span>';
-    }
-
-    $return .= '<input type="text" id="recaptcha_response_field_noscript" name="recaptcha_response_field" />';
-    $return .= '<input type="hidden" id="recaptcha_challenge_field_noscript" name="recaptcha_challenge_field" value="' . $challengehash . '" />';
-    $return .= '<div><a href="signup.php">' . $strgetanothercaptcha . '</a></div>';
-
-    // Disabling audio recaptchas for now: not language-independent
-    /*
-    if ($recaptchatype == 'image') {
-        $return .= '<div class="recaptcha_only_if_image"><a href="signup.php?recaptcha=audio">' . $strgetanaudiocaptcha . '</a></div>';
-    } elseif ($recaptchatype == 'audio') {
-        $return .= '<div class="recaptcha_only_if_audio"><a href="signup.php?recaptcha=image">' . $strgetanimagecaptcha . '</a></div>';
-    }
-    */
-
-    $return .= '
-        </div>
-    </noscript>';
-
-    return $return;
-}
-
-
-
-
-/**
- * A ReCaptchaResponse is returned from recaptcha_check_answer()
- *
- * @package moodlecore
- * @copyright (c) 2007 reCAPTCHA -- {@link http://www.google.com/recaptcha}
- */
-class ReCaptchaResponse {
-        var $is_valid;
-        var $error;
-}
-
-
-/**
-  * Calls an HTTP POST function to verify if the user's guess was correct
-  * @param string $privkey
-  * @param string $remoteip
-  * @param string $challenge
-  * @param string $response
-  * @return ReCaptchaResponse
-  */
-function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $https=false)
-{
-    if ($privkey == null || $privkey == '') {
-               die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
-    }
-
-    if ($remoteip == null || $remoteip == '') {
-        die ("For security reasons, you must pass the remote ip to reCAPTCHA");
-    }
-
-        //discard spam submissions
-        if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) {
-                $recaptcha_response = new ReCaptchaResponse();
-                $recaptcha_response->is_valid = false;
-                $recaptcha_response->error = 'incorrect-captcha-sol';
-                return $recaptcha_response;
-        }
-
-        $response = _recaptcha_http_post(RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify",
-                                         array (
-                                                'privatekey' => $privkey,
-                                                'remoteip' => $remoteip,
-                                                'challenge' => $challenge,
-                                                'response' => $response
-                                                ),
-                                         $https
-                                        );
-
-        $answers = explode ("\n", $response [1]);
-        $recaptcha_response = new ReCaptchaResponse();
-
-        if (trim ($answers [0]) == 'true') {
-                $recaptcha_response->is_valid = true;
-        }
-        else {
-                $recaptcha_response->is_valid = false;
-                $recaptcha_response->error = $answers [1];
-        }
-        return $recaptcha_response;
-
-}
-
-/**
- * gets a URL where the user can sign up for reCAPTCHA. If your application
- * has a configuration page where you enter a key, you should provide a link
- * using this function.
- * @param string $domain The domain where the page is hosted
- * @param string $appname The name of your application
- */
-function recaptcha_get_signup_url ($domain = null, $appname = null) {
-       return "https://www.google.com/recaptcha/admin/create?" .  _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname));
-}
-
-function _recaptcha_aes_pad($val) {
-    $block_size = 16;
-    $numpad = $block_size - (strlen ($val) % $block_size);
-    return str_pad($val, strlen ($val) + $numpad, chr($numpad));
-}
-
-/* Mailhide related code */
-
-function _recaptcha_aes_encrypt($val,$ky) {
-    if (! function_exists ("mcrypt_encrypt")) {
-        die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
-    }
-    $mode=MCRYPT_MODE_CBC;
-    $enc=MCRYPT_RIJNDAEL_128;
-    $val=_recaptcha_aes_pad($val);
-    return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
-}
-
-
-function _recaptcha_mailhide_urlbase64 ($x) {
-    return strtr(base64_encode ($x), '+/', '-_');
-}
-
-/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
-function recaptcha_mailhide_url($pubkey, $privkey, $email) {
-    if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) {
-        die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
-                    "you can do so at <a href='http://www.google.com/recaptcha/mailhide/apikey'>http://www.google.com/recaptcha/mailhide/apikey</a>");
-    }
-
-
-    $ky = pack('H*', $privkey);
-    $cryptmail = _recaptcha_aes_encrypt ($email, $ky);
-
-       return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail);
-}
-
-/**
- * gets the parts of the email to expose to the user.
- * eg, given johndoe@example,com return ["john", "example.com"].
- * the email is then displayed as john...@example.com
- */
-function _recaptcha_mailhide_email_parts ($email) {
-    $arr = preg_split("/@/", $email );
-
-    if (strlen ($arr[0]) <= 4) {
-        $arr[0] = substr ($arr[0], 0, 1);
-    } else if (strlen ($arr[0]) <= 6) {
-        $arr[0] = substr ($arr[0], 0, 3);
-    } else {
-        $arr[0] = substr ($arr[0], 0, 4);
-    }
-    return $arr;
-}
-
-/**
- * Gets html to display an email address given a public an private key.
- * to get a key, go to:
- *
- * http://www.google.com/recaptcha/mailhide/apikey
- */
-function recaptcha_mailhide_html($pubkey, $privkey, $email) {
-    $emailparts = _recaptcha_mailhide_email_parts ($email);
-    $url = recaptcha_mailhide_url ($pubkey, $privkey, $email);
-
-    return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) .
-        "' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]);
-
-}
index c7b74ef..5db32ce 100644 (file)
@@ -610,49 +610,24 @@ function get_exception_info($ex) {
 }
 
 /**
- * Generate a uuid.
+ * Generate a V4 UUID.
  *
- * Unique is hard. Very hard. Attempt to use the PECL UUID functions if available, and if not then revert to
+ * Unique is hard. Very hard. Attempt to use the PECL UUID function if available, and if not then revert to
  * constructing the uuid using mt_rand.
  *
  * It is important that this token is not solely based on time as this could lead
  * to duplicates in a clustered environment (especially on VMs due to poor time precision).
  *
+ * @see https://tools.ietf.org/html/rfc4122
+ *
+ * @deprecated since Moodle 3.8 MDL-61038 - please do not use this function any more.
+ * @see \core\uuid::generate()
+ *
  * @return string The uuid.
  */
 function generate_uuid() {
-    $uuid = '';
-
-    if (function_exists("uuid_create")) {
-        $context = null;
-        uuid_create($context);
-
-        uuid_make($context, UUID_MAKE_V4);
-        uuid_export($context, UUID_FMT_STR, $uuid);
-    } else {
-        // Fallback uuid generation based on:
-        // "http://www.php.net/manual/en/function.uniqid.php#94959".
-        $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
-
-            // 32 bits for "time_low".
-            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
-
-            // 16 bits for "time_mid".
-            mt_rand(0, 0xffff),
-
-            // 16 bits for "time_hi_and_version",
-            // four most significant bits holds version number 4.
-            mt_rand(0, 0x0fff) | 0x4000,
-
-            // 16 bits, 8 bits for "clk_seq_hi_res",
-            // 8 bits for "clk_seq_low",
-            // two most significant bits holds zero and one for variant DCE1.1.
-            mt_rand(0, 0x3fff) | 0x8000,
-
-            // 48 bits for "node".
-            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
-    }
-    return trim($uuid);
+    debugging('generate_uuid() is deprecated. Please use \core\uuid::generate() instead.', DEBUG_DEVELOPER);
+    return \core\uuid::generate();
 }
 
 /**
@@ -1513,7 +1488,7 @@ function make_unique_writable_directory($basedir, $exceptiononerror = true) {
 
     do {
         // Generate a new (hopefully unique) directory name.
-        $uniquedir = $basedir . DIRECTORY_SEPARATOR . generate_uuid();
+        $uniquedir = $basedir . DIRECTORY_SEPARATOR . \core\uuid::generate();
     } while (
             // Ensure that basedir is still writable - if we do not check, we could get stuck in a loop here.
             is_writable($basedir) &&
index e87f6c4..e468ae1 100644 (file)
@@ -213,6 +213,16 @@ class behat_data_generators extends behat_base {
             'required' => array('user', 'group', 'message'),
             'switchids' => array('user' => 'userid', 'group' => 'groupid')
         ),
+        'muted group conversations' => array(
+            'datagenerator' => 'mute_group_conversations',
+            'required' => array('user', 'group', 'course'),
+            'switchids' => array('user' => 'userid', 'group' => 'groupid', 'course' => 'courseid')
+        ),
+        'muted private conversations' => array(
+            'datagenerator' => 'mute_private_conversations',
+            'required' => array('user', 'contact'),
+            'switchids' => array('user' => 'userid', 'contact' => 'contactid')
+        ),
         'language customisations' => array(
             'datagenerator' => 'customlang',
             'required' => array('component', 'stringid', 'value'),
@@ -1032,4 +1042,42 @@ class behat_data_generators extends behat_base {
         }
         \core_message\api::set_favourite_conversation($conversationid, $data['userid']);
     }
+
+    /**
+     * Mute an existing group conversation for user
+     *
+     * @param array $data
+     * @return void
+     */
+    protected function process_mute_group_conversations(array $data) {
+        if (groups_is_member($data['groupid'], $data['userid'])) {
+            $context = context_course::instance($data['courseid']);
+            $conversation = \core_message\api::get_conversation_by_area(
+                'core_group',
+                'groups',
+                $data['groupid'],
+                $context->id
+            );
+            if ($conversation) {
+                \core_message\api::mute_conversation($data['userid'], $conversation->id);
+            }
+        }
+    }
+
+    /**
+     * Mute a private conversation for user
+     *
+     * @param array $data
+     * @return void
+     */
+    protected function process_mute_private_conversations(array $data) {
+        if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) {
+            $conversation = \core_message\api::create_conversation(
+                \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
+                [$data['userid'], $data['contactid']]
+            );
+            $conversationid = $conversation->id;
+        }
+        \core_message\api::mute_conversation($data['userid'], $conversationid);
+    }
 }
index e38124e..6e3ce5b 100644 (file)
@@ -25,7 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-
 /**
  * Unit tests for setuplib.php
  *
@@ -476,4 +475,65 @@ class core_setuplib_testcase extends advanced_testcase {
     public function test_get_real_size($input, $expectedbytes) {
         $this->assertEquals($expectedbytes, get_real_size($input));
     }
+
+    /**
+     * Validate the given V4 UUID.
+     *
+     * @param string $value The candidate V4 UUID
+     * @return bool True if valid; otherwise, false.
+     */
+    protected static function is_valid_uuid_v4($value) {
+        // Version 4 UUIDs have the form xxxxxxxx-xxxx-4xxx-Yxxx-xxxxxxxxxxxx
+        // where x is any hexadecimal digit and Y is one of 8, 9, aA, or bB.
+        // First, the size is 36 (32 + 4 dashes).
+        if (strlen($value) != 36) {
+            return false;
+        }
+        // Finally, check the format.
+        $uuidv4pattern = '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i';
+        return (preg_match($uuidv4pattern, $value) === 1);
+    }
+
+    /**
+     * Test the \core\uuid::generate_uuid_via_pecl_uuid_extension() function.
+     */
+    public function test_core_uuid_generate_uuid_via_pecl_uuid_extension() {
+        if (!extension_loaded('uuid')) {
+            $this->markTestSkipped("PHP 'uuid' extension not loaded.");
+        }
+        if (!function_exists('uuid_time')) {
+            $this->markTestSkipped("PHP PECL 'uuid' extension not loaded.");
+        }
+
+        // The \core\uuid::generate_uuid_via_pecl_uuid_extension static method is protected. Use Reflection to call the method.
+        $method = new ReflectionMethod('\core\uuid', 'generate_uuid_via_pecl_uuid_extension');
+        $method->setAccessible(true);
+        $uuid = $method->invoke(null);
+        $this->assertTrue(self::is_valid_uuid_v4($uuid), "Invalid v4 uuid: '$uuid'");
+    }
+
+    /**
+     * Test the \core\uuid::generate_uuid_via_random_bytes() function.
+     */
+    public function test_core_uuid_generate_uuid_via_random_bytes() {
+        try {
+            random_bytes(1);
+        } catch (Exception $e) {
+            $this->markTestSkipped('No source of entropy for random_bytes. ' . $e->getMessage());
+        }
+
+        // The \core\uuid::generate_uuid_via_random_bytes static method is protected. Use Reflection to call the method.
+        $method = new ReflectionMethod('\core\uuid', 'generate_uuid_via_random_bytes');
+        $method->setAccessible(true);
+        $uuid = $method->invoke(null);
+        $this->assertTrue(self::is_valid_uuid_v4($uuid), "Invalid v4 uuid: '$uuid'");
+    }
+
+    /**
+     * Test the \core\uuid::generate() function.
+     */
+    public function test_core_uuid_generate() {
+        $uuid = \core\uuid::generate();
+        $this->assertTrue(self::is_valid_uuid_v4($uuid), "Invalid v4 UUID: '$uuid'");
+    }
 }
index a61cb83..2487374 100644 (file)
@@ -413,15 +413,11 @@ class core_analytics_targets_testcase extends advanced_testcase {
         $student1 = $dg->create_user();
         $student2 = $dg->create_user();
         $student3 = $dg->create_user();
-        $student4 = $dg->create_user();
         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
         $dg->enrol_user($student1->id, $course1->id, $studentrole->id);
         $dg->enrol_user($student2->id, $course1->id, $studentrole->id);
         $dg->enrol_user($student3->id, $course1->id, $studentrole->id);
 
-        $enrolstart = mktime(0, 0, 0, 10, 25, 2015);
-        $dg->enrol_user($student4->id, $course1->id, $studentrole->id, 'manual', $enrolstart);
-
         // get_all_samples() does not guarantee any order, so let's
         // explicitly define the expectations here for later comparing.
         // Expectations format being array($userid => expectation, ...)
@@ -439,9 +435,6 @@ class core_analytics_targets_testcase extends advanced_testcase {
         // Student 3 (has no grade) fails, so it's non achieved sample.
         $expectations[$student3->id] = 1;
 
-        // Student 4 should be null as its enrolment timestart is after the this range.
-        $expectations[$student4->id] = null;
-
         $courseitem->gradepass = 50;
         $DB->update_record('grade_items', $courseitem);
 
@@ -460,12 +453,9 @@ class core_analytics_targets_testcase extends advanced_testcase {
         $method = $class->getMethod('calculate_sample');
         $method->setAccessible(true);
 
-        $starttime = mktime(0, 0, 0, 10, 24, 2015);
-
         // Verify all the expectations are fulfilled.
         foreach ($sampleids as $sampleid => $key) {
-            $this->assertEquals($expectations[$samplesdata[$key]['user']->id], $method->invoke($target, $sampleid,
-                $analysable, $starttime));
+            $this->assertEquals($expectations[$samplesdata[$key]['user']->id], $method->invoke($target, $sampleid, $analysable));
         }
     }
 }
index 85d046b..ed721af 100644 (file)
     <version>1.1.0</version>
     <licenseversion></licenseversion>
   </library>
-  <library>
-    <location>pear/Auth/RADIUS.php</location>
-    <name>Pear_Auth_Radius</name>
-    <license>BSD</license>
-    <version>1.1.0</version>
-    <licenseversion></licenseversion>
-  </library>
   <library>
     <location>pear/Crypt/CHAP.php</location>
     <name>Pear_Crypt_CHAP</name>
     <version>1.7.0</version>
     <licenseversion></licenseversion>
   </library>
-  <library>
-    <location>recaptchalib.php</location>
-    <name>ReCAPTCHA</name>
-    <license>MIT</license>
-    <version>1.10</version>
-    <licenseversion></licenseversion>
-  </library>
   <library>
     <location>xhprof</location>
     <name>XHProf</name>
index 631f3e6..1f59d6f 100644 (file)
@@ -3,6 +3,8 @@ information provided here is intended especially for developers.
 
 === 3.8 ===
 * The yui checknet module is removed. Call \core\session\manager::keepalive instead.
+* The generate_uuid() function has been deprecated. Please use \core\uuid::generate() instead.
+* Remove lib/pear/auth/RADIUS.php (MDL-65746)
 
 === 3.7 ===
 * Nodes in the navigation api can have labels for each group. See set/get_collectionlabel().
index be34b85..3c96571 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 11bcd12..4d0b821 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 be34b85..3c96571 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 f41a06f..dfcc468 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 79172f7..076c61c 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 f41a06f..dfcc468 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 230f228..1fe40dd 100644 (file)
@@ -20,7 +20,7 @@ var AJAXEXCEPTION_NAME = 'Moodle AJAX exception',
  * @extends M.core.dialogue
  */
 AJAXEXCEPTION = function(config) {
-    config.name = config.name || 'Error';
+    config.name = config.name || M.util.get_string('error', 'moodle');
     config.closeButton = true;
     AJAXEXCEPTION.superclass.constructor.apply(this, [config]);
 };
@@ -29,19 +29,22 @@ Y.extend(AJAXEXCEPTION, M.core.notification.info, {
     initializer: function(config) {
         var content,
             self = this,
-            delay = this.get('hideTimeoutDelay');
+            delay = this.get('hideTimeoutDelay'),
+            labelsep = M.util.get_string('labelsep', 'langconfig');
         this.get(BASE).addClass('moodle-dialogue-exception');
         this.setStdModContent(Y.WidgetStdMod.HEADER,
                 '<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" data-rel="fatalerror"></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> ' +
+                .append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>' +
+                        M.util.get_string('url', 'moodle') + labelsep + '</label> ' +
                         this.get('reproductionlink') + '</div>'))
-                .append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>Debug info:</label> ' +
+                .append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>' +
+                        M.util.get_string('debuginfo', 'debug') + labelsep + '</label> ' +
                         Y.Escape.html(this.get('debuginfo')) + '</div>'))
-                .append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace">' +
-                                      '<label>Stack trace:</label> <pre>' +
+                .append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>' +
+                        M.util.get_string('stacktrace', 'debug') + labelsep + '</label> <pre>' +
                         Y.Escape.html(this.get('stacktrace')) + '</pre></div>'));
         if (M.cfg.developerdebug) {
             content.all('.moodle-exception-param').removeClass('hidden');
index e9581f7..21a6e88 100644 (file)
@@ -45,19 +45,22 @@ Y.extend(EXCEPTION, M.core.notification.info, {
     initializer: function(config) {
         var content,
             self = this,
-            delay = this.get('hideTimeoutDelay');
+            delay = this.get('hideTimeoutDelay'),
+            labelsep = M.util.get_string('labelsep', 'langconfig');
         this.get(BASE).addClass('moodle-dialogue-exception');
         this.setStdModContent(Y.WidgetStdMod.HEADER,
                 '<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" data-rel="fatalerror"></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> ' +
+                .append(Y.Node.create('<div class="moodle-exception-param hidden param-filename"><label>' +
+                        M.util.get_string('file', 'moodle') + labelsep + '</label> ' +
                         Y.Escape.html(this.get('fileName')) + '</div>'))
-                .append(Y.Node.create('<div class="moodle-exception-param hidden param-linenumber"><label>Line:</label> ' +
+                .append(Y.Node.create('<div class="moodle-exception-param hidden param-linenumber"><label>' +
+                        M.util.get_string('line', 'debug') + labelsep + '</label> ' +
                         Y.Escape.html(this.get('lineNumber')) + '</div>'))
-                .append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace">' +
-                                      '<label>Stack trace:</label> <pre>' +
+                .append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>' +
+                        M.util.get_string('stacktrace', 'debug') + labelsep + '</label> <pre>' +
                         this.get('stack') + '</pre></div>'));
         if (M.cfg.developerdebug) {
             content.all('.moodle-exception-param').removeClass('hidden');
diff --git a/message/tests/behat/mute_conversations.feature b/message/tests/behat/mute_conversations.feature
new file mode 100644 (file)
index 0000000..4a40e60
--- /dev/null
@@ -0,0 +1,94 @@
+@core @core_message @javascript
+Feature: Mute and unmute conversations
+  In order to manage my conversations
+  As a user
+  I need to be able to mute and unmute conversations
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1        | 0        | 1         |
+    And the following "users" exist:
+      | username | firstname | lastname | email                |
+      | student1 | Student   | 1        | student1@example.com |
+      | student2 | Student   | 2        | student2@example.com |
+    And the following "course enrolments" exist:
+      | user     | course | role |
+      | student1 | C1     | student |
+      | student2 | C1     | student |
+    And the following "groups" exist:
+      | name    | course | idnumber | enablemessaging |
+      | Group 1 | C1     | G1       | 1               |
+    And the following "group members" exist:
+      | user     | group | course |
+      | student1 | G1    | C1     |
+      | student2 | G1    | C1     |
+    And the following config values are set as admin:
+      | messaging | 1 |
+    And the following "private messages" exist:
+      | user     | contact  | message |
+      | student1 | student2 | Hi!     |
+
+  Scenario: Mute a group conversation
+    Given I log in as "student1"
+    When I open messaging
+    And I open the "Group" conversations list
+    Then "Group 1" "group_message" should exist
+    And "muted" "icon_container" in the "Group 1" "group_message" should not be visible
+    And I select "Group 1" conversation in messaging
+    And "muted" "icon_container" in the "Group 1" "group_message_header" should not be visible
+    And I open contact menu
+    And I click on "Mute" "link" in the "[data-region='header-container']" "css_element"
+    And "muted" "icon_container" in the "Group 1" "group_message_header" should be visible
+    And I go back in "view-conversation" message drawer
+    And "muted" "icon_container" in the "Group 1" "group_message" should be visible
+
+  Scenario: Mute a private conversation
+    When I log in as "student1"
+    And I open messaging
+    Then I should see "Private"
+    And I open the "Private" conversations list
+    And I should see "Student 2"
+    And "muted" "icon_container" in the "Student 2" "group_message" should not be visible
+    And I select "Student 2" conversation in messaging
+    And "muted" "icon_container" in the "[data-action='view-contact']" "css_element" should not be visible
+    And I open contact menu
+    And I click on "Mute" "link" in the "[data-region='header-container']" "css_element"
+    And "muted" "icon_container" in the "[data-action='view-contact']" "css_element" should be visible
+    And I go back in "view-conversation" message drawer
+    And "muted" "icon_container" in the "Student 2" "group_message" should be visible
+
+  Scenario: Unmute a group conversation
+    Given the following "muted group conversations" exist:
+      | user     | group | course |
+      | student1 | G1    | C1     |
+    When I log in as "student1"
+    And I open messaging
+    And I open the "Group" conversations list
+    Then "Group 1" "group_message" should exist
+    And "muted" "icon_container" in the "Group 1" "group_message" should be visible
+    And I select "Group 1" conversation in messaging
+    And "muted" "icon_container" in the "Group 1" "group_message_header" should be visible
+    And I open contact menu
+    And I click on "Unmute" "link" in the "[data-region='header-container']" "css_element"
+    And "muted" "icon_container" in the "Group 1" "group_message_header" should not be visible
+    And I go back in "view-conversation" message drawer
+    And "muted" "icon_container" in the "Group 1" "group_message" should not be visible
+
+  Scenario: Unmute a private conversation
+    Given the following "muted private conversations" exist:
+      | user     | contact  |
+      | student1 | student2 |
+    When I log in as "student1"
+    And I open messaging
+    Then I should see "Private"
+    And I open the "Private" conversations list
+    And I should see "Student 2"
+    And "muted" "icon_container" in the "Student 2" "group_message" should be visible
+    And I select "Student 2" conversation in messaging
+    And "muted" "icon_container" in the "[data-action='view-contact']" "css_element" should be visible
+    And I open contact menu
+    And I click on "Unmute" "link" in the "[data-region='header-container']" "css_element"
+    And "muted" "icon_container" in the "[data-action='view-contact']" "css_element" should not be visible
+    And I go back in "view-conversation" message drawer
+    And "muted" "icon_container" in the "Student 2" "group_message" should not be visible
index 5da7e4b..03f9f26 100644 (file)
@@ -164,6 +164,14 @@ class assign_feedback_editpdf extends assign_feedback_plugin {
 
         $renderer = $PAGE->get_renderer('assignfeedback_editpdf');
 
+        // Links to download the generated pdf...
+        if ($attempt > -1 && page_editor::has_annotations_or_comments($grade->id, false)) {
+            $html = $this->assignment->render_area_files('assignfeedback_editpdf',
+                                                         document_services::FINAL_PDF_FILEAREA,
+                                                         $grade->id);
+            $mform->addElement('static', 'editpdf_files', get_string('downloadfeedback', 'assignfeedback_editpdf'), $html);
+        }
+
         $widget = $this->get_widget($userid, $grade, false);
 
         $html = $renderer->render($widget);
index 47a07ea..6cfd233 100644 (file)
@@ -194,9 +194,6 @@ class feedback_item_captcha extends feedback_item_base {
         }
 
         // With reCAPTCHA v2 the captcha will be rendered by the mobile client using just the publickey.
-        // For now include placeholders for the v1 paramaters to support older mobile app versions.
-        // recaptchachallengehash, recaptchachallengeimage and recaptchachallengejs.
-        $data = array('', '', '');
         $data[] = $CFG->recaptchapublickey;
         return json_encode($data);
     }
index 75b3456..09498e5 100644 (file)
@@ -58,7 +58,7 @@ class backup_forum_activity_structure_step extends backup_activity_structure_ste
         $post = new backup_nested_element('post', array('id'), array(
             'parent', 'userid', 'created', 'modified',
             'mailed', 'subject', 'message', 'messageformat',
-            'messagetrust', 'attachment', 'totalscore', 'mailnow'));
+            'messagetrust', 'attachment', 'totalscore', 'mailnow', 'privatereplyto'));
 
         $tags = new backup_nested_element('poststags');
         $tag = new backup_nested_element('tag', array('id'), array('itemid', 'rawname'));
index 2be6141..655e33e 100644 (file)
Binary files a/mod/lti/amd/build/tool_configure_controller.min.js and b/mod/lti/amd/build/tool_configure_controller.min.js differ
index a8fed07..40dc885 100644 (file)
@@ -162,9 +162,12 @@ define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'mod_lti/e
     var showCartridgeRegistration = function(url) {
         hideExternalRegistration();
         hideRegistrationChoices();
-        getCartridgeRegistrationContainer().removeClass('hidden');
-        getCartridgeRegistrationContainer().find(SELECTORS.CARTRIDGE_REGISTRATION_FORM).attr('data-cartridge-url', url);
-        screenReaderAnnounce(getCartridgeRegistrationContainer());
+        // Don't save the key and secret from the last tool.
+        var container = getCartridgeRegistrationContainer();
+        container.find('input').val('');
+        container.removeClass('hidden');
+        container.find(SELECTORS.CARTRIDGE_REGISTRATION_FORM).attr('data-cartridge-url', url);
+        screenReaderAnnounce(container);
     };
 
     /**
index e21e8db..646a6e6 100644 (file)
@@ -43,7 +43,7 @@ $string['predictioncalculations'] = 'Indicator calculations';
 $string['predictiondetails'] = 'Prediction details';
 $string['nodetailsavailable'] = 'No prediction details are relevant.';
 $string['timecreated'] = 'Time predicted';
-$string['timerange'] = 'Time range';
+$string['timerange'] = 'Analysis interval';
 $string['timerangewithdata'] = '{$a->timestart} to {$a->timeend}';
 $string['selectotherinsights'] = 'Select other insights...';
 $string['privacy:metadata'] = 'The Insights plugin does not store any personal data.';
index 4ff2984..56d6e0d 100644 (file)
@@ -61,11 +61,11 @@ $string['check_frontpagerole_error'] = 'Incorrectly defined frontpage role "{$a}
 $string['check_frontpagerole_name'] = 'Frontpage role';
 $string['check_frontpagerole_notset'] = 'Frontpage role is not set.';
 $string['check_frontpagerole_ok'] = 'Frontpage role definition is OK.';
-$string['check_google_details'] = '<p>The Open to Google setting enables search engines to enter courses with guest access. There is no point in enabling this setting if guest login is not allowed.</p>';
-$string['check_google_error'] = 'Search engine access is allowed but guest access is disabled.';
-$string['check_google_info'] = 'Search engines may enter as guests.';
-$string['check_google_name'] = 'Open to Google';
-$string['check_google_ok'] = 'Search engine access is not enabled.';
+$string['check_crawlers_details'] = '<p>The "Open to search engines" setting enables search engines to enter courses with guest access. There is no point in enabling this setting if guest login is not allowed.</p>';
+$string['check_crawlers_error'] = 'Search engine access is allowed but guest access is disabled.';
+$string['check_crawlers_info'] = 'Search engines may enter as guests.';
+$string['check_crawlers_name'] = 'Open to search engines';
+$string['check_crawlers_ok'] = 'Search engine access is not enabled.';
 $string['check_guestrole_details'] = '<p>The guest role is used for guests, not logged in users and temporary guest course access. Please make sure no risky capabilities are allowed in this role.</p>
 <p>The only supported legacy type for guest role is <em>Guest</em>.</p>';
 $string['check_guestrole_error'] = 'The guest role "{$a}" is incorrectly defined!';
index e696b83..8358e4a 100644 (file)
@@ -47,7 +47,7 @@ function report_security_get_issue_list() {
         'report_security_check_embed',
         'report_security_check_mediafilterswf',
         'report_security_check_openprofiles',
-        'report_security_check_google',
+        'report_security_check_crawlers',
         'report_security_check_passwordpolicy',
         'report_security_check_emailchangeconfirmation',
         'report_security_check_cookiesecure',
@@ -308,35 +308,35 @@ function report_security_check_openprofiles($detailed=false) {
 }
 
 /**
- * Verifies google access not combined with disabled guest access
+ * Verifies web crawler (search engine) access not combined with disabled guest access
  * because attackers might gain guest access by modifying browser signature.
  * @param bool $detailed
  * @return object result
  */
-function report_security_check_google($detailed=false) {
+function report_security_check_crawlers($detailed=false) {
     global $CFG;
 
     $result = new stdClass();
-    $result->issue   = 'report_security_check_google';
-    $result->name    = get_string('check_google_name', 'report_security');
+    $result->issue   = 'report_security_check_crawlers';
+    $result->name    = get_string('check_crawlers_name', 'report_security');
     $result->info    = null;
     $result->details = null;
     $result->status  = null;
     $result->link    = "<a href=\"$CFG->wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').'</a>';
 
-    if (empty($CFG->opentogoogle)) {
+    if (empty($CFG->opentowebcrawlers)) {
         $result->status = REPORT_SECURITY_OK;
-        $result->info   = get_string('check_google_ok', 'report_security');
+        $result->info   = get_string('check_crawlers_ok', 'report_security');
     } else if (!empty($CFG->guestloginbutton)) {
         $result->status = REPORT_SECURITY_INFO;
-        $result->info   = get_string('check_google_info', 'report_security');
+        $result->info   = get_string('check_crawlers_info', 'report_security');
     } else {
         $result->status = REPORT_SECURITY_SERIOUS;
-        $result->info   = get_string('check_google_error', 'report_security');
+        $result->info   = get_string('check_crawlers_error', 'report_security');
     }
 
     if ($detailed) {
-        $result->details = get_string('check_google_details', 'report_security');
+        $result->details = get_string('check_crawlers_details', 'report_security');
     }
 
     return $result;
index cd947ae..14eeb72 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2019060600.01;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2019060600.02;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.