Merge branch 'MDL-57244-master-text' of git://github.com/abgreeve/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Thu, 2 Aug 2018 17:16:58 +0000 (19:16 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Thu, 2 Aug 2018 17:16:58 +0000 (19:16 +0200)
lib/moodlelib.php
lib/tests/moodlelib_test.php
lib/upgrade.txt
user/index.php
user/selector/lib.php
user/tests/behat/filter_participants_showall.feature
user/tests/fixtures/testable_user_selector.php [new file with mode: 0644]
user/tests/userselector_test.php [new file with mode: 0644]

index 79f91fd..dc52ee5 100644 (file)
@@ -3636,25 +3636,38 @@ function get_extra_user_fields($context, $already = array()) {
         return array();
     }
 
-    // Split showuseridentity on comma.
-    if (empty($CFG->showuseridentity)) {
-        // Explode gives wrong result with empty string.
-        $extra = array();
-    } else {
-        $extra =  explode(',', $CFG->showuseridentity);
-    }
-    $renumber = false;
+    // Split showuseridentity on comma (filter needed in case the showuseridentity is empty).
+    $extra = array_filter(explode(',', $CFG->showuseridentity));
+
     foreach ($extra as $key => $field) {
         if (in_array($field, $already)) {
             unset($extra[$key]);
-            $renumber = true;
         }
     }
-    if ($renumber) {
-        // For consistency, if entries are removed from array, renumber it
-        // so they are numbered as you would expect.
-        $extra = array_merge($extra);
+
+    // If the identity fields are also among hidden fields, make sure the user can see them.
+    $hiddenfields = array_filter(explode(',', $CFG->hiddenuserfields));
+    $hiddenidentifiers = array_intersect($extra, $hiddenfields);
+
+    if ($hiddenidentifiers) {
+        if ($context->get_course_context(false)) {
+            // We are somewhere inside a course.
+            $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
+
+        } else {
+            // We are not inside a course.
+            $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
+        }
+
+        if (!$canviewhiddenuserfields) {
+            // Remove hidden identifiers from the list.
+            $extra = array_diff($extra, $hiddenidentifiers);
+        }
     }
+
+    // Re-index the entries.
+    $extra = array_values($extra);
+
     return $extra;
 }
 
index 80dc799..c429fb8 100644 (file)
@@ -1465,16 +1465,14 @@ class core_moodlelib_testcase extends advanced_testcase {
         $this->assertNull(get_user_preferences('test_pref'));
     }
 
