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('