diff --git a/classes/condition.php b/classes/condition.php
index 1298853..fefa963 100644
--- a/classes/condition.php
+++ b/classes/condition.php
@@ -41,7 +41,7 @@ class condition extends \core_availability\condition {
public function __construct($structure) {
$this->userids = [];
if (isset($structure->userids)) {
- $this->userids = $structure->userids;
+ $this->userids = (array)$structure->userids;
}
// Ensure compatibility with old version.
if (isset($structure->userid)) {
diff --git a/tests/availability_user_condition_testcase.php b/tests/availability_user_condition_testcase.php
index 5b7cc05..41e8f92 100644
--- a/tests/availability_user_condition_testcase.php
+++ b/tests/availability_user_condition_testcase.php
@@ -27,25 +27,36 @@
/**
* Testcase for availability_user
*/
-class availability_user_condition_testcase extends \advanced_testcase {
- /** @var \core_availability\mock_info */
- protected \core_availability\mock_info $info;
- /** @var \core_availability\capability_checker */
- protected \core_availability\capability_checker $capabilitychecker;
- /** @var condition A condition using the old structure (single userid) */
- protected condition $cond;
- /** @var condition A condition using the new structure (multiple userids) */
- protected condition $newcond;
- /** @var condition A condition using multiple userids */
- protected condition $multiplecond;
- /** @var \stdClass */
- protected \stdClass $user1;
- /** @var \stdClass */
- protected \stdClass $user2;
- /** @var \stdClass */
- protected \stdClass $user3;
- /** @var \stdClass */
- protected \stdClass $user4;
+class availability_user_condition_testcase extends advanced_testcase {
+ /** @var $info */
+ protected $info;
+
+ /** @var $capabilitychecker */
+ protected $capabilitychecker;
+
+ /** @var $user1 */
+ protected $user1;
+
+ /** @var $user2 */
+ protected $user2;
+
+ /** @var $user3 */
+ protected $user3;
+
+ /** @var $user4 */
+ protected $user4;
+
+ /** @var $cond */
+ protected $cond;
+
+ /** @var $newcond */
+ protected $newcond;
+
+ /** @var $multiplecond */
+ protected $multiplecond;
+
+ /** @var $emptycond */
+ protected $emptycond;
/**
* Load necessary libs
@@ -96,6 +107,10 @@ public function setUp(): void {
$multiplestructure = new \stdClass();
$multiplestructure->userids = [$this->user1->id, $this->user2->id, $this->user3->id];
$this->multiplecond = new condition($multiplestructure);
+
+ $emptystructure = new stdClass();
+ $emptystructure->userids = [];
+ $this->emptycond = new condition($emptystructure);
}
/**
@@ -291,4 +306,28 @@ public function test_users_multiple_filter_not() {
$this->assertFalse(in_array($this->user3->id, $filtereduserids));
$this->assertTrue(in_array($this->user4->id, $filtereduserids));
}
+
+ /**
+ * Check availability logic when no users are selected.
+ *
+ * @return void
+ */
+ public function test_empty_user_list_availability() {
+ global $USER;
+ $this->setUser($this->user1);
+ $this->assertFalse($this->emptycond->is_available(false, $this->info, true, $USER->id));
+ $this->assertTrue($this->emptycond->is_available(true, $this->info, true, $USER->id));
+ }
+
+ /**
+ * Ensure full description does not fail when no users are selected.
+ *
+ * @return void
+ */
+ public function test_empty_user_list_full_description() {
+ $this->assertEquals(
+ get_string('requires_certain_user', 'availability_user'),
+ $this->emptycond->get_description(true, false, $this->info)
+ );
+ }
}
diff --git a/tests/behat/availability_user.feature b/tests/behat/availability_user.feature
new file mode 100644
index 0000000..ba35867
--- /dev/null
+++ b/tests/behat/availability_user.feature
@@ -0,0 +1,92 @@
+@availability @availability_user
+Feature: availability_user
+ In order to control student access to activities
+ As a teacher
+ I need to set user conditions which prevent student access
+
+ Background:
+ Given the following "courses" exist:
+ | fullname | shortname | format | numsections |
+ | Course 1 | C1 | topics | 3 |
+ And the following "users" exist:
+ | username | firstname | lastname |
+ | teacher1 | Teacher | One |
+ | student1 | Alice | Smith |
+ | student2 | Bob | Jones |
+ | student3 | Carol | White |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ | student3 | C1 | student |
+ And the following "activities" exist:
+ | activity | course | name |
+ | page | C1 | P1 |
+
+ @javascript
+ Scenario: Restricting access to a single user works correctly
+ Given I am on the "P1" "page activity editing" page logged in as "teacher1"
+ And I expand all fieldsets
+ And I click on "Add restriction..." "button"
+ And I click on "#availability_addrestriction_user" "css_element"
+ And I set the field "availability_user_userids" to "Alice Smith"
+ And I click on ".availability-item .availability-eye img" "css_element"
+ And I click on "Save and return to course" "button"
+
+ # student1 (Alice) should see P1, others should not.
+ When I am on the "Course 1" "course" page logged in as "student1"
+ Then I should see "P1" in the "region-main" "region"
+
+ When I am on the "Course 1" "course" page logged in as "student2"
+ Then I should not see "P1" in the "region-main" "region"
+
+ @javascript
+ Scenario: Selecting all users via Ctrl+A saves all of them correctly
+ # Regression test: Ctrl+A on the multi-select was broken because the plugin
+ # listened to 'click' (never fired for keyboard selection) and fillValue used
+ # a broken YUI chain get('options').get('_nodes') returning array-of-arrays.
+ Given I am on the "P1" "page activity editing" page logged in as "teacher1"
+ And I expand all fieldsets
+ And I click on "Add restriction..." "button"
+ And I click on "#availability_addrestriction_user" "css_element"
+ # Click the first option to give the select keyboard focus, then Ctrl+A.
+ And I click on "Alice Smith" "option" in the "#availability_user_userids" "css_element"
+ And I press the ctrl a key
+ And I click on ".availability-item .availability-eye img" "css_element"
+ And I click on "Save and return to course" "button"
+
+ # All three students must be allowed — none should be excluded.
+ When I am on the "Course 1" "course" page logged in as "student1"
+ Then I should see "P1" in the "region-main" "region"
+
+ When I am on the "Course 1" "course" page logged in as "student2"
+ Then I should see "P1" in the "region-main" "region"
+
+ When I am on the "Course 1" "course" page logged in as "student3"
+ Then I should see "P1" in the "region-main" "region"
+
+ # Re-open and verify all three names are still selected (save/load round-trip).
+ When I am on the "P1" "page activity editing" page logged in as "teacher1"
+ And I expand all fieldsets
+ Then the field "availability_user_userids" matches value "Bob Jones, Teacher One, Alice Smith, Carol White"
+
+ @javascript
+ Scenario: Selecting a subset of users saves the correct subset
+ Given I am on the "P1" "page activity editing" page logged in as "teacher1"
+ And I expand all fieldsets
+ And I click on "Add restriction..." "button"
+ And I click on "#availability_addrestriction_user" "css_element"
+ And I set the field "availability_user_userids" to "Alice Smith, Bob Jones"
+ And I click on ".availability-item .availability-eye img" "css_element"
+ And I click on "Save and return to course" "button"
+
+ # Alice and Bob can see P1; Carol cannot.
+ When I am on the "Course 1" "course" page logged in as "student1"
+ Then I should see "P1" in the "region-main" "region"
+
+ When I am on the "Course 1" "course" page logged in as "student2"
+ Then I should see "P1" in the "region-main" "region"
+
+ When I am on the "Course 1" "course" page logged in as "student3"
+ Then I should not see "P1" in the "region-main" "region"
diff --git a/version.php b/version.php
index 1027aa7..19646f6 100644
--- a/version.php
+++ b/version.php
@@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'availability_user';
-$plugin->release = '0.2.2';
-$plugin->version = 2024082101;
+$plugin->release = '0.4.0';
+$plugin->version = 2026050600;
$plugin->requires = 2020061500;
$plugin->maturity = MATURITY_STABLE;
diff --git a/yui/build/moodle-availability_user-form/moodle-availability_user-form-debug.js b/yui/build/moodle-availability_user-form/moodle-availability_user-form-debug.js
index a6ac176..3f040c4 100644
--- a/yui/build/moodle-availability_user-form/moodle-availability_user-form-debug.js
+++ b/yui/build/moodle-availability_user-form/moodle-availability_user-form-debug.js
@@ -41,7 +41,7 @@ M.availability_user.form.getNode = function(json) {
if (!M.availability_user.form.addedEvents) {
M.availability_user.form.addedEvents = true;
var root = Y.one('#fitem_id_availabilityconditionsjson');
- root.delegate('click', function() {
+ root.delegate('change', function() {
M.core_availability.form.update();
}, '.availability_user select');
}
@@ -50,14 +50,13 @@ M.availability_user.form.getNode = function(json) {
};
M.availability_user.form.fillValue = function(value, node) {
- var userSelect = node.one('#availability_user_userids');
- var options = userSelect.get('options').get('_nodes');
var users = [];
- options.forEach(function(o) {
- if (o.get('selected')) {
- users.push(o.get('value'));
+ var options = node.getDOMNode().querySelectorAll('select option');
+ for (var i = 0; i < options.length; i++) {
+ if (options[i].selected) {
+ users.push(options[i].value);
}
- });
+ }
value.userids = users;
};
diff --git a/yui/build/moodle-availability_user-form/moodle-availability_user-form-min.js b/yui/build/moodle-availability_user-form/moodle-availability_user-form-min.js
index 77ff80a..10ec64e 100644
--- a/yui/build/moodle-availability_user-form/moodle-availability_user-form-min.js
+++ b/yui/build/moodle-availability_user-form/moodle-availability_user-form-min.js
@@ -1 +1 @@
-YUI.add("moodle-availability_user-form",function(t,i){M.availability_user=M.availability_user||{},M.availability_user.form=t.Object(M.core_availability.plugin),M.availability_user.form.initInner=function(i){this.params=i},M.availability_user.form.getNode=function(i){var a,e,l='",a=t.Node.create(""+l+""),e=i.userids||[],i.userid&&e.push(i.userid),e.forEach(function(i){null===a.one("option[value="+i+"]")&&a.one("select").appendChild(t.Node.create('