Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  declare(strict_types=1);
  18  
  19  namespace core_user\reportbuilder\datasource;
  20  
  21  use core_collator;
  22  use core_reportbuilder_testcase;
  23  use core_reportbuilder_generator;
  24  use core_reportbuilder\local\filters\boolean_select;
  25  use core_reportbuilder\local\filters\date;
  26  use core_reportbuilder\local\filters\select;
  27  use core_reportbuilder\local\filters\text;
  28  use core_reportbuilder\local\helpers\user_filter_manager;
  29  
  30  defined('MOODLE_INTERNAL') || die();
  31  
  32  global $CFG;
  33  require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
  34  
  35  /**
  36   * Unit tests for users datasource
  37   *
  38   * @package     core_user
  39   * @covers      \core_user\reportbuilder\datasource\users
  40   * @copyright   2022 Paul Holden <paulh@moodle.com>
  41   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class users_test extends core_reportbuilder_testcase {
  44  
  45  
  46      /**
  47       * Test default datasource
  48       */
  49      public function test_datasource_default(): void {
  50          $this->resetAfterTest();
  51  
  52          $user2 = $this->getDataGenerator()->create_user(['firstname' => 'Charles']);
  53          $user3 = $this->getDataGenerator()->create_user(['firstname' => 'Brian']);
  54  
  55          /** @var core_reportbuilder_generator $generator */
  56          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  57          $report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 1]);
  58  
  59          $content = $this->get_custom_report_content($report->get('id'));
  60          $this->assertCount(3, $content);
  61  
  62          // Default columns are fullname, username, email. Results are sorted by the fullname.
  63          [$adminrow, $userrow1, $userrow2] = array_map('array_values', $content);
  64  
  65          $this->assertEquals(['Admin User', 'admin', 'admin@example.com'], $adminrow);
  66          $this->assertEquals([fullname($user3), $user3->username, $user3->email], $userrow1);
  67          $this->assertEquals([fullname($user2), $user2->username, $user2->email], $userrow2);
  68      }
  69  
  70      /**
  71       * Test datasource columns that aren't added by default
  72       */
  73      public function test_datasource_non_default_columns(): void {
  74          $this->resetAfterTest();
  75  
  76          $user = $this->getDataGenerator()->create_user([
  77              'firstname' => 'Zoe',
  78              'idnumber' => 'U0001',
  79              'city' => 'London',
  80              'country' => 'GB',
  81          ]);
  82  
  83          /** @var core_reportbuilder_generator $generator */
  84          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  85          $report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
  86  
  87          // User.
  88          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullnamewithlink']);
  89          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullnamewithpicture']);
  90          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullnamewithpicturelink']);
  91          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:picture']);
  92          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
  93          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
  94          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
  95          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
  96          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstnamephonetic']);
  97          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastnamephonetic']);
  98          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:middlename']);
  99          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:alternatename']);
 100          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:idnumber']);
 101          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:institution']);
 102          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:department']);
 103          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:phone1']);
 104          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:phone2']);
 105          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:address']);
 106          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastaccess']);
 107          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:suspended']);
 108          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:confirmed']);
 109          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:moodlenetprofile']);
 110  
 111          $content = $this->get_custom_report_content($report->get('id'));
 112          $this->assertCount(2, $content);
 113  
 114          // Consistent order by firstname, just in case.
 115          core_collator::asort_array_of_arrays_by_key($content, 'c4_firstname');
 116          $content = array_values($content);
 117  
 118          [$adminrow, $userrow] = array_map('array_values', $content);
 119  
 120          $this->assertStringContainsString('Admin User', $adminrow[0]);
 121          $this->assertStringContainsString('Admin User', $adminrow[1]);
 122          $this->assertStringContainsString('Admin User', $adminrow[2]);
 123          $this->assertNotEmpty($adminrow[3]);
 124          $this->assertEquals('Admin', $adminrow[4]);
 125          $this->assertEquals('User', $adminrow[5]);
 126  
 127          $this->assertStringContainsString(fullname($user), $userrow[0]);
 128          $this->assertStringContainsString(fullname($user), $userrow[1]);
 129          $this->assertStringContainsString(fullname($user), $userrow[2]);
 130          $this->assertNotEmpty($userrow[3]);
 131          $this->assertEquals($user->firstname, $userrow[4]);
 132          $this->assertEquals($user->lastname, $userrow[5]);
 133          $this->assertEquals($user->city, $userrow[6]);
 134          $this->assertEquals('United Kingdom', $userrow[7]);
 135          $this->assertEquals($user->firstnamephonetic, $userrow[8]);
 136          $this->assertEquals($user->lastnamephonetic, $userrow[9]);
 137          $this->assertEquals($user->middlename, $userrow[10]);
 138          $this->assertEquals($user->alternatename, $userrow[11]);
 139          $this->assertEquals($user->idnumber, $userrow[12]);
 140          $this->assertEquals($user->institution, $userrow[13]);
 141          $this->assertEquals($user->department, $userrow[14]);
 142          $this->assertEquals($user->phone1, $userrow[15]);
 143          $this->assertEquals($user->phone2, $userrow[16]);
 144          $this->assertEquals($user->address, $userrow[17]);
 145          $this->assertEmpty($userrow[18]);
 146          $this->assertEquals('No', $userrow[19]);
 147          $this->assertEquals('Yes', $userrow[20]);
 148          $this->assertEquals($user->moodlenetprofile, $userrow[21]);
 149      }
 150  
 151      /**
 152       * Data provider for {@see test_datasource_filters}
 153       *
 154       * @return array[]
 155       */
 156      public function datasource_filters_provider(): array {
 157          return [
 158              // User.
 159              'Filter fullname' => ['user:fullname', [
 160                  'user:fullname_operator' => text::CONTAINS,
 161                  'user:fullname_value' => 'Zoe',
 162              ], true],
 163              'Filter fullname (no match)' => ['user:fullname', [
 164                  'user:fullname_operator' => text::CONTAINS,
 165                  'user:fullname_value' => 'Alfie',
 166              ], false],
 167              'Filter firstname' => ['user:firstname', [
 168                  'user:firstname_operator' => text::IS_EQUAL_TO,
 169                  'user:firstname_value' => 'Zoe',
 170              ], true],
 171              'Filter firstname (no match)' => ['user:firstname', [
 172                  'user:firstname_operator' => text::IS_EQUAL_TO,
 173                  'user:firstname_value' => 'Alfie',
 174              ], false],
 175              'Filter middlename' => ['user:middlename', [
 176                  'user:middlename_operator' => text::IS_EQUAL_TO,
 177                  'user:middlename_value' => 'Zebediah',
 178              ], true],
 179              'Filter middlename (no match)' => ['user:middlename', [
 180                  'user:middlename_operator' => text::IS_EQUAL_TO,
 181                  'user:middlename_value' => 'Aardvark',
 182              ], false],
 183              'Filter lastname' => ['user:lastname', [
 184                  'user:lastname_operator' => text::IS_EQUAL_TO,
 185                  'user:lastname_value' => 'Zebra',
 186              ], true],
 187              'Filter lastname (no match)' => ['user:lastname', [
 188                  'user:lastname_operator' => text::IS_EQUAL_TO,
 189                  'user:lastname_value' => 'Aardvark',
 190              ], false],
 191              'Filter firstnamephonetic' => ['user:firstnamephonetic', [
 192                  'user:firstnamephonetic_operator' => text::IS_EQUAL_TO,
 193                  'user:firstnamephonetic_value' => 'Eoz',
 194              ], true],
 195              'Filter firstnamephonetic (no match)' => ['user:firstnamephonetic', [
 196                  'user:firstnamephonetic_operator' => text::IS_EQUAL_TO,
 197                  'user:firstnamephonetic_value' => 'Alfie',
 198              ], false],
 199              'Filter lastnamephonetic' => ['user:lastnamephonetic', [
 200                  'user:lastnamephonetic_operator' => text::IS_EQUAL_TO,
 201                  'user:lastnamephonetic_value' => 'Arbez',
 202              ], true],
 203              'Filter lastnamephonetic (no match)' => ['user:lastnamephonetic', [
 204                  'user:lastnamephonetic_operator' => text::IS_EQUAL_TO,
 205                  'user:lastnamephonetic_value' => 'Aardvark',
 206              ], false],
 207              'Filter alternatename' => ['user:alternatename', [
 208                  'user:alternatename_operator' => text::IS_EQUAL_TO,
 209                  'user:alternatename_value' => 'Zee',
 210              ], true],
 211              'Filter alternatename (no match)' => ['user:alternatename', [
 212                  'user:alternatename_operator' => text::IS_EQUAL_TO,
 213                  'user:alternatename_value' => 'Aardvark',
 214              ], false],
 215              'Filter email' => ['user:email', [
 216                  'user:email_operator' => text::CONTAINS,
 217                  'user:email_value' => 'zoe1',
 218              ], true],
 219              'Filter email (no match)' => ['user:email', [
 220                  'user:email_operator' => text::CONTAINS,
 221                  'user:email_value' => 'alfie1',
 222              ], false],
 223              'Filter phone1' => ['user:phone1', [
 224                  'user:phone1_operator' => text::IS_EQUAL_TO,
 225                  'user:phone1_value' => '111',
 226              ], true],
 227              'Filter phone1 (no match)' => ['user:phone1', [
 228                  'user:phone1_operator' => text::IS_EQUAL_TO,
 229                  'user:phone1_value' => '119',
 230              ], false],
 231              'Filter phone2' => ['user:phone2', [
 232                  'user:phone2_operator' => text::IS_EQUAL_TO,
 233                  'user:phone2_value' => '222',
 234              ], true],
 235              'Filter phone2 (no match)' => ['user:phone2', [
 236                  'user:phone2_operator' => text::IS_EQUAL_TO,
 237                  'user:phone2_value' => '229',
 238              ], false],
 239              'Filter address' => ['user:address', [
 240                  'user:address_operator' => text::IS_EQUAL_TO,
 241                  'user:address_value' => 'Big Farm',
 242              ], true],
 243              'Filter address (no match)' => ['user:address', [
 244                  'user:address_operator' => text::IS_EQUAL_TO,
 245                  'user:address_value' => 'Small Farm',
 246              ], false],
 247  
 248              'Filter city' => ['user:city', [
 249                  'user:city_operator' => text::IS_EQUAL_TO,
 250                  'user:city_value' => 'Barcelona',
 251              ], true],
 252              'Filter city (no match)' => ['user:city', [
 253                  'user:city_operator' => text::IS_EQUAL_TO,
 254                  'user:city_value' => 'Perth',
 255              ], false],
 256              'Filter country' => ['user:country', [
 257                  'user:country_operator' => select::EQUAL_TO,
 258                  'user:country_value' => 'ES',
 259              ], true],
 260              'Filter country (no match)' => ['user:country', [
 261                  'user:country_operator' => select::EQUAL_TO,
 262                  'user:country_value' => 'AU',
 263              ], false],
 264              'Filter username' => ['user:username', [
 265                  'user:username_operator' => text::IS_EQUAL_TO,
 266                  'user:username_value' => 'zoe1',
 267              ], true],
 268              'Filter username (no match)' => ['user:username', [
 269                  'user:username_operator' => text::IS_EQUAL_TO,
 270                  'user:username_value' => 'alfie1',
 271              ], false],
 272              'Filter idnumber' => ['user:idnumber', [
 273                  'user:idnumber_operator' => text::IS_EQUAL_TO,
 274                  'user:idnumber_value' => 'Z0001',
 275              ], true],
 276              'Filter idnumber (no match)' => ['user:idnumber', [
 277                  'user:idnumber_operator' => text::IS_EQUAL_TO,
 278                  'user:idnumber_value' => 'A0001',
 279              ], false],
 280              'Filter institution' => ['user:institution', [
 281                  'user:institution_operator' => text::IS_EQUAL_TO,
 282                  'user:institution_value' => 'Farm',
 283              ], true],
 284              'Filter institution (no match)' => ['user:institution', [
 285                  'user:institution_operator' => text::IS_EQUAL_TO,
 286                  'user:institution_value' => 'University',
 287              ], false],
 288              'Filter department' => ['user:department', [
 289                  'user:department_operator' => text::IS_EQUAL_TO,
 290                  'user:department_value' => 'Stable',
 291              ], true],
 292              'Filter department (no match)' => ['user:department', [
 293                  'user:department_operator' => text::IS_EQUAL_TO,
 294                  'user:department_value' => 'Office',
 295              ], false],
 296              'Filter moodlenetprofile' => ['user:moodlenetprofile', [
 297                  'user:moodlenetprofile_operator' => text::IS_EQUAL_TO,
 298                  'user:moodlenetprofile_value' => '@zoe1@example.com',
 299              ], true],
 300              'Filter moodlenetprofile (no match)' => ['user:moodlenetprofile', [
 301                  'user:moodlenetprofile_operator' => text::IS_EQUAL_TO,
 302                  'user:moodlenetprofile_value' => '@alfie1@example.com',
 303              ], false],
 304              'Filter suspended' => ['user:suspended', [
 305                  'user:suspended_operator' => boolean_select::NOT_CHECKED,
 306              ], true],
 307              'Filter suspended (no match)' => ['user:suspended', [
 308                  'user:suspended_operator' => boolean_select::CHECKED,
 309              ], false],
 310              'Filter confirmed' => ['user:confirmed', [
 311                  'user:confirmed_operator' => boolean_select::CHECKED,
 312              ], true],
 313              'Filter confirmed (no match)' => ['user:confirmed', [
 314                  'user:confirmed_operator' => boolean_select::NOT_CHECKED,
 315              ], false],
 316              'Filter lastaccess' => ['user:lastaccess', [
 317                  'user:lastaccess_operator' => date::DATE_EMPTY,
 318              ], true],
 319              'Filter lastaccess (no match)' => ['user:lastaccess', [
 320                  'user:lastaccess_operator' => date::DATE_RANGE,
 321                  'user:lastaccess_from' => 1619823600,
 322                  'user:lastaccess_to' => 1622502000,
 323              ], false],
 324          ];
 325      }
 326  
 327      /**
 328       * Test datasource filters
 329       *
 330       * @param string $filtername
 331       * @param array $filtervalues
 332       * @param bool $expectmatch
 333       *
 334       * @dataProvider datasource_filters_provider
 335       */
 336      public function test_datasource_filters(string $filtername, array $filtervalues, bool $expectmatch): void {
 337          $this->resetAfterTest();
 338  
 339          $user = $this->getDataGenerator()->create_user([
 340              'username' => 'zoe1',
 341              'email' => 'zoe1@example.com',
 342              'firstname' => 'Zoe',
 343              'middlename' => 'Zebediah',
 344              'lastname' => 'Zebra',
 345              'firstnamephonetic' => 'Eoz',
 346              'lastnamephonetic' => 'Arbez',
 347              'alternatename' => 'Zee',
 348              'idnumber' => 'Z0001',
 349              'institution' => 'Farm',
 350              'department' => 'Stable',
 351              'phone1' => '111',
 352              'phone2' => '222',
 353              'address' => 'Big Farm',
 354              'city' => 'Barcelona',
 355              'country' => 'ES',
 356              'description' => 'Hello there',
 357              'moodlenetprofile' => '@zoe1@example.com',
 358          ]);
 359  
 360          /** @var core_reportbuilder_generator $generator */
 361          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 362  
 363          // Create report containing single column, and given filter.
 364          $report = $generator->create_report(['name' => 'Tasks', 'source' => users::class, 'default' => 0]);
 365          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']);
 366  
 367          // Add filter, set it's values.
 368          $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
 369          user_filter_manager::set($report->get('id'), $filtervalues);
 370  
 371          $content = $this->get_custom_report_content($report->get('id'));
 372  
 373          if ($expectmatch) {
 374              $this->assertNotEmpty($content);
 375  
 376              // Merge report usernames into easily traversable array.
 377              $usernames = array_merge(...array_map('array_values', $content));
 378              $this->assertContains($user->username, $usernames);
 379          } else {
 380              $this->assertEmpty($content);
 381          }
 382      }
 383  
 384      /**
 385       * Stress test datasource
 386       *
 387       * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
 388       */
 389      public function test_stress_datasource(): void {
 390          if (!PHPUNIT_LONGTEST) {
 391              $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
 392          }
 393  
 394          $this->resetAfterTest();
 395  
 396          $this->getDataGenerator()->create_custom_profile_field(['datatype' => 'text', 'name' => 'Hi', 'shortname' => 'hi']);
 397          $user = $this->getDataGenerator()->create_user(['profile_field_hi' => 'Hello']);
 398  
 399          $this->datasource_stress_test_columns(users::class);
 400          $this->datasource_stress_test_columns_aggregation(users::class);
 401          $this->datasource_stress_test_conditions(users::class, 'user:username');
 402      }
 403  }