From 4f620f537e86f7247e65d099d9834c8a71662337 Mon Sep 17 00:00:00 2001 From: Laurent David Date: Wed, 5 Apr 2023 15:13:02 +0200 Subject: [PATCH] MDL-76713 core_availability: Fix date restriction conflict * Use a unique ID the date nodes in the HTML tree to be able to find the current node. * Look for nodes in the same tree "leaf" and work on conflicts in this single leaf/branch. --- .../behat/availability_date_conflict.feature | 105 ++++++++++++++++++ .../tests/behat/behat_availability_date.php | 39 +++++++ .../moodle-availability_date-form-debug.js | 100 ++++++++++------- .../moodle-availability_date-form-min.js | 2 +- .../moodle-availability_date-form.js | 100 ++++++++++------- .../condition/date/yui/src/form/js/form.js | 100 ++++++++++------- .../tests/behat/behat_availability.php | 67 +++++++++++ .../moodle-core_availability-form-debug.js | 3 +- .../moodle-core_availability-form-min.js | 4 +- .../moodle-core_availability-form.js | 3 +- availability/yui/src/form/js/form.js | 3 +- 11 files changed, 394 insertions(+), 132 deletions(-) create mode 100644 availability/condition/date/tests/behat/availability_date_conflict.feature create mode 100644 availability/condition/date/tests/behat/behat_availability_date.php create mode 100644 availability/tests/behat/behat_availability.php diff --git a/availability/condition/date/tests/behat/availability_date_conflict.feature b/availability/condition/date/tests/behat/availability_date_conflict.feature new file mode 100644 index 00000000000..e9e2429167a --- /dev/null +++ b/availability/condition/date/tests/behat/availability_date_conflict.feature @@ -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" diff --git a/availability/condition/date/tests/behat/behat_availability_date.php b/availability/condition/date/tests/behat/behat_availability_date.php new file mode 100644 index 00000000000..f6740a3fbe9 --- /dev/null +++ b/availability/condition/date/tests/behat/behat_availability_date.php @@ -0,0 +1,39 @@ +. +use Behat\Mink\Element\NodeElement; + +/** + * Behat availabilty-related steps definitions. + * + * @package availability_date + * @category test + * @copyright 2023 Laurent David + * @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')]]"] + ), + ]; + } +} diff --git a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-debug.js b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-debug.js index e1ab688d539..5db031832e2 100644 --- a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-debug.js +++ b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-debug.js @@ -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) { diff --git a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js index 57b4ba56ae3..ea7f0646a18 100644 --- a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js +++ b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js @@ -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=''+M.util.get_string("direction_before","availability_date")+' "+this.html,n=o.Node.create(""+a+"");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"]}); \ No newline at end of file +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=''+M.util.get_string("direction_before","availability_date")+' "+this.html,l=o.Node.create(""+a+"");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&&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"]}); \ No newline at end of file diff --git a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js index e1ab688d539..5db031832e2 100644 --- a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js +++ b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js @@ -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) { diff --git a/availability/condition/date/yui/src/form/js/form.js b/availability/condition/date/yui/src/form/js/form.js index ca0ca736d44..b2d1866e19d 100644 --- a/availability/condition/date/yui/src/form/js/form.js +++ b/availability/condition/date/yui/src/form/js/form.js @@ -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) { diff --git a/availability/tests/behat/behat_availability.php b/availability/tests/behat/behat_availability.php new file mode 100644 index 00000000000..4d89157d2c1 --- /dev/null +++ b/availability/tests/behat/behat_availability.php @@ -0,0 +1,67 @@ +. + +require_once(__DIR__ . '/../../../lib/behat/behat_base.php'); + +/** + * Availability related behat steps and selectors definitions. + * + * @package core_availability + * @category test + * @copyright 2023 Amaia Anabitarte + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_availability 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( + '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', [".//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')]", + ], + ), + ]; + } +} diff --git a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js index 34c0df0af14..8477127903f 100644 --- a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js +++ b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js @@ -560,7 +560,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]; @@ -1008,6 +1008,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'; }; /** diff --git a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js index 7d341375412..4f1927b6c26 100644 --- a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js +++ b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js @@ -1,3 +1,3 @@ YUI.add("moodle-core_availability-form",function(d,i){M.core_availability=M.core_availability||{},M.core_availability.form={plugins:{},field:null,mainDiv:null,rootList:null,idCounter:0,restrictByGroup:null,init:function(i){var t,e,a,l,n;for(t in i)e=i[t],(a=M[e[0]].form).init.apply(a,e);if(this.field=d.one("#id_availabilityconditionsjson"),this.field.setAttribute("aria-hidden","true"),this.mainDiv=d.Node.create(''),this.field.insert(this.mainDiv,"after"),n=null,""!==(l=this.field.get("value")))try{n=d.JSON.parse(l)}catch(o){this.field.set("value","")}this.rootList=new M.core_availability.List(n,!0),this.mainDiv.appendChild(this.rootList.node),this.update(),this.rootList.renumber(),this.mainDiv.setAttribute("aria-live","polite"),this.field.ancestor("form").on("submit",function(){this.mainDiv.all("input,textarea,select").set("disabled",!0)},this),this.restrictByGroup=d.one("#restrictbygroup"),this.restrictByGroup&&(this.restrictByGroup.on("click",this.addRestrictByGroup,this),l=d.one("#id_groupmode"),n=d.one("#id_groupingid"),l&&l.on("change",this.updateRestrictByGroup,this),n&&n.on("change",this.updateRestrictByGroup,this),this.updateRestrictByGroup())},update:function(){var i=this.rootList.getValue(),t=[];this.rootList.fillErrors(t),0!==t.length&&(i.errors=t),this.field.set("value",d.JSON.stringify(i)),this.updateRestrictByGroup()},updateRestrictByGroup:function(){var i,t,e,a;this.restrictByGroup&&("&"!==this.rootList.getValue().op||(this.rootList.hasItemOfType("group")||this.rootList.hasItemOfType("grouping"))?this.restrictByGroup.set("disabled",!0):(i=d.one("#id_groupmode"),t=d.one("#id_groupingid"),e=1===Number(this.restrictByGroup.getData("groupavailability")),a=1===Number(this.restrictByGroup.getData("groupingavailability")),i&&0!==Number(i.get("value"))&&e||t&&0!==Number(t.get("value"))&&a?this.restrictByGroup.set("disabled",!1):this.restrictByGroup.set("disabled",!0)))},addRestrictByGroup:function(i){var t,e,a,l;i.preventDefault(),i=d.one("#id_groupmode"),t=d.one("#id_groupingid"),e=1===Number(this.restrictByGroup.getData("groupavailability")),a=1===Number(this.restrictByGroup.getData("groupingavailability")),t&&0!==Number(t.get("value"))&&a?l=new M.core_availability.Item({type:"grouping",id:Number(t.get("value"))},!0):i&&e&&(l=new M.core_availability.Item({type:"group"},!0)),null!==l&&(this.rootList.addChild(l),this.update(),this.rootList.renumber(),this.rootList.updateHtml())}},M.core_availability.plugin={allowAdd:!1,init:function(i,t,e){i=i.replace(/^availability_/,"");this.allowAdd=t,(M.core_availability.form.plugins[i]=this).initInner.apply(this,e)},initInner:function(){},getNode:function(){throw"getNode not implemented"},fillValue:function(){throw"fillValue not implemented"},fillErrors:function(){},focusAfterAdd:function(i){i.one("input:not([disabled]),select:not([disabled])").focus()}},M.core_availability.List=function(i,t,e){var a,l,n;if(this.children=[],t!==undefined&&(this.root=t),this.node=d.Node.create('

'+M.util.get_string("listheader_sign_before","availability")+' '+M.util.get_string("listheader_single","availability")+''+M.util.get_string("listheader_multi_before","availability")+' "+M.util.get_string("listheader_multi_after","availability")+'
'+M.util.get_string("none","moodle")+'
'),t||this.node.addClass("availability-childlist d-sm-flex align-items-center"),this.inner=this.node.one("> .availability-inner"),a=!0,t?(i&&i.show!==undefined&&(a=i.show),this.eyeIcon=new M.core_availability.EyeIcon(!1,a),this.node.one(".availability-header").get("firstChild").insert(this.eyeIcon.span,"before")):e&&(i&&i.showc!==undefined&&(a=i.showc),this.eyeIcon=new M.core_availability.EyeIcon(!1,a),this.inner.insert(this.eyeIcon.span,"before")),t||(e=new M.core_availability.DeleteIcon(this),(a=this.node.one(".availability-none")).appendChild(document.createTextNode(" ")),a.appendChild(e.span),a.appendChild(d.Node.create(''+M.util.get_string("invalid","availability")+""))),(t=d.Node.create('")).on("click",function(){this.clickAdd()},this),this.node.one("div.availability-button").appendChild(t),i){switch(i.op){case"&":case"|":this.node.one(".availability-neg").set("value","");break;case"!&":case"!|":this.node.one(".availability-neg").set("value","!")}switch(i.op){case"&":case"!&":this.node.one(".availability-op").set("value","&");break;case"|":case"!|":this.node.one(".availability-op").set("value","|")}for(l=0;l')),this.children.push(i),this.inner.one(".availability-children").appendChild(i.node)},M.core_availability.List.prototype.focusAfterAdd=function(){this.inner.one("button").focus()},M.core_availability.List.prototype.isIndividualShowIcons=function(){var i,t;if(!this.root)throw"Can only call this on root list";return i="!"===this.node.one(".availability-neg").get("value"),t="|"===this.node.one(".availability-op").get("value"),!i&&!t||i&&t},M.core_availability.List.prototype.renumber=function(i){var t,e={count:this.children.length},a=i===undefined?e.number="":(e.number=i+":",i+"."),i=M.util.get_string("setheading","availability",e);for(this.node.one("> h3").set("innerHTML",i),t=0;t .availability-children").removeAttribute("aria-hidden"),this.inner.one("> .availability-none").setAttribute("aria-hidden","true"),this.inner.one("> .availability-header").removeAttribute("aria-hidden"),1 .availability-children").setAttribute("aria-hidden","true"),this.inner.one("> .availability-none").removeAttribute("aria-hidden"),this.inner.one("> .availability-header").setAttribute("aria-hidden","true")),this.root){for(i=this.isIndividualShowIcons(),t=0;t .availability-children > .availability-connector span.label").each(function(i){i.set("innerHTML",a)})},M.core_availability.List.prototype.deleteDescendant=function(i){for(var t,e,a=0;a .availability-children").removeChild(e),M.core_availability.form.update(),this.updateHtml(),this.inner.one("> .availability-button").one("button").focus(),!0;if(t instanceof M.core_availability.List&&t.deleteDescendant(i))return!0}return!1},M.core_availability.List.prototype.clickAdd=function(){var i,t,e,a,l,n=d.Node.create('
    "),o=n.one("button"),s={dialog:null},r=n.one("ul");for(l in M.core_availability.form.plugins)M.core_availability.form.plugins[l].allowAdd&&(i=d.Node.create('
  • '),(e=d.Node.create('
    ")).on("click",this.getAddHandler(l,s),this),i.appendChild(e),a=d.Node.create('
    "),i.appendChild(a),r.appendChild(i));i=d.Node.create('
  • '),(e=d.Node.create('
    ")).on("click",this.getAddHandler(null,s),this),i.appendChild(e),a=d.Node.create('
    "),i.appendChild(a),r.appendChild(i),n={headerContent:M.util.get_string("addrestriction","availability"),bodyContent:n,additionalBaseClass:"availability-dialogue",draggable:!0,modal:!0,closeButton:!1,width:"450px"},s.dialog=new M.core.dialogue(n),s.dialog.show(),o.on("click",function(){s.dialog.destroy(),this.inner.one("> .availability-button").one("button").focus()},this)},M.core_availability.List.prototype.getAddHandler=function(t,e){return function(){var i=t?new M.core_availability.Item({type:t,creating:!0},this.root):new M.core_availability.List({c:[],showc:!0},!1,this.root);this.addChild(i),M.core_availability.form.update(),M.core_availability.form.rootList.renumber(),this.updateHtml(),e.dialog.destroy(),i.focusAfterAdd()}},M.core_availability.List.prototype.getValue=function(){var i,t={};for(t.op=this.node.one(".availability-neg").get("value")+this.node.one(".availability-op").get("value"),t.c=[],i=0;i'+M.util.get_string("missingplugin","availability")+"")):(this.plugin=M.core_availability.form.plugins[i.type],this.pluginNode=this.plugin.getNode(i),this.pluginNode.addClass("availability_"+i.type)),this.node=d.Node.create('

    '),t&&(t=!0,i.showc!==undefined&&(t=i.showc),this.eyeIcon=new M.core_availability.EyeIcon(!0,t),this.node.appendChild(this.eyeIcon.span)),this.pluginNode.addClass("availability-plugincontrols"),this.node.appendChild(this.pluginNode),i=new M.core_availability.DeleteIcon(this),this.node.appendChild(i.span),this.node.appendChild(document.createTextNode(" ")),this.node.appendChild(d.Node.create(''))},M.core_availability.Item.prototype.getValue=function(){var i={type:this.pluginType};return this.plugin&&this.plugin.fillValue(i,this.pluginNode),i},M.core_availability.Item.prototype.fillErrors=function(i){var t,e,a,l=i.length;this.plugin?this.plugin.fillErrors(i,this.pluginNode):i.push("core_availability:item_unknowntype"),t=this.node.one("> .badge-warning"),i.length===l||t.get("firstChild")?i.length===l&&t.get("firstChild")&&t.get("firstChild").remove():(l="",e=(i=i[i.length-1].split(":"))[0],a="[["+(i=i[1])+","+e+"]]",(l=M.util.get_string(i,e))===a&&(l=M.util.get_string("invalid","availability")),t.appendChild(document.createTextNode(l)))},M.core_availability.Item.prototype.renumber=function(i){var t={number:i};this.plugin?t.type=M.util.get_string("title","availability_"+this.pluginType):t.type="["+this.pluginType+"]",t.number=i+":",i=M.util.get_string("itemheading","availability",t),this.node.one("> h3").set("innerHTML",i)},M.core_availability.Item.prototype.focusAfterAdd=function(){this.plugin.focusAfterAdd(this.pluginNode)},M.core_availability.Item.prototype.pluginType=null,M.core_availability.Item.prototype.plugin=null,M.core_availability.Item.prototype.eyeIcon=null,M.core_availability.Item.prototype.node=null,M.core_availability.Item.prototype.pluginNode=null,M.core_availability.EyeIcon=function(i,t){var e,a,l,n;this.individual=i,this.span=d.Node.create(''),e=d.Node.create(""),this.span.appendChild(e),a=i?"_individual":"_all",l=function(){var i=M.util.get_string("hidden"+a,"availability");e.set("src",M.util.image_url("i/show","core")),e.set("alt",i),this.span.set("title",i+" • "+M.util.get_string("show_verb","availability"))},n=function(){var i=M.util.get_string("shown"+a,"availability");e.set("src",M.util.image_url("i/hide","core")),e.set("alt",i),this.span.set("title",i+" • "+M.util.get_string("hide_verb","availability"))},(t?n:l).call(this),this.span.on("click",i=function(i){i.preventDefault(),(this.isHidden()?n:l).call(this),M.core_availability.form.update()},this),this.span.on("key",i,"up:32",this),this.span.on("key",function(i){i.preventDefault()},"down:32",this)},M.core_availability.EyeIcon.prototype.individual=!1,M.core_availability.EyeIcon.prototype.span=null,M.core_availability.EyeIcon.prototype.isHidden=function(){var i=this.individual?"_individual":"_all",i=M.util.get_string("hidden"+i,"availability");return this.span.one("img").get("alt")===i},M.core_availability.DeleteIcon=function(t){var i;this.span=d.Node.create(''),i=d.Node.create(''+M.util.get_string('),this.span.appendChild(i),this.span.on("click",i=function(i){i.preventDefault(),M.core_availability.form.rootList.deleteDescendant(t),M.core_availability.form.rootList.renumber()},this),this.span.on("key",i,"up:32",this),this.span.on("key",function(i){i.preventDefault()},"down:32",this)},M.core_availability.DeleteIcon.prototype.span=null},"@VERSION@",{requires:["base","node","event","event-delegate","panel","moodle-core-notification-dialogue","json"]}); \ No newline at end of file +):new M.core_availability.List(n,!1,this.root),this.addChild(n)}this.node.one(".availability-neg").on("change",function(){M.core_availability.form.update(),this.updateHtml()},this),this.node.one(".availability-op").on("change",function(){M.core_availability.form.update(),this.updateHtml()},this),this.updateHtml()},M.core_availability.List.prototype.addChild=function(i){0')),this.children.push(i),this.inner.one(".availability-children").appendChild(i.node)},M.core_availability.List.prototype.focusAfterAdd=function(){this.inner.one("button").focus()},M.core_availability.List.prototype.isIndividualShowIcons=function(){var i,t;if(!this.root)throw"Can only call this on root list";return i="!"===this.node.one(".availability-neg").get("value"),t="|"===this.node.one(".availability-op").get("value"),!i&&!t||i&&t},M.core_availability.List.prototype.renumber=function(i){var t,e={count:this.children.length},a=i===undefined?e.number="":(e.number=i+":",i+"."),e=M.util.get_string("setheading","availability",e);for(this.node.one("> h3").set("innerHTML",e),this.node.one("> h3").getDOMNode().dataset.restrictionOrder=i||"root",t=0;t .availability-children").removeAttribute("aria-hidden"),this.inner.one("> .availability-none").setAttribute("aria-hidden","true"),this.inner.one("> .availability-header").removeAttribute("aria-hidden"),1 .availability-children").setAttribute("aria-hidden","true"),this.inner.one("> .availability-none").removeAttribute("aria-hidden"),this.inner.one("> .availability-header").setAttribute("aria-hidden","true")),this.root){for(i=this.isIndividualShowIcons(),t=0;t .availability-children > .availability-connector span.label").each(function(i){i.set("innerHTML",a)})},M.core_availability.List.prototype.deleteDescendant=function(i){for(var t,e,a=0;a .availability-children").removeChild(e),M.core_availability.form.update(),this.updateHtml(),this.inner.one("> .availability-button").one("button").focus(),!0;if(t instanceof M.core_availability.List&&t.deleteDescendant(i))return!0}return!1},M.core_availability.List.prototype.clickAdd=function(){var i,t,e,a,l,n=d.Node.create('
      "),o=n.one("button"),s={dialog:null},r=n.one("ul");for(l in M.core_availability.form.plugins)M.core_availability.form.plugins[l].allowAdd&&(i=d.Node.create('
    • '),(e=d.Node.create('
      ")).on("click",this.getAddHandler(l,s),this),i.appendChild(e),a=d.Node.create('
      "),i.appendChild(a),r.appendChild(i));i=d.Node.create('
    • '),(e=d.Node.create('
      ")).on("click",this.getAddHandler(null,s),this),i.appendChild(e),a=d.Node.create('
      "),i.appendChild(a),r.appendChild(i),n={headerContent:M.util.get_string("addrestriction","availability"),bodyContent:n,additionalBaseClass:"availability-dialogue",draggable:!0,modal:!0,closeButton:!1,width:"450px"},s.dialog=new M.core.dialogue(n),s.dialog.show(),o.on("click",function(){s.dialog.destroy(),this.inner.one("> .availability-button").one("button").focus()},this)},M.core_availability.List.prototype.getAddHandler=function(t,e){return function(){var i=t?new M.core_availability.Item({type:t,creating:!0},this.root):new M.core_availability.List({c:[],showc:!0},!1,this.root);this.addChild(i),M.core_availability.form.update(),M.core_availability.form.rootList.renumber(),this.updateHtml(),e.dialog.destroy(),i.focusAfterAdd()}},M.core_availability.List.prototype.getValue=function(){var i,t={};for(t.op=this.node.one(".availability-neg").get("value")+this.node.one(".availability-op").get("value"),t.c=[],i=0;i'+M.util.get_string("missingplugin","availability")+"")):(this.plugin=M.core_availability.form.plugins[i.type],this.pluginNode=this.plugin.getNode(i),this.pluginNode.addClass("availability_"+i.type)),this.node=d.Node.create('

      '),t&&(t=!0,i.showc!==undefined&&(t=i.showc),this.eyeIcon=new M.core_availability.EyeIcon(!0,t),this.node.appendChild(this.eyeIcon.span)),this.pluginNode.addClass("availability-plugincontrols"),this.node.appendChild(this.pluginNode),i=new M.core_availability.DeleteIcon(this),this.node.appendChild(i.span),this.node.appendChild(document.createTextNode(" ")),this.node.appendChild(d.Node.create(''))},M.core_availability.Item.prototype.getValue=function(){var i={type:this.pluginType};return this.plugin&&this.plugin.fillValue(i,this.pluginNode),i},M.core_availability.Item.prototype.fillErrors=function(i){var t,e,a,l=i.length;this.plugin?this.plugin.fillErrors(i,this.pluginNode):i.push("core_availability:item_unknowntype"),t=this.node.one("> .badge-warning"),i.length===l||t.get("firstChild")?i.length===l&&t.get("firstChild")&&t.get("firstChild").remove():(l="",e=(i=i[i.length-1].split(":"))[0],a="[["+(i=i[1])+","+e+"]]",(l=M.util.get_string(i,e))===a&&(l=M.util.get_string("invalid","availability")),t.appendChild(document.createTextNode(l)))},M.core_availability.Item.prototype.renumber=function(i){var t={number:i};this.plugin?t.type=M.util.get_string("title","availability_"+this.pluginType):t.type="["+this.pluginType+"]",t.number=i+":",t=M.util.get_string("itemheading","availability",t),this.node.one("> h3").set("innerHTML",t),this.node.one("> h3").getDOMNode().dataset.restrictionOrder=i||"root"},M.core_availability.Item.prototype.focusAfterAdd=function(){this.plugin.focusAfterAdd(this.pluginNode)},M.core_availability.Item.prototype.pluginType=null,M.core_availability.Item.prototype.plugin=null,M.core_availability.Item.prototype.eyeIcon=null,M.core_availability.Item.prototype.node=null,M.core_availability.Item.prototype.pluginNode=null,M.core_availability.EyeIcon=function(i,t){var e,a,l,n;this.individual=i,this.span=d.Node.create('
      '),e=d.Node.create(""),this.span.appendChild(e),a=i?"_individual":"_all",l=function(){var i=M.util.get_string("hidden"+a,"availability");e.set("src",M.util.image_url("i/show","core")),e.set("alt",i),this.span.set("title",i+" • "+M.util.get_string("show_verb","availability"))},n=function(){var i=M.util.get_string("shown"+a,"availability");e.set("src",M.util.image_url("i/hide","core")),e.set("alt",i),this.span.set("title",i+" • "+M.util.get_string("hide_verb","availability"))},(t?n:l).call(this),this.span.on("click",i=function(i){i.preventDefault(),(this.isHidden()?n:l).call(this),M.core_availability.form.update()},this),this.span.on("key",i,"up:32",this),this.span.on("key",function(i){i.preventDefault()},"down:32",this)},M.core_availability.EyeIcon.prototype.individual=!1,M.core_availability.EyeIcon.prototype.span=null,M.core_availability.EyeIcon.prototype.isHidden=function(){var i=this.individual?"_individual":"_all",i=M.util.get_string("hidden"+i,"availability");return this.span.one("img").get("alt")===i},M.core_availability.DeleteIcon=function(t){var i;this.span=d.Node.create(''),i=d.Node.create(''+M.util.get_string('),this.span.appendChild(i),this.span.on("click",i=function(i){i.preventDefault(),M.core_availability.form.rootList.deleteDescendant(t),M.core_availability.form.rootList.renumber()},this),this.span.on("key",i,"up:32",this),this.span.on("key",function(i){i.preventDefault()},"down:32",this)},M.core_availability.DeleteIcon.prototype.span=null},"@VERSION@",{requires:["base","node","event","event-delegate","panel","moodle-core-notification-dialogue","json"]}); \ No newline at end of file diff --git a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js index 34c0df0af14..8477127903f 100644 --- a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js +++ b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js @@ -560,7 +560,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]; @@ -1008,6 +1008,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'; }; /** diff --git a/availability/yui/src/form/js/form.js b/availability/yui/src/form/js/form.js index 5e5e7399598..7e80eee41b6 100644 --- a/availability/yui/src/form/js/form.js +++ b/availability/yui/src/form/js/form.js @@ -558,7 +558,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]; @@ -1006,6 +1006,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'; }; /**