-    public function test_get_extra_user_fields() {
+    /**
+     * Test essential features implementation of {@link get_extra_user_fields()} as the admin user with all capabilities.
+     */
+    public function test_get_extra_user_fields_essentials() {
         global $CFG, $USER, $DB;
         $this->resetAfterTest();
 
         $this->setAdminUser();
-
-        // It would be really nice if there were a way to 'mock' has_capability
-        // checks (either to return true or false) but as there is not, this
-        // test doesn't test the capability check. Presumably, anyone running
-        // unit tests will have the capability.
         $context = context_system::instance();
 
         // No fields.
@@ -1502,6 +1500,121 @@ class core_moodlelib_testcase extends advanced_testcase {
         $this->assertEquals(array('zombie'), get_extra_user_fields($context, array('frog')));
     }
 
+    /**
+     * Prepare environment for couple of tests related to permission checks in {@link get_extra_user_fields()}.
+     *
+     * @return stdClass
+     */
+    protected function environment_for_get_extra_user_fields_tests() {
+        global $CFG, $DB;
+
+        $CFG->showuseridentity = 'idnumber,country,city';
+        $CFG->hiddenuserfields = 'country,city';
+
+        $env = new stdClass();
+
+        $env->course = $this->getDataGenerator()->create_course();
+        $env->coursecontext = context_course::instance($env->course->id);
+
+        $env->teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+        $env->studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $env->managerrole = $DB->get_record('role', array('shortname' => 'manager'));
+
+        $env->student = $this->getDataGenerator()->create_user();
+        $env->teacher = $this->getDataGenerator()->create_user();
+        $env->manager = $this->getDataGenerator()->create_user();
+
+        role_assign($env->studentrole->id, $env->student->id, $env->coursecontext->id);
+        role_assign($env->teacherrole->id, $env->teacher->id, $env->coursecontext->id);
+        role_assign($env->managerrole->id, $env->manager->id, SYSCONTEXTID);
+
+        return $env;
+    }
+
+    /**
+     * No identity fields shown to student user (no permission to view identity fields).
+     */
+    public function test_get_extra_user_fields_no_access() {
+
+        $this->resetAfterTest();
+        $env = $this->environment_for_get_extra_user_fields_tests();
+        $this->setUser($env->student);
+
+        $this->assertEquals(array(), get_extra_user_fields($env->coursecontext));
+        $this->assertEquals(array(), get_extra_user_fields(context_system::instance()));
+    }
+
+    /**
+     * Teacher can see students' identity fields only within the course.
+     */
+    public function test_get_extra_user_fields_course_only_access() {
+
+        $this->resetAfterTest();
+        $env = $this->environment_for_get_extra_user_fields_tests();
+        $this->setUser($env->teacher);
+
+        $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields($env->coursecontext));
+        $this->assertEquals(array(), get_extra_user_fields(context_system::instance()));
+    }
+
+    /**
+     * Teacher can be prevented from seeing students' identity fields even within the course.
+     */
+    public function test_get_extra_user_fields_course_prevented_access() {
+
+        $this->resetAfterTest();
+        $env = $this->environment_for_get_extra_user_fields_tests();
+        $this->setUser($env->teacher);
+
+        assign_capability('moodle/course:viewhiddenuserfields', CAP_PREVENT, $env->teacherrole->id, $env->coursecontext->id);
+        $this->assertEquals(array('idnumber'), get_extra_user_fields($env->coursecontext));
+    }
+
+    /**
+     * Manager can see students' identity fields anywhere.
+     */
+    public function test_get_extra_user_fields_anywhere_access() {
+
+        $this->resetAfterTest();
+        $env = $this->environment_for_get_extra_user_fields_tests();
+        $this->setUser($env->manager);
+
+        $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields($env->coursecontext));
+        $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields(context_system::instance()));
+    }
+
+    /**
+     * Manager can be prevented from seeing hidden fields outside the course.
+     */
+    public function test_get_extra_user_fields_schismatic_access() {
+
+        $this->resetAfterTest();
+        $env = $this->environment_for_get_extra_user_fields_tests();
+        $this->setUser($env->manager);
+
+        assign_capability('moodle/user:viewhiddendetails', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
+        $this->assertEquals(array('idnumber'), get_extra_user_fields(context_system::instance()));
+        // Note that inside the course, the manager can still see the hidden identifiers as this is currently
+        // controlled by a separate capability for legacy reasons.
+        $this->assertEquals(array('idnumber', 'country', 'city'), get_extra_user_fields($env->coursecontext));
+    }
+
+    /**
+     * Two capabilities must be currently set to prevent manager from seeing hidden fields.
+     */
+    public function test_get_extra_user_fields_hard_to_prevent_access() {
+
+        $this->resetAfterTest();
+        $env = $this->environment_for_get_extra_user_fields_tests();
+        $this->setUser($env->manager);
+
+        assign_capability('moodle/user:viewhiddendetails', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
+        assign_capability('moodle/course:viewhiddenuserfields', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
+
+        $this->assertEquals(array('idnumber'), get_extra_user_fields(context_system::instance()));
+        $this->assertEquals(array('idnumber'), get_extra_user_fields($env->coursecontext));
+    }
+
     public function test_get_extra_user_fields_sql() {
         global $CFG, $USER, $DB;
         $this->resetAfterTest();
index 31c7834..fb3dee5 100644 (file)
@@ -51,6 +51,10 @@ information provided here is intended especially for developers.
 * coursecat::get() now has optional $user parameter.
 * coursecat::is_uservisible() now has optional $user parameter.
 * Removed the lib/form/submitlink.php element which was deprecated in 3.2.
+* The user_selector classes do not support custom list of extra identity fields any more. They obey the configured user
+  policy and respect the privacy setting made by site administrators. The list of user identifiers should never be
+  hard-coded. Instead, the setting $CFG->showuseridentity should be always respected, which has always been the default
+  behaviour (MDL-59847).
 
 === 3.5 ===
 
index 958cb05..f36491c 100644 (file)
@@ -225,8 +225,8 @@ echo $renderer->unified_filter($course, $context, $filtersapplied, $baseurl);
 echo '<div class="userlist">';
 
 // Add filters to the baseurl after creating unified_filter to avoid losing them.
-foreach ($filtersapplied as $filter) {
-    $baseurl->param('unified-filters[]', $filter);
+foreach (array_unique($filtersapplied) as $filterix => $filter) {
+    $baseurl->param('unified-filters[' . $filterix . ']', $filter);
 }
 $participanttable = new \core_user\participants_table($course->id, $groupid, $lastaccess, $roleid, $enrolid, $status,
     $searchkeywords, $bulkoperations, $selectall);
index ee0619a..34a553a 100644 (file)
@@ -105,14 +105,17 @@ abstract class user_selector_base {
             $this->accesscontext = context_system::instance();
         }
 
+        // Check if some legacy code tries to override $CFG->showuseridentity.
         if (isset($options['extrafields'])) {
-            $this->extrafields = $options['extrafields'];
-        } else if (!empty($CFG->showuseridentity) &&
-                has_capability('moodle/site:viewuseridentity', $this->accesscontext)) {
-            $this->extrafields = explode(',', $CFG->showuseridentity);
-        } else {
-            $this->extrafields = array();
+            debugging('The user_selector classes do not support custom list of extra identity fields any more. '.
+                'Instead, the user identity fields defined by the site administrator will be used to respect '.
+                'the configured privacy setting.', DEBUG_DEVELOPER);
+            unset($options['extrafields']);
         }
+
+        // Populate the list of additional user identifiers to display.
+        $this->extrafields = get_extra_user_fields($this->accesscontext);
+
         if (isset($options['exclude']) && is_array($options['exclude'])) {
             $this->exclude = $options['exclude'];
         }
index 7f1ac9a..6cec23e 100644 (file)
@@ -39,7 +39,7 @@ Feature: Course participants can be filtered to display all the users
     And the following "course enrolments" exist:
       | user      | course | role           | status | timeend       |
       | student1  | C1     | student        |    0   |               |
-      | student2  | C1     | student        |    1   |               |
+      | student2  | C1     | student        |    0   |               |
       | student3  | C1     | student        |    0   |               |
       | student4  | C1     | student        |    0   |               |
       | student5  | C1     | student        |    0   |               |
@@ -61,7 +61,7 @@ Feature: Course participants can be filtered to display all the users
       | student21 | C1     | student        |    0   |               |
       | student22 | C1     | student        |    0   |               |
       | student23 | C1     | student        |    0   |               |
-      | student24 | C1     | student        |    0   |               |
+      | student24 | C1     | student        |    1   |               |
       | student1  | C2     | student        |    0   |               |
       | student2  | C2     | student        |    0   |               |
       | student3  | C2     | student        |    0   |               |
@@ -88,3 +88,20 @@ Feature: Course participants can be filtered to display all the users
     Then I should see "Role: Student"
     And I should see "Number of participants: 24" in the "//div[@class='userlist']" "xpath_element"
     And I should see "Show 20 per page"
+
+  @javascript
+  Scenario: Apply more than one filter and show all users
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    When I open the autocomplete suggestions list
+    And I click on "Role: Student" item in the autocomplete list
+    And I open the autocomplete suggestions list
+    And I click on "Status: Active" item in the autocomplete list
+    And I click on "Show all 23" "link"
+    Then I should see "Role: Student"
+    And I should see "Status: Active"
+    And I should see "Number of participants: 23" in the "//div[@class='userlist']" "xpath_element"
+    And I should see "Student 1"
+    And I should not see "Student 24"
+    And I should see "Show 20 per page"
diff --git a/user/tests/fixtures/testable_user_selector.php b/user/tests/fixtures/testable_user_selector.php
new file mode 100644 (file)
index 0000000..73bdf98
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+// This file is part of Moodle - https://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provides {@link testable_user_selector} class.
+ *
+ * @package     core_user
+ * @subpackage  fixtures
+ * @category    test
+ * @copyright   2018 David Mudrák <david@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Testable subclass of the user selector base class.
+ *
+ * @copyright 2018 David Mudrák <david@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class testable_user_selector extends user_selector_base {
+
+    /**
+     * Basic implementation of the users finder.
+     *
+     * @param string $search
+     * @return array of (string)optgroupname => array of users
+     */
+    public function find_users($search) {
+        global $DB;
+
+        list($wherecondition, $whereparams) = $this->search_sql($search, 'u');
+        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
+        $params = array_merge($whereparams, $sortparams);
+        $fields = $this->required_fields_sql('u');
+
+        $sql = "SELECT $fields
+                  FROM {user} u
+                 WHERE $wherecondition
+              ORDER BY $sort";
+
+        $found = $DB->get_records_sql($sql, $params);
+
+        if (empty($found)) {
+            return [];
+        }
+
+        return [get_string('potusers', 'core_role') => $found];
+    }
+
+}
diff --git a/user/tests/userselector_test.php b/user/tests/userselector_test.php
new file mode 100644 (file)
index 0000000..fcb39b1
--- /dev/null
@@ -0,0 +1,266 @@
+<?php
+// This file is part of Moodle - https://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provides {@link core_user_selector_testcase} class.
+ *
+ * @package     core_user
+ * @category    test
+ * @copyright   2018 David Mudrák <david@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot.'/user/selector/lib.php');
+require_once($CFG->dirroot.'/user/tests/fixtures/testable_user_selector.php');
+
+/**
+ * Tests for the implementation of {@link user_selector_base} class.
+ *
+ * @copyright 2018 David Mudrák <david@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_user_selector_testcase extends advanced_testcase {
+
+    /**
+     * Setup the environment for the tests.
+     */
+    protected function setup_hidden_siteidentity() {
+        global $CFG, $DB;
+
+        $CFG->showuseridentity = 'idnumber,country,city';
+        $CFG->hiddenuserfields = 'country,city';
+
+        $env = new stdClass();
+
+        $env->student = $this->getDataGenerator()->create_user();
+        $env->teacher = $this->getDataGenerator()->create_user();
+        $env->manager = $this->getDataGenerator()->create_user();
+
+        $env->course = $this->getDataGenerator()->create_course();
+        $env->coursecontext = context_course::instance($env->course->id);
+
+        $env->teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+        $env->studentrole = $DB->get_record('role', array('shortname' => 'student'));
+        $env->managerrole = $DB->get_record('role', array('shortname' => 'manager'));
+
+        role_assign($env->studentrole->id, $env->student->id, $env->coursecontext->id);
+        role_assign($env->teacherrole->id, $env->teacher->id, $env->coursecontext->id);
+        role_assign($env->managerrole->id, $env->manager->id, SYSCONTEXTID);
+
+        return $env;
+    }
+
+    /**
+     * No identity fields are not shown to student user (no permission to view identity fields).
+     */
+    public function test_hidden_siteidentity_fields_no_access() {
+        $this->resetAfterTest();
+        $env = $this->setup_hidden_siteidentity();
+        $this->setUser($env->student);
+
+        $selector = new testable_user_selector('test');
+
+        foreach ($selector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectNotHasAttribute('idnumber', $user);
+                $this->assertObjectNotHasAttribute('country', $user);
+                $this->assertObjectNotHasAttribute('city', $user);
+            }
+        }
+    }
+
+    /**
+     * Teacher can see students' identity fields only within the course.
+     */
+    public function test_hidden_siteidentity_fields_course_only_access() {
+        $this->resetAfterTest();
+        $env = $this->setup_hidden_siteidentity();
+        $this->setUser($env->teacher);
+
+        $systemselector = new testable_user_selector('test');
+        $courseselector = new testable_user_selector('test', ['accesscontext' => $env->coursecontext]);
+
+        foreach ($systemselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectNotHasAttribute('idnumber', $user);
+                $this->assertObjectNotHasAttribute('country', $user);
+                $this->assertObjectNotHasAttribute('city', $user);
+            }
+        }
+
+        foreach ($courseselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectHasAttribute('idnumber', $user);
+                $this->assertObjectHasAttribute('country', $user);
+                $this->assertObjectHasAttribute('city', $user);
+            }
+        }
+    }
+
+    /**
+     * Teacher can be prevented from seeing students' identity fields even within the course.
+     */
+    public function test_hidden_siteidentity_fields_course_prevented_access() {
+        $this->resetAfterTest();
+        $env = $this->setup_hidden_siteidentity();
+        $this->setUser($env->teacher);
+
+        assign_capability('moodle/course:viewhiddenuserfields', CAP_PREVENT, $env->teacherrole->id, $env->coursecontext->id);
+
+        $courseselector = new testable_user_selector('test', ['accesscontext' => $env->coursecontext]);
+
+        foreach ($courseselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectHasAttribute('idnumber', $user);
+                $this->assertObjectNotHasAttribute('country', $user);
+                $this->assertObjectNotHasAttribute('city', $user);
+            }
+        }
+    }
+
+    /**
+     * Manager can see students' identity fields anywhere.
+     */
+    public function test_hidden_siteidentity_fields_anywhere_access() {
+        $this->resetAfterTest();
+        $env = $this->setup_hidden_siteidentity();
+        $this->setUser($env->manager);
+
+        $systemselector = new testable_user_selector('test');
+        $courseselector = new testable_user_selector('test', ['accesscontext' => $env->coursecontext]);
+
+        foreach ($systemselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectHasAttribute('idnumber', $user);
+                $this->assertObjectHasAttribute('country', $user);
+                $this->assertObjectHasAttribute('city', $user);
+            }
+        }
+
+        foreach ($courseselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectHasAttribute('idnumber', $user);
+                $this->assertObjectHasAttribute('country', $user);
+                $this->assertObjectHasAttribute('city', $user);
+            }
+        }
+    }
+
+    /**
+     * Manager can be prevented from seeing hidden fields outside the course.
+     */
+    public function test_hidden_siteidentity_fields_schismatic_access() {
+        $this->resetAfterTest();
+        $env = $this->setup_hidden_siteidentity();
+        $this->setUser($env->manager);
+
+        // Revoke the capability to see hidden user fields outside the course.
+        // Note that inside the course, the manager can still see the hidden identifiers as this is currently
+        // controlled by a separate capability for legacy reasons. This is counter-intuitive behaviour and is
+        // likely to be fixed in MDL-51630.
+        assign_capability('moodle/user:viewhiddendetails', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
+
+        $systemselector = new testable_user_selector('test');
+        $courseselector = new testable_user_selector('test', ['accesscontext' => $env->coursecontext]);
+
+        foreach ($systemselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectHasAttribute('idnumber', $user);
+                $this->assertObjectNotHasAttribute('country', $user);
+                $this->assertObjectNotHasAttribute('city', $user);
+            }
+        }
+
+        foreach ($courseselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectHasAttribute('idnumber', $user);
+                $this->assertObjectHasAttribute('country', $user);
+                $this->assertObjectHasAttribute('city', $user);
+            }
+        }
+    }
+
+    /**
+     * Two capabilities must be currently set to prevent manager from seeing hidden fields.
+     */
+    public function test_hidden_siteidentity_fields_hard_to_prevent_access() {
+        $this->resetAfterTest();
+        $env = $this->setup_hidden_siteidentity();
+        $this->setUser($env->manager);
+
+        assign_capability('moodle/user:viewhiddendetails', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
+        assign_capability('moodle/course:viewhiddenuserfields', CAP_PREVENT, $env->managerrole->id, SYSCONTEXTID, true);
+
+        $systemselector = new testable_user_selector('test');
+        $courseselector = new testable_user_selector('test', ['accesscontext' => $env->coursecontext]);
+
+        foreach ($systemselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectHasAttribute('idnumber', $user);
+                $this->assertObjectNotHasAttribute('country', $user);
+                $this->assertObjectNotHasAttribute('city', $user);
+            }
+        }
+
+        foreach ($courseselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectHasAttribute('idnumber', $user);
+                $this->assertObjectNotHasAttribute('country', $user);
+                $this->assertObjectNotHasAttribute('city', $user);
+            }
+        }
+    }
+
+    /**
+     * For legacy reasons, user selectors supported ability to override $CFG->showuseridentity.
+     *
+     * However, this was found as violating the principle of respecting site privacy settings. So the feature has been
+     * dropped in Moodle 3.6.
+     */
+    public function test_hidden_siteidentity_fields_explicit_extrafields() {
+        $this->resetAfterTest();
+        $env = $this->setup_hidden_siteidentity();
+        $this->setUser($env->manager);
+
+        $implicitselector = new testable_user_selector('test');
+        $explicitselector = new testable_user_selector('test', ['extrafields' => ['email', 'department']]);
+
+        $this->assertDebuggingCalled();
+
+        foreach ($implicitselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectHasAttribute('idnumber', $user);
+                $this->assertObjectHasAttribute('country', $user);
+                $this->assertObjectHasAttribute('city', $user);
+                $this->assertObjectNotHasAttribute('email', $user);
+                $this->assertObjectNotHasAttribute('department', $user);
+            }
+        }
+
+        foreach ($explicitselector->find_users('') as $found) {
+            foreach ($found as $user) {
+                $this->assertObjectHasAttribute('idnumber', $user);
+                $this->assertObjectHasAttribute('country', $user);
+                $this->assertObjectHasAttribute('city', $user);
+                $this->assertObjectNotHasAttribute('email', $user);
+                $this->assertObjectNotHasAttribute('department', $user);
+            }
+        }
+    }
+}