Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1724362afc | |||
| c3ec88f12d | |||
| 3f985bf3cf | |||
| b896c303f3 | |||
| d7c348cfe1 | |||
| 213fd0cdb3 | |||
| be8093e2c7 | |||
| 6d6ed6822f | |||
| 96b03bc501 | |||
| 4a7259f236 | |||
| 6ee9522dfa | |||
| 9938ee3a04 | |||
| cfc14c45bb | |||
| 88b78ea367 | |||
| 83461bcf5f | |||
| 6401ca4959 | |||
| f591de2b58 | |||
| 94a13aeeff | |||
| c80e366051 | |||
| 0d6fb2546d | |||
| 8b395dcfc3 | |||
| f284cd9eaf | |||
| 4fcb52cd45 | |||
| 2e7514f4b0 | |||
| 43d5aec47e | |||
| 36b7d90e5e | |||
| 7cf8ef39ad | |||
| 336ec5580a | |||
| 7998d80302 | |||
| af22c038b7 | |||
| bd380a11ec | |||
| 45031e8205 | |||
| 96aac92ddc | |||
| a8c8967055 | |||
| d7db5f08b0 | |||
| 458172696f | |||
| d5bf67a67a | |||
| 3839d226ca | |||
| d4709d817e | |||
| 0094121765 | |||
| f1721161c3 | |||
| 4f6240f630 | |||
| 12b5c2e952 | |||
| 3f8a1b0107 | |||
| c571d3a0da | |||
| 492fded0ec | |||
| 369fee885f | |||
| 3cda11b55c | |||
| a78921005f | |||
| 89eae3a811 | |||
| f61b060182 | |||
| 5c131030fd | |||
| 202d57eef7 | |||
| 1dda6c86b8 | |||
| 0e8cb9c10b | |||
| d2ad5c8c03 | |||
| 98d07e8514 | |||
| 78cbb2204d | |||
| d1c1acb155 | |||
| e02ea67301 | |||
| 3c7b5c983e | |||
| e5a7a18ae2 | |||
| d22da878f1 | |||
| 1ecb374344 | |||
| 5dd79c7781 | |||
| a72d935dbc | |||
| 346fe1f78a | |||
| 65b75f18cc | |||
| 8b9efc3135 | |||
| 4fa621a62f | |||
| 01687d9125 | |||
| 867e05ea34 | |||
| 48f29f73e0 | |||
| 9a4a2df0e4 | |||
| 9739399741 | |||
| 1fbe7c1113 | |||
| 2f29bac4f9 | |||
| 9f0778ddb2 | |||
| 08a669cba0 | |||
| 82b661d8fa | |||
| 6459f9f6b8 | |||
| d561e151e5 | |||
| 53717729a5 | |||
| 3da39da392 | |||
| 386f29a46d | |||
| 71bc684c41 | |||
| 8ef2974af1 | |||
| bc70bd69ed | |||
| c16587f52f | |||
| 4fd6045c2a | |||
| fbc7a8f5f3 | |||
| c6e727732a | |||
| abfff66a86 | |||
| 0184a477d5 | |||
| 6de6f64199 | |||
| 2aa56c770d | |||
| 8874725c53 | |||
| cc9430929d | |||
| e38aa09dee | |||
| 0f4efffb76 | |||
| 6246db3d30 | |||
| 4cb2b3c758 | |||
| 4569711e18 | |||
| 535747fbc7 | |||
| 2f3033646c | |||
| a24e9872ef | |||
| 3c2589fb58 | |||
| 7a369aa0a8 | |||
| a26954ce4a | |||
| d644c3d89d | |||
| acc510ced7 | |||
| 440bf51af2 | |||
| 8790501437 | |||
| df76743906 | |||
| b27f6b0eca | |||
| 832ee471be | |||
| 9e9dab8045 | |||
| 9c07defb77 | |||
| 3f6dd17042 | |||
| 4cbce0bae2 | |||
| bf6dcc5779 | |||
| 76a0561f91 | |||
| 379bac1fe7 | |||
| efe3e377b6 | |||
| 83e840b8e7 | |||
| c518c78ca4 | |||
| 5a5c6ff179 | |||
| 81a1695e71 | |||
| 72e867f9e6 | |||
| 26d0ac156e | |||
| b7fbbaf7f4 | |||
| d7d8119201 | |||
| 54226e8157 | |||
| 0304eacfd5 | |||
| 38668033ff | |||
| 90e3a3d994 | |||
| 2bc1598138 | |||
| 06a9b91e17 | |||
| 9d607df0d9 | |||
| a92e5c5770 | |||
| f35b2d1cb3 | |||
| a77158fd96 |
@@ -16,4 +16,4 @@ Moodle - the world's open source learning platform
|
||||
This generated documentation includes API documentation for JavaScript written in the AMD and ES2015 module formats within Moodle.
|
||||
|
||||
## Related information
|
||||
See [https://docs.moodle.org/dev](https://docs.moodle.org/dev) for other related Developer Documentation.
|
||||
See [https://moodledev.io](https://moodledev.io) for other related Developer Documentation.
|
||||
|
||||
@@ -114,7 +114,7 @@ module.exports = {
|
||||
],
|
||||
"menu": {
|
||||
"Developer Docs": {
|
||||
href: "https://docs.moodle.org/dev",
|
||||
href: "https://moodledev.io",
|
||||
target: "_blank",
|
||||
"class": "menu-item",
|
||||
id: "devdocs"
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ of developers, designers, teachers, testers, translators and other users. We
|
||||
work in universities, schools, companies and other places. You are very welcome
|
||||
to join us and contribute to the project.
|
||||
|
||||
See <https://docs.moodle.org/dev/Contributing_to_Moodle> for the many ways you
|
||||
See <https://moodledev.io/general/community/contribute> for the many ways you
|
||||
can help, not only with coding.
|
||||
|
||||
Moodle is open to community contributions to core, though all code must go
|
||||
@@ -35,7 +35,7 @@ submitted patches has evolved.
|
||||
* New features are developed on the master branch. Bug fixes are also
|
||||
backported to currently supported maintenance (stable) branches.
|
||||
|
||||
For further details, see <https://docs.moodle.org/dev/Process>.
|
||||
For further details, see <https://moodledev.io/general/development/process>.
|
||||
|
||||
Moodle plugins
|
||||
--------------
|
||||
@@ -53,4 +53,4 @@ be easily installed and updated via the Moodle administration interface.
|
||||
the plugins directory. We do not pull from your code repository; you must do
|
||||
it explicitly.
|
||||
|
||||
For further details, see <https://docs.moodle.org/dev/Plugin_contribution>.
|
||||
For further details, see <https://moodledev.io/general/community/plugincontribution>.
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ Moodle is widely used around the world by universities, schools, companies and
|
||||
all manner of organisations and individuals.
|
||||
|
||||
Moodle is provided freely as open source software, under the GNU General Public
|
||||
License <https://docs.moodle.org/dev/License>.
|
||||
License <https://moodledev.io/general/license>.
|
||||
|
||||
Moodle is written in PHP and JavaScript and uses an SQL database for storing
|
||||
the data.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* Displays the list of found local plugins, their version (if found) and
|
||||
* a link to delete the local plugin.
|
||||
*
|
||||
* @see http://docs.moodle.org/dev/Local_customisation
|
||||
* @see https://moodledev.io/docs/apis/plugintypes/local
|
||||
* @package admin
|
||||
* @copyright 2010 David Mudrak <david.mudrak@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* Displays the list of found plagiarism plugins, their version (if found) and
|
||||
* a link to uninstall the plagiarism plugin.
|
||||
*
|
||||
* @see http://docs.moodle.org/dev/Plagiarism_API
|
||||
* @see https://moodledev.io/docs/apis/subsystems/plagiarism
|
||||
* @package admin
|
||||
* @copyright 2012 Dan Marsden <dan@danmarsden.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
|
||||
+4
-4
@@ -44,7 +44,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$output .= $this->heading(get_string('copyrightnotice'));
|
||||
$output .= $this->box($copyrightnotice, 'copyrightnotice');
|
||||
$output .= html_writer::empty_tag('br');
|
||||
$output .= $this->confirm(get_string('doyouagree'), $continue, "http://docs.moodle.org/dev/License");
|
||||
$output .= $this->confirm(get_string('doyouagree'), $continue, "https://moodledev.io/general/license");
|
||||
$output .= $this->footer();
|
||||
|
||||
return $output;
|
||||
@@ -745,10 +745,10 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//// IT IS ILLEGAL AND A VIOLATION OF THE GPL TO HIDE, REMOVE OR MODIFY THIS COPYRIGHT NOTICE ///
|
||||
$copyrighttext = '<a href="http://moodle.org/">Moodle</a> '.
|
||||
'<a href="http://docs.moodle.org/dev/Releases" title="'.$CFG->version.'">'.$CFG->release.'</a><br />'.
|
||||
'<a href="https://moodledev.io/general/releases" title="'.$CFG->version.'">'.$CFG->release.'</a><br />'.
|
||||
'Copyright © 1999 onwards, Martin Dougiamas<br />'.
|
||||
'and <a href="http://moodle.org/dev">many other contributors</a>.<br />'.
|
||||
'<a href="http://docs.moodle.org/dev/License">GNU Public License</a>';
|
||||
'<a href="https://moodledev.io/general/license">GNU Public License</a>';
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
return $this->box($copyrighttext, 'copyright');
|
||||
@@ -992,7 +992,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
protected function release_notes_link() {
|
||||
$releasenoteslink = get_string('releasenoteslink', 'admin', 'http://docs.moodle.org/dev/Releases');
|
||||
$releasenoteslink = get_string('releasenoteslink', 'admin', 'https://moodledev.io/general/releases');
|
||||
$releasenoteslink = str_replace('target="_blank"', 'onclick="this.target=\'_blank\'"', $releasenoteslink); // extremely ugly validation hack
|
||||
return $this->box($releasenoteslink, 'generalbox alert alert-info');
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
get_enabled_providers_and_default();
|
||||
|
||||
$temp->add(new admin_setting_configselect('moodlecourse/coursecommunicationprovider',
|
||||
new lang_string('seleccommunicationprovider', 'communication'),
|
||||
new lang_string('selectcommunicationprovider', 'communication'),
|
||||
new lang_string('coursecommunication_desc', 'course'),
|
||||
$defaulprovider, $communicationproviders));
|
||||
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@
|
||||
* @copyright 2017 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("tool_analytics/model",["jquery","core/str","core/log","core/notification","core/modal_save_cancel","core/modal_cancel","core/modal_events","core/templates"],(function($,Str,log,Notification,ModalSaveCancel,ModalCancel,ModalEvents,Templates){var actionsList={clear:{title:{key:"clearpredictions",component:"tool_analytics"},body:{key:"clearmodelpredictions",component:"tool_analytics"}},delete:{title:{key:"delete",component:"tool_analytics"},body:{key:"deletemodelconfirmation",component:"tool_analytics"}}};return{confirmAction:function(actionId,actionType){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget);if(void 0!==actionsList[actionType]){var wrap,reqStrings=[actionsList[actionType].title,actionsList[actionType].body];reqStrings[1].param=(wrap=$(a).closest("[data-model-name]")).length?wrap.attr("data-model-name"):(log.error("Unexpected DOM error - unable to obtain the model name"),"");var stringsPromise=Str.get_strings(reqStrings),modalPromise=ModalSaveCancel.create({});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.setTitle(strings[0]),modal.setBody(strings[1]),modal.setSaveButtonText(strings[0]),modal.getRoot().on(ModalEvents.save,(function(){window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}else log.error('Action "'+actionType+'" is not allowed.')}))},selectEvaluationOptions:function(actionId,trainedOnlyExternally){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget),timeSplittingMethods=$(this).attr("data-timesplitting-methods");ModalSaveCancel.create({title:Str.get_string("evaluatemodel","tool_analytics"),body:Templates.render("tool_analytics/evaluation_options",{trainedexternally:trainedOnlyExternally,timesplittingmethods:JSON.parse(timeSplittingMethods)}),removeOnClose:!0,buttons:{save:Str.get_string("evaluate","tool_analytics")},show:!0}).then((modal=>(modal.getRoot().on(ModalEvents.save,(function(){"trainedmodel"==$("input[name='evaluationmode']:checked").val()&&a.attr("href",a.attr("href")+"&mode=trainedmodel");var timeSplittingMethod=$("#id-evaluation-timesplitting").val();a.attr("href",a.attr("href")+"×plitting="+timeSplittingMethod),window.location.href=a.attr("href")})),modal))).catch(Notification.exception)}))},selectExportOptions:function(actionId,isTrained){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget);if(!isTrained)return a.attr("href",a.attr("href")+"&action=exportmodel&includeweights=0"),void(window.location.href=a.attr("href"));var stringsPromise=Str.get_strings([{key:"export",component:"tool_analytics"}]),modalPromise=ModalCancel.create({body:Templates.render("tool_analytics/export_options",{}),removeOnClose:!0});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.setTitle(strings[0]),modal.setSaveButtonText(strings[0]),modal.getRoot().on(ModalEvents.save,(function(){"exportdata"==$("input[name='exportoption']:checked").val()?a.attr("href",a.attr("href")+"&action=exportdata"):(a.attr("href",a.attr("href")+"&action=exportmodel"),$("#id-includeweights").is(":checked")?a.attr("href",a.attr("href")+"&includeweights=1"):a.attr("href",a.attr("href")+"&includeweights=0")),window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}))}}}));
|
||||
define("tool_analytics/model",["jquery","core/str","core/log","core/notification","core/modal_save_cancel","core/modal_cancel","core/modal_events","core/templates"],(function($,Str,log,Notification,ModalSaveCancel,ModalCancel,ModalEvents,Templates){var actionsList={clear:{title:{key:"clearpredictions",component:"tool_analytics"},body:{key:"clearmodelpredictions",component:"tool_analytics"}},delete:{title:{key:"delete",component:"tool_analytics"},body:{key:"deletemodelconfirmation",component:"tool_analytics"}}};return{confirmAction:function(actionId,actionType){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget);if(void 0!==actionsList[actionType]){var wrap,reqStrings=[actionsList[actionType].title,actionsList[actionType].body];reqStrings[1].param=(wrap=$(a).closest("[data-model-name]")).length?wrap.attr("data-model-name"):(log.error("Unexpected DOM error - unable to obtain the model name"),"");var stringsPromise=Str.get_strings(reqStrings),modalPromise=ModalSaveCancel.create({});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.setTitle(strings[0]),modal.setBody(strings[1]),modal.setSaveButtonText(strings[0]),modal.getRoot().on(ModalEvents.save,(function(){window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}else log.error('Action "'+actionType+'" is not allowed.')}))},selectEvaluationOptions:function(actionId,trainedOnlyExternally){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget),timeSplittingMethods=$(this).attr("data-timesplitting-methods");ModalSaveCancel.create({title:Str.get_string("evaluatemodel","tool_analytics"),body:Templates.render("tool_analytics/evaluation_options",{trainedexternally:trainedOnlyExternally,timesplittingmethods:JSON.parse(timeSplittingMethods)}),removeOnClose:!0,buttons:{save:Str.get_string("evaluate","tool_analytics")},show:!0}).then((modal=>(modal.getRoot().on(ModalEvents.save,(function(){"trainedmodel"==$("input[name='evaluationmode']:checked").val()&&a.attr("href",a.attr("href")+"&mode=trainedmodel");var timeSplittingMethod=$("#id-evaluation-timesplitting").val();a.attr("href",a.attr("href")+"×plitting="+timeSplittingMethod),window.location.href=a.attr("href")})),modal))).catch(Notification.exception)}))},selectExportOptions:function(actionId,isTrained){$('[data-action-id="'+actionId+'"]').on("click",(function(ev){ev.preventDefault();var a=$(ev.currentTarget);if(!isTrained)return a.attr("href",a.attr("href")+"&action=exportmodel&includeweights=0"),void(window.location.href=a.attr("href"));var stringsPromise=Str.get_strings([{key:"export",component:"tool_analytics"}]),modalPromise=ModalSaveCancel.create({body:Templates.render("tool_analytics/export_options",{}),removeOnClose:!0});$.when(stringsPromise,modalPromise).then((function(strings,modal){return modal.setTitle(strings[0]),modal.setSaveButtonText(strings[0]),modal.getRoot().on(ModalEvents.save,(function(){"exportdata"==$("input[name='exportoption']:checked").val()?a.attr("href",a.attr("href")+"&action=exportdata"):(a.attr("href",a.attr("href")+"&action=exportmodel"),$("#id-includeweights").is(":checked")?a.attr("href",a.attr("href")+"&includeweights=1"):a.attr("href",a.attr("href")+"&includeweights=0")),window.location.href=a.attr("href")})),modal.show(),modal})).fail(Notification.exception)}))}}}));
|
||||
|
||||
//# sourceMappingURL=model.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -185,7 +185,7 @@ define([
|
||||
component: 'tool_analytics'
|
||||
}
|
||||
]);
|
||||
var modalPromise = ModalCancel.create({
|
||||
var modalPromise = ModalSaveCancel.create({
|
||||
body: Templates.render('tool_analytics/export_options', {}),
|
||||
removeOnClose: true,
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ menu: "main"
|
||||
|
||||
## Moodle templates
|
||||
|
||||
[Moodle templates](https://docs.moodle.org/dev/Templates) are use to write HTML and Javascript using mustache files.
|
||||
[Moodle templates](https://moodledev.io/docs/guides/templates) are use to write HTML and Javascript using mustache files.
|
||||
|
||||
If you are creating your own pages in the UI Component library you can load core templates using this (shortcode) syntax:
|
||||
|
||||
@@ -51,4 +51,3 @@ This is the result of adding the core/notification template on this page:
|
||||
"extraclasses": "foo bar"
|
||||
}
|
||||
{{< /mustache >}}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ tags:
|
||||
|
||||
## How to use moodle forms
|
||||
|
||||
Forms are constructed using lib/formslib.php. Using the addElement methods in php a set of different form element types can be added to a form. For more info visit the [Moodledocs](https://docs.moodle.org/dev/lib/formslib.php_Form_Definition) page for forms
|
||||
Forms are constructed using lib/formslib.php. Using the addElement methods in php a set of different form element types can be added to a form. For more info visit the [Moodledocs](https://moodledev.io/docs/apis/subsystems/form) page for forms
|
||||
|
||||
{{< php >}}
|
||||
$mform->addElement('button', 'intro', get_string("buttonlabel"));
|
||||
|
||||
@@ -61,7 +61,7 @@ Disabled toggle with extra classes.
|
||||
|
||||
## Use toggle as a template block
|
||||
|
||||
It is also possible to include *core/toggle* in any other template using [blocks](https://docs.moodle.org/dev/Templates#Blocks), instead of rendering it with a context.
|
||||
It is also possible to include *core/toggle* in any other template using [blocks](https://moodledev.io/docs/guides/templates#blocks), instead of rendering it with a context.
|
||||
The parameters that you can define are:
|
||||
* id: Unique id for the toggle input.
|
||||
* extraclasses: Any extra classes added to the toggle input outer container.
|
||||
|
||||
@@ -58,7 +58,7 @@ class behat_tool_dataprivacy extends behat_base {
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates the specified element. More info about available elements in http://docs.moodle.org/dev/Acceptance_testing#Fixtures.
|
||||
* Creates the specified element. More info about available elements in https://moodledev.io/general/development/tools/behat.
|
||||
*
|
||||
* @Given /^the following data privacy "(?P<element_string>(?:[^"]|\\")*)" exist:$/
|
||||
*
|
||||
|
||||
@@ -94,7 +94,7 @@ class behat_tool_lp_data_generators extends behat_base {
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates the specified element. More info about available elements in http://docs.moodle.org/dev/Acceptance_testing#Fixtures.
|
||||
* Creates the specified element. More info about available elements in https://moodledev.io/general/development/tools/behat.
|
||||
*
|
||||
* @Given /^the following lp "(?P<element_string>(?:[^"]|\\")*)" exist:$/
|
||||
*
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
namespace tool_mfa\local;
|
||||
|
||||
use tool_mfa\local\factor\object_factor_base;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/ddllib.php');
|
||||
@@ -200,14 +202,13 @@ class admin_setting_managemfa extends \admin_setting {
|
||||
$table->colclasses = ['leftalign', 'centeralign'];
|
||||
$table->data = [];
|
||||
|
||||
$factorstringconnector = get_string('connector', 'tool_mfa');
|
||||
foreach ($combinations as $combination) {
|
||||
$string = '';
|
||||
foreach ($combination['combination'] as $factor) {
|
||||
$string .= ' ' . get_string('connector', 'tool_mfa') . ' ' . $factor->get_summary_condition()
|
||||
. ' <sup>' . $factor->get_weight() . '</sup>';
|
||||
}
|
||||
$factorstrings = array_map(static function(object_factor_base $factor): string {
|
||||
return $factor->get_summary_condition() . ' <sup>' . $factor->get_weight() . '</sup>';
|
||||
}, $combination['combination']);
|
||||
|
||||
$string = substr($string, 4);
|
||||
$string = implode(" {$factorstringconnector} ", $factorstrings);
|
||||
$table->data[] = new \html_table_row([$string, $combination['totalweight']]);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
* @author Alex Morris <alex.morris@catalyst.net.nz>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("factor_webauthn/register",["factor_webauthn/utils"],(function(utils){return{init:function(createArgs){document.getElementById("factor_webauthn-register").addEventListener("click",(async function(e){if(e.preventDefault(),!navigator.credentials||!navigator.credentials.create)throw new Error("Browser not supported.");if(!1===(createArgs=JSON.parse(createArgs)).success)throw new Error(createArgs.msg||"unknown error occured");utils.recursiveBase64StrToArrayBuffer(createArgs);const cred=await navigator.credentials.create(createArgs),authenticatorResponse={transports:cred.response.getTransports?cred.response.getTransports():null,clientDataJSON:cred.response.clientDataJSON?utils.arrayBufferToBase64(cred.response.clientDataJSON):null,attestationObject:cred.response.attestationObject?utils.arrayBufferToBase64(cred.response.attestationObject):null};document.getElementById("id_response_input").value=JSON.stringify(authenticatorResponse)}))}}}));
|
||||
define("factor_webauthn/register",["factor_webauthn/utils","core/log"],(function(utils,Log){return{init:function(createArgs){createArgs=JSON.parse(createArgs),document.getElementById("factor_webauthn-register").addEventListener("click",(async function(e){if(e.preventDefault(),!navigator.credentials||!navigator.credentials.create)throw new Error("Browser not supported.");if(!1===createArgs.success)throw new Error(createArgs.msg||"unknown error occured");try{utils.recursiveBase64StrToArrayBuffer(createArgs);const cred=await navigator.credentials.create(createArgs),authenticatorResponse={transports:cred.response.getTransports?cred.response.getTransports():null,clientDataJSON:cred.response.clientDataJSON?utils.arrayBufferToBase64(cred.response.clientDataJSON):null,attestationObject:cred.response.attestationObject?utils.arrayBufferToBase64(cred.response.attestationObject):null};document.getElementById("id_response_input").value=JSON.stringify(authenticatorResponse)}catch(e){Log.debug("The request timed out or you have canceled the request. Please try again later.")}}))}}}));
|
||||
|
||||
//# sourceMappingURL=register.min.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"register.min.js","sources":["../src/register.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * For collecting WebAuthn authenticator details on factor setup\n *\n * @module factor_webauthn/register\n * @copyright Catalyst IT\n * @author Alex Morris <alex.morris@catalyst.net.nz>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['factor_webauthn/utils'], function(utils) {\n return {\n init: function(createArgs) {\n document.getElementById('factor_webauthn-register').addEventListener('click', async function(e) {\n e.preventDefault();\n if (!navigator.credentials || !navigator.credentials.create) {\n throw new Error('Browser not supported.');\n }\n\n createArgs = JSON.parse(createArgs);\n\n if (createArgs.success === false) {\n throw new Error(createArgs.msg || 'unknown error occured');\n }\n\n utils.recursiveBase64StrToArrayBuffer(createArgs);\n\n const cred = await navigator.credentials.create(createArgs);\n\n const authenticatorResponse = {\n transports: cred.response.getTransports ? cred.response.getTransports() : null,\n clientDataJSON: cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,\n attestationObject:\n cred.response.attestationObject ? utils.arrayBufferToBase64(cred.response.attestationObject) : null\n };\n\n document.getElementById('id_response_input').value = JSON.stringify(authenticatorResponse);\n });\n }\n };\n});\n"],"names":["define","utils","init","createArgs","document","getElementById","addEventListener","async","e","preventDefault","navigator","credentials","create","Error","JSON","parse","success","msg","recursiveBase64StrToArrayBuffer","cred","authenticatorResponse","transports","response","getTransports","clientDataJSON","arrayBufferToBase64","attestationObject","value","stringify"],"mappings":";;;;;;;;AAwBAA,kCAAO,CAAC,0BAA0B,SAASC,aAChC,CACHC,KAAM,SAASC,YACXC,SAASC,eAAe,4BAA4BC,iBAAiB,SAASC,eAAeC,MACzFA,EAAEC,kBACGC,UAAUC,cAAgBD,UAAUC,YAAYC,aAC3C,IAAIC,MAAM,8BAKO,KAF3BV,WAAaW,KAAKC,MAAMZ,aAETa,cACL,IAAIH,MAAMV,WAAWc,KAAO,yBAGtChB,MAAMiB,gCAAgCf,kBAEhCgB,WAAaT,UAAUC,YAAYC,OAAOT,YAE1CiB,sBAAwB,CAC1BC,WAAYF,KAAKG,SAASC,cAAgBJ,KAAKG,SAASC,gBAAkB,KAC1EC,eAAgBL,KAAKG,SAASE,eAAiBvB,MAAMwB,oBAAoBN,KAAKG,SAASE,gBAAkB,KACzGE,kBACIP,KAAKG,SAASI,kBAAoBzB,MAAMwB,oBAAoBN,KAAKG,SAASI,mBAAqB,MAGvGtB,SAASC,eAAe,qBAAqBsB,MAAQb,KAAKc,UAAUR"}
|
||||
{"version":3,"file":"register.min.js","sources":["../src/register.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * For collecting WebAuthn authenticator details on factor setup\n *\n * @module factor_webauthn/register\n * @copyright Catalyst IT\n * @author Alex Morris <alex.morris@catalyst.net.nz>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['factor_webauthn/utils', 'core/log'], function(utils, Log) {\n return {\n init: function(createArgs) {\n createArgs = JSON.parse(createArgs);\n document.getElementById('factor_webauthn-register').addEventListener('click', async function(e) {\n e.preventDefault();\n if (!navigator.credentials || !navigator.credentials.create) {\n throw new Error('Browser not supported.');\n }\n\n if (createArgs.success === false) {\n throw new Error(createArgs.msg || 'unknown error occured');\n }\n\n try {\n utils.recursiveBase64StrToArrayBuffer(createArgs);\n const cred = await navigator.credentials.create(createArgs);\n const authenticatorResponse = {\n transports: cred.response.getTransports ? cred.response.getTransports() : null,\n clientDataJSON: cred.response.clientDataJSON ?\n utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,\n attestationObject: cred.response.attestationObject ?\n utils.arrayBufferToBase64(cred.response.attestationObject) : null,\n };\n document.getElementById('id_response_input').value = JSON.stringify(authenticatorResponse);\n } catch (e) {\n Log.debug('The request timed out or you have canceled the request. Please try again later.');\n }\n });\n }\n };\n});\n"],"names":["define","utils","Log","init","createArgs","JSON","parse","document","getElementById","addEventListener","async","e","preventDefault","navigator","credentials","create","Error","success","msg","recursiveBase64StrToArrayBuffer","cred","authenticatorResponse","transports","response","getTransports","clientDataJSON","arrayBufferToBase64","attestationObject","value","stringify","debug"],"mappings":";;;;;;;;AAwBAA,kCAAO,CAAC,wBAAyB,aAAa,SAASC,MAAOC,WACnD,CACHC,KAAM,SAASC,YACXA,WAAaC,KAAKC,MAAMF,YACxBG,SAASC,eAAe,4BAA4BC,iBAAiB,SAASC,eAAeC,MACzFA,EAAEC,kBACGC,UAAUC,cAAgBD,UAAUC,YAAYC,aAC3C,IAAIC,MAAM,8BAGO,IAAvBZ,WAAWa,cACL,IAAID,MAAMZ,WAAWc,KAAO,6BAIlCjB,MAAMkB,gCAAgCf,kBAChCgB,WAAaP,UAAUC,YAAYC,OAAOX,YAC1CiB,sBAAwB,CAC1BC,WAAYF,KAAKG,SAASC,cAAgBJ,KAAKG,SAASC,gBAAkB,KAC1EC,eAAgBL,KAAKG,SAASE,eAC1BxB,MAAMyB,oBAAoBN,KAAKG,SAASE,gBAAkB,KAC9DE,kBAAmBP,KAAKG,SAASI,kBAC7B1B,MAAMyB,oBAAoBN,KAAKG,SAASI,mBAAqB,MAErEpB,SAASC,eAAe,qBAAqBoB,MAAQvB,KAAKwB,UAAUR,uBACtE,MAAOV,GACLT,IAAI4B,MAAM"}
|
||||
@@ -22,33 +22,34 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define(['factor_webauthn/utils'], function(utils) {
|
||||
define(['factor_webauthn/utils', 'core/log'], function(utils, Log) {
|
||||
return {
|
||||
init: function(createArgs) {
|
||||
createArgs = JSON.parse(createArgs);
|
||||
document.getElementById('factor_webauthn-register').addEventListener('click', async function(e) {
|
||||
e.preventDefault();
|
||||
if (!navigator.credentials || !navigator.credentials.create) {
|
||||
throw new Error('Browser not supported.');
|
||||
}
|
||||
|
||||
createArgs = JSON.parse(createArgs);
|
||||
|
||||
if (createArgs.success === false) {
|
||||
throw new Error(createArgs.msg || 'unknown error occured');
|
||||
}
|
||||
|
||||
utils.recursiveBase64StrToArrayBuffer(createArgs);
|
||||
|
||||
const cred = await navigator.credentials.create(createArgs);
|
||||
|
||||
const authenticatorResponse = {
|
||||
transports: cred.response.getTransports ? cred.response.getTransports() : null,
|
||||
clientDataJSON: cred.response.clientDataJSON ? utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||
attestationObject:
|
||||
cred.response.attestationObject ? utils.arrayBufferToBase64(cred.response.attestationObject) : null
|
||||
};
|
||||
|
||||
document.getElementById('id_response_input').value = JSON.stringify(authenticatorResponse);
|
||||
try {
|
||||
utils.recursiveBase64StrToArrayBuffer(createArgs);
|
||||
const cred = await navigator.credentials.create(createArgs);
|
||||
const authenticatorResponse = {
|
||||
transports: cred.response.getTransports ? cred.response.getTransports() : null,
|
||||
clientDataJSON: cred.response.clientDataJSON ?
|
||||
utils.arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||
attestationObject: cred.response.attestationObject ?
|
||||
utils.arrayBufferToBase64(cred.response.attestationObject) : null,
|
||||
};
|
||||
document.getElementById('id_response_input').value = JSON.stringify(authenticatorResponse);
|
||||
} catch (e) {
|
||||
Log.debug('The request timed out or you have canceled the request. Please try again later.');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
$string['achievedweight'] = 'Achieved weight';
|
||||
$string['alltime'] = 'All time';
|
||||
$string['areyousure'] = 'Are you sure you want to revoke factor?';
|
||||
$string['cancellogin'] = 'Cancel login';
|
||||
$string['combination'] = 'Combination';
|
||||
$string['connector'] = 'AND';
|
||||
$string['created'] = 'Created';
|
||||
|
||||
@@ -85,12 +85,12 @@
|
||||
<input type="hidden" id="{{uniqid}}-logout" name="logout" value="true">
|
||||
<input type="submit" class="btn btn-secondary"
|
||||
name="cancelmfa" id="cancelmfa_button"
|
||||
value="Cancel login" data-initial-value="Cancel login">
|
||||
value="{{#str}} cancellogin, tool_mfa {{/str}}">
|
||||
</form>
|
||||
</div>
|
||||
{{#supportlink}}
|
||||
<div class="tool-mfa-site-support mt-4">
|
||||
Need help? {{{supportlink}}}
|
||||
{{#str}} needhelp, tool_mfa {{/str}} {{{supportlink}}}
|
||||
</div>
|
||||
{{/supportlink}}
|
||||
</div>
|
||||
|
||||
@@ -98,7 +98,15 @@ class remote_resource_test extends \advanced_testcase {
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertGreaterThan(0, $remoteres->get_download_size());
|
||||
// We need to handle size of -1 (missing "Content-Length" header), or where it is set and greater than zero.
|
||||
$this->assertThat(
|
||||
$remoteres->get_download_size(),
|
||||
$this->logicalOr(
|
||||
$this->equalTo(-1),
|
||||
$this->greaterThan(0),
|
||||
),
|
||||
);
|
||||
|
||||
[$path, $name] = $remoteres->download_to_requestdir();
|
||||
$this->assertIsString($path);
|
||||
$this->assertEquals('test.html', $name);
|
||||
|
||||
+1
-3
@@ -760,9 +760,7 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
$rs->close();
|
||||
|
||||
} else {
|
||||
$fields_obj = $rs->FetchObj();
|
||||
$columns = array_keys((array)$fields_obj);
|
||||
|
||||
$columns = array_keys($rs->fetchRow());
|
||||
echo $OUTPUT->notification(get_string('auth_dbcolumnlist', 'auth_db', implode(', ', $columns)), 'notifysuccess');
|
||||
$rs->close();
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ information provided here is intended especially for developers.
|
||||
* The auth_db and auth_ldap plugins' implementations of update_user_record() have been removed and both now
|
||||
call the new implementation added in the base class.
|
||||
* Self registration plugins should use core_privacy\local\sitepolicy\manager instead of directly checking
|
||||
$CFG->sitepolicy , especially in custom signup forms. See https://docs.moodle.org/dev/Site_policy_handler
|
||||
$CFG->sitepolicy , especially in custom signup forms.
|
||||
|
||||
=== 3.3 ===
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
@availability @availability_date @javascript
|
||||
Feature: As a teacher I can set availability dates restriction to an activity and see a warning when conflicting dates are set
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | format | enablecompletion |
|
||||
| Course 1 | C1 | topics | 1 |
|
||||
And the following "users" exist:
|
||||
| username |
|
||||
| teacher1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | introformat | course | content | contentformat | idnumber |
|
||||
| page | PageName1 | PageDesc1 | 1 | C1 | Page 1 | 1 | 1 |
|
||||
|
||||
Scenario: When I set dates to potential conflicting dates in the same subset, I should see a warning.
|
||||
Given I am on the PageName1 "page activity editing" page logged in as teacher1
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the field "year" in the "1" "availability_date > Date Restriction" to "2023"
|
||||
And I set the field "Month" in the "1" "availability_date > Date Restriction" to "April"
|
||||
And I set the field "day" in the "1" "availability_date > Date Restriction" to "4"
|
||||
And I set the field "Direction" in the "1" "availability_date > Date Restriction" to "from"
|
||||
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the field "year" in the "2" "availability_date > Date Restriction" to "2023"
|
||||
And I set the field "Month" in the "2" "availability_date > Date Restriction" to "April"
|
||||
And I set the field "day" in the "2" "availability_date > Date Restriction" to "6"
|
||||
And I set the field "Direction" in the "2" "availability_date > Date Restriction" to "until"
|
||||
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the field "year" in the "3" "availability_date > Date Restriction" to "2023"
|
||||
And I set the field "Month" in the "3" "availability_date > Date Restriction" to "April"
|
||||
And I set the field "day" in the "3" "availability_date > Date Restriction" to "6"
|
||||
When I set the field "Direction" in the "3" "availability_date > Date Restriction" to "from"
|
||||
Then I should see "Conflicts with other date restrictions"
|
||||
|
||||
Scenario: If there are conflicting dates in the same subset, I should not see a warning if condition are separated by "any".
|
||||
Given I am on the PageName1 "page activity editing" page logged in as teacher1
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
|
||||
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the field "year" in the "1.1" "availability_date > Date Restriction" to "2023"
|
||||
And I set the field "Month" in the "1.1" "availability_date > Date Restriction" to "April"
|
||||
And I set the field "day" in the "1.1" "availability_date > Date Restriction" to "4"
|
||||
And I set the field "Direction" in the "1.1" "availability_date > Date Restriction" to "from"
|
||||
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the field "year" in the "1.2" "availability_date > Date Restriction" to "2023"
|
||||
And I set the field "Month" in the "1.2" "availability_date > Date Restriction" to "April"
|
||||
And I set the field "day" in the "1.2" "availability_date > Date Restriction" to "6"
|
||||
And I set the field "Direction" in the "1.2" "availability_date > Date Restriction" to "until"
|
||||
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the field "year" in the "1.3" "availability_date > Date Restriction" to "2023"
|
||||
And I set the field "Month" in the "1.3" "availability_date > Date Restriction" to "April"
|
||||
And I set the field "day" in the "1.3" "availability_date > Date Restriction" to "6"
|
||||
And I set the field "Direction" in the "1.3" "availability_date > Date Restriction" to "from"
|
||||
When I set the field "Required restrictions" in the "1" "core_availability > Set Of Restrictions" to "any"
|
||||
Then I should not see "Conflicts with other date restrictions"
|
||||
|
||||
Scenario: There should a conflicting availability dates are in the same subset separated by "all".
|
||||
Given I am on the PageName1 "page activity editing" page logged in as teacher1
|
||||
And I expand all fieldsets
|
||||
# Root level: Student "must" match the following.
|
||||
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
|
||||
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
|
||||
# This is the second level: Student "must" match any of the following.
|
||||
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
|
||||
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
|
||||
# And now the third and final level.
|
||||
And I click on "Add restriction..." "button" in the "1.1" "core_availability > Availability Button Area"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the field "year" in the "1.1.1" "availability_date > Date Restriction" to "2023"
|
||||
And I set the field "Month" in the "1.1.1" "availability_date > Date Restriction" to "April"
|
||||
And I set the field "day" in the "1.1.1" "availability_date > Date Restriction" to "2"
|
||||
And I set the field "Direction" in the "1.1.1" "availability_date > Date Restriction" to "from"
|
||||
And I click on "Add restriction..." "button" in the "1.1" "core_availability > Availability Button Area"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the field "year" in the "1.1.2" "availability_date > Date Restriction" to "2023"
|
||||
And I set the field "Month" in the "1.1.2" "availability_date > Date Restriction" to "April"
|
||||
And I set the field "day" in the "1.1.2" "availability_date > Date Restriction" to "3"
|
||||
And I set the field "Direction" in the "1.1.2" "availability_date > Date Restriction" to "until"
|
||||
# Then add a restriction to the second level.
|
||||
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
|
||||
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
|
||||
And I click on "Add restriction..." "button" in the "1.2" "core_availability > Availability Button Area"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the field "year" in the "1.2.1" "availability_date > Date Restriction" to "2023"
|
||||
And I set the field "Month" in the "1.2.1" "availability_date > Date Restriction" to "April"
|
||||
And I set the field "day" in the "1.2.1" "availability_date > Date Restriction" to "4"
|
||||
And I set the field "Direction" in the "1.2.1" "availability_date > Date Restriction" to "from"
|
||||
And I click on "Add restriction..." "button" in the "1.2" "core_availability > Availability Button Area"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the field "year" in the "1.2.2" "availability_date > Date Restriction" to "2023"
|
||||
And I set the field "Month" in the "1.2.2" "availability_date > Date Restriction" to "April"
|
||||
And I set the field "day" in the "1.2.2" "availability_date > Date Restriction" to "3"
|
||||
When I set the field "Direction" in the "1.2.2" "availability_date > Date Restriction" to "until"
|
||||
# Same subset, we can detect conflicts.
|
||||
Then I should see "Conflicts with other date restrictions"
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
|
||||
/**
|
||||
* Behat availabilty-related steps definitions.
|
||||
*
|
||||
* @package availability_date
|
||||
* @category test
|
||||
* @copyright 2023 Laurent David <laurent.david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_availability_date extends behat_base {
|
||||
/**
|
||||
* Return the list of partial named selectors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_partial_named_selectors(): array {
|
||||
return [
|
||||
new behat_component_named_selector(
|
||||
'Date Restriction', ["//div[h3[@data-restriction-order=%locator% and contains(text(), 'Date restriction')]]"]
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
+58
-42
@@ -65,6 +65,11 @@ M.availability_date.form.getNode = function(json) {
|
||||
// Set default time that corresponds to the HTML selectors.
|
||||
node.setData('time', this.defaultTime);
|
||||
}
|
||||
if (json.nodeUID === undefined) {
|
||||
var miliTime = new Date();
|
||||
json.nodeUID = miliTime.getTime();
|
||||
}
|
||||
node.setData('nodeUID', json.nodeUID);
|
||||
if (json.d !== undefined) {
|
||||
node.one('select[name=direction]').set('value', json.d);
|
||||
}
|
||||
@@ -114,7 +119,7 @@ M.availability_date.form.getNode = function(json) {
|
||||
* gets an AJAX response.
|
||||
*
|
||||
* @method updateTime
|
||||
* @param {Y.Node} component Node for plugin controls
|
||||
* @param {Y.Node} node Node for plugin controls
|
||||
*/
|
||||
M.availability_date.form.updateTime = function(node) {
|
||||
// After a change to the date/time we need to recompute the
|
||||
@@ -140,39 +145,53 @@ M.availability_date.form.updateTime = function(node) {
|
||||
M.availability_date.form.fillValue = function(value, node) {
|
||||
value.d = node.one('select[name=direction]').get('value');
|
||||
value.t = parseInt(node.getData('time'), 10);
|
||||
value.nodeUID = node.getData('nodeUID');
|
||||
};
|
||||
|
||||
/**
|
||||
* List out Date node value in an array node.
|
||||
* List out Date node value in the same branch.
|
||||
*
|
||||
* This will go through all array node and list from earlier date node to current date node.
|
||||
* This will go through all array node and list nodes that are sibling of the current node.
|
||||
*
|
||||
* @method convertTreeDateValue
|
||||
* @param {array} tree Tree node to convert
|
||||
* @param {array} arrayDateNode
|
||||
* @param {array} currentNode current node.
|
||||
*
|
||||
* @return {array} arrayDateNode
|
||||
* @method findAllDateSiblings
|
||||
* @param {Array} tree Tree items to convert
|
||||
* @param {Number} nodeUIDToFind node UID to find.
|
||||
* @return {Array|null} array of surrounding date avaiability values
|
||||
*/
|
||||
M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, currentNode) {
|
||||
var shouldSkip = false;
|
||||
tree.forEach(function(node) {
|
||||
if (shouldSkip) {
|
||||
return;
|
||||
}
|
||||
if (node.type === 'date') {
|
||||
// We go through all tree node, if we meet the current node then return.
|
||||
if (node.t === parseInt(currentNode.getData('time'), 10)
|
||||
&& currentNode.one('select[name=direction]').get('value') == node.d) {
|
||||
shouldSkip = true;
|
||||
return;
|
||||
M.availability_date.form.findAllDateSiblings = function(tree, nodeUIDToFind) {
|
||||
var itemValue = null;
|
||||
var siblingsFinderRecursive = function(itemsTree) {
|
||||
var dateSiblings = [];
|
||||
var nodeFound = false;
|
||||
var index;
|
||||
var childDates;
|
||||
var currentOp = itemsTree.op !== undefined ? itemsTree.op : null;
|
||||
if (itemsTree.c !== undefined) {
|
||||
var children = itemsTree.c;
|
||||
for (index = 0; index < children.length; index++) {
|
||||
itemValue = children.at(index);
|
||||
if (itemValue.type === undefined) {
|
||||
childDates = siblingsFinderRecursive(itemValue);
|
||||
if (childDates) {
|
||||
return childDates;
|
||||
}
|
||||
}
|
||||
if (itemValue.type === 'date') {
|
||||
// We go through all tree node, if we meet the current node then we add all nodes in the current branch.
|
||||
if (nodeUIDToFind === itemValue.nodeUID) {
|
||||
nodeFound = true;
|
||||
} else if (currentOp === '&') {
|
||||
dateSiblings.push(itemValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nodeFound) {
|
||||
return dateSiblings;
|
||||
}
|
||||
arrayDateNode.push(node);
|
||||
} else if (node.type === undefined) {
|
||||
M.availability_date.form.convertTreeDateValue(node.c, arrayDateNode, currentNode);
|
||||
}
|
||||
});
|
||||
return arrayDateNode;
|
||||
return null;
|
||||
};
|
||||
return siblingsFinderRecursive(tree);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -181,37 +200,34 @@ M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, cu
|
||||
* This will check current date node with all date node in tree node.
|
||||
*
|
||||
* @method checkConditionDate
|
||||
* @param {array} currentNode The curent node.
|
||||
* @param {Y.Node} currentNode The curent node.
|
||||
*
|
||||
* @return {boolean} error Return true if the date is conflict.
|
||||
*/
|
||||
M.availability_date.form.checkConditionDate = function(currentNode) {
|
||||
var error = false;
|
||||
if (M.core_availability.form.rootList.getValue().op === '&') {
|
||||
var jsValue = M.core_availability.form.rootList.getValue().c;
|
||||
var arrayDateNode = M.availability_date.form.convertTreeDateValue(jsValue, [], currentNode);
|
||||
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
|
||||
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
|
||||
arrayDateNode.forEach(function(checkNode) {
|
||||
var currentNodeUID = currentNode.getData('nodeUID');
|
||||
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
|
||||
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
|
||||
var dateSiblings = M.availability_date.form.findAllDateSiblings(
|
||||
M.core_availability.form.rootList.getValue(),
|
||||
currentNodeUID);
|
||||
if (dateSiblings) {
|
||||
dateSiblings.forEach(function(dateSibling) {
|
||||
// Validate if the date is conflict.
|
||||
if (checkNode.d === '<') {
|
||||
if (currentNodeDirection === '>=' && currentNodeTime >= checkNode.t) {
|
||||
if (dateSibling.d === '<') {
|
||||
if (currentNodeDirection === '>=' && currentNodeTime >= dateSibling.t) {
|
||||
error = true;
|
||||
}
|
||||
} else {
|
||||
if (currentNodeDirection === '<' && currentNodeTime <= checkNode.t) {
|
||||
if (currentNodeDirection === '<' && currentNodeTime <= dateSibling.t) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
});
|
||||
return error;
|
||||
} else {
|
||||
if (currentNode.one('div > .badge-warning')) {
|
||||
currentNode.one('div > .badge-warning').remove();
|
||||
}
|
||||
return error;
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
M.availability_date.form.fillErrors = function(errors, node) {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
YUI.add("moodle-availability_date-form",function(o,e){M.availability_date=M.availability_date||{},M.availability_date.form=o.Object(M.core_availability.plugin),M.availability_date.form.initInner=function(e,a){this.html=e,this.defaultTime=a},M.availability_date.form.getNode=function(e){var t,i,a='<span class="col-form-label pr-3">'+M.util.get_string("direction_before","availability_date")+'</span> <span class="availability-group"><label><span class="accesshide">'+M.util.get_string("direction_label","availability_date")+' </span><select name="direction" class="custom-select"><option value=">=">'+M.util.get_string("direction_from","availability_date")+'</option><option value="<">'+M.util.get_string("direction_until","availability_date")+"</option></select></label></span> "+this.html,n=o.Node.create("<span>"+a+"</span>");return e.t!==undefined?(n.setData("time",e.t),n.all("select:not([name=direction])").each(function(e){e.set("disabled",!0)}),a=M.cfg.wwwroot+"/availability/condition/date/ajax.php?action=fromtime&time="+e.t,o.io(a,{on:{success:function(e,a){var t,i,l=o.JSON.parse(a.responseText);for(t in l)(i=n.one("select[name=x\\["+t+"\\]]")).set("value",""+l[t]),i.set("disabled",!1)},failure:function(){window.alert(M.util.get_string("ajaxerror","availability_date"))}}})):n.setData("time",this.defaultTime),e.d!==undefined&&n.one("select[name=direction]").set("value",e.d),M.availability_date.form.addedEvents||(M.availability_date.form.addedEvents=!0,(a=o.one(".availability-field")).delegate("change",function(){M.core_availability.form.update()},".availability_date select[name=direction]"),a.delegate("change",function(){M.availability_date.form.updateTime(this.ancestor("span.availability_date"))},".availability_date select:not([name=direction])")),n.one("a[href=#]")&&(M.form.dateselector.init_single_date_selector(n),t=n.one("select[name=x\\[year\\]]"),i=t.set,t.set=function(e,a){i.call(t,e,a),"selectedIndex"===e&&setTimeout(function(){M.availability_date.form.updateTime(n)},0)}),n},M.availability_date.form.updateTime=function(t){var e=M.cfg.wwwroot+"/availability/condition/date/ajax.php?action=totime&year="+t.one("select[name=x\\[year\\]]").get("value")+"&month="+t.one("select[name=x\\[month\\]]").get("value")+"&day="+t.one("select[name=x\\[day\\]]").get("value")+"&hour="+t.one("select[name=x\\[hour\\]]").get("value")+"&minute="+t.one("select[name=x\\[minute\\]]").get("value");o.io(e,{on:{success:function(e,a){t.setData("time",a.responseText),M.core_availability.form.update()},failure:function(){window.alert(M.util.get_string("ajaxerror","availability_date"))}}})},M.availability_date.form.fillValue=function(e,a){e.d=a.one("select[name=direction]").get("value"),e.t=parseInt(a.getData("time"),10)},M.availability_date.form.convertTreeDateValue=function(e,a,t){var i=!1;return e.forEach(function(e){i||("date"===e.type?e.t!==parseInt(t.getData("time"),10)||t.one("select[name=direction]").get("value")!=e.d?a.push(e):i=!0:e.type===undefined&&M.availability_date.form.convertTreeDateValue(e.c,a,t))}),a},M.availability_date.form.checkConditionDate=function(e){var a,t,i,l=!1;return"&"===M.core_availability.form.rootList.getValue().op?(a=M.core_availability.form.rootList.getValue().c,a=M.availability_date.form.convertTreeDateValue(a,[],e),t=e.one("select[name=direction]").get("value"),i=parseInt(e.getData("time"),10),a.forEach(function(e){return"<"===e.d?">="===t&&i>=e.t&&(l=!0):"<"===t&&i<=e.t&&(l=!0),l})):e.one("div > .badge-warning")&&e.one("div > .badge-warning").remove(),l},M.availability_date.form.fillErrors=function(e,a){M.availability_date.form.checkConditionDate(a)&&e.push("availability_date:error_dateconflict")}},"@VERSION@",{requires:["base","node","event","io","moodle-core_availability-form"]});
|
||||
YUI.add("moodle-availability_date-form",function(o,e){M.availability_date=M.availability_date||{},M.availability_date.form=o.Object(M.core_availability.plugin),M.availability_date.form.initInner=function(e,a){this.html=e,this.defaultTime=a},M.availability_date.form.getNode=function(e){var t,i,a='<span class="col-form-label pr-3">'+M.util.get_string("direction_before","availability_date")+'</span> <span class="availability-group"><label><span class="accesshide">'+M.util.get_string("direction_label","availability_date")+' </span><select name="direction" class="custom-select"><option value=">=">'+M.util.get_string("direction_from","availability_date")+'</option><option value="<">'+M.util.get_string("direction_until","availability_date")+"</option></select></label></span> "+this.html,l=o.Node.create("<span>"+a+"</span>");return e.t!==undefined?(l.setData("time",e.t),l.all("select:not([name=direction])").each(function(e){e.set("disabled",!0)}),a=M.cfg.wwwroot+"/availability/condition/date/ajax.php?action=fromtime&time="+e.t,o.io(a,{on:{success:function(e,a){var t,i,n=o.JSON.parse(a.responseText);for(t in n)(i=l.one("select[name=x\\["+t+"\\]]")).set("value",""+n[t]),i.set("disabled",!1)},failure:function(){window.alert(M.util.get_string("ajaxerror","availability_date"))}}})):l.setData("time",this.defaultTime),e.nodeUID===undefined&&(a=new Date,e.nodeUID=a.getTime()),l.setData("nodeUID",e.nodeUID),e.d!==undefined&&l.one("select[name=direction]").set("value",e.d),M.availability_date.form.addedEvents||(M.availability_date.form.addedEvents=!0,(a=o.one(".availability-field")).delegate("change",function(){M.core_availability.form.update()},".availability_date select[name=direction]"),a.delegate("change",function(){M.availability_date.form.updateTime(this.ancestor("span.availability_date"))},".availability_date select:not([name=direction])")),l.one("a[href=#]")&&(M.form.dateselector.init_single_date_selector(l),t=l.one("select[name=x\\[year\\]]"),i=t.set,t.set=function(e,a){i.call(t,e,a),"selectedIndex"===e&&setTimeout(function(){M.availability_date.form.updateTime(l)},0)}),l},M.availability_date.form.updateTime=function(t){var e=M.cfg.wwwroot+"/availability/condition/date/ajax.php?action=totime&year="+t.one("select[name=x\\[year\\]]").get("value")+"&month="+t.one("select[name=x\\[month\\]]").get("value")+"&day="+t.one("select[name=x\\[day\\]]").get("value")+"&hour="+t.one("select[name=x\\[hour\\]]").get("value")+"&minute="+t.one("select[name=x\\[minute\\]]").get("value");o.io(e,{on:{success:function(e,a){t.setData("time",a.responseText),M.core_availability.form.update()},failure:function(){window.alert(M.util.get_string("ajaxerror","availability_date"))}}})},M.availability_date.form.fillValue=function(e,a){e.d=a.one("select[name=direction]").get("value"),e.t=parseInt(a.getData("time"),10),e.nodeUID=a.getData("nodeUID")},M.availability_date.form.findAllDateSiblings=function(e,d){var r,c=function(e){var a,t,i,n=[],l=!1,o=e.op!==undefined?e.op:null;if(e.c!==undefined){for(i=e.c,a=0;a<i.length;a++){if((r=i.at(a)).type===undefined&&(t=c(r)))return t;"date"===r.type&&(d===r.nodeUID?l=!0:"&"===o&&n.push(r))}if(l)return n}return null};return c(e)},M.availability_date.form.checkConditionDate=function(e){var a=!1,t=e.getData("nodeUID"),i=e.one("select[name=direction]").get("value"),n=parseInt(e.getData("time"),10),e=M.availability_date.form.findAllDateSiblings(M.core_availability.form.rootList.getValue(),t);return e&&e.forEach(function(e){return"<"===e.d?">="===i&&n>=e.t&&(a=!0):"<"===i&&n<=e.t&&(a=!0),a}),a},M.availability_date.form.fillErrors=function(e,a){M.availability_date.form.checkConditionDate(a)&&e.push("availability_date:error_dateconflict")}},"@VERSION@",{requires:["base","node","event","io","moodle-core_availability-form"]});
|
||||
availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js
Vendored
+58
-42
@@ -65,6 +65,11 @@ M.availability_date.form.getNode = function(json) {
|
||||
// Set default time that corresponds to the HTML selectors.
|
||||
node.setData('time', this.defaultTime);
|
||||
}
|
||||
if (json.nodeUID === undefined) {
|
||||
var miliTime = new Date();
|
||||
json.nodeUID = miliTime.getTime();
|
||||
}
|
||||
node.setData('nodeUID', json.nodeUID);
|
||||
if (json.d !== undefined) {
|
||||
node.one('select[name=direction]').set('value', json.d);
|
||||
}
|
||||
@@ -114,7 +119,7 @@ M.availability_date.form.getNode = function(json) {
|
||||
* gets an AJAX response.
|
||||
*
|
||||
* @method updateTime
|
||||
* @param {Y.Node} component Node for plugin controls
|
||||
* @param {Y.Node} node Node for plugin controls
|
||||
*/
|
||||
M.availability_date.form.updateTime = function(node) {
|
||||
// After a change to the date/time we need to recompute the
|
||||
@@ -140,39 +145,53 @@ M.availability_date.form.updateTime = function(node) {
|
||||
M.availability_date.form.fillValue = function(value, node) {
|
||||
value.d = node.one('select[name=direction]').get('value');
|
||||
value.t = parseInt(node.getData('time'), 10);
|
||||
value.nodeUID = node.getData('nodeUID');
|
||||
};
|
||||
|
||||
/**
|
||||
* List out Date node value in an array node.
|
||||
* List out Date node value in the same branch.
|
||||
*
|
||||
* This will go through all array node and list from earlier date node to current date node.
|
||||
* This will go through all array node and list nodes that are sibling of the current node.
|
||||
*
|
||||
* @method convertTreeDateValue
|
||||
* @param {array} tree Tree node to convert
|
||||
* @param {array} arrayDateNode
|
||||
* @param {array} currentNode current node.
|
||||
*
|
||||
* @return {array} arrayDateNode
|
||||
* @method findAllDateSiblings
|
||||
* @param {Array} tree Tree items to convert
|
||||
* @param {Number} nodeUIDToFind node UID to find.
|
||||
* @return {Array|null} array of surrounding date avaiability values
|
||||
*/
|
||||
M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, currentNode) {
|
||||
var shouldSkip = false;
|
||||
tree.forEach(function(node) {
|
||||
if (shouldSkip) {
|
||||
return;
|
||||
}
|
||||
if (node.type === 'date') {
|
||||
// We go through all tree node, if we meet the current node then return.
|
||||
if (node.t === parseInt(currentNode.getData('time'), 10)
|
||||
&& currentNode.one('select[name=direction]').get('value') == node.d) {
|
||||
shouldSkip = true;
|
||||
return;
|
||||
M.availability_date.form.findAllDateSiblings = function(tree, nodeUIDToFind) {
|
||||
var itemValue = null;
|
||||
var siblingsFinderRecursive = function(itemsTree) {
|
||||
var dateSiblings = [];
|
||||
var nodeFound = false;
|
||||
var index;
|
||||
var childDates;
|
||||
var currentOp = itemsTree.op !== undefined ? itemsTree.op : null;
|
||||
if (itemsTree.c !== undefined) {
|
||||
var children = itemsTree.c;
|
||||
for (index = 0; index < children.length; index++) {
|
||||
itemValue = children.at(index);
|
||||
if (itemValue.type === undefined) {
|
||||
childDates = siblingsFinderRecursive(itemValue);
|
||||
if (childDates) {
|
||||
return childDates;
|
||||
}
|
||||
}
|
||||
if (itemValue.type === 'date') {
|
||||
// We go through all tree node, if we meet the current node then we add all nodes in the current branch.
|
||||
if (nodeUIDToFind === itemValue.nodeUID) {
|
||||
nodeFound = true;
|
||||
} else if (currentOp === '&') {
|
||||
dateSiblings.push(itemValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nodeFound) {
|
||||
return dateSiblings;
|
||||
}
|
||||
arrayDateNode.push(node);
|
||||
} else if (node.type === undefined) {
|
||||
M.availability_date.form.convertTreeDateValue(node.c, arrayDateNode, currentNode);
|
||||
}
|
||||
});
|
||||
return arrayDateNode;
|
||||
return null;
|
||||
};
|
||||
return siblingsFinderRecursive(tree);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -181,37 +200,34 @@ M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, cu
|
||||
* This will check current date node with all date node in tree node.
|
||||
*
|
||||
* @method checkConditionDate
|
||||
* @param {array} currentNode The curent node.
|
||||
* @param {Y.Node} currentNode The curent node.
|
||||
*
|
||||
* @return {boolean} error Return true if the date is conflict.
|
||||
*/
|
||||
M.availability_date.form.checkConditionDate = function(currentNode) {
|
||||
var error = false;
|
||||
if (M.core_availability.form.rootList.getValue().op === '&') {
|
||||
var jsValue = M.core_availability.form.rootList.getValue().c;
|
||||
var arrayDateNode = M.availability_date.form.convertTreeDateValue(jsValue, [], currentNode);
|
||||
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
|
||||
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
|
||||
arrayDateNode.forEach(function(checkNode) {
|
||||
var currentNodeUID = currentNode.getData('nodeUID');
|
||||
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
|
||||
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
|
||||
var dateSiblings = M.availability_date.form.findAllDateSiblings(
|
||||
M.core_availability.form.rootList.getValue(),
|
||||
currentNodeUID);
|
||||
if (dateSiblings) {
|
||||
dateSiblings.forEach(function(dateSibling) {
|
||||
// Validate if the date is conflict.
|
||||
if (checkNode.d === '<') {
|
||||
if (currentNodeDirection === '>=' && currentNodeTime >= checkNode.t) {
|
||||
if (dateSibling.d === '<') {
|
||||
if (currentNodeDirection === '>=' && currentNodeTime >= dateSibling.t) {
|
||||
error = true;
|
||||
}
|
||||
} else {
|
||||
if (currentNodeDirection === '<' && currentNodeTime <= checkNode.t) {
|
||||
if (currentNodeDirection === '<' && currentNodeTime <= dateSibling.t) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
});
|
||||
return error;
|
||||
} else {
|
||||
if (currentNode.one('div > .badge-warning')) {
|
||||
currentNode.one('div > .badge-warning').remove();
|
||||
}
|
||||
return error;
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
M.availability_date.form.fillErrors = function(errors, node) {
|
||||
|
||||
+58
-42
@@ -63,6 +63,11 @@ M.availability_date.form.getNode = function(json) {
|
||||
// Set default time that corresponds to the HTML selectors.
|
||||
node.setData('time', this.defaultTime);
|
||||
}
|
||||
if (json.nodeUID === undefined) {
|
||||
var miliTime = new Date();
|
||||
json.nodeUID = miliTime.getTime();
|
||||
}
|
||||
node.setData('nodeUID', json.nodeUID);
|
||||
if (json.d !== undefined) {
|
||||
node.one('select[name=direction]').set('value', json.d);
|
||||
}
|
||||
@@ -112,7 +117,7 @@ M.availability_date.form.getNode = function(json) {
|
||||
* gets an AJAX response.
|
||||
*
|
||||
* @method updateTime
|
||||
* @param {Y.Node} component Node for plugin controls
|
||||
* @param {Y.Node} node Node for plugin controls
|
||||
*/
|
||||
M.availability_date.form.updateTime = function(node) {
|
||||
// After a change to the date/time we need to recompute the
|
||||
@@ -138,39 +143,53 @@ M.availability_date.form.updateTime = function(node) {
|
||||
M.availability_date.form.fillValue = function(value, node) {
|
||||
value.d = node.one('select[name=direction]').get('value');
|
||||
value.t = parseInt(node.getData('time'), 10);
|
||||
value.nodeUID = node.getData('nodeUID');
|
||||
};
|
||||
|
||||
/**
|
||||
* List out Date node value in an array node.
|
||||
* List out Date node value in the same branch.
|
||||
*
|
||||
* This will go through all array node and list from earlier date node to current date node.
|
||||
* This will go through all array node and list nodes that are sibling of the current node.
|
||||
*
|
||||
* @method convertTreeDateValue
|
||||
* @param {array} tree Tree node to convert
|
||||
* @param {array} arrayDateNode
|
||||
* @param {array} currentNode current node.
|
||||
*
|
||||
* @return {array} arrayDateNode
|
||||
* @method findAllDateSiblings
|
||||
* @param {Array} tree Tree items to convert
|
||||
* @param {Number} nodeUIDToFind node UID to find.
|
||||
* @return {Array|null} array of surrounding date avaiability values
|
||||
*/
|
||||
M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, currentNode) {
|
||||
var shouldSkip = false;
|
||||
tree.forEach(function(node) {
|
||||
if (shouldSkip) {
|
||||
return;
|
||||
}
|
||||
if (node.type === 'date') {
|
||||
// We go through all tree node, if we meet the current node then return.
|
||||
if (node.t === parseInt(currentNode.getData('time'), 10)
|
||||
&& currentNode.one('select[name=direction]').get('value') == node.d) {
|
||||
shouldSkip = true;
|
||||
return;
|
||||
M.availability_date.form.findAllDateSiblings = function(tree, nodeUIDToFind) {
|
||||
var itemValue = null;
|
||||
var siblingsFinderRecursive = function(itemsTree) {
|
||||
var dateSiblings = [];
|
||||
var nodeFound = false;
|
||||
var index;
|
||||
var childDates;
|
||||
var currentOp = itemsTree.op !== undefined ? itemsTree.op : null;
|
||||
if (itemsTree.c !== undefined) {
|
||||
var children = itemsTree.c;
|
||||
for (index = 0; index < children.length; index++) {
|
||||
itemValue = children.at(index);
|
||||
if (itemValue.type === undefined) {
|
||||
childDates = siblingsFinderRecursive(itemValue);
|
||||
if (childDates) {
|
||||
return childDates;
|
||||
}
|
||||
}
|
||||
if (itemValue.type === 'date') {
|
||||
// We go through all tree node, if we meet the current node then we add all nodes in the current branch.
|
||||
if (nodeUIDToFind === itemValue.nodeUID) {
|
||||
nodeFound = true;
|
||||
} else if (currentOp === '&') {
|
||||
dateSiblings.push(itemValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nodeFound) {
|
||||
return dateSiblings;
|
||||
}
|
||||
arrayDateNode.push(node);
|
||||
} else if (node.type === undefined) {
|
||||
M.availability_date.form.convertTreeDateValue(node.c, arrayDateNode, currentNode);
|
||||
}
|
||||
});
|
||||
return arrayDateNode;
|
||||
return null;
|
||||
};
|
||||
return siblingsFinderRecursive(tree);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -179,37 +198,34 @@ M.availability_date.form.convertTreeDateValue = function(tree, arrayDateNode, cu
|
||||
* This will check current date node with all date node in tree node.
|
||||
*
|
||||
* @method checkConditionDate
|
||||
* @param {array} currentNode The curent node.
|
||||
* @param {Y.Node} currentNode The curent node.
|
||||
*
|
||||
* @return {boolean} error Return true if the date is conflict.
|
||||
*/
|
||||
M.availability_date.form.checkConditionDate = function(currentNode) {
|
||||
var error = false;
|
||||
if (M.core_availability.form.rootList.getValue().op === '&') {
|
||||
var jsValue = M.core_availability.form.rootList.getValue().c;
|
||||
var arrayDateNode = M.availability_date.form.convertTreeDateValue(jsValue, [], currentNode);
|
||||
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
|
||||
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
|
||||
arrayDateNode.forEach(function(checkNode) {
|
||||
var currentNodeUID = currentNode.getData('nodeUID');
|
||||
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
|
||||
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
|
||||
var dateSiblings = M.availability_date.form.findAllDateSiblings(
|
||||
M.core_availability.form.rootList.getValue(),
|
||||
currentNodeUID);
|
||||
if (dateSiblings) {
|
||||
dateSiblings.forEach(function(dateSibling) {
|
||||
// Validate if the date is conflict.
|
||||
if (checkNode.d === '<') {
|
||||
if (currentNodeDirection === '>=' && currentNodeTime >= checkNode.t) {
|
||||
if (dateSibling.d === '<') {
|
||||
if (currentNodeDirection === '>=' && currentNodeTime >= dateSibling.t) {
|
||||
error = true;
|
||||
}
|
||||
} else {
|
||||
if (currentNodeDirection === '<' && currentNodeTime <= checkNode.t) {
|
||||
if (currentNodeDirection === '<' && currentNodeTime <= dateSibling.t) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
});
|
||||
return error;
|
||||
} else {
|
||||
if (currentNode.one('div > .badge-warning')) {
|
||||
currentNode.one('div > .badge-warning').remove();
|
||||
}
|
||||
return error;
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
M.availability_date.form.fillErrors = function(errors, node) {
|
||||
|
||||
@@ -24,7 +24,7 @@ require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
|
||||
* @copyright 2023 Amaia Anabitarte <amaia@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_availability extends \behat_base {
|
||||
class behat_availability extends behat_base {
|
||||
|
||||
/**
|
||||
* Return the list of partial named selectors.
|
||||
@@ -33,18 +33,34 @@ class behat_availability extends \behat_base {
|
||||
*/
|
||||
public static function get_partial_named_selectors(): array {
|
||||
return [
|
||||
new \behat_component_named_selector(
|
||||
'Activity availability', [<<<XPATH
|
||||
.//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][descendant::*[contains(normalize-space(.), %locator%)]]
|
||||
//div[@data-region='availabilityinfo']
|
||||
XPATH
|
||||
new behat_component_named_selector(
|
||||
'Activity availability', [
|
||||
".//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]"
|
||||
. "[descendant::*[contains(normalize-space(.), %locator%)]]//div[@data-region='availabilityinfo']",
|
||||
]
|
||||
),
|
||||
new \behat_component_named_selector(
|
||||
'Section availability', [<<<XPATH
|
||||
.//li[@id = %locator%]//div[@data-region='availabilityinfo']
|
||||
XPATH
|
||||
]
|
||||
new behat_component_named_selector(
|
||||
'Section availability', [".//li[@id = %locator%]//div[@data-region='availabilityinfo']"],
|
||||
),
|
||||
new behat_component_named_selector(
|
||||
'Set Of Restrictions', ["//div[h3[@data-restriction-order=%locator% and contains(text(), 'Set of')]]"],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of exact named selectors
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_exact_named_selectors(): array {
|
||||
return [
|
||||
new behat_component_named_selector(
|
||||
'Availability Button Area',
|
||||
[
|
||||
"//h3[@data-restriction-order=%locator%]/following-sibling::div[contains(@class,'availability-inner')]/"
|
||||
. "div[contains(@class,'availability-button')]",
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Vendored
+2
-1
@@ -584,7 +584,7 @@ M.core_availability.List.prototype.renumber = function(parentNumber) {
|
||||
}
|
||||
var heading = M.util.get_string('setheading', 'availability', headingParams);
|
||||
this.node.one('> h3').set('innerHTML', heading);
|
||||
|
||||
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = parentNumber ? parentNumber : 'root';
|
||||
// Do children.
|
||||
for (var i = 0; i < this.children.length; i++) {
|
||||
var child = this.children[i];
|
||||
@@ -1078,6 +1078,7 @@ M.core_availability.Item.prototype.renumber = function(number) {
|
||||
headingParams.number = number + ':';
|
||||
var heading = M.util.get_string('itemheading', 'availability', headingParams);
|
||||
this.node.one('> h3').set('innerHTML', heading);
|
||||
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = number ? number : 'root';
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
File diff suppressed because one or more lines are too long
+2
-1
@@ -584,7 +584,7 @@ M.core_availability.List.prototype.renumber = function(parentNumber) {
|
||||
}
|
||||
var heading = M.util.get_string('setheading', 'availability', headingParams);
|
||||
this.node.one('> h3').set('innerHTML', heading);
|
||||
|
||||
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = parentNumber ? parentNumber : 'root';
|
||||
// Do children.
|
||||
for (var i = 0; i < this.children.length; i++) {
|
||||
var child = this.children[i];
|
||||
@@ -1078,6 +1078,7 @@ M.core_availability.Item.prototype.renumber = function(number) {
|
||||
headingParams.number = number + ':';
|
||||
var heading = M.util.get_string('itemheading', 'availability', headingParams);
|
||||
this.node.one('> h3').set('innerHTML', heading);
|
||||
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = number ? number : 'root';
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+2
-1
@@ -582,7 +582,7 @@ M.core_availability.List.prototype.renumber = function(parentNumber) {
|
||||
}
|
||||
var heading = M.util.get_string('setheading', 'availability', headingParams);
|
||||
this.node.one('> h3').set('innerHTML', heading);
|
||||
|
||||
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = parentNumber ? parentNumber : 'root';
|
||||
// Do children.
|
||||
for (var i = 0; i < this.children.length; i++) {
|
||||
var child = this.children[i];
|
||||
@@ -1076,6 +1076,7 @@ M.core_availability.Item.prototype.renumber = function(number) {
|
||||
headingParams.number = number + ':';
|
||||
var heading = M.util.get_string('itemheading', 'availability', headingParams);
|
||||
this.node.one('> h3').set('innerHTML', heading);
|
||||
this.node.one('> h3').getDOMNode().dataset.restrictionOrder = number ? number : 'root';
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -867,7 +867,9 @@ class moodle1_course_outline_handler extends moodle1_xml_handler {
|
||||
$plugin->version = null;
|
||||
$module = $plugin;
|
||||
include($versionfile);
|
||||
$data['version'] = $plugin->version;
|
||||
// Have to hardcode - since quiz uses some hardcoded version numbers when restoring.
|
||||
// This is the lowest number used minus one.
|
||||
$data['version'] = 2011010099;
|
||||
} else {
|
||||
$data['version'] = null;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ Feature: Edit activities in main menu block
|
||||
| activity | course | section | name |
|
||||
| forum | Acceptance test site | 0 | Visible forum |
|
||||
| forum | Acceptance test site | 0 | My forum name |
|
||||
And I change window size to "large"
|
||||
And I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I turn editing mode on
|
||||
|
||||
Vendored
+1
-1
@@ -5,6 +5,6 @@
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define("core_calendar/crud",["jquery","core/str","core/notification","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors","core/pending","core/modal_save_cancel"],(function($,Str,Notification,ModalEvents,ModalEventForm,CalendarRepository,CalendarEvents,CalendarModalDelete,CalendarSelectors,Pending,ModalSaveCancel){return{registerRemove:function(root){root.on("click",CalendarSelectors.actions.remove,(function(e){var eventSource=$(this).closest(CalendarSelectors.eventItem);!function(eventId,eventTitle,eventCount){var deletePromise,pendingPromise=new Pending("core_calendar/crud:confirmDeletion"),deleteStrings=[{key:"deleteevent",component:"calendar"}],isRepeatedEvent=(eventCount=parseInt(eventCount,10))>1;isRepeatedEvent?(deleteStrings.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:eventTitle,count:eventCount}}),deletePromise=CalendarModalDelete.create()):(deleteStrings.push({key:"confirmeventdelete",component:"calendar",param:eventTitle}),deletePromise=ModalSaveCancel.create());var stringsPromise=Str.get_strings(deleteStrings);$.when(stringsPromise,deletePromise).then((function(strings,deleteModal){return deleteModal.setRemoveOnClose(!0),deleteModal.setTitle(strings[0]),deleteModal.setBody(strings[1]),isRepeatedEvent||deleteModal.setSaveButtonText(strings[0]),deleteModal.show(),deleteModal.getRoot().on(ModalEvents.save,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedevent");CalendarRepository.deleteEvent(eventId,!1).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!1])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal.getRoot().on(CalendarEvents.deleteAll,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedallevent");CalendarRepository.deleteEvent(eventId,!0).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!0])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)}(eventSource.data("eventId"),eventSource.data("eventTitle"),eventSource.data("eventCount")),e.preventDefault()}))},registerEditListeners:function(root,eventFormModalPromise){var pendingPromise=new Pending("core_calendar/crud:registerEditListeners");return eventFormModalPromise.then((function(modal){return $("body").on(CalendarEvents.editEvent,(function(e,eventId){var target=root.find("[data-event-id=".concat(eventId,"]")),calendarWrapper=root.find(CalendarSelectors.wrapper);modal.setEventId(eventId),modal.setContextId(calendarWrapper.data("contextId")),modal.setReturnElement(target),modal.show(),e.stopImmediatePropagation()})),modal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)},registerEventFormModal:function(root){var eventFormPromise=ModalEventForm.create();return root.on("click",CalendarSelectors.actions.create,(function(e){eventFormPromise.then((function(modal){var wrapper=root.find(CalendarSelectors.wrapper),categoryId=wrapper.data("categoryid");void 0!==categoryId&&modal.setCategoryId(categoryId);var today=root.find(CalendarSelectors.today),firstDay=root.find(CalendarSelectors.day);!today.length&&firstDay.length&&modal.setStartTime(firstDay.data("newEventTimestamp")),modal.setContextId(wrapper.data("contextId")),modal.setCourseId(wrapper.data("courseid")),modal.show()})).fail(Notification.exception),e.preventDefault()})),root.on("click",CalendarSelectors.actions.edit,(function(e){e.preventDefault();var target=$(e.currentTarget),calendarWrapper=target.closest(CalendarSelectors.wrapper),eventWrapper=target.closest(CalendarSelectors.eventItem);eventFormPromise.then((function(modal){modal.setEventId(eventWrapper.data("eventId")),modal.setContextId(calendarWrapper.data("contextId")),modal.setCourseId(eventWrapper.data("courseId")),modal.show(),e.stopImmediatePropagation()})).fail(Notification.exception)})),eventFormPromise}}}));
|
||||
define("core_calendar/crud",["jquery","core/str","core/notification","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors","core/pending","core/modal_save_cancel"],(function($,Str,Notification,ModalEvents,ModalEventForm,CalendarRepository,CalendarEvents,CalendarModalDelete,CalendarSelectors,Pending,ModalSaveCancel){return{registerRemove:function(root){root.on("click",CalendarSelectors.actions.remove,(function(e){var eventSource=$(this).closest(CalendarSelectors.eventItem);!function(eventId,eventTitle,eventCount){var deletePromise,pendingPromise=new Pending("core_calendar/crud:confirmDeletion"),deleteStrings=[{key:"deleteevent",component:"calendar"}],isRepeatedEvent=(eventCount=parseInt(eventCount,10))>1;isRepeatedEvent?(deleteStrings.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:eventTitle,count:eventCount}}),deletePromise=CalendarModalDelete.create()):(deleteStrings.push({key:"confirmeventdelete",component:"calendar",param:eventTitle}),deletePromise=ModalSaveCancel.create());var stringsPromise=Str.get_strings(deleteStrings);$.when(stringsPromise,deletePromise).then((function(strings,deleteModal){return deleteModal.setRemoveOnClose(!0),deleteModal.setTitle(strings[0]),deleteModal.setBody(strings[1]),isRepeatedEvent||deleteModal.setSaveButtonText(strings[0]),deleteModal.show(),deleteModal.getRoot().on(ModalEvents.save,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedevent");CalendarRepository.deleteEvent(eventId,!1).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!1])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal.getRoot().on(CalendarEvents.deleteAll,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedallevent");CalendarRepository.deleteEvent(eventId,!0).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!0])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)}(eventSource.data("eventId"),eventSource.data("eventTitle"),eventSource.data("eventCount")),e.preventDefault()}))},registerEditListeners:function(root,eventFormModalPromise){var pendingPromise=new Pending("core_calendar/crud:registerEditListeners");return eventFormModalPromise.then((function(modal){return $("body").on(CalendarEvents.editEvent,(function(e,eventId){var target=root.find("[data-event-id=".concat(eventId,"]")),calendarWrapper=root.find(CalendarSelectors.wrapper);modal.setEventId(eventId),modal.setContextId(calendarWrapper.data("contextId")),modal.setReturnElement(target),modal.show(),e.stopImmediatePropagation()})),modal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)},registerEventFormModal:function(root){var eventFormPromise=ModalEventForm.create();return root.on("click",CalendarSelectors.actions.create,(function(e){eventFormPromise.then((function(modal){var wrapper=root.find(CalendarSelectors.wrapper),categoryId=wrapper.data("categoryid");void 0!==categoryId&&modal.setCategoryId(categoryId);var today=root.find(CalendarSelectors.today),firstDay=root.find(CalendarSelectors.day);!today.length&&firstDay.length&&modal.setStartTime(firstDay.data("newEventTimestamp")),modal.setContextId(wrapper.data("contextId")),modal.setCourseId(wrapper.data("courseid")),modal.show()})).catch(Notification.exception),e.preventDefault()})),root.on("click",CalendarSelectors.actions.edit,(function(e){e.preventDefault();var target=$(e.currentTarget),calendarWrapper=target.closest(CalendarSelectors.wrapper),eventWrapper=target.closest(CalendarSelectors.eventItem);eventFormPromise.then((function(modal){modal.setEventId(eventWrapper.data("eventId")),modal.setContextId(calendarWrapper.data("contextId")),modal.setCourseId(eventWrapper.data("courseId")),modal.show(),e.stopImmediatePropagation()})).catch(Notification.exception)})),eventFormPromise}}}));
|
||||
|
||||
//# sourceMappingURL=crud.min.js.map
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -170,7 +170,7 @@ function(
|
||||
modal.show();
|
||||
return;
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
.catch(Notification.exception);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
@@ -192,7 +192,7 @@ function(
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
return;
|
||||
}).fail(Notification.exception);
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ export const foldDayEvents = () => {
|
||||
moreEventsLink.attr('data-event-folded', 'false');
|
||||
link.text(str);
|
||||
return str;
|
||||
}).fail();
|
||||
}).catch(Notification.exception);
|
||||
} else {
|
||||
moreEventsLink.hide();
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ abstract class type_base {
|
||||
* @param int $time the timestamp in UTC, as obtained from the database
|
||||
* @param string $format strftime format
|
||||
* @param int|float|string $timezone the timezone to use
|
||||
* {@link http://docs.moodle.org/dev/Time_API#Timezone}
|
||||
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
|
||||
* @param bool $fixday if true then the leading zero from %d is removed,
|
||||
* if false then the leading zero is maintained
|
||||
* @param bool $fixhour if true then the leading zero from %I is removed,
|
||||
@@ -180,7 +180,7 @@ abstract class type_base {
|
||||
*
|
||||
* @param int $time timestamp in GMT
|
||||
* @param float|int|string $timezone the timezone to use to calculate the time
|
||||
* {@link http://docs.moodle.org/dev/Time_API#Timezone}
|
||||
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
|
||||
* @return array an array that represents the date in user time
|
||||
*/
|
||||
public abstract function timestamp_to_date_array($time, $timezone = 99);
|
||||
|
||||
@@ -1936,6 +1936,7 @@ function calendar_get_link_href($linkbase, $d, $m, $y, $time = 0) {
|
||||
/**
|
||||
* Build and return a previous month HTML link, with an arrow.
|
||||
*
|
||||
* @deprecated since Moodle 4.3
|
||||
* @param string $text The text label.
|
||||
* @param string|moodle_url $linkbase The URL stub.
|
||||
* @param int $d The number of the date.
|
||||
@@ -1947,6 +1948,8 @@ function calendar_get_link_href($linkbase, $d, $m, $y, $time = 0) {
|
||||
* @return string HTML string.
|
||||
*/
|
||||
function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide = false, $time = 0) {
|
||||
debugging(__FUNCTION__ . ' has been deprecated and should not be used anymore.', DEBUG_DEVELOPER);
|
||||
|
||||
$href = calendar_get_link_href(new \moodle_url($linkbase), $d, $m, $y, $time);
|
||||
|
||||
if (empty($href)) {
|
||||
@@ -1964,6 +1967,7 @@ function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide =
|
||||
/**
|
||||
* Build and return a next month HTML link, with an arrow.
|
||||
*
|
||||
* @deprecated since Moodle 4.3
|
||||
* @param string $text The text label.
|
||||
* @param string|moodle_url $linkbase The URL stub.
|
||||
* @param int $d the number of the Day
|
||||
@@ -1975,6 +1979,8 @@ function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide =
|
||||
* @return string HTML string.
|
||||
*/
|
||||
function calendar_get_link_next($text, $linkbase, $d, $m, $y, $accesshide = false, $time = 0) {
|
||||
debugging(__FUNCTION__ . ' has been deprecated and should not be used anymore.', DEBUG_DEVELOPER);
|
||||
|
||||
$href = calendar_get_link_href(new \moodle_url($linkbase), $d, $m, $y, $time);
|
||||
|
||||
if (empty($href)) {
|
||||
|
||||
@@ -34,7 +34,17 @@
|
||||
}
|
||||
}}
|
||||
{{#modulename}}
|
||||
{{#pix}} monologo, {{modulename}} {{/pix}}
|
||||
{{#icon}}
|
||||
{{#iconurl}}
|
||||
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon {{iconclass}}">
|
||||
{{/iconurl}}
|
||||
{{^iconurl}}
|
||||
{{#pix}} monologo, {{modulename}} {{/pix}}
|
||||
{{/iconurl}}
|
||||
{{/icon}}
|
||||
{{^icon}}
|
||||
{{#pix}} monologo, {{modulename}} {{/pix}}
|
||||
{{/icon}}
|
||||
{{/modulename}}
|
||||
{{^modulename}}
|
||||
{{#icon}}{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}{{/icon}}
|
||||
|
||||
@@ -75,7 +75,16 @@
|
||||
{{/isactionevent}}
|
||||
{{/canedit}}
|
||||
</div>
|
||||
{{#icon}}<div class="d-inline-block mt-1 align-top">{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}</div>{{/icon}}
|
||||
{{#icon}}
|
||||
<div class="d-inline-block mt-1 align-top">
|
||||
{{#iconurl}}
|
||||
<img alt="{{alttext}}" title="{{alttext}}" src="{{{iconurl}}}" class="icon {{iconclass}}">
|
||||
{{/iconurl}}
|
||||
{{^iconurl}}
|
||||
{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}
|
||||
{{/iconurl}}
|
||||
</div>
|
||||
{{/icon}}
|
||||
<div class="d-inline-block">
|
||||
<h3 class="name d-inline-block">{{{name}}}</h3>
|
||||
</div>
|
||||
|
||||
@@ -222,7 +222,7 @@ class structure extends type_base {
|
||||
* @param int $time the timestamp in UTC, as obtained from the database
|
||||
* @param string $format strftime format
|
||||
* @param int|float|string $timezone the timezone to use
|
||||
* {@link http://docs.moodle.org/dev/Time_API#Timezone}
|
||||
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
|
||||
* @param bool $fixday if true then the leading zero from %d is removed,
|
||||
* if false then the leading zero is maintained
|
||||
* @param bool $fixhour if true then the leading zero from %I is removed,
|
||||
@@ -239,7 +239,7 @@ class structure extends type_base {
|
||||
*
|
||||
* @param int $time timestamp in GMT
|
||||
* @param float|int|string $timezone the timezone to use to calculate the time
|
||||
* {@link http://docs.moodle.org/dev/Time_API#Timezone}
|
||||
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
|
||||
* @return array an array that represents the date in user time
|
||||
*/
|
||||
public function timestamp_to_date_array($time, $timezone = 99) {
|
||||
|
||||
@@ -289,7 +289,7 @@ class structure extends type_base {
|
||||
* @param int $time the timestamp in UTC, as obtained from the database
|
||||
* @param string $format strftime format
|
||||
* @param int|float|string $timezone the timezone to use
|
||||
* {@link http://docs.moodle.org/dev/Time_API#Timezone}
|
||||
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
|
||||
* @param bool $fixday if true then the leading zero from %d is removed,
|
||||
* if false then the leading zero is maintained
|
||||
* @param bool $fixhour if true then the leading zero from %I is removed,
|
||||
@@ -348,7 +348,7 @@ class structure extends type_base {
|
||||
*
|
||||
* @param int $time Timestamp in GMT
|
||||
* @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
|
||||
* dst offset is applied {@link http://docs.moodle.org/dev/Time_API#Timezone}
|
||||
* dst offset is applied {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
|
||||
* @return array an array that represents the date in user time
|
||||
*/
|
||||
public function timestamp_to_date_array($time, $timezone = 99) {
|
||||
|
||||
@@ -4,7 +4,10 @@ information provided here is intended especially for developers.
|
||||
=== 4.3 ===
|
||||
* The `navigation` property has been removed from `\core_calendar\external\day_exporter` as it is not being used by any of the
|
||||
calendar templates.
|
||||
* calendar_top_controls() has been deprecated and should no longer be used.
|
||||
* The following functions have been deprecated and should no longer be used:
|
||||
- calendar_top_controls()
|
||||
- calendar_get_link_previous()
|
||||
- calendar_get_link_next()
|
||||
|
||||
=== 4.1 ===
|
||||
* New method `calendar_format_event_location` which will format the location property of an event, converting any
|
||||
@@ -35,7 +38,7 @@ information provided here is intended especially for developers.
|
||||
=== 3.9 ===
|
||||
* Plugins can now create their own calendar events, both standard and action ones. To do it they need to specify
|
||||
$event->component when creating an event. Component events can not be edited or deleted manually.
|
||||
See https://docs.moodle.org/dev/Calendar_API#Component_events
|
||||
See https://moodledev.io/docs/apis/core/calendar#component-events
|
||||
* The following functions have been deprecated because they were no longer used:
|
||||
- calendar_add_event_metadata()
|
||||
- core_calendar_renderer::event()
|
||||
|
||||
@@ -207,11 +207,11 @@ class api {
|
||||
$mform->addElement(
|
||||
'select',
|
||||
'selectedcommunication',
|
||||
get_string('seleccommunicationprovider', 'communication'),
|
||||
get_string('selectcommunicationprovider', 'communication'),
|
||||
$communicationproviders,
|
||||
['data-communicationchooser-field' => 'selector'],
|
||||
);
|
||||
$mform->addHelpButton('selectedcommunication', 'seleccommunicationprovider', 'communication');
|
||||
$mform->addHelpButton('selectedcommunication', 'selectcommunicationprovider', 'communication');
|
||||
$mform->setDefault('selectedcommunication', $defaulprovider);
|
||||
|
||||
$mform->registerNoSubmitButton('updatecommunicationprovider');
|
||||
@@ -245,9 +245,18 @@ class api {
|
||||
),
|
||||
'addcommunicationoptionshere'
|
||||
);
|
||||
$mform->addHelpButton('communicationroomname', 'communicationroomname', 'communication');
|
||||
$mform->setType('communicationroomname', PARAM_TEXT);
|
||||
|
||||
$mform->insertElementBefore(
|
||||
$mform->createElement(
|
||||
'static',
|
||||
'communicationroomnameinfo',
|
||||
'',
|
||||
get_string('communicationroomnameinfo', 'communication'),
|
||||
),
|
||||
'addcommunicationoptionshere',
|
||||
);
|
||||
|
||||
processor::set_provider_specific_form_definition($provider, $mform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,4 +34,12 @@ interface communication_provider {
|
||||
* @param processor $communication The communication object
|
||||
*/
|
||||
public static function load_for_instance(processor $communication): self;
|
||||
|
||||
/**
|
||||
* Check if the provider is configured or not.
|
||||
*
|
||||
* This method is intended to check if the plugin have got any settings and if all the settings are set properly.
|
||||
* This checking helps to reduce errors in future when a communication instance is added for the provider and not configured.
|
||||
*/
|
||||
public static function is_configured(): bool;
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ class processor {
|
||||
public static function load_by_id(int $id): ?self {
|
||||
global $DB;
|
||||
$record = $DB->get_record('communication', ['id' => $id]);
|
||||
if ($record && self::is_provider_enabled($record->provider)) {
|
||||
if ($record && self::is_provider_available($record->provider)) {
|
||||
return new self($record);
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ class processor {
|
||||
'instancetype' => $instancetype,
|
||||
]);
|
||||
|
||||
if ($record && self::is_provider_enabled($record->provider)) {
|
||||
if ($record && self::is_provider_available($record->provider)) {
|
||||
return new self($record);
|
||||
}
|
||||
|
||||
@@ -671,12 +671,16 @@ class processor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Is communication provider enabled/disabled.
|
||||
* Is the communication provider enabled and configured, or disabled.
|
||||
*
|
||||
* @param string $provider provider component name
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_provider_enabled(string $provider): bool {
|
||||
return \core\plugininfo\communication::is_plugin_enabled($provider);
|
||||
public static function is_provider_available(string $provider): bool {
|
||||
if (\core\plugininfo\communication::is_plugin_enabled($provider)) {
|
||||
$providerclass = "{$provider}\\communication_feature";
|
||||
return $providerclass::is_configured();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,4 +170,8 @@ class communication_feature implements
|
||||
'addcommunicationoptionshere'
|
||||
), 'addcommunicationoptionshere');
|
||||
}
|
||||
|
||||
public static function is_configured(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ Feature: Communication custom link
|
||||
Given I am on the "Course 1" "Course" page logged in as "teacher1"
|
||||
And "Chat to course participants" "button" should not be visible
|
||||
When I navigate to "Communication" in current page administration
|
||||
And the "Communication service" select box should contain "Custom link"
|
||||
And the "Provider" select box should contain "Custom link"
|
||||
And I should not see "Custom link URL"
|
||||
And I select "Custom link" from the "Communication service" singleselect
|
||||
And I select "Custom link" from the "Provider" singleselect
|
||||
And I should see "Custom link URL"
|
||||
And I set the following fields to these values:
|
||||
| communicationroomname | Test URL |
|
||||
@@ -57,7 +57,7 @@ Feature: Communication custom link
|
||||
Given I am on the "Course 1" "Course" page logged in as "teacher1"
|
||||
And "Chat to course participants" "button" should not be visible
|
||||
When I navigate to "Communication" in current page administration
|
||||
And I select "Custom link" from the "Communication service" singleselect
|
||||
And I select "Custom link" from the "Provider" singleselect
|
||||
And I set the following fields to these values:
|
||||
| communicationroomname | Test URL |
|
||||
| customlinkurl | #wwwroot#/communication/provider/customlink/tests/behat/fixtures/custom_link_test_page.php |
|
||||
@@ -65,14 +65,14 @@ Feature: Communication custom link
|
||||
And "Chat to course participants" "button" should be visible
|
||||
And I run all adhoc tasks
|
||||
And I navigate to "Communication" in current page administration
|
||||
And I select "None" from the "Communication service" singleselect
|
||||
And I select "None" from the "Provider" singleselect
|
||||
And I press "Save changes"
|
||||
And "Chat to course participants" "button" should not be visible
|
||||
And I run all adhoc tasks
|
||||
And I am on the "Course 1" course page
|
||||
And "Chat to course participants" "button" should not be visible
|
||||
And I navigate to "Communication" in current page administration
|
||||
And I select "Custom link" from the "Communication service" singleselect
|
||||
And I select "Custom link" from the "Provider" singleselect
|
||||
And I set the following fields to these values:
|
||||
| communicationroomname | Test URL |
|
||||
| customlinkurl | #wwwroot#/communication/provider/customlink/tests/behat/fixtures/custom_link_test_page.php |
|
||||
|
||||
@@ -113,4 +113,14 @@ class communication_feature_test extends \advanced_testcase {
|
||||
|
||||
return $communicationprocessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the selected provider is configured.
|
||||
*
|
||||
* @covers ::is_configured
|
||||
*/
|
||||
public function test_is_configured(): void {
|
||||
$communicationprocessor = $this->get_test_communication_processor();
|
||||
$this->assertTrue($communicationprocessor->get_form_provider()->is_configured());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ class communication_feature implements
|
||||
$this->homeserverurl = get_config('communication_matrix', 'matrixhomeserverurl');
|
||||
$this->webclienturl = get_config('communication_matrix', 'matrixelementurl');
|
||||
|
||||
if ($this->homeserverurl) {
|
||||
if ($processor::is_provider_available('communication_matrix')) {
|
||||
// Generate the API instance.
|
||||
$this->matrixapi = matrix_client::instance(
|
||||
serverurl: $this->homeserverurl,
|
||||
@@ -751,4 +751,25 @@ class communication_feature implements
|
||||
|
||||
return $powerlevel;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if matrix settings are configured
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_configured(): bool {
|
||||
// Matrix communication settings.
|
||||
$matrixhomeserverurl = get_config('communication_matrix', 'matrixhomeserverurl');
|
||||
$matrixaccesstoken = get_config('communication_matrix', 'matrixaccesstoken');
|
||||
$matrixelementurl = get_config('communication_matrix', 'matrixelementurl');
|
||||
|
||||
if (
|
||||
!empty($matrixhomeserverurl) &&
|
||||
!empty($matrixaccesstoken) &&
|
||||
(PHPUNIT_TEST || defined('BEHAT_SITE_RUNNING') || !empty($matrixelementurl))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ class matrix_user_manager {
|
||||
public static function get_formatted_matrix_home_server(): string {
|
||||
$homeserver = get_config('communication_matrix', 'matrixhomeserverurl');
|
||||
if ($homeserver === false) {
|
||||
throw new \moodle_exception('Unknown matrix home server url');
|
||||
throw new \moodle_exception('Unknown matrix homeserver url');
|
||||
}
|
||||
|
||||
$homeserver = parse_url($homeserver)['host'];
|
||||
@@ -150,8 +150,6 @@ class matrix_user_manager {
|
||||
'shortname' => 'matrixuserid',
|
||||
'name' => get_string('matrixuserid', 'communication_matrix'),
|
||||
'datatype' => 'text',
|
||||
'description' => get_string('matrixuserid_desc', 'communication_matrix'),
|
||||
'descriptionformat' => 1,
|
||||
'categoryid' => $categoryid,
|
||||
'forceunique' => 1,
|
||||
'visible' => 0,
|
||||
|
||||
@@ -24,17 +24,15 @@
|
||||
|
||||
$string['cachedef_serverversions'] = 'Matrix server version information for running servers';
|
||||
$string['matrixuserid'] = 'Matrix user ID';
|
||||
$string['matrixuserid_desc'] = 'The user ID to be used for Matrix';
|
||||
$string['matrixhomeserverurl'] = 'Home server URL';
|
||||
$string['matrixhomeserverurl_desc'] = 'The URL of the Synapse home server to connect to, for user and room creation.';
|
||||
$string['matrixhomeserverurl'] = 'Homeserver URL';
|
||||
$string['matrixhomeserverurl_desc'] = 'The URL of the Synapse homeserver to connect to, for user and room creation.';
|
||||
$string['matrixaccesstoken'] = 'Access token';
|
||||
$string['matrixaccesstoken_desc'] = 'Admin access token to authenticate against the Synapse Home server.';
|
||||
$string['matrixaccesstoken_desc'] = 'Access token for the account which will perform actions on the homeserver.';
|
||||
$string['matrixrefreshtoken'] = 'Refresh token';
|
||||
$string['matrixrefreshtoken_desc'] = 'Admin refresh token to associated with the access token.';
|
||||
$string['matrixelementurl'] = 'Element web URL';
|
||||
$string['matrixelementurl_desc'] = 'The URL to Element Web instance.';
|
||||
$string['matrixroomtopic'] = 'Room topic';
|
||||
$string['matrixroomtopic_help'] = 'A short description of what this room is for.';
|
||||
$string['matrix:moderator'] = 'Matrix moderator';
|
||||
$string['pluginname'] = 'Matrix';
|
||||
$string['privacy:metadata'] = 'Matrix communication plugin does not store any personal data.';
|
||||
$string['privacy:metadata'] = 'The Matrix communication plugin does not store any personal data.';
|
||||
|
||||
@@ -28,20 +28,14 @@ if ($hassiteconfig) {
|
||||
// Home server URL.
|
||||
$name = new lang_string('matrixhomeserverurl', 'communication_matrix');
|
||||
$desc = new lang_string('matrixhomeserverurl_desc', 'communication_matrix');
|
||||
$settings->add(new admin_setting_requiredtext('communication_matrix/matrixhomeserverurl', $name, $desc, ''));
|
||||
$settings->add(new admin_setting_configtext('communication_matrix/matrixhomeserverurl', $name, $desc, ''));
|
||||
|
||||
// Access token.
|
||||
$name = new lang_string('matrixaccesstoken', 'communication_matrix');
|
||||
$desc = new lang_string('matrixaccesstoken_desc', 'communication_matrix');
|
||||
$settings->add(new admin_setting_requiredpasswordunmask('communication_matrix/matrixaccesstoken', $name, $desc, ''));
|
||||
|
||||
// Refresh token.
|
||||
$name = new lang_string('matrixrefreshtoken', 'communication_matrix');
|
||||
$desc = new lang_string('matrixrefreshtoken_desc', 'communication_matrix');
|
||||
$settings->add(new admin_setting_requiredpasswordunmask('communication_matrix/matrixrefreshtoken', $name, $desc, ''));
|
||||
$settings->add(new admin_setting_configpasswordunmask('communication_matrix/matrixaccesstoken', $name, $desc, ''));
|
||||
|
||||
// Element web URL.
|
||||
$name = new lang_string('matrixelementurl', 'communication_matrix');
|
||||
$desc = new lang_string('matrixelementurl_desc', 'communication_matrix');
|
||||
$settings->add(new admin_setting_requiredtext('communication_matrix/matrixelementurl', $name, $desc, ''));
|
||||
$settings->add(new admin_setting_configtext('communication_matrix/matrixelementurl', $name, '', ''));
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ Feature: Display communication room status banner
|
||||
# Then I should see "Your Matrix room is ready!" in the "page-content" "region"
|
||||
# # This is a one time message per user.
|
||||
# When I reload the page
|
||||
# Then I should not see "Your Matrix room is ready!" in the "page-content" "region"
|
||||
# Then I should not see "Your Matrix room is ready." in the "page-content" "region"
|
||||
# # Not for students to see.
|
||||
# When I am on the "Test course" "Course" page logged in as "student1"
|
||||
# Then I should not see "Your Matrix room is ready!" in the "page-content" "region"
|
||||
# Then I should not see "Your Matrix room is ready." in the "page-content" "region"
|
||||
|
||||
Scenario: Enabling or disabling the matrix plugin hides the banner accordingly
|
||||
Given I am on the "Test course" "Course" page logged in as "teacher1"
|
||||
|
||||
@@ -52,7 +52,7 @@ class communication_feature_test extends \advanced_testcase {
|
||||
*
|
||||
* @covers ::create_chat_room
|
||||
*/
|
||||
public function test_create_chat_room() {
|
||||
public function test_create_chat_room(): void {
|
||||
// Set up the test data first.
|
||||
$communication = \core_communication\api::load_by_instance(
|
||||
component: 'communication_matrix',
|
||||
@@ -296,7 +296,7 @@ class communication_feature_test extends \advanced_testcase {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function avatar_provider(): array {
|
||||
public static function avatar_provider(): array {
|
||||
return [
|
||||
'Empty to avatar' => [
|
||||
null,
|
||||
@@ -539,4 +539,24 @@ class communication_feature_test extends \advanced_testcase {
|
||||
$communication->reload();
|
||||
return $communication;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the selected provider is configured.
|
||||
*
|
||||
* @covers ::is_configured
|
||||
*/
|
||||
public function test_is_configured(): void {
|
||||
$course = $this->get_course();
|
||||
$communicationprocessor = processor::load_by_instance(
|
||||
component: 'core_course',
|
||||
instancetype: 'coursecommunication',
|
||||
instanceid: $course->id
|
||||
);
|
||||
$this->assertTrue($communicationprocessor->get_room_provider()->is_configured());
|
||||
|
||||
// Unset communication_matrix settings.
|
||||
unset_config('matrixhomeserverurl', 'communication_matrix');
|
||||
unset_config('matrixaccesstoken', 'communication_matrix');
|
||||
$this->assertFalse($communicationprocessor->get_room_provider()->is_configured());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +217,9 @@ class matrix_user_manager_test extends \advanced_testcase {
|
||||
* Test creation of matrix user profile fields.
|
||||
*/
|
||||
public function test_create_matrix_user_profile_fields(): void {
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/user/profile/lib.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$matrixprofilefield = get_config('communication_matrix', 'matrixuserid_field');
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'communication_matrix';
|
||||
$plugin->version = 2023090600;
|
||||
$plugin->version = 2023092300;
|
||||
$plugin->requires = 2023011300;
|
||||
$plugin->maturity = MATURITY_ALPHA;
|
||||
|
||||
@@ -18,8 +18,11 @@ namespace core_communication;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/../provider/matrix/tests/matrix_test_helper_trait.php');
|
||||
require_once(__DIR__ . '/communication_test_helper_trait.php');
|
||||
|
||||
use \communication_matrix\matrix_test_helper_trait;
|
||||
|
||||
/**
|
||||
* Class api_test to test the communication public api and its associated methods.
|
||||
*
|
||||
@@ -30,12 +33,15 @@ require_once(__DIR__ . '/communication_test_helper_trait.php');
|
||||
* @covers \core_communication\api
|
||||
*/
|
||||
class api_test extends \advanced_testcase {
|
||||
|
||||
use matrix_test_helper_trait;
|
||||
use communication_test_helper_trait;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->resetAfterTest();
|
||||
$this->setup_communication_configs();
|
||||
$this->initialise_mock_server();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,7 +33,8 @@ Feature: Access the communication configuration page
|
||||
|
||||
@javascript
|
||||
Scenario: The communication form fields toggle dynamically when valid provider is set
|
||||
Given I am on the "Test course" "Course" page logged in as "teacher1"
|
||||
Given a Matrix mock server is configured
|
||||
And I am on the "Test course" "Course" page logged in as "teacher1"
|
||||
When I navigate to "Communication" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| selectedcommunication | communication_matrix |
|
||||
|
||||
@@ -18,8 +18,11 @@ namespace core_communication;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/../provider/matrix/tests/matrix_test_helper_trait.php');
|
||||
require_once(__DIR__ . '/communication_test_helper_trait.php');
|
||||
|
||||
use \communication_matrix\matrix_test_helper_trait;
|
||||
|
||||
/**
|
||||
* Class processor_test to test the communication internal api and its associated methods.
|
||||
*
|
||||
@@ -30,8 +33,17 @@ require_once(__DIR__ . '/communication_test_helper_trait.php');
|
||||
* @coversDefaultClass \core_communication\processor
|
||||
*/
|
||||
class processor_test extends \advanced_testcase {
|
||||
|
||||
use matrix_test_helper_trait;
|
||||
use communication_test_helper_trait;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->resetAfterTest();
|
||||
$this->setup_communication_configs();
|
||||
$this->initialise_mock_server();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create instance.
|
||||
*
|
||||
@@ -417,18 +429,18 @@ class processor_test extends \advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the provider is enabled or disabled.
|
||||
* Test if the provider is enabled and configured, or disabled.
|
||||
*
|
||||
* @covers ::is_provider_enabled
|
||||
* @covers ::is_provider_available
|
||||
*/
|
||||
public function test_is_provider_enabled(): void {
|
||||
public function test_is_provider_available(): void {
|
||||
$this->resetAfterTest();
|
||||
$communicationprovider = 'communication_matrix';
|
||||
$this->assertTrue(processor::is_provider_enabled($communicationprovider));
|
||||
$this->assertTrue(processor::is_provider_available($communicationprovider));
|
||||
|
||||
// Now test is disabling the plugin returns false.
|
||||
set_config('disabled', 1, $communicationprovider);
|
||||
$this->assertFalse(processor::is_provider_enabled($communicationprovider));
|
||||
$this->assertFalse(processor::is_provider_available($communicationprovider));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,7 +82,7 @@ class contenttype extends \core_contentbank\contenttype {
|
||||
public function get_icon(\core_contentbank\content $content): string {
|
||||
global $OUTPUT, $DB;
|
||||
|
||||
$iconurl = $OUTPUT->image_url('f/h5p-64', 'moodle')->out(false);
|
||||
$iconurl = $OUTPUT->image_url('f/h5p')->out(false);
|
||||
$file = $content->get_file();
|
||||
if (!empty($file)) {
|
||||
$h5p = \core_h5p\api::get_content_from_pathnamehash($file->get_pathnamehash());
|
||||
|
||||
@@ -383,7 +383,7 @@ abstract class base {
|
||||
* This method ensures that 3rd party course format plugins that still use 'numsections' continue to
|
||||
* work but at the same time we no longer expect formats to have 'numsections' property.
|
||||
*
|
||||
* @return int
|
||||
* @return int The last section number, or -1 if sections are entirely missing
|
||||
*/
|
||||
public function get_last_section_number() {
|
||||
$course = $this->get_course();
|
||||
@@ -392,6 +392,12 @@ abstract class base {
|
||||
}
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$sections = $modinfo->get_section_info_all();
|
||||
|
||||
// Sections seem to be missing entirely. Avoid subsequent errors and return early.
|
||||
if (count($sections) === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int)max(array_keys($sections));
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
{{#editing}}
|
||||
{{#editurl}}
|
||||
<div class="editavailability border-top mt-2 py-2">
|
||||
<a href="{{editurl}}" class="px-2 py-1">{{#pix}} i/edit, core {{/pix}}{{#str}}editsettings, availability{{/str}}</a>
|
||||
<a href="{{editurl}}" class="btn btn-sm py-0">{{#pix}} i/edit, core {{/pix}}{{#str}}editsettings, availability{{/str}}</a>
|
||||
</div>
|
||||
{{/editurl}}
|
||||
{{/editing}}
|
||||
|
||||
@@ -14,14 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Course related unit tests
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2014 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \core_courseformat\base
|
||||
*/
|
||||
class base_test extends advanced_testcase {
|
||||
|
||||
@@ -368,6 +367,32 @@ class base_test extends advanced_testcase {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that retrieving last section number for a course
|
||||
*
|
||||
* @covers ::get_last_section_number
|
||||
*/
|
||||
public function test_get_last_section_number(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Course with two additional sections.
|
||||
$courseone = $this->getDataGenerator()->create_course(['numsections' => 2]);
|
||||
$this->assertEquals(2, course_get_format($courseone)->get_last_section_number());
|
||||
|
||||
// Course without additional sections, section zero is the "default" section that always exists.
|
||||
$coursetwo = $this->getDataGenerator()->create_course(['numsections' => 0]);
|
||||
$this->assertEquals(0, course_get_format($coursetwo)->get_last_section_number());
|
||||
|
||||
// Course without additional sections, manually remove section zero, as "course_delete_section" prevents that. This
|
||||
// simulates course data integrity issues that previously triggered errors.
|
||||
$coursethree = $this->getDataGenerator()->create_course(['numsections' => 0]);
|
||||
$DB->delete_records('course_sections', ['course' => $coursethree->id, 'section' => 0]);
|
||||
|
||||
$this->assertEquals(-1, course_get_format($coursethree)->get_last_section_number());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for the default delete format data behaviour.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
This files describes API changes for course formats
|
||||
|
||||
Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
|
||||
Overview of this plugin type at https://moodledev.io/docs/apis/plugintypes/format
|
||||
|
||||
=== 4.3 ===
|
||||
* New core_courseformat\output\activitybadge class that can be extended by any module to display content near the activity name.
|
||||
|
||||
@@ -406,14 +406,25 @@ class format_weeks extends core_courseformat\base {
|
||||
} else {
|
||||
$sectionnum = $section;
|
||||
}
|
||||
$oneweekseconds = 604800;
|
||||
// Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight
|
||||
// savings and the date changes.
|
||||
$startdate = $startdate + 7200;
|
||||
|
||||
// Create a DateTime object for the start date.
|
||||
$startdateobj = new DateTime("@$startdate");
|
||||
|
||||
// Calculate the interval for one week.
|
||||
$oneweekinterval = new DateInterval('P7D');
|
||||
|
||||
// Calculate the interval for the specified number of sections.
|
||||
for ($i = 1; $i < $sectionnum; $i++) {
|
||||
$startdateobj->add($oneweekinterval);
|
||||
}
|
||||
|
||||
// Calculate the end date.
|
||||
$enddateobj = clone $startdateobj;
|
||||
$enddateobj->add($oneweekinterval);
|
||||
|
||||
$dates = new stdClass();
|
||||
$dates->start = $startdate + ($oneweekseconds * ($sectionnum - 1));
|
||||
$dates->end = $dates->start + $oneweekseconds;
|
||||
$dates->start = $startdateobj->getTimestamp();
|
||||
$dates->end = $enddateobj->getTimestamp();
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
@@ -221,8 +221,8 @@ class format_weeks_test extends \advanced_testcase {
|
||||
$courseform = new \testable_course_edit_form(null, $args);
|
||||
$courseform->definition_after_data();
|
||||
|
||||
// format_weeks::get_section_dates is adding 2h to avoid DST problems, we need to replicate it here.
|
||||
$enddate = $params['startdate'] + (WEEKSECS * $params['numsections']) + 7200;
|
||||
// Calculate the expected end date.
|
||||
$enddate = $params['startdate'] + (WEEKSECS * $params['numsections']);
|
||||
|
||||
$weeksformat = course_get_format($course->id);
|
||||
$this->assertEquals($enddate, $weeksformat->get_default_course_enddate($courseform->get_quick_form()));
|
||||
|
||||
@@ -97,8 +97,6 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
*
|
||||
* @deprecated since 2.5
|
||||
*
|
||||
* Please see http://docs.moodle.org/dev/Courses_lists_upgrade_to_2.5
|
||||
*
|
||||
* @param array $ignored argument ignored
|
||||
* @return string
|
||||
*/
|
||||
@@ -112,8 +110,6 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
*
|
||||
* @deprecated since 2.5
|
||||
*
|
||||
* Please see http://docs.moodle.org/dev/Courses_lists_upgrade_to_2.5
|
||||
*
|
||||
* @param array $category
|
||||
* @param int $depth
|
||||
* @return string
|
||||
|
||||
@@ -99,7 +99,6 @@ Feature: Toggle activities visibility from the course page
|
||||
Scenario: Activities can be made available but not visible on a course page
|
||||
Given the following config values are set as admin:
|
||||
| allowstealth | 1 |
|
||||
And I change window size to "large"
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
When I open "Test assignment name" actions menu
|
||||
|
||||
@@ -20,9 +20,9 @@ Feature: Course communication
|
||||
| Course short name | C2 |
|
||||
And I press "Save and display"
|
||||
When I navigate to "Communication" in current page administration
|
||||
Then the field "Communication service" matches value "None"
|
||||
Then the field "Provider" matches value "None"
|
||||
|
||||
Scenario: I should have communication disabled by default for existing courses
|
||||
Given I am on "Course 1" course homepage
|
||||
When I navigate to "Communication" in current page administration
|
||||
Then the field "Communication service" matches value "None"
|
||||
Then the field "Provider" matches value "None"
|
||||
|
||||
@@ -1042,9 +1042,7 @@ class enrol_database_plugin extends enrol_plugin {
|
||||
$rs->Close();
|
||||
|
||||
} else {
|
||||
$fields_obj = $rs->FetchObj();
|
||||
$columns = array_keys((array)$fields_obj);
|
||||
|
||||
$columns = array_keys($rs->fetchRow());
|
||||
echo $OUTPUT->notification('External enrolment table contains following columns:<br />'.implode(', ', $columns), 'notifysuccess');
|
||||
$rs->Close();
|
||||
}
|
||||
@@ -1061,9 +1059,7 @@ class enrol_database_plugin extends enrol_plugin {
|
||||
$rs->Close();
|
||||
|
||||
} else {
|
||||
$fields_obj = $rs->FetchObj();
|
||||
$columns = array_keys((array)$fields_obj);
|
||||
|
||||
$columns = array_keys($rs->fetchRow());
|
||||
echo $OUTPUT->notification('External course table contains following columns:<br />'.implode(', ', $columns), 'notifysuccess');
|
||||
$rs->Close();
|
||||
}
|
||||
|
||||
@@ -176,7 +176,9 @@ class filter_test extends \basic_testcase {
|
||||
'<p>text www.moodle.org</p> text' => '<p>text <a href="http://www.moodle.org" class="_blanktarget">www.moodle.org</a></p> text',
|
||||
// Some more urls.
|
||||
'<link rel="search" type="application/opensearchdescription+xml" href="/osd.jsp" title="Peer review - Moodle Tracker"/>' => '<link rel="search" type="application/opensearchdescription+xml" href="/osd.jsp" title="Peer review - Moodle Tracker"/>',
|
||||
'<a href="https://docs.moodle.org/dev/Main_Page"></a><span>www.google.com</span><span class="placeholder"></span>' => '<a href="https://docs.moodle.org/dev/Main_Page"></a><span><a href="http://www.google.com" class="_blanktarget">www.google.com</a></span><span class="placeholder"></span>',
|
||||
'<a href="https://moodledev.io"></a><span>www.google.com</span><span class="placeholder"></span>' =>
|
||||
'<a href="https://moodledev.io"></a><span><a href="http://www.google.com" class="_blanktarget">www.google.com</a>' .
|
||||
'</span><span class="placeholder"></span>',
|
||||
'http://nolandforzombies.com <a href="zombiesFTW.com">Zombies FTW</a> http://aliens.org' => '<a href="http://nolandforzombies.com" class="_blanktarget">http://nolandforzombies.com</a> <a href="zombiesFTW.com">Zombies FTW</a> <a href="http://aliens.org" class="_blanktarget">http://aliens.org</a>',
|
||||
// Test 'nolink' class.
|
||||
'URL: <span class="nolink">http://moodle.org</span>' => 'URL: <span class="nolink">http://moodle.org</span>',
|
||||
|
||||
@@ -604,12 +604,6 @@ class add_category extends dynamic_form {
|
||||
$local = $this->get_gradecategory();
|
||||
$gradecategory = $local['gradecategory'];
|
||||
|
||||
// GRADE ITEM.
|
||||
// Grade item data saved with prefix "grade_item_".
|
||||
$data->grade_item_gradepass = $local['categoryitem']->grade_item_gradepass;
|
||||
$data->grade_item_grademax = $local['categoryitem']->grade_item_grademax;
|
||||
$data->grade_item_grademin = $local['categoryitem']->grade_item_grademin;
|
||||
|
||||
grade_edit_tree::update_gradecategory($gradecategory, $data);
|
||||
|
||||
return [
|
||||
|
||||
@@ -133,7 +133,8 @@ if ($id) {
|
||||
$mform = new edit_category_form(null, array('current'=>$category, 'gpr'=>$gpr));
|
||||
|
||||
$simpleform = new add_category(null, ['category' => $grade_category->id, 'courseid' => $courseid, 'gpr' => $gpr]);
|
||||
if ($simpledata = $simpleform->get_data()) {
|
||||
// Data has been carried over from the dynamic form.
|
||||
if ($simpledata = $simpleform->get_submitted_data()) {
|
||||
$mform->set_data($simpledata);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,8 @@ $item->cancontrolvisibility = $grade_item->can_control_visibility();
|
||||
$mform = new edit_item_form(null, array('current'=>$item, 'gpr'=>$gpr));
|
||||
|
||||
$simpleform = new add_item(null, ['itemid' => $grade_item->id, 'courseid' => $courseid, 'gpr' => $gpr]);
|
||||
if ($simpledata = $simpleform->get_data()) {
|
||||
// Data has been carried over from the dynamic form.
|
||||
if ($simpledata = $simpleform->get_submitted_data()) {
|
||||
$mform->set_data($simpledata);
|
||||
}
|
||||
|
||||
|
||||
@@ -276,7 +276,7 @@ class grade_edit_tree {
|
||||
}
|
||||
$categoryrow->attributes['data-aggregation'] = $category->aggregation;
|
||||
$categoryrow->attributes['data-grademax'] = $category->grade_item->grademax;
|
||||
$categoryrow->attributes['data-aggregationcoef'] = $category->grade_item->aggregationcoef;
|
||||
$categoryrow->attributes['data-aggregationcoef'] = floatval($category->grade_item->aggregationcoef);
|
||||
$categoryrow->attributes['data-itemid'] = $category->grade_item->id;
|
||||
$categoryrow->attributes['data-hidden'] = 'false';
|
||||
foreach ($rowclasses as $class) {
|
||||
@@ -350,7 +350,7 @@ class grade_edit_tree {
|
||||
} else {
|
||||
$gradeitemrow->attributes['data-parent-category'] = $parent_eid;
|
||||
$gradeitemrow->attributes['data-grademax'] = $object->grademax;
|
||||
$gradeitemrow->attributes['data-aggregationcoef'] = $object->aggregationcoef;
|
||||
$gradeitemrow->attributes['data-aggregationcoef'] = floatval($object->aggregationcoef);
|
||||
}
|
||||
foreach ($rowclasses as $class) {
|
||||
$gradeitemrow->attributes['class'] .= ' ' . $class;
|
||||
|
||||
@@ -114,7 +114,8 @@ if (empty($parent_category)) {
|
||||
$mform->set_data($item);
|
||||
|
||||
$simpleform = new add_outcome(null, ['itemid' => $grade_item->id, 'courseid' => $courseid, 'gpr' => $gpr]);
|
||||
if ($simpledata = $simpleform->get_data()) {
|
||||
// Data has been carried over from the dynamic form.
|
||||
if ($simpledata = $simpleform->get_submitted_data()) {
|
||||
$mform->set_data($simpledata);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
define("gradereport_grader/stickycolspan",["exports","jquery"],(function(_exports,_jquery){var obj;
|
||||
define("gradereport_grader/stickycolspan",["exports","jquery","core/sticky-footer"],(function(_exports,_jquery,_stickyFooter){var obj;
|
||||
/**
|
||||
* Javascript module for fixing the position of sticky headers with multiple colspans
|
||||
*
|
||||
* @module gradereport_grader/stickycolspan
|
||||
* @copyright 2022 Bas Brands <bas@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const SELECTORS_GRADEPARENT=".gradeparent",SELECTORS_STUDENTHEADER="#studentheader",SELECTORS_TABLEHEADER="th.header",SELECTORS_BEHAT="body.behat-site",SELECTORS_USERDROPDOWN=".userrow th .dropdown",SELECTORS_AVERAGEROWHEADER=".lastrow th";_exports.init=()=>{(0,_jquery.default)(SELECTORS_USERDROPDOWN).on("show.bs.dropdown hide.bs.dropdown",(e=>{e.target.closest(SELECTORS_TABLEHEADER).classList.toggle("actions-menu-active")}));if(new IntersectionObserver((_ref=>{let[e]=_ref;return e.target.closest("tr").classList.toggle("pinned",e.intersectionRatio<1)}),{threshold:[1]}).observe(document.querySelector(SELECTORS_AVERAGEROWHEADER)),!document.querySelector(SELECTORS_BEHAT)){const grader=document.querySelector(SELECTORS_GRADEPARENT),tableHeaders=grader.querySelectorAll(SELECTORS_TABLEHEADER),studentHeader=grader.querySelector(SELECTORS_STUDENTHEADER),leftOffset=getComputedStyle(studentHeader).getPropertyValue("left"),rightOffset=getComputedStyle(studentHeader).getPropertyValue("right");tableHeaders.forEach((tableHeader=>{if(tableHeader.colSpan>1){const addOffset=tableHeader.offsetWidth-studentHeader.offsetWidth;window.right_to_left()?tableHeader.style.right="calc("+rightOffset+" - "+addOffset+"px )":tableHeader.style.left="calc("+leftOffset+" - "+addOffset+"px )"}}))}}}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const SELECTORS_GRADEPARENT=".gradeparent",SELECTORS_STUDENTHEADER="#studentheader",SELECTORS_TABLEHEADER="th.header",SELECTORS_BEHAT="body.behat-site",SELECTORS_USERDROPDOWN=".userrow th .dropdown",SELECTORS_AVERAGEROWHEADER=".lastrow th";_exports.init=()=>{if((0,_jquery.default)(SELECTORS_USERDROPDOWN).on("show.bs.dropdown hide.bs.dropdown",(e=>{e.target.closest(SELECTORS_TABLEHEADER).classList.toggle("actions-menu-active")})),defineAverageRowIntersectionObserver(!0),document.addEventListener(_stickyFooter.eventTypes.stickyFooterStateChanged,(e=>{defineAverageRowIntersectionObserver(e.detail.enabled)})),!document.querySelector(SELECTORS_BEHAT)){const grader=document.querySelector(SELECTORS_GRADEPARENT),tableHeaders=grader.querySelectorAll(SELECTORS_TABLEHEADER),studentHeader=grader.querySelector(SELECTORS_STUDENTHEADER),leftOffset=getComputedStyle(studentHeader).getPropertyValue("left"),rightOffset=getComputedStyle(studentHeader).getPropertyValue("right");tableHeaders.forEach((tableHeader=>{if(tableHeader.colSpan>1){const addOffset=tableHeader.offsetWidth-studentHeader.offsetWidth;window.right_to_left()?tableHeader.style.right="calc("+rightOffset+" - "+addOffset+"px )":tableHeader.style.left="calc("+leftOffset+" - "+addOffset+"px )"}}))}};const defineAverageRowIntersectionObserver=stickyFooterEnabled=>{const stickyFooterHeight=stickyFooterEnabled?document.querySelector(_stickyFooter.SELECTORS.STICKYFOOTER).offsetHeight:null;new IntersectionObserver((_ref=>{let[e]=_ref;return e.target.closest("tr").classList.toggle("pinned",e.intersectionRatio<1)}),{rootMargin:stickyFooterHeight?"0px 0px -".concat(stickyFooterHeight,"px 0px"):"0px",threshold:[1]}).observe(document.querySelector(SELECTORS_AVERAGEROWHEADER))}}));
|
||||
|
||||
//# sourceMappingURL=stickycolspan.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import {SELECTORS as stickyFooterSelectors, eventTypes as stickyFooterEvents} from 'core/sticky-footer';
|
||||
|
||||
const SELECTORS = {
|
||||
GRADEPARENT: '.gradeparent',
|
||||
@@ -43,14 +44,14 @@ export const init = () => {
|
||||
// The closest heading element has sticky positioning which affects the stacking context in this case.
|
||||
e.target.closest(SELECTORS.TABLEHEADER).classList.toggle('actions-menu-active');
|
||||
});
|
||||
// Register an observer that will bump up the z-index value of the average overall row when it's pinned to prevent
|
||||
// the row being cut-off by the user column cells or other components within the report table that have higher
|
||||
// z-index values.
|
||||
const observer = new IntersectionObserver(
|
||||
([e]) => e.target.closest('tr').classList.toggle('pinned', e.intersectionRatio < 1),
|
||||
{threshold: [1]}
|
||||
);
|
||||
observer.observe(document.querySelector(SELECTORS.AVERAGEROWHEADER));
|
||||
|
||||
defineAverageRowIntersectionObserver(true);
|
||||
// Add an event listener to the sticky footer toggled event to re-define the average row intersection observer
|
||||
// accordingly. This is needed as on narrow screens when scrolling vertically the sticky footer is enabled and
|
||||
// disabled dynamically.
|
||||
document.addEventListener(stickyFooterEvents.stickyFooterStateChanged, (e) => {
|
||||
defineAverageRowIntersectionObserver(e.detail.enabled);
|
||||
});
|
||||
|
||||
if (!document.querySelector(SELECTORS.BEHAT)) {
|
||||
const grader = document.querySelector(SELECTORS.GRADEPARENT);
|
||||
@@ -71,3 +72,24 @@ export const init = () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Define the intersection observer that will make sure that the average overall row is properly pinned.
|
||||
*
|
||||
* @param {boolean} stickyFooterEnabled Whether the page shows a sticky footer or not.
|
||||
*/
|
||||
const defineAverageRowIntersectionObserver = (stickyFooterEnabled) => {
|
||||
const stickyFooterHeight = stickyFooterEnabled ? document.querySelector(stickyFooterSelectors.STICKYFOOTER).offsetHeight : null;
|
||||
// Register an observer that will bump up the z-index value of the average overall row when it's pinned to prevent
|
||||
// the row being cut-off by the user column cells or other components within the report table that have higher
|
||||
// z-index values. If the page has a sticky footer, we need to make sure that the bottom root margin of the observer
|
||||
// subtracts the height of the sticky footer to prevent the row being cut-off by the footer.
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
([e]) => e.target.closest('tr').classList.toggle('pinned', e.intersectionRatio < 1),
|
||||
{
|
||||
rootMargin: stickyFooterHeight ? `0px 0px -${stickyFooterHeight}px 0px` : "0px",
|
||||
threshold: [1]
|
||||
}
|
||||
);
|
||||
intersectionObserver.observe(document.querySelector(SELECTORS.AVERAGEROWHEADER));
|
||||
};
|
||||
|
||||
@@ -269,3 +269,9 @@
|
||||
width: 300px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
.path-grade-report-grader .gradeparent table {
|
||||
padding-right: 6em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ Feature: We can change what we are viewing on the grader report
|
||||
@javascript
|
||||
Scenario: View and minimise the grader report containing hidden activities
|
||||
When I am on "Course 1" course homepage with editing mode on
|
||||
And I change window size to "large"
|
||||
And I open "Test assignment name 2" actions menu
|
||||
And I choose "Availability > Hide on course page" in the open action menu
|
||||
And I am on "Course 1" course homepage with editing mode off
|
||||
|
||||
@@ -445,7 +445,7 @@ class user extends grade_report {
|
||||
$depth = $element['depth'];
|
||||
$gradeobject = $element['object'];
|
||||
$eid = $gradeobject->id;
|
||||
$element['userid'] = $this->user->id;
|
||||
$element['userid'] = $userid = $this->user->id;
|
||||
$fullname = $this->gtree->get_element_header($element, true, false, true, false, true);
|
||||
$data = [];
|
||||
$gradeitemdata = [];
|
||||
@@ -587,7 +587,7 @@ class user extends grade_report {
|
||||
if ($this->showweight) {
|
||||
$data['weight']['class'] = $class;
|
||||
$data['weight']['content'] = '-';
|
||||
$data['weight']['headers'] = "$headercat $headerrow weight";
|
||||
$data['weight']['headers'] = "$headercat $headerrow weight$userid";
|
||||
// Has a weight assigned, might be extra credit.
|
||||
|
||||
// This obliterates the weight because it provides a more informative description.
|
||||
@@ -690,7 +690,7 @@ class user extends grade_report {
|
||||
$gradegrade->grade_item, true) . $gradestatus;
|
||||
$gradeitemdata['graderaw'] = $gradeval;
|
||||
}
|
||||
$data['grade']['headers'] = "$headercat $headerrow grade";
|
||||
$data['grade']['headers'] = "$headercat $headerrow grade$userid";
|
||||
$gradeitemdata['gradeformatted'] = $data['grade']['content'];
|
||||
}
|
||||
|
||||
@@ -701,7 +701,7 @@ class user extends grade_report {
|
||||
GRADE_DISPLAY_TYPE_REAL,
|
||||
$this->rangedecimals
|
||||
);
|
||||
$data['range']['headers'] = "$headercat $headerrow range";
|
||||
$data['range']['headers'] = "$headercat $headerrow range$userid";
|
||||
|
||||
$gradeitemdata['rangeformatted'] = $data['range']['content'];
|
||||
$gradeitemdata['grademin'] = $gradegrade->grade_item->grademin;
|
||||
@@ -733,7 +733,7 @@ class user extends grade_report {
|
||||
GRADE_DISPLAY_TYPE_PERCENTAGE
|
||||
);
|
||||
}
|
||||
$data['percentage']['headers'] = "$headercat $headerrow percentage";
|
||||
$data['percentage']['headers'] = "$headercat $headerrow percentage$userid";
|
||||
$gradeitemdata['percentageformatted'] = $data['percentage']['content'];
|
||||
}
|
||||
|
||||
@@ -763,7 +763,7 @@ class user extends grade_report {
|
||||
GRADE_DISPLAY_TYPE_LETTER
|
||||
);
|
||||
}
|
||||
$data['lettergrade']['headers'] = "$headercat $headerrow lettergrade";
|
||||
$data['lettergrade']['headers'] = "$headercat $headerrow lettergrade$userid";
|
||||
$gradeitemdata['lettergradeformatted'] = $data['lettergrade']['content'];
|
||||
}
|
||||
|
||||
@@ -797,7 +797,7 @@ class user extends grade_report {
|
||||
$gradeitemdata['rank'] = $rank;
|
||||
$gradeitemdata['numusers'] = $numusers;
|
||||
}
|
||||
$data['rank']['headers'] = "$headercat $headerrow rank";
|
||||
$data['rank']['headers'] = "$headercat $headerrow rank$userid";
|
||||
}
|
||||
|
||||
// Average.
|
||||
@@ -811,7 +811,7 @@ class user extends grade_report {
|
||||
} else {
|
||||
$data['average']['content'] = '-';
|
||||
}
|
||||
$data['average']['headers'] = "$headercat $headerrow average";
|
||||
$data['average']['headers'] = "$headercat $headerrow average$userid";
|
||||
}
|
||||
|
||||
// Feedback.
|
||||
@@ -838,13 +838,13 @@ class user extends grade_report {
|
||||
['context' => $gradegrade->get_context()]);
|
||||
$gradeitemdata['feedback'] = $gradegrade->feedback;
|
||||
}
|
||||
$data['feedback']['headers'] = "$headercat $headerrow feedback";
|
||||
$data['feedback']['headers'] = "$headercat $headerrow feedback$userid";
|
||||
}
|
||||
// Contribution to the course total column.
|
||||
if ($this->showcontributiontocoursetotal) {
|
||||
$data['contributiontocoursetotal']['class'] = $class;
|
||||
$data['contributiontocoursetotal']['content'] = '-';
|
||||
$data['contributiontocoursetotal']['headers'] = "$headercat $headerrow contributiontocoursetotal";
|
||||
$data['contributiontocoursetotal']['headers'] = "$headercat $headerrow contributiontocoursetotal$userid";
|
||||
|
||||
}
|
||||
$this->gradeitemsdata[] = $gradeitemdata;
|
||||
@@ -1026,9 +1026,10 @@ class user extends grade_report {
|
||||
];
|
||||
|
||||
// Set the table headings.
|
||||
$userid = $this->user->id;
|
||||
foreach ($this->tableheaders as $index => $heading) {
|
||||
$headingcell = new \html_table_cell($heading);
|
||||
$headingcell->attributes['id'] = $this->tablecolumns[$index];
|
||||
$headingcell->attributes['id'] = $this->tablecolumns[$index] . $userid;
|
||||
$headingcell->attributes['class'] = "header column-{$this->tablecolumns[$index]}";
|
||||
if ($index == 0) {
|
||||
$headingcell->colspan = $this->maxdepth;
|
||||
@@ -1054,6 +1055,12 @@ class user extends grade_report {
|
||||
if (!is_null($content)) {
|
||||
$rowcell = new \html_table_cell($content);
|
||||
|
||||
// Grade item names and cateogry names are referenced in the `headers` attribute of table cells.
|
||||
// These table cells should be set to <th> tags.
|
||||
if ($tablecolumn === 'itemname') {
|
||||
$rowcell->header = true;
|
||||
}
|
||||
|
||||
if (isset($rowdata[$tablecolumn]['class'])) {
|
||||
$rowcell->attributes['class'] = $rowdata[$tablecolumn]['class'];
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@
|
||||
.grade-report-user .user-grade .baggt,
|
||||
.grade-report-user .user-grade .baggb {
|
||||
font-weight: bold;
|
||||
background-color: #f8f9fa;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.path-grade-report-user .user-report-container,
|
||||
@@ -99,23 +101,30 @@
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.path-grade-report-user .user-grade td.category,
|
||||
.grade-report-user .user-grade td.category {
|
||||
.path-grade-report-user .user-grade th.column-itemname:not(.header,.category,.baggt,.baggb),
|
||||
.grade-report-user .user-grade th.column-itemname:not(.header,.category,.baggt,.baggb) {
|
||||
background-color: white;
|
||||
font-weight: normal;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.path-grade-report-user .user-grade th.category,
|
||||
.grade-report-user .user-grade th.category {
|
||||
background-color: white;
|
||||
border: 1px solid #dee2e6;
|
||||
padding-left: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.path-grade-report-user .user-grade td.category a[aria-expanded="true"] .expanded,
|
||||
.path-grade-report-user .user-grade td.category a[aria-expanded="false"] .collapsed,
|
||||
.grade-report-user .user-grade td.category a[aria-expanded="true"] .expanded,
|
||||
.grade-report-user .user-grade td.category a[aria-expanded="false"] .collapsed {
|
||||
.path-grade-report-user .user-grade th.category a[aria-expanded="true"] .expanded,
|
||||
.path-grade-report-user .user-grade th.category a[aria-expanded="false"] .collapsed,
|
||||
.grade-report-user .user-grade th.category a[aria-expanded="true"] .expanded,
|
||||
.grade-report-user .user-grade th.category a[aria-expanded="false"] .collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.path-grade-report-user .user-grade td.category a.toggle-category,
|
||||
.grade-report-user .user-grade td.category a.toggle-category {
|
||||
.path-grade-report-user .user-grade th.category a.toggle-category,
|
||||
.grade-report-user .user-grade th.category a.toggle-category {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
font-size: 12px;
|
||||
@@ -123,8 +132,8 @@
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.path-grade-report-user .user-grade td.category a.toggle-category i,
|
||||
.grade-report-user .user-grade td.category a.toggle-category i {
|
||||
.path-grade-report-user .user-grade th.category a.toggle-category i,
|
||||
.grade-report-user .user-grade th.category a.toggle-category i {
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
@@ -173,14 +182,14 @@
|
||||
}
|
||||
|
||||
@media print {
|
||||
.path-grade-report-user .user-grade td.category,
|
||||
.grade-report-user .user-grade td.category {
|
||||
.path-grade-report-user .user-grade th.category,
|
||||
.grade-report-user .user-grade th.category {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.path-grade-report-user .user-grade td.category a.toggle-category,
|
||||
.grade-report-user .user-grade td.category a.toggle-category
|
||||
.path-grade-report-user .user-grade th.category a.toggle-category,
|
||||
.grade-report-user .user-grade th.category a.toggle-category
|
||||
.path-grade-report-user #page-footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -253,7 +253,8 @@ Feature: Teachers can perform bulk actions on grade items and categories in the
|
||||
And I press tab
|
||||
And the focused element is "Move" "button" in the "Move items" "dialogue"
|
||||
When I press the enter key
|
||||
And I wait to be redirected
|
||||
And I wait until the page is ready
|
||||
And I wait "2" seconds
|
||||
And I press tab key in "region-main" "region"
|
||||
# Confirm that 'Grade item 1' and 'Grade item 2' have been moved to 'Category 3'
|
||||
And I click on grade item menu "Grade item 1" of type "gradeitem" on "setup" page
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@core @core_grades @javascript
|
||||
@core @core_grades @javascript @testtt
|
||||
Feature: We carry over data from modal to advanced grade item settings
|
||||
In order to setup grade items quickly
|
||||
As an teacher
|
||||
@@ -103,6 +103,25 @@ Feature: We carry over data from modal to advanced grade item settings
|
||||
| grade_item_aggregationcoef2 | 100 |
|
||||
| Parent category | Some cool grade category |
|
||||
And I press "Cancel"
|
||||
And I choose the "Add category" item in the "Add" action menu
|
||||
# Confirm that the form values are carried over even if some mandatory fields are missing (e.g. Category name).
|
||||
And I set the following fields to these values:
|
||||
| Aggregation | Mean of grades |
|
||||
| Minimum grade | 1 |
|
||||
| Maximum grade | 99 |
|
||||
| Weight adjusted | 1 |
|
||||
| grade_item_aggregationcoef2 | 100 |
|
||||
| Parent category | Some cool grade category |
|
||||
And I click on "Show more..." "link" in the ".modal-dialog" "css_element"
|
||||
And the following fields match these values:
|
||||
| Category name | |
|
||||
| Aggregation | Mean of grades |
|
||||
| Minimum grade | 1 |
|
||||
| Maximum grade | 99 |
|
||||
| Weight adjusted | 1 |
|
||||
| grade_item_aggregationcoef2 | 100 |
|
||||
| Parent category | Some cool grade category |
|
||||
And I press "Cancel"
|
||||
And I wait until the page is ready
|
||||
And I choose the "Add outcome item" item in the "Add" action menu
|
||||
And I set the following fields to these values:
|
||||
|
||||
@@ -86,6 +86,6 @@ $string['welcomep20'] = 'Vous voyez cette page, car vous avez installé Moodle c
|
||||
$string['welcomep30'] = 'Cette version de <b>{$a->installername}</b> comprend des logiciels qui créent un environnement dans lequel <b>Moodle</b> va fonctionner, à savoir :';
|
||||
$string['welcomep40'] = 'Ce paquet contient également <b>Moodle {$a->moodlerelease} ({$a->moodleversion})</b>.';
|
||||
$string['welcomep50'] = 'L\'utilisation de tous les logiciels de ce paquetage est soumis à l\'acceptation de leurs licences respectives. Le paquetage complet <strong>{$a->installername}</strong> est un <a href="https://www.opensource.org/docs/definition_plain.html">logiciel libre</a>. Il est distribué sous licence <a href="https://www.gnu.org/copyleft/gpl.html">GPL</a>.';
|
||||
$string['welcomep60'] = 'Les pages suivantes vous aideront pas à pas à configurer et mettre en place <strong>Moodle</strong> sur votre ordinateur. Il vous sera possible d\'accepter les réglages par défaut ou, facultativement, de les adapter à vos propres besoins.';
|
||||
$string['welcomep60'] = 'Les pages suivantes vous aideront pas à pas à configurer et installer <strong>Moodle</strong> sur votre ordinateur. Il vous sera possible d\'accepter les réglages par défaut ou, facultativement, de les adapter à vos propres besoins.';
|
||||
$string['welcomep70'] = 'Cliquer sur le bouton « Suivant » ci-dessous pour continuer l\'installation de <b>Moodle</b>.';
|
||||
$string['wwwroot'] = 'Adresse web';
|
||||
|
||||
+3
-3
@@ -585,8 +585,8 @@ $string['enableanalytics'] = 'Analytics';
|
||||
$string['enableblogs'] = 'Enable blogs';
|
||||
$string['enablecalendarexport'] = 'Enable calendar export';
|
||||
$string['enablecomments'] = 'Enable comments';
|
||||
$string['enablecommunicationsubsystem'] = 'Enable communication subsystem';
|
||||
$string['enablecommunicationsubsystem_desc'] = 'This setting enables the new communication subsystem. This new subsystem allow teachers and students to more easily communicate while using Moodle for teaching and learning';
|
||||
$string['enablecommunicationsubsystem'] = 'Enable communication providers';
|
||||
$string['enablecommunicationsubsystem_desc'] = 'Allow integration with communication providers such as Matrix so teachers and students can communicate more easily. You can manage these integrations in <a href="settings.php?section=managecommunicationproviders">Plugins</a>.';
|
||||
$string['enablecourserelativedates'] = 'Enable course relative dates';
|
||||
$string['enablecourserelativedates_desc'] = 'Allow courses to be set up to display dates relative to the user\'s start date in the course.';
|
||||
$string['enablecourserequests'] = 'Enable course requests';
|
||||
@@ -637,7 +637,7 @@ $string['environmentmariadbwrongdbtype'] = 'Wrong $CFG->dbtype. You need to chan
|
||||
$string['errordeletingconfig'] = 'An error occurred while deleting the configuration records for plugin \'{$a}\'.';
|
||||
$string['errorsetting'] = 'Could not save setting:';
|
||||
$string['errorwithsettings'] = 'Some settings were not changed due to an error.';
|
||||
$string['eventshandlersinuse'] = 'The following plugins in your system are using Events 1 API deprecated handlers: \'{$a}\'. Please, update them to use Events 2 API. See https://docs.moodle.org/dev/Event_2#Event_dispatching_and_observers.';
|
||||
$string['eventshandlersinuse'] = 'The following plugins in your system are using Events 1 API deprecated handlers: \'{$a}\'. Please, update them to use Events 2 API. See https://docs.moodle.org/dev/Events_API#Event_dispatching_and_observers.';
|
||||
$string['everyonewhocan'] = 'Everyone who can \'{$a}\'';
|
||||
$string['exceptions'] = 'exceptions';
|
||||
$string['execpathnotallowed'] = 'Setting executable and local paths disabled in config.php';
|
||||
|
||||
@@ -26,9 +26,9 @@ $string['communication'] = 'Communication';
|
||||
$string['communicationdisabled'] = 'Communication is disabled.';
|
||||
$string['communicationprovidernotfound'] = 'The \'{$a}\' communication provider doesn\'t exist or is not recognised.';
|
||||
$string['communicationroomname'] = 'Room name';
|
||||
$string['communicationroomname_help'] = 'The name that participants see when they visit the room. If you leave this blank, a default room name will be automatically set.';
|
||||
$string['communicationroomnameinfo'] = 'If the field is left blank, the course name is used as the room name.';
|
||||
$string['communicationroompending'] = 'Your {$a} room will be ready soon.';
|
||||
$string['communicationroomready'] = 'Your {$a} room is ready!';
|
||||
$string['communicationroomready'] = 'Your {$a} room is ready.';
|
||||
$string['managecommunicationproviders'] = 'Manage communication providers';
|
||||
$string['nocommunicationinstance'] = 'No communication instance found.';
|
||||
$string['nocommunicationprovider'] = 'No communication provider found.';
|
||||
@@ -37,5 +37,5 @@ $string['privacy:metadata:communication_user'] = 'The communication user table s
|
||||
$string['privacy:metadata:communication_user:commid'] = 'The communication ID for user mapping';
|
||||
$string['privacy:metadata:communication_user:userid'] = 'The user ID for communication provider mapping for users';
|
||||
$string['privacy:metadata:communication_user:synced'] = 'The user is synced with the provider or not';
|
||||
$string['seleccommunicationprovider'] = 'Communication service';
|
||||
$string['seleccommunicationprovider_help'] = 'The tool available for chat and related communication methods.';
|
||||
$string['selectcommunicationprovider'] = 'Provider';
|
||||
$string['selectcommunicationprovider_help'] = 'The tool available for chat and related communication methods.';
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ $string['completion_setby:manual:markdone'] = '{$a->activityname} is marked by {
|
||||
$string['completionrequirements'] = 'Completion requirements for {$a}';
|
||||
$string['courseaccess'] = 'Course access';
|
||||
$string['coursealreadyfinished'] = 'Course already finished';
|
||||
$string['coursecommunication_desc'] = 'The default communication service for new courses. Existing courses will not have any default provider selected.';
|
||||
$string['coursecommunication_desc'] = 'The default communication service for new courses. Existing courses will not have any provider selected by default.';
|
||||
$string['coursecontentnotification'] = 'Send content change notification';
|
||||
$string['coursecontentnotifnew'] = '{$a->coursename} new content';
|
||||
$string['coursecontentnotifnewbody'] = '<p>{$a->moduletypename} <a href="{$a->link}">{$a->modulename}</a> is new in the course <a href="{$a->courselink}">{$a->coursename}</a>.</p><p><a href="{$a->notificationpreferenceslink}">Change your notification preferences</a></p>';
|
||||
|
||||
+2
-2
@@ -978,7 +978,7 @@ This program is free software; you can redistribute it and/or modify it under th
|
||||
|
||||
This program 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 Moodle License information page for full details: https://docs.moodle.org/dev/License';
|
||||
See the Moodle License information page for full details: https://moodledev.io/general/license';
|
||||
$string['gpllicense'] = 'GPL license';
|
||||
$string['gpl3'] = 'Copyright (C) 1999 onwards Martin Dougiamas (https://moodle.com)
|
||||
|
||||
@@ -986,7 +986,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
|
||||
This program 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 Moodle License information page for full details: https://docs.moodle.org/dev/License';
|
||||
See the Moodle License information page for full details: https://moodledev.io/general/license';
|
||||
$string['grades'] = 'Grades';
|
||||
$string['gradenoun'] = 'Grade';
|
||||
$string['gradeverb'] = 'Grade';
|
||||
|
||||
+6
-6
@@ -135,17 +135,17 @@ define('CONTEXT_MODULE', 70);
|
||||
*/
|
||||
define('CONTEXT_BLOCK', 80);
|
||||
|
||||
/** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
|
||||
/** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
|
||||
define('RISK_MANAGETRUST', 0x0001);
|
||||
/** Capability allows changes in system configuration - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
|
||||
/** Capability allows changes in system configuration - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
|
||||
define('RISK_CONFIG', 0x0002);
|
||||
/** Capability allows user to add scripted content - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
|
||||
/** Capability allows user to add scripted content - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
|
||||
define('RISK_XSS', 0x0004);
|
||||
/** Capability allows access to personal user information - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
|
||||
/** Capability allows access to personal user information - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
|
||||
define('RISK_PERSONAL', 0x0008);
|
||||
/** Capability allows users to add content others may see - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
|
||||
/** Capability allows users to add content others may see - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
|
||||
define('RISK_SPAM', 0x0010);
|
||||
/** capability allows mass delete of data belonging to other users - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
|
||||
/** capability allows mass delete of data belonging to other users - see {@link https://moodledev.io/docs/apis/subsystems/roles} */
|
||||
define('RISK_DATALOSS', 0x0020);
|
||||
|
||||
/** rolename displays - the name as defined in the role definition, localised if name empty */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user