diff --git a/availability/tests/behat/edit_availability.feature b/availability/tests/behat/edit_availability.feature index 3f2e3a2112c..82f36628e42 100644 --- a/availability/tests/behat/edit_availability.feature +++ b/availability/tests/behat/edit_availability.feature @@ -172,3 +172,62 @@ Feature: edit_availability And I should not see "None" in the "Restrict access" "fieldset" And "Restriction type" "select" should be visible And I should see "Date" in the "Restrict access" "fieldset" + + @javascript + Scenario: 'Add group/grouping access restriction' button unavailable + # Button does not exist when conditional access restrictions are turned off. + Given I log in as "admin" + And I follow "Course 1" + And I turn editing mode on + And I add a "Forum" to section "1" + When I expand all fieldsets + Then "Add group/grouping access restriction" "button" should not exist + + @javascript + Scenario: Use the 'Add group/grouping access restriction' button + # Button should initially be disabled. + Given I log in as "admin" + And I set the following administration settings values: + | Enable conditional access | 1 | + And the following "groupings" exist: + | name | course | idnumber | + | GX1 | C1 | GXI1 | + And I am on homepage + And I follow "Course 1" + And I turn editing mode on + And I add a "Forum" to section "1" + And I set the following fields to these values: + | Forum name | MyForum | + | Description | x | + When I expand all fieldsets + Then the "Add group/grouping access restriction" "button" should be disabled + + # Turn on separate groups. + And I set the field "Group mode" to "Separate groups" + And the "Add group/grouping access restriction" "button" should be enabled + + # Press the button and check it adds a restriction and disables itself. + And I should see "None" in the "Restrict access" "fieldset" + And I press "Add group/grouping access restriction" + And I should see "Group" in the "Restrict access" "fieldset" + And the "Add group/grouping access restriction" "button" should be disabled + + # Delete the restriction and check it is enabled again. + And I click on "Delete" "link" in the "Restrict access" "fieldset" + And the "Add group/grouping access restriction" "button" should be enabled + + # Try a grouping instead. + And I set the field "Grouping" to "GX1" + And I press "Add group/grouping access restriction" + And I should see "Grouping" in the "Restrict access" "fieldset" + + # Check the button still works after saving and editing. + And I press "Save and display" + And I navigate to "Edit settings" node in "Forum administration" + And I expand all fieldsets + And the "Add group/grouping access restriction" "button" should be disabled + And I should see "Grouping" in the "Restrict access" "fieldset" + + # And check it's still active if I delete the condition. + And I click on "Delete" "link" in the "Restrict access" "fieldset" + And the "Add group/grouping access restriction" "button" should be enabled 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 1af7df270f9..61b4781bad6 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 @@ -66,6 +66,14 @@ M.core_availability.form = { */ idCounter : 0, + /** + * The 'Restrict by group' button if present. + * + * @property restrictByGroup + * @type Y.Node + */ + restrictByGroup : null, + /** * Called to initialise the system when the page loads. This method will * also call the init method for each plugin. @@ -121,6 +129,22 @@ M.core_availability.form = { this.field.ancestor('form').on('submit', function() { this.mainDiv.all('input,textarea,select').set('disabled', true); }, this); + + // If the form has group mode and/or grouping options, there is a + // 'add restriction' button there. + this.restrictByGroup = Y.one('#restrictbygroup'); + if (this.restrictByGroup) { + this.restrictByGroup.on('click', this.addRestrictByGroup, this); + var groupmode = Y.one('#id_groupmode'); + var groupingid = Y.one('#id_groupingid'); + if (groupmode) { + groupmode.on('change', this.updateRestrictByGroup, this); + } + if (groupingid) { + groupingid.on('change', this.updateRestrictByGroup, this); + } + this.updateRestrictByGroup(); + } }, /** @@ -143,6 +167,75 @@ M.core_availability.form = { // Set into hidden form field, JS-encoded. this.field.set('value', Y.JSON.stringify(jsValue)); + + // Also update the restrict by group button if present. + this.updateRestrictByGroup(); + }, + + /** + * Updates the status of the 'restrict by group' button (enables or disables + * it) based on current availability restrictions and group/grouping settings. + */ + updateRestrictByGroup : function() { + if (!this.restrictByGroup) { + return; + } + + // If the root list is anything other than the default 'and' type, disable. + if (this.rootList.getValue().op !== '&') { + this.restrictByGroup.set('disabled', true); + return; + } + + // If there's already a group restriction, disable it. + var alreadyGot = this.rootList.hasItemOfType('group') || + this.rootList.hasItemOfType('grouping'); + if (alreadyGot) { + this.restrictByGroup.set('disabled', true); + return; + } + + // If the groupmode and grouping id aren't set, disable it. + var groupmode = Y.one('#id_groupmode'); + var groupingid = Y.one('#id_groupingid'); + if ((!groupmode || Number(groupmode.get('value')) === 0) && + (!groupingid || Number(groupingid.get('value')) === 0)) { + this.restrictByGroup.set('disabled', true); + return; + } + + this.restrictByGroup.set('disabled', false); + }, + + /** + * Called when the user clicks on the 'restrict by group' button. This is + * a special case that adds a group or grouping restriction. + * + * By default this restriction is not shown which makes it similar to the + * + * @param e Button click event + */ + addRestrictByGroup : function(e) { + // If you don't prevent default, it submits the form for some reason. + e.preventDefault(); + + // Add the condition. + var groupingid = Y.one('#id_groupingid'); + var newChild; + if (groupingid && Number(groupingid.get('value')) !== 0) { + // Add a grouping restriction if one is specified. + newChild = new M.core_availability.Item( + {type : 'grouping', id : Number(groupingid.get('value'))}, true); + } else { + // Otherwise just add a group restriction. + newChild = new M.core_availability.Item({type : 'group'}, true); + } + + // Refresh HTML. + this.rootList.addChild(newChild); + this.update(); + this.rootList.renumber(); + this.rootList.updateHtml(); } }; @@ -709,6 +802,31 @@ M.core_availability.List.prototype.fillErrors = function(errors) { } }; +/** + * Checks whether the list contains any items of the given type name. + * + * @method hasItemOfType + * @param {String} pluginType Required plugin type (name) + * @return {Boolean} True if there is one + */ +M.core_availability.List.prototype.hasItemOfType = function(pluginType) { + // Check each item. + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + if (child instanceof M.core_availability.List) { + // Recursive call. + if (child.hasItemOfType(pluginType)) { + return true; + } + } else { + if (child.pluginType === pluginType) { + return true; + } + } + } + return false; +}; + /** * Eye icon for this list (null if none). * 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 4733ed58a6a..92740e759da 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(e,t){M.core_availability=M.core_availability||{},M.core_availability.form={plugins:{},field:null,mainDiv:null,rootList:null,idCounter:0,init:function(t){for(var n in t){var r=t[n],i=M[r[0]].form;i.init.apply(i,r)}this.field=e.one("#id_availabilityconditionsjson"),this.field.setAttribute("aria-hidden","true"),this.mainDiv=e.Node.create(''),this.field.insert(this.mainDiv,"after");var s=this.field.get("value"),o=null;if(s!=="")try{o=e.JSON.parse(s)}catch(u){this.field.set("value","")}this.rootList=new M.core_availability.List(o,!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)},update:function(){var t=this.rootList.getValue(),n=[];this.rootList.fillErrors(n),n.length!==0&&(t.errors=n),this.field.set("value",e.JSON.stringify(t))}},M.core_availability.plugin={allowAdd:!1,init:function(e,t,n){var r=e.replace(/^availability_/,"");this.allowAdd=t,M.core_availability.form.plugins[r]=this,this.initInner.apply(this,n)},initInner:function(){},getNode:function(){throw"getNode not implemented"},fillValue:function(){throw"fillValue not implemented"},fillErrors:function(){},focusAfterAdd:function(e){var t=e.one("input:not([disabled]),select:not([disabled])");t.focus()}},M.core_availability.List=function(t,n,r){this.children=[],n!==undefined&&(this.root=n),this.node=e.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")+"
"+'
'),n||this.node.addClass("availability-childlist"),this.inner=this.node.one("> .availability-inner");var i=!0;n?(t&&t.show!==undefined&&(i=t.show),this.eyeIcon=new M.core_availability.EyeIcon(!1,i),this.node.one(".availability-header").get("firstChild").insert(this.eyeIcon.span,"before")):r&&(t&&t.showc!==undefined&&(i=t.showc),this.eyeIcon=new M.core_availability.EyeIcon(!1,i),this.inner.insert(this.eyeIcon.span,"before"));if(!n){var s=new M.core_availability.DeleteIcon(this),o=this.node.one(".availability-none");o.appendChild(document.createTextNode(" ")),o.appendChild(s.span),o.appendChild(e.Node.create(''+M.util.get_string("invalid","availability")+""))}var u=e.Node.create('");u.on("click",function(){this.clickAdd()},this),this.node.one("div.availability-button").appendChild(u);if(t){switch(t.op){case"&":case"|":this.node.one(".availability-neg").set("value","");break;case"!&":case"!|":this.node.one(".availability-neg").set("value","!")}switch(t.op){case"&":case"!&":this.node.one(".availability-op").set("value","&");break;case"|":case"!|":this.node.one(".availability-op").set("value","|")}for(var a=0;a0&&this.inner.one(".availability-children").appendChild(e.Node.create('
')),this.children.push(t),this.inner.one(".availability-children").appendChild(t.node)},M.core_availability.List.prototype.focusAfterAdd=function(){this.inner.one("button").focus()},M.core_availability.List.prototype.isIndividualShowIcons=function(){if(!this.root)throw"Can only call this on root list";var e=this.node.one(".availability-neg").get("value")==="!",t=this.node.one(".availability-op").get("value")==="|";return!e&&!t||e&&t},M.core_availability.List.prototype.renumber=function(e){var t={count:this.children.length},n;e===undefined?(t.number="",n=""):(t.number=e+":",n=e+".");var r=M.util.get_string("setheading","availability",t);this.node.one("> h3").set("innerHTML",r);for(var i=0;i0?(this.inner.one("> .availability-children").removeAttribute("aria-hidden"),this.inner.one("> .availability-none").setAttribute("aria-hidden","true"),this.inner.one("> .availability-header").removeAttribute -("aria-hidden"),this.children.length>1?(this.inner.one(".availability-single").setAttribute("aria-hidden","true"),this.inner.one(".availability-multi").removeAttribute("aria-hidden")):(this.inner.one(".availability-single").removeAttribute("aria-hidden"),this.inner.one(".availability-multi").setAttribute("aria-hidden","true"))):(this.inner.one("> .availability-children").setAttribute("aria-hidden","true"),this.inner.one("> .availability-none").removeAttribute("aria-hidden"),this.inner.one("> .availability-header").setAttribute("aria-hidden","true"));if(this.root){var e=this.isIndividualShowIcons();for(var t=0;t .availability-children > .availability-connector span.label").each(function(e){e.set("innerHTML",r)})},M.core_availability.List.prototype.deleteDescendant=function(e){for(var t=0;t0&&(r.previous(".availability-connector")?r.previous(".availability-connector").remove():r.next(".availability-connector").remove()),this.inner.one("> .availability-children").removeChild(r),M.core_availability.form.update(),this.updateHtml(),this.inner.one("> .availability-button").one("button").focus(),!0}if(n instanceof M.core_availability.List){var i=n.deleteDescendant(e);if(i)return!0}}return!1},M.core_availability.List.prototype.clickAdd=function(){var t=e.Node.create('
    "),n=t.one("button"),r={dialog:null},i=t.one("ul"),s,o,u,a;for(var f in M.core_availability.form.plugins){if(!M.core_availability.form.plugins[f].allowAdd)continue;s=e.Node.create('
  • '),o="availability_addrestriction_"+f,u=e.Node.create('"),u.on("click",this.getAddHandler(f,r),this),s.appendChild(u),a=e.Node.create('"),s.appendChild(a),i.appendChild(s)}s=e.Node.create('
  • '),o="availability_addrestriction_list_",u=e.Node.create('"),u.on("click",this.getAddHandler(null,r),this),s.appendChild(u),a=e.Node.create('"),s.appendChild(a),i.appendChild(s);var l={headerContent:M.util.get_string("addrestriction","availability"),bodyContent:t,additionalBaseClass:"availability-dialogue",draggable:!0,modal:!0,closeButton:!1,width:"450px"};r.dialog=new M.core.dialogue(l),r.dialog.show(),n.on("click",function(){r.dialog.destroy(),this.inner.one("> .availability-button").one("button").focus()},this)},M.core_availability.List.prototype.getAddHandler=function(e,t){return function(){e?newItem=new M.core_availability.Item({type:e,creating:!0},this.root):newItem=new M.core_availability.List({c:[],showc:!0},!1,this.root),this.addChild(newItem),M.core_availability.form.update(),M.core_availability.form.rootList.renumber(),this.updateHtml(),t.dialog.destroy(),newItem.focusAfterAdd()}},M.core_availability.List.prototype.getValue=function(){var e={};e.op=this.node.one(".availability-neg").get("value")+this.node.one(".availability-op").get("value"),e.c=[];var t;for(t=0;t'+M.util.get_string("missingplugin","availability")+"")):(this.plugin=M.core_availability.form.plugins[t.type],this.pluginNode=this.plugin.getNode(t),this.pluginNode.addClass("availability_"+t.type)),this.node=e.Node.create('

    ');if(n){var r=!0;t.showc!==undefined&&(r=t.showc),this.eyeIcon=new M.core_availability.EyeIcon(!0,r),this.node.appendChild(this.eyeIcon.span)}this.pluginNode.addClass("availability-plugincontrols"),this.node.appendChild(this.pluginNode);var i=new M.core_availability.DeleteIcon(this);this.node.appendChild(i.span),this.node.appendChild(document.createTextNode(" ")),this.node.appendChild(e.Node.create(''))},M.core_availability.Item.prototype.getValue=function(){return value={type:this.pluginType},this.plugin&&this.plugin.fillValue(value,this.pluginNode),value},M.core_availability.Item.prototype.fillErrors=function(e){var t=e.length;this.plugin?this.plugin.fillErrors(e,this.pluginNode):e.push("core_availability:item_unknowntype");var n=this.node.one("> .label-warning");e.length!==t&&!n.get("firstChild")?n.appendChild(document.createTextNode(M.util -.get_string("invalid","availability"))):e.length===t&&n.get("firstChild")&&n.get("firstChild").remove()},M.core_availability.Item.prototype.renumber=function(e){var t={number:e};this.plugin?t.type=M.util.get_string("title","availability_"+this.pluginType):t.type="["+this.pluginType+"]",t.number=e+":";var n=M.util.get_string("itemheading","availability",t);this.node.one("> h3").set("innerHTML",n)},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(t,n){this.individual=t,this.span=e.Node.create('');var r=e.Node.create("");this.span.appendChild(r);var i=t?"_individual":"_all",s=function(){var e=M.util.get_string("hidden"+i,"availability");r.set("src",M.util.image_url("i/show","core")),r.set("alt",e),this.span.set("title",e+" \u2022 "+M.util.get_string("show_verb","availability"))},o=function(){var e=M.util.get_string("shown"+i,"availability");r.set("src",M.util.image_url("i/hide","core")),r.set("alt",e),this.span.set("title",e+" \u2022 "+M.util.get_string("hide_verb","availability"))};n?o.call(this):s.call(this);var u=function(e){e.preventDefault(),this.isHidden()?o.call(this):s.call(this),M.core_availability.form.update()};this.span.on("click",u,this),this.span.on("key",u,"up:32",this),this.span.on("key",function(e){e.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 e=this.individual?"_individual":"_all",t=M.util.get_string("hidden"+e,"availability");return this.span.one("img").get("alt")===t},M.core_availability.DeleteIcon=function(t){this.span=e.Node.create('');var n=e.Node.create(''+M.util.get_string(');this.span.appendChild(n);var r=function(e){e.preventDefault(),M.core_availability.form.rootList.deleteDescendant(t),M.core_availability.form.rootList.renumber()};this.span.on("click",r,this),this.span.on("key",r,"up:32",this),this.span.on("key",function(e){e.preventDefault()},"down:32",this)},M.core_availability.DeleteIcon.prototype.span=null},"@VERSION@",{requires:["base","node","event","panel","moodle-core-notification-dialogue","json"]}); +YUI.add("moodle-core_availability-form",function(e,t){M.core_availability=M.core_availability||{},M.core_availability.form={plugins:{},field:null,mainDiv:null,rootList:null,idCounter:0,restrictByGroup:null,init:function(t){for(var n in t){var r=t[n],i=M[r[0]].form;i.init.apply(i,r)}this.field=e.one("#id_availabilityconditionsjson"),this.field.setAttribute("aria-hidden","true"),this.mainDiv=e.Node.create(''),this.field.insert(this.mainDiv,"after");var s=this.field.get("value"),o=null;if(s!=="")try{o=e.JSON.parse(s)}catch(u){this.field.set("value","")}this.rootList=new M.core_availability.List(o,!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=e.one("#restrictbygroup");if(this.restrictByGroup){this.restrictByGroup.on("click",this.addRestrictByGroup,this);var a=e.one("#id_groupmode"),f=e.one("#id_groupingid");a&&a.on("change",this.updateRestrictByGroup,this),f&&f.on("change",this.updateRestrictByGroup,this),this.updateRestrictByGroup()}},update:function(){var t=this.rootList.getValue(),n=[];this.rootList.fillErrors(n),n.length!==0&&(t.errors=n),this.field.set("value",e.JSON.stringify(t)),this.updateRestrictByGroup()},updateRestrictByGroup:function(){if(!this.restrictByGroup)return;if(this.rootList.getValue().op!=="&"){this.restrictByGroup.set("disabled",!0);return}var t=this.rootList.hasItemOfType("group")||this.rootList.hasItemOfType("grouping");if(t){this.restrictByGroup.set("disabled",!0);return}var n=e.one("#id_groupmode"),r=e.one("#id_groupingid");if((!n||Number(n.get("value"))===0)&&(!r||Number(r.get("value"))===0)){this.restrictByGroup.set("disabled",!0);return}this.restrictByGroup.set("disabled",!1)},addRestrictByGroup:function(t){t.preventDefault();var n=e.one("#id_groupingid"),r;n&&Number(n.get("value"))!==0?r=new M.core_availability.Item({type:"grouping",id:Number(n.get("value"))},!0):r=new M.core_availability.Item({type:"group"},!0),this.rootList.addChild(r),this.update(),this.rootList.renumber(),this.rootList.updateHtml()}},M.core_availability.plugin={allowAdd:!1,init:function(e,t,n){var r=e.replace(/^availability_/,"");this.allowAdd=t,M.core_availability.form.plugins[r]=this,this.initInner.apply(this,n)},initInner:function(){},getNode:function(){throw"getNode not implemented"},fillValue:function(){throw"fillValue not implemented"},fillErrors:function(){},focusAfterAdd:function(e){var t=e.one("input:not([disabled]),select:not([disabled])");t.focus()}},M.core_availability.List=function(t,n,r){this.children=[],n!==undefined&&(this.root=n),this.node=e.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")+"
    "+'
    '),n||this.node.addClass("availability-childlist"),this.inner=this.node.one("> .availability-inner");var i=!0;n?(t&&t.show!==undefined&&(i=t.show),this.eyeIcon=new M.core_availability.EyeIcon(!1,i),this.node.one(".availability-header").get("firstChild").insert(this.eyeIcon.span,"before")):r&&(t&&t.showc!==undefined&&(i=t.showc),this.eyeIcon=new M.core_availability.EyeIcon(!1,i),this.inner.insert(this.eyeIcon.span,"before"));if(!n){var s=new M.core_availability.DeleteIcon(this),o=this.node.one(".availability-none");o.appendChild(document.createTextNode(" ")),o.appendChild(s.span),o.appendChild(e.Node.create(''+M.util.get_string("invalid","availability")+""))}var u=e.Node.create('");u.on("click",function(){this.clickAdd()},this),this.node.one("div.availability-button").appendChild(u);if(t){switch(t.op){case"&":case"|":this.node.one(".availability-neg").set("value","");break;case"!&":case"!|":this.node.one(".availability-neg").set("value","!")}switch(t.op){case"&":case"!&":this.node.one(".availability-op").set("value","&");break;case"|":case"!|":this.node.one(".availability-op").set("value","|")}for(var a=0;a0&&this.inner.one(".availability-children").appendChild(e.Node.create('
    ')),this.children.push(t),this.inner.one(".availability-children").appendChild(t.node)},M.core_availability.List.prototype.focusAfterAdd=function(){this.inner.one("button").focus()},M.core_availability.List.prototype.isIndividualShowIcons=function(){if(!this.root)throw"Can only call this on root list";var e=this.node.one(".availability-neg").get("value")==="!",t=this.node.one(".availability-op").get("value")==="|";return!e&&!t||e&&t},M.core_availability.List.prototype.renumber=function(e){var t={count:this.children.length},n;e===undefined?(t.number="",n=""):(t.number=e+":",n=e+".");var r=M.util.get_string("setheading","availability",t);this.node.one("> h3").set("innerHTML",r);for(var i=0;i0?(this.inner.one("> .availability-children").removeAttribute("aria-hidden"),this.inner.one("> .availability-none").setAttribute("aria-hidden","true"),this.inner.one("> .availability-header").removeAttribute("aria-hidden"),this.children.length>1?(this.inner.one(".availability-single").setAttribute("aria-hidden","true"),this.inner.one(".availability-multi").removeAttribute("aria-hidden")):(this.inner.one(".availability-single").removeAttribute("aria-hidden"),this.inner.one(".availability-multi").setAttribute("aria-hidden","true"))):(this.inner.one("> .availability-children").setAttribute("aria-hidden","true"),this.inner.one("> .availability-none").removeAttribute("aria-hidden"),this.inner.one("> .availability-header").setAttribute("aria-hidden","true"));if(this.root){var e=this.isIndividualShowIcons();for(var t=0;t .availability-children > .availability-connector span.label").each(function(e){e.set("innerHTML",r)})},M.core_availability.List.prototype.deleteDescendant=function(e){for(var t=0;t0&&(r.previous(".availability-connector")?r.previous(".availability-connector").remove():r.next(".availability-connector").remove()),this.inner.one("> .availability-children").removeChild(r),M.core_availability.form.update(),this.updateHtml(),this.inner.one("> .availability-button").one("button").focus(),!0}if(n instanceof M.core_availability.List){var i=n.deleteDescendant(e);if(i)return!0}}return!1},M.core_availability.List.prototype.clickAdd=function(){var t=e.Node.create('
      "),n=t.one("button"),r={dialog:null},i=t.one("ul"),s,o,u,a;for(var f in M.core_availability.form.plugins){if(!M.core_availability.form.plugins[f].allowAdd)continue;s=e.Node.create('
    • '),o="availability_addrestriction_"+f,u=e.Node.create('"),u.on("click",this.getAddHandler(f,r),this),s.appendChild(u),a=e.Node.create('"),s.appendChild(a),i.appendChild(s)}s=e.Node.create('
    • '),o="availability_addrestriction_list_",u=e.Node.create('"),u.on("click",this.getAddHandler(null,r),this),s.appendChild(u),a=e.Node.create('"),s.appendChild(a),i.appendChild(s);var l={headerContent:M.util.get_string("addrestriction","availability"),bodyContent:t,additionalBaseClass:"availability-dialogue",draggable:!0,modal:!0,closeButton:!1,width:"450px"};r.dialog=new M.core.dialogue(l),r.dialog.show(),n.on("click",function(){r.dialog.destroy(),this.inner.one("> .availability-button").one("button").focus()},this)},M.core_availability.List.prototype.getAddHandler=function(e,t){return function(){e?newItem=new M.core_availability.Item({type:e,creating:!0},this.root):newItem=new M.core_availability.List({c:[],showc:!0},!1,this.root),this.addChild(newItem),M.core_availability.form.update(),M.core_availability.form.rootList.renumber(),this.updateHtml(),t.dialog.destroy(),newItem.focusAfterAdd()}},M.core_availability.List.prototype.getValue=function(){var e={};e.op=this.node.one(".availability-neg").get("value")+this.node.one(".availability-op").get("value"),e.c=[];var t;for(t=0;t'+M.util.get_string("missingplugin","availability")+"")):(this.plugin=M.core_availability.form.plugins[t.type],this.pluginNode=this.plugin.getNode(t),this.pluginNode.addClass("availability_"+t.type)),this.node=e.Node.create('

      ');if(n){var r=!0;t.showc!==undefined&&(r=t.showc),this.eyeIcon=new M.core_availability.EyeIcon(!0,r),this.node.appendChild(this.eyeIcon.span)}this.pluginNode.addClass("availability-plugincontrols"),this.node.appendChild(this.pluginNode);var i=new M.core_availability.DeleteIcon(this);this.node.appendChild(i.span),this.node.appendChild(document.createTextNode(" ")),this.node.appendChild(e.Node.create(''))},M.core_availability.Item.prototype.getValue=function(){return value={type:this.pluginType},this.plugin&&this.plugin.fillValue(value,this.pluginNode),value},M.core_availability.Item.prototype.fillErrors=function(e){var t=e.length;this.plugin?this.plugin.fillErrors(e,this.pluginNode):e.push("core_availability:item_unknowntype");var n=this.node.one("> .label-warning");e.length!==t&&!n.get("firstChild")?n.appendChild(document.createTextNode(M.util.get_string("invalid","availability"))):e.length===t&&n.get("firstChild")&&n.get("firstChild").remove()},M.core_availability.Item.prototype.renumber=function(e){var t={number:e};this.plugin?t.type=M.util.get_string("title","availability_"+this.pluginType):t.type="["+this.pluginType+"]",t.number=e+":";var n=M.util.get_string("itemheading","availability",t);this.node.one("> h3").set("innerHTML",n)},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(t,n){this.individual=t,this.span=e.Node.create('
      ');var r=e.Node.create("");this.span.appendChild(r);var i=t?"_individual":"_all",s=function(){var e=M.util.get_string("hidden"+i,"availability");r.set("src",M.util.image_url("i/show","core")),r.set("alt",e),this.span.set("title",e+" \u2022 "+M.util.get_string("show_verb","availability"))},o=function(){var e=M.util.get_string("shown"+i,"availability");r.set("src",M.util.image_url("i/hide","core")),r.set("alt",e),this.span.set("title",e+" \u2022 "+M.util.get_string("hide_verb","availability"))};n?o.call(this):s.call(this);var u=function(e){e.preventDefault(),this.isHidden()?o.call(this):s.call(this),M.core_availability.form.update()};this.span.on("click",u,this),this.span.on("key",u,"up:32",this),this.span.on("key",function(e){e.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 e=this.individual?"_individual":"_all",t=M.util.get_string("hidden"+e,"availability");return this.span.one("img").get("alt")===t},M.core_availability.DeleteIcon=function(t){this.span=e.Node.create('');var n=e.Node.create(''+M.util.get_string(');this.span.appendChild(n);var r=function(e){e.preventDefault(),M.core_availability.form.rootList.deleteDescendant(t),M.core_availability.form.rootList.renumber()};this.span.on("click",r,this),this.span.on("key",r,"up:32",this),this.span.on("key",function(e){e.preventDefault()},"down:32",this)},M.core_availability.DeleteIcon.prototype.span=null},"@VERSION@",{requires:["base","node","event","panel","moodle-core-notification-dialogue","json"]}); 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 1af7df270f9..61b4781bad6 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 @@ -66,6 +66,14 @@ M.core_availability.form = { */ idCounter : 0, + /** + * The 'Restrict by group' button if present. + * + * @property restrictByGroup + * @type Y.Node + */ + restrictByGroup : null, + /** * Called to initialise the system when the page loads. This method will * also call the init method for each plugin. @@ -121,6 +129,22 @@ M.core_availability.form = { this.field.ancestor('form').on('submit', function() { this.mainDiv.all('input,textarea,select').set('disabled', true); }, this); + + // If the form has group mode and/or grouping options, there is a + // 'add restriction' button there. + this.restrictByGroup = Y.one('#restrictbygroup'); + if (this.restrictByGroup) { + this.restrictByGroup.on('click', this.addRestrictByGroup, this); + var groupmode = Y.one('#id_groupmode'); + var groupingid = Y.one('#id_groupingid'); + if (groupmode) { + groupmode.on('change', this.updateRestrictByGroup, this); + } + if (groupingid) { + groupingid.on('change', this.updateRestrictByGroup, this); + } + this.updateRestrictByGroup(); + } }, /** @@ -143,6 +167,75 @@ M.core_availability.form = { // Set into hidden form field, JS-encoded. this.field.set('value', Y.JSON.stringify(jsValue)); + + // Also update the restrict by group button if present. + this.updateRestrictByGroup(); + }, + + /** + * Updates the status of the 'restrict by group' button (enables or disables + * it) based on current availability restrictions and group/grouping settings. + */ + updateRestrictByGroup : function() { + if (!this.restrictByGroup) { + return; + } + + // If the root list is anything other than the default 'and' type, disable. + if (this.rootList.getValue().op !== '&') { + this.restrictByGroup.set('disabled', true); + return; + } + + // If there's already a group restriction, disable it. + var alreadyGot = this.rootList.hasItemOfType('group') || + this.rootList.hasItemOfType('grouping'); + if (alreadyGot) { + this.restrictByGroup.set('disabled', true); + return; + } + + // If the groupmode and grouping id aren't set, disable it. + var groupmode = Y.one('#id_groupmode'); + var groupingid = Y.one('#id_groupingid'); + if ((!groupmode || Number(groupmode.get('value')) === 0) && + (!groupingid || Number(groupingid.get('value')) === 0)) { + this.restrictByGroup.set('disabled', true); + return; + } + + this.restrictByGroup.set('disabled', false); + }, + + /** + * Called when the user clicks on the 'restrict by group' button. This is + * a special case that adds a group or grouping restriction. + * + * By default this restriction is not shown which makes it similar to the + * + * @param e Button click event + */ + addRestrictByGroup : function(e) { + // If you don't prevent default, it submits the form for some reason. + e.preventDefault(); + + // Add the condition. + var groupingid = Y.one('#id_groupingid'); + var newChild; + if (groupingid && Number(groupingid.get('value')) !== 0) { + // Add a grouping restriction if one is specified. + newChild = new M.core_availability.Item( + {type : 'grouping', id : Number(groupingid.get('value'))}, true); + } else { + // Otherwise just add a group restriction. + newChild = new M.core_availability.Item({type : 'group'}, true); + } + + // Refresh HTML. + this.rootList.addChild(newChild); + this.update(); + this.rootList.renumber(); + this.rootList.updateHtml(); } }; @@ -709,6 +802,31 @@ M.core_availability.List.prototype.fillErrors = function(errors) { } }; +/** + * Checks whether the list contains any items of the given type name. + * + * @method hasItemOfType + * @param {String} pluginType Required plugin type (name) + * @return {Boolean} True if there is one + */ +M.core_availability.List.prototype.hasItemOfType = function(pluginType) { + // Check each item. + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + if (child instanceof M.core_availability.List) { + // Recursive call. + if (child.hasItemOfType(pluginType)) { + return true; + } + } else { + if (child.pluginType === pluginType) { + return true; + } + } + } + return false; +}; + /** * Eye icon for this list (null if none). * diff --git a/availability/yui/src/form/js/form.js b/availability/yui/src/form/js/form.js index f0012e4d485..c2474410dae 100644 --- a/availability/yui/src/form/js/form.js +++ b/availability/yui/src/form/js/form.js @@ -64,6 +64,14 @@ M.core_availability.form = { */ idCounter : 0, + /** + * The 'Restrict by group' button if present. + * + * @property restrictByGroup + * @type Y.Node + */ + restrictByGroup : null, + /** * Called to initialise the system when the page loads. This method will * also call the init method for each plugin. @@ -119,6 +127,22 @@ M.core_availability.form = { this.field.ancestor('form').on('submit', function() { this.mainDiv.all('input,textarea,select').set('disabled', true); }, this); + + // If the form has group mode and/or grouping options, there is a + // 'add restriction' button there. + this.restrictByGroup = Y.one('#restrictbygroup'); + if (this.restrictByGroup) { + this.restrictByGroup.on('click', this.addRestrictByGroup, this); + var groupmode = Y.one('#id_groupmode'); + var groupingid = Y.one('#id_groupingid'); + if (groupmode) { + groupmode.on('change', this.updateRestrictByGroup, this); + } + if (groupingid) { + groupingid.on('change', this.updateRestrictByGroup, this); + } + this.updateRestrictByGroup(); + } }, /** @@ -141,6 +165,75 @@ M.core_availability.form = { // Set into hidden form field, JS-encoded. this.field.set('value', Y.JSON.stringify(jsValue)); + + // Also update the restrict by group button if present. + this.updateRestrictByGroup(); + }, + + /** + * Updates the status of the 'restrict by group' button (enables or disables + * it) based on current availability restrictions and group/grouping settings. + */ + updateRestrictByGroup : function() { + if (!this.restrictByGroup) { + return; + } + + // If the root list is anything other than the default 'and' type, disable. + if (this.rootList.getValue().op !== '&') { + this.restrictByGroup.set('disabled', true); + return; + } + + // If there's already a group restriction, disable it. + var alreadyGot = this.rootList.hasItemOfType('group') || + this.rootList.hasItemOfType('grouping'); + if (alreadyGot) { + this.restrictByGroup.set('disabled', true); + return; + } + + // If the groupmode and grouping id aren't set, disable it. + var groupmode = Y.one('#id_groupmode'); + var groupingid = Y.one('#id_groupingid'); + if ((!groupmode || Number(groupmode.get('value')) === 0) && + (!groupingid || Number(groupingid.get('value')) === 0)) { + this.restrictByGroup.set('disabled', true); + return; + } + + this.restrictByGroup.set('disabled', false); + }, + + /** + * Called when the user clicks on the 'restrict by group' button. This is + * a special case that adds a group or grouping restriction. + * + * By default this restriction is not shown which makes it similar to the + * + * @param e Button click event + */ + addRestrictByGroup : function(e) { + // If you don't prevent default, it submits the form for some reason. + e.preventDefault(); + + // Add the condition. + var groupingid = Y.one('#id_groupingid'); + var newChild; + if (groupingid && Number(groupingid.get('value')) !== 0) { + // Add a grouping restriction if one is specified. + newChild = new M.core_availability.Item( + {type : 'grouping', id : Number(groupingid.get('value'))}, true); + } else { + // Otherwise just add a group restriction. + newChild = new M.core_availability.Item({type : 'group'}, true); + } + + // Refresh HTML. + this.rootList.addChild(newChild); + this.update(); + this.rootList.renumber(); + this.rootList.updateHtml(); } }; @@ -707,6 +800,31 @@ M.core_availability.List.prototype.fillErrors = function(errors) { } }; +/** + * Checks whether the list contains any items of the given type name. + * + * @method hasItemOfType + * @param {String} pluginType Required plugin type (name) + * @return {Boolean} True if there is one + */ +M.core_availability.List.prototype.hasItemOfType = function(pluginType) { + // Check each item. + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + if (child instanceof M.core_availability.List) { + // Recursive call. + if (child.hasItemOfType(pluginType)) { + return true; + } + } else { + if (child.pluginType === pluginType) { + return true; + } + } + } + return false; +}; + /** * Eye icon for this list (null if none). * diff --git a/course/moodleform_mod.php b/course/moodleform_mod.php index 33014d668ac..6a3a845ff3e 100644 --- a/course/moodleform_mod.php +++ b/course/moodleform_mod.php @@ -436,6 +436,14 @@ abstract class moodleform_mod extends moodleform { } if (!empty($CFG->enableavailability)) { + // Add special button to end of previous section if groups/groupings + // are enabled. + if ($this->_features->groups || $this->_features->groupings) { + $mform->addElement('static', 'restrictgroupbutton', '', + html_writer::tag('button', get_string('restrictbygroup', 'availability'), + array('id' => 'restrictbygroup', 'disabled' => 'disabled'))); + } + // Availability field. This is just a textarea; the user interface // interaction is all implemented in JavaScript. $mform->addElement('header', 'availabilityconditionsheader', diff --git a/lang/en/availability.php b/lang/en/availability.php index 9951d3ff9d1..0ec9ace3558 100644 --- a/lang/en/availability.php +++ b/lang/en/availability.php @@ -62,5 +62,6 @@ $string['manageplugins'] = 'Manage restrictions'; $string['missingplugin'] = 'Missing restriction plugin'; $string['or'] = 'or'; $string['restrictaccess'] = 'Restrict access'; +$string['restrictbygroup'] = 'Add group/grouping access restriction'; $string['setheading'] = '{$a->number} Set of {$a->count} restriction(s)'; $string['unknowncondition'] = 'Unknown condition (deleted condition plugin)';