Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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_reportbuilder_testcase;
  22  use core_reportbuilder_generator;
  23  use core_reportbuilder\local\filters\boolean_select;
  24  use core_reportbuilder\local\filters\date;
  25  use core_reportbuilder\local\filters\select;
  26  use core_reportbuilder\local\filters\tags;
  27  use core_reportbuilder\local\filters\text;
  28  use core_reportbuilder\local\filters\user as user_filter;
  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       * Test default datasource
  47       */
  48      public function test_datasource_default(): void {
  49          $this->resetAfterTest();
  50  
  51          $user2 = $this->getDataGenerator()->create_user(['firstname' => 'Charles']);
  52          $user3 = $this->getDataGenerator()->create_user(['firstname' => 'Brian']);
  53  
  54          /** @var core_reportbuilder_generator $generator */
  55          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  56          $report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 1]);
  57  
  58          $content = $this->get_custom_report_content($report->get('id'));
  59          $this->assertCount(3, $content);
  60  
  61          // Default columns are fullname, username, email. Results are sorted by the fullname.
  62          [$adminrow, $userrow1, $userrow2] = array_map('array_values', $content);
  63  
  64          $this->assertEquals(['Admin User', 'admin', 'admin@example.com'], $adminrow);
  65          $this->assertEquals([fullname($user3), $user3->username, $user3->email], $userrow1);
  66          $this->assertEquals([fullname($user2), $user2->username, $user2->email], $userrow2);
  67      }
  68  
  69      /**
  70       * Test datasource columns that aren't added by default
  71       */
  72      public function test_datasource_non_default_columns(): void {
  73          $this->resetAfterTest();
  74  
  75          $user = $this->getDataGenerator()->create_user([
  76              'firstname' => 'Zoe',
  77              'idnumber' => 'U0001',
  78              'city' => 'London',
  79              'country' => 'GB',
  80              'interests' => ['Horses'],
  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              'sortenabled' => 1]);
  90          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullnamewithpicture']);
  91          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullnamewithpicturelink']);
  92          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:picture']);
  93          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
  94          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname']);
  95          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:city']);
  96          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:country']);
  97          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:description']);
  98          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstnamephonetic']);
  99          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastnamephonetic']);
 100          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:middlename']);
 101          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:alternatename']);
 102          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:idnumber']);
 103          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:institution']);
 104          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:department']);
 105          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:phone1']);
 106          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:phone2']);
 107          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:address']);
 108          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastaccess']);
 109          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:suspended']);
 110          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:confirmed']);
 111          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:moodlenetprofile']);
 112          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:timecreated']);
 113  
 114          // Tags.
 115          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:name']);
 116          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:namewithlink']);
 117  
 118          $content = $this->get_custom_report_content($report->get('id'));
 119          $this->assertCount(2, $content);
 120  
 121          [$adminrow, $userrow] = array_map('array_values', $content);
 122  
 123          $this->assertStringContainsString('Admin User', $adminrow[0]);
 124          $this->assertStringContainsString('Admin User', $adminrow[1]);
 125          $this->assertStringContainsString('Admin User', $adminrow[2]);
 126          $this->assertNotEmpty($adminrow[3]);
 127          $this->assertEquals('Admin', $adminrow[4]);
 128          $this->assertEquals('User', $adminrow[5]);
 129  
 130          $this->assertStringContainsString(fullname($user), $userrow[0]);
 131          $this->assertStringContainsString(fullname($user), $userrow[1]);
 132          $this->assertStringContainsString(fullname($user), $userrow[2]);
 133          $this->assertNotEmpty($userrow[3]);
 134          $this->assertEquals($user->firstname, $userrow[4]);
 135          $this->assertEquals($user->lastname, $userrow[5]);
 136          $this->assertEquals($user->city, $userrow[6]);
 137          $this->assertEquals('United Kingdom', $userrow[7]);
 138          $this->assertEquals($user->description, $userrow[8]);
 139          $this->assertEquals($user->firstnamephonetic, $userrow[9]);
 140          $this->assertEquals($user->lastnamephonetic, $userrow[10]);
 141          $this->assertEquals($user->middlename, $userrow[11]);
 142          $this->assertEquals($user->alternatename, $userrow[12]);
 143          $this->assertEquals($user->idnumber, $userrow[13]);
 144          $this->assertEquals($user->institution, $userrow[14]);
 145          $this->assertEquals($user->department, $userrow[15]);
 146          $this->assertEquals($user->phone1, $userrow[16]);
 147          $this->assertEquals($user->phone2, $userrow[17]);
 148          $this->assertEquals($user->address, $userrow[18]);
 149          $this->assertEmpty($userrow[19]);
 150          $this->assertEquals('No', $userrow[20]);
 151          $this->assertEquals('Yes', $userrow[21]);
 152          $this->assertEquals($user->moodlenetprofile, $userrow[22]);
 153          $this->assertNotEmpty($userrow[23]);
 154          $this->assertEquals('Horses', $userrow[24]);
 155          $this->assertStringContainsString('Horses', $userrow[25]);
 156      }
 157  
 158      /**
 159       * Data provider for {@see test_datasource_filters}
 160       *
 161       * @return array[]
 162       */
 163      public function datasource_filters_provider(): array {
 164          return [
 165              // User.
 166              'Filter user' => ['user:userselect', [
 167                  'user:userselect_operator' => user_filter::USER_SELECT,
 168                  'user:userselect_value' => [-1],
 169              ], false],
 170              'Filter fullname' => ['user:fullname', [
 171                  'user:fullname_operator' => text::CONTAINS,
 172                  'user:fullname_value' => 'Zoe',
 173              ], true],
 174              'Filter fullname (no match)' => ['user:fullname', [
 175                  'user:fullname_operator' => text::CONTAINS,
 176                  'user:fullname_value' => 'Alfie',
 177              ], false],
 178              'Filter firstname' => ['user:firstname', [
 179                  'user:firstname_operator' => text::IS_EQUAL_TO,
 180                  'user:firstname_value' => 'Zoe',
 181              ], true],
 182              'Filter firstname (no match)' => ['user:firstname', [
 183                  'user:firstname_operator' => text::IS_EQUAL_TO,
 184                  'user:firstname_value' => 'Alfie',
 185              ], false],
 186              'Filter middlename' => ['user:middlename', [
 187                  'user:middlename_operator' => text::IS_EQUAL_TO,
 188                  'user:middlename_value' => 'Zebediah',
 189              ], true],
 190              'Filter middlename (no match)' => ['user:middlename', [
 191                  'user:middlename_operator' => text::IS_EQUAL_TO,
 192                  'user:middlename_value' => 'Aardvark',
 193              ], false],
 194              'Filter lastname' => ['user:lastname', [
 195                  'user:lastname_operator' => text::IS_EQUAL_TO,
 196                  'user:lastname_value' => 'Zebra',
 197              ], true],
 198              'Filter lastname (no match)' => ['user:lastname', [
 199                  'user:lastname_operator' => text::IS_EQUAL_TO,
 200                  'user:lastname_value' => 'Aardvark',
 201              ], false],
 202              'Filter firstnamephonetic' => ['user:firstnamephonetic', [
 203                  'user:firstnamephonetic_operator' => text::IS_EQUAL_TO,
 204                  'user:firstnamephonetic_value' => 'Eoz',
 205              ], true],
 206              'Filter firstnamephonetic (no match)' => ['user:firstnamephonetic', [
 207                  'user:firstnamephonetic_operator' => text::IS_EQUAL_TO,
 208                  'user:firstnamephonetic_value' => 'Alfie',
 209              ], false],
 210              'Filter lastnamephonetic' => ['user:lastnamephonetic', [
 211                  'user:lastnamephonetic_operator' => text::IS_EQUAL_TO,
 212                  'user:lastnamephonetic_value' => 'Arbez',
 213              ], true],
 214              'Filter lastnamephonetic (no match)' => ['user:lastnamephonetic', [
 215                  'user:lastnamephonetic_operator' => text::IS_EQUAL_TO,
 216                  'user:lastnamephonetic_value' => 'Aardvark',
 217              ], false],
 218              'Filter alternatename' => ['user:alternatename', [
 219                  'user:alternatename_operator' => text::IS_EQUAL_TO,
 220                  'user:alternatename_value' => 'Zee',
 221              ], true],
 222              'Filter alternatename (no match)' => ['user:alternatename', [
 223                  'user:alternatename_operator' => text::IS_EQUAL_TO,
 224                  'user:alternatename_value' => 'Aardvark',
 225              ], false],
 226              'Filter email' => ['user:email', [
 227                  'user:email_operator' => text::CONTAINS,
 228                  'user:email_value' => 'zoe1',
 229              ], true],
 230              'Filter email (no match)' => ['user:email', [
 231                  'user:email_operator' => text::CONTAINS,
 232                  'user:email_value' => 'alfie1',
 233              ], false],
 234              'Filter phone1' => ['user:phone1', [
 235                  'user:phone1_operator' => text::IS_EQUAL_TO,
 236                  'user:phone1_value' => '111',
 237              ], true],
 238              'Filter phone1 (no match)' => ['user:phone1', [
 239                  'user:phone1_operator' => text::IS_EQUAL_TO,
 240                  'user:phone1_value' => '119',
 241              ], false],
 242              'Filter phone2' => ['user:phone2', [
 243                  'user:phone2_operator' => text::IS_EQUAL_TO,
 244                  'user:phone2_value' => '222',
 245              ], true],
 246              'Filter phone2 (no match)' => ['user:phone2', [
 247                  'user:phone2_operator' => text::IS_EQUAL_TO,
 248                  'user:phone2_value' => '229',
 249              ], false],
 250              'Filter address' => ['user:address', [
 251                  'user:address_operator' => text::IS_EQUAL_TO,
 252                  'user:address_value' => 'Big Farm',
 253              ], true],
 254              'Filter address (no match)' => ['user:address', [
 255                  'user:address_operator' => text::IS_EQUAL_TO,
 256                  'user:address_value' => 'Small Farm',
 257              ], false],
 258  
 259              'Filter city' => ['user:city', [
 260                  'user:city_operator' => text::IS_EQUAL_TO,
 261                  'user:city_value' => 'Barcelona',
 262              ], true],
 263              'Filter city (no match)' => ['user:city', [
 264                  'user:city_operator' => text::IS_EQUAL_TO,
 265                  'user:city_value' => 'Perth',
 266              ], false],
 267              'Filter country' => ['user:country', [
 268                  'user:country_operator' => select::EQUAL_TO,
 269                  'user:country_value' => 'ES',
 270              ], true],
 271              'Filter country (no match)' => ['user:country', [
 272                  'user:country_operator' => select::EQUAL_TO,
 273                  'user:country_value' => 'AU',
 274              ], false],
 275              'Filter description' => ['user:description', [
 276                  'user:description_operator' => text::CONTAINS,
 277                  'user:description_value' => 'Hello there',
 278              ], true],
 279              'Filter description (no match)' => ['user:description', [
 280                  'user:description_operator' => text::CONTAINS,
 281                  'user:description_value' => 'Goodbye',
 282              ], false],
 283              'Filter auth' => ['user:auth', [
 284                  'user:auth_operator' => select::EQUAL_TO,
 285                  'user:auth_value' => 'manual',
 286              ], true],
 287              'Filter auth (no match)' => ['user:auth', [
 288                  'user:auth_operator' => select::EQUAL_TO,
 289                  'user:auth_value' => 'ldap',
 290              ], false],
 291              'Filter username' => ['user:username', [
 292                  'user:username_operator' => text::IS_EQUAL_TO,
 293                  'user:username_value' => 'zoe1',
 294              ], true],
 295              'Filter username (no match)' => ['user:username', [
 296                  'user:username_operator' => text::IS_EQUAL_TO,
 297                  'user:username_value' => 'alfie1',
 298              ], false],
 299              'Filter idnumber' => ['user:idnumber', [
 300                  'user:idnumber_operator' => text::IS_EQUAL_TO,
 301                  'user:idnumber_value' => 'Z0001',
 302              ], true],
 303              'Filter idnumber (no match)' => ['user:idnumber', [
 304                  'user:idnumber_operator' => text::IS_EQUAL_TO,
 305                  'user:idnumber_value' => 'A0001',
 306              ], false],
 307              'Filter institution' => ['user:institution', [
 308                  'user:institution_operator' => text::IS_EQUAL_TO,
 309                  'user:institution_value' => 'Farm',
 310              ], true],
 311              'Filter institution (no match)' => ['user:institution', [
 312                  'user:institution_operator' => text::IS_EQUAL_TO,
 313                  'user:institution_value' => 'University',
 314              ], false],
 315              'Filter department' => ['user:department', [
 316                  'user:department_operator' => text::IS_EQUAL_TO,
 317                  'user:department_value' => 'Stable',
 318              ], true],
 319              'Filter department (no match)' => ['user:department', [
 320                  'user:department_operator' => text::IS_EQUAL_TO,
 321                  'user:department_value' => 'Office',
 322              ], false],
 323              'Filter moodlenetprofile' => ['user:moodlenetprofile', [
 324                  'user:moodlenetprofile_operator' => text::IS_EQUAL_TO,
 325                  'user:moodlenetprofile_value' => '@zoe1@example.com',
 326              ], true],
 327              'Filter moodlenetprofile (no match)' => ['user:moodlenetprofile', [
 328                  'user:moodlenetprofile_operator' => text::IS_EQUAL_TO,
 329                  'user:moodlenetprofile_value' => '@alfie1@example.com',
 330              ], false],
 331              'Filter suspended' => ['user:suspended', [
 332                  'user:suspended_operator' => boolean_select::NOT_CHECKED,
 333              ], true],
 334              'Filter suspended (no match)' => ['user:suspended', [
 335                  'user:suspended_operator' => boolean_select::CHECKED,
 336              ], false],
 337              'Filter confirmed' => ['user:confirmed', [
 338                  'user:confirmed_operator' => boolean_select::CHECKED,
 339              ], true],
 340              'Filter confirmed (no match)' => ['user:confirmed', [
 341                  'user:confirmed_operator' => boolean_select::NOT_CHECKED,
 342              ], false],
 343              'Filter timecreated' => ['user:timecreated', [
 344                  'user:timecreated_operator' => date::DATE_RANGE,
 345                  'user:timecreated_from' => 1622502000,
 346              ], true],
 347              'Filter timecreated (no match)' => ['user:timecreated', [
 348                  'user:timecreated_operator' => date::DATE_RANGE,
 349                  'user:timecreated_from' => 1619823600,
 350                  'user:timecreated_to' => 1622502000,
 351              ], false],
 352              'Filter lastaccess' => ['user:lastaccess', [
 353                  'user:lastaccess_operator' => date::DATE_EMPTY,
 354              ], true],
 355              'Filter lastaccess (no match)' => ['user:lastaccess', [
 356                  'user:lastaccess_operator' => date::DATE_RANGE,
 357                  'user:lastaccess_from' => 1619823600,
 358                  'user:lastaccess_to' => 1622502000,
 359              ], false],
 360  
 361              // Tags.
 362              'Filter tag name' => ['tag:name', [
 363                  'tag:name_operator' => tags::EQUAL_TO,
 364                  'tag:name_value' => [-1],
 365              ], false],
 366              'Filter tag name not empty' => ['tag:name', [
 367                  'tag:name_operator' => tags::NOT_EMPTY,
 368              ], true],
 369          ];
 370      }
 371  
 372      /**
 373       * Test datasource filters
 374       *
 375       * @param string $filtername
 376       * @param array $filtervalues
 377       * @param bool $expectmatch
 378       *
 379       * @dataProvider datasource_filters_provider
 380       */
 381      public function test_datasource_filters(string $filtername, array $filtervalues, bool $expectmatch): void {
 382          $this->resetAfterTest();
 383  
 384          $user = $this->getDataGenerator()->create_user([
 385              'username' => 'zoe1',
 386              'email' => 'zoe1@example.com',
 387              'firstname' => 'Zoe',
 388              'middlename' => 'Zebediah',
 389              'lastname' => 'Zebra',
 390              'firstnamephonetic' => 'Eoz',
 391              'lastnamephonetic' => 'Arbez',
 392              'alternatename' => 'Zee',
 393              'idnumber' => 'Z0001',
 394              'institution' => 'Farm',
 395              'department' => 'Stable',
 396              'phone1' => '111',
 397              'phone2' => '222',
 398              'address' => 'Big Farm',
 399              'city' => 'Barcelona',
 400              'country' => 'ES',
 401              'description' => 'Hello there',
 402              'moodlenetprofile' => '@zoe1@example.com',
 403              'interests' => ['Horses'],
 404          ]);
 405  
 406          /** @var core_reportbuilder_generator $generator */
 407          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 408  
 409          // Create report containing single column, and given filter.
 410          $report = $generator->create_report(['name' => 'Tasks', 'source' => users::class, 'default' => 0]);
 411          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']);
 412  
 413          // Add filter, set it's values.
 414          $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
 415          $content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
 416  
 417          if ($expectmatch) {
 418              $this->assertNotEmpty($content);
 419  
 420              // Merge report usernames into easily traversable array.
 421              $usernames = array_merge(...array_map('array_values', $content));
 422              $this->assertContains($user->username, $usernames);
 423          } else {
 424              $this->assertEmpty($content);
 425          }
 426      }
 427  
 428      /**
 429       * Stress test datasource
 430       *
 431       * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
 432       */
 433      public function test_stress_datasource(): void {
 434          if (!PHPUNIT_LONGTEST) {
 435              $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
 436          }
 437  
 438          $this->resetAfterTest();
 439  
 440          $user = $this->getDataGenerator()->create_user();
 441  
 442          $this->datasource_stress_test_columns(users::class);
 443          $this->datasource_stress_test_columns_aggregation(users::class);
 444          $this->datasource_stress_test_conditions(users::class, 'user:username');
 445      }
 446  }