Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 400 and 401]

   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_reportbuilder\local\helpers;
  20  
  21  use core_reportbuilder_generator;
  22  use core_reportbuilder_testcase;
  23  use core_reportbuilder\local\entities\user;
  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\report\column;
  29  use core_reportbuilder\local\report\filter;
  30  use core_user\reportbuilder\datasource\users;
  31  
  32  defined('MOODLE_INTERNAL') || die();
  33  
  34  global $CFG;
  35  require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
  36  
  37  /**
  38   * Unit tests for user profile fields helper
  39   *
  40   * @package     core_reportbuilder
  41   * @covers      \core_reportbuilder\local\helpers\user_profile_fields
  42   * @copyright   2021 David Matamoros <davidmc@moodle.com>
  43   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  class user_profile_fields_test extends core_reportbuilder_testcase {
  46  
  47      /**
  48       * Generate custom profile fields, one of each type
  49       *
  50       * @return user_profile_fields
  51       */
  52      private function generate_userprofilefields(): user_profile_fields {
  53          $this->getDataGenerator()->create_custom_profile_field([
  54              'shortname' => 'checkbox', 'name' => 'Checkbox field', 'datatype' => 'checkbox']);
  55  
  56          $this->getDataGenerator()->create_custom_profile_field([
  57              'shortname' => 'datetime', 'name' => 'Date field', 'datatype' => 'datetime', 'param2' => 2022, 'param3' => 0]);
  58  
  59          $this->getDataGenerator()->create_custom_profile_field([
  60              'shortname' => 'menu', 'name' => 'Menu field', 'datatype' => 'menu', 'param1' => "Cat\nDog"]);
  61  
  62          $this->getDataGenerator()->create_custom_profile_field([
  63              'shortname' => 'Social', 'name' => 'msn', 'datatype' => 'social', 'param1' => 'msn']);
  64  
  65          $this->getDataGenerator()->create_custom_profile_field([
  66              'shortname' => 'text', 'name' => 'Text field', 'datatype' => 'text']);
  67  
  68          $this->getDataGenerator()->create_custom_profile_field([
  69              'shortname' => 'textarea', 'name' => 'Textarea field', 'datatype' => 'textarea']);
  70  
  71          $userentity = new user();
  72          $useralias = $userentity->get_table_alias('user');
  73  
  74          // Create an instance of the userprofilefield helper.
  75          return new user_profile_fields("$useralias.id", $userentity->get_entity_name());
  76      }
  77  
  78      /**
  79       * Test for get_columns
  80       */
  81      public function test_get_columns(): void {
  82          $this->resetAfterTest();
  83  
  84          $userentity = new user();
  85          $useralias = $userentity->get_table_alias('user');
  86  
  87          // Get pre-existing user profile fields.
  88          $initialuserprofilefields = new user_profile_fields("$useralias.id", $userentity->get_entity_name());
  89          $initialcolumns = $initialuserprofilefields->get_columns();
  90          $initialcolumntitles = array_map(static function(column $column): string {
  91              return $column->get_title();
  92          }, $initialcolumns);
  93          $initialcolumntypes = array_map(static function(column $column): int {
  94              return $column->get_type();
  95          }, $initialcolumns);
  96  
  97          // Add new custom profile fields.
  98          $userprofilefields = $this->generate_userprofilefields();
  99          $columns = $userprofilefields->get_columns();
 100  
 101          // Columns count should be equal to start + 6.
 102          $this->assertCount(count($initialcolumns) + 6, $columns);
 103          $this->assertContainsOnlyInstancesOf(column::class, $columns);
 104  
 105          // Assert column titles.
 106          $columntitles = array_map(static function(column $column): string {
 107              return $column->get_title();
 108          }, $columns);
 109          $expectedcolumntitles = array_merge($initialcolumntitles, [
 110              'Checkbox field',
 111              'Date field',
 112              'Menu field',
 113              'MSN ID',
 114              'Text field',
 115              'Textarea field',
 116          ]);
 117          $this->assertEquals($expectedcolumntitles, $columntitles);
 118  
 119          // Assert column types.
 120          $columntypes = array_map(static function(column $column): int {
 121              return $column->get_type();
 122          }, $columns);
 123          $expectedcolumntypes = array_merge($initialcolumntypes, [
 124              column::TYPE_BOOLEAN,
 125              column::TYPE_TIMESTAMP,
 126              column::TYPE_TEXT,
 127              column::TYPE_TEXT,
 128              column::TYPE_TEXT,
 129              column::TYPE_LONGTEXT,
 130          ]);
 131          $this->assertEquals($expectedcolumntypes, $columntypes);
 132      }
 133  
 134      /**
 135       * Test for add_join
 136       */
 137      public function test_add_join(): void {
 138          $this->resetAfterTest();
 139  
 140          $userprofilefields = $this->generate_userprofilefields();
 141          $columns = $userprofilefields->get_columns();
 142          $this->assertCount(1, ($columns[0])->get_joins());
 143  
 144          $userprofilefields->add_join('JOIN {test} t ON t.id = id');
 145          $columns = $userprofilefields->get_columns();
 146          $this->assertCount(2, ($columns[0])->get_joins());
 147      }
 148  
 149      /**
 150       * Test for add_joins
 151       */
 152      public function test_add_joins(): void {
 153          $this->resetAfterTest();
 154  
 155          $userprofilefields = $this->generate_userprofilefields();
 156          $columns = $userprofilefields->get_columns();
 157          $this->assertCount(1, ($columns[0])->get_joins());
 158  
 159          $userprofilefields->add_joins(['JOIN {test} t ON t.id = id', 'JOIN {test2} t2 ON t2.id = id']);
 160          $columns = $userprofilefields->get_columns();
 161          $this->assertCount(3, ($columns[0])->get_joins());
 162      }
 163  
 164      /**
 165       * Test for get_filters
 166       */
 167      public function test_get_filters(): void {
 168          $this->resetAfterTest();
 169  
 170          $userentity = new user();
 171          $useralias = $userentity->get_table_alias('user');
 172  
 173          // Get pre-existing user profile fields.
 174          $initialuserprofilefields = new user_profile_fields("$useralias.id", $userentity->get_entity_name());
 175          $initialfilters = $initialuserprofilefields->get_filters();
 176          $initialfilterheaders = array_map(static function(filter $filter): string {
 177              return $filter->get_header();
 178          }, $initialfilters);
 179  
 180          // Add new custom profile fields.
 181          $userprofilefields = $this->generate_userprofilefields();
 182          $filters = $userprofilefields->get_filters();
 183  
 184          // Filters count should be equal to start + 6.
 185          $this->assertCount(count($initialfilters) + 6, $filters);
 186          $this->assertContainsOnlyInstancesOf(filter::class, $filters);
 187  
 188          // Assert filter headers.
 189          $filterheaders = array_map(static function(filter $filter): string {
 190              return $filter->get_header();
 191          }, $filters);
 192          $expectedfilterheaders = array_merge($initialfilterheaders, [
 193              'Checkbox field',
 194              'Date field',
 195              'Menu field',
 196              'MSN ID',
 197              'Text field',
 198              'Textarea field',
 199          ]);
 200          $this->assertEquals($expectedfilterheaders, $filterheaders);
 201      }
 202  
 203      /**
 204       * Test that adding user profile field columns to a report returns expected values
 205       */
 206      public function test_custom_report_content(): void {
 207          $this->resetAfterTest();
 208  
 209          $userprofilefields = $this->generate_userprofilefields();
 210  
 211          // Create test subject with user profile fields content.
 212          $user = $this->getDataGenerator()->create_user([
 213              'firstname' => 'Zebedee',
 214              'profile_field_checkbox' => true,
 215              'profile_field_datetime' => '2021-12-09',
 216              'profile_field_menu' => 'Cat',
 217              'profile_field_Social' => 12345,
 218              'profile_field_text' => 'Hello',
 219              'profile_field_textarea' => 'Goodbye',
 220          ]);
 221  
 222          /** @var core_reportbuilder_generator $generator */
 223          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 224          $report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
 225  
 226          // Add user profile field columns to the report.
 227          $firstname = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
 228          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_checkbox']);
 229          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_datetime']);
 230          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_menu']);
 231          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_social']);
 232          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_text']);
 233          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:profilefield_textarea']);
 234  
 235          // Sort the report, Admin -> Zebedee for consistency.
 236          report::toggle_report_column_sorting($report->get('id'), $firstname->get('id'), true);
 237  
 238          $content = $this->get_custom_report_content($report->get('id'));
 239          $this->assertEquals([
 240              [
 241                  'c0_firstname' => 'Admin',
 242                  'c1_data' => '',
 243                  'c2_data' => '',
 244                  'c3_data' => '',
 245                  'c4_data' => '',
 246                  'c5_data' => '',
 247                  'c6_data' => '',
 248              ], [
 249                  'c0_firstname' => 'Zebedee',
 250                  'c1_data' => 'Yes',
 251                  'c2_data' => '9 December 2021',
 252                  'c3_data' => 'Cat',
 253                  'c4_data' => '12345',
 254                  'c5_data' => 'Hello',
 255                  'c6_data' => '<div class="no-overflow">Goodbye</div>',
 256              ],
 257          ], $content);
 258      }
 259  
 260      /**
 261       * Data provider for {@see test_custom_report_filter}
 262       *
 263       * @return array[]
 264       */
 265      public function custom_report_filter_provider(): array {
 266          return [
 267              'Filter by checkbox profile field' => ['user:profilefield_checkbox', [
 268                  'user:profilefield_checkbox_operator' => boolean_select::CHECKED,
 269              ], 'testuser'],
 270              'Filter by checkbox profile field (empty)' => ['user:profilefield_checkbox', [
 271                  'user:profilefield_checkbox_operator' => boolean_select::NOT_CHECKED,
 272              ], 'admin'],
 273              'Filter by datetime profile field' => ['user:profilefield_datetime', [
 274                  'user:profilefield_datetime_operator' => date::DATE_RANGE,
 275                  'user:profilefield_datetime_from' => 1622502000,
 276              ], 'testuser'],
 277              'Filter by datetime profile field (empty)' => ['user:profilefield_datetime', [
 278                  'user:profilefield_datetime_operator' => date::DATE_EMPTY,
 279              ], 'admin'],
 280              'Filter by menu profile field' => ['user:profilefield_menu', [
 281                  'user:profilefield_menu_operator' => select::EQUAL_TO,
 282                  'user:profilefield_menu_value' => 'Dog',
 283              ], 'testuser'],
 284              'Filter by menu profile field (empty)' => ['user:profilefield_menu', [
 285                  'user:profilefield_menu_operator' => select::NOT_EQUAL_TO,
 286                  'user:profilefield_menu_value' => 'Dog',
 287              ], 'admin'],
 288              'Filter by social profile field' => ['user:profilefield_social', [
 289                  'user:profilefield_social_operator' => text::IS_EQUAL_TO,
 290                  'user:profilefield_social_value' => '12345',
 291              ], 'testuser'],
 292              'Filter by social profile field (empty)' => ['user:profilefield_social', [
 293                  'user:profilefield_social_operator' => text::IS_EMPTY,
 294              ], 'admin'],
 295              'Filter by text profile field' => ['user:profilefield_text', [
 296                  'user:profilefield_text_operator' => text::IS_EQUAL_TO,
 297                  'user:profilefield_text_value' => 'Hello',
 298              ], 'testuser'],
 299              'Filter by text profile field (empty)' => ['user:profilefield_text', [
 300                  'user:profilefield_text_operator' => text::IS_NOT_EQUAL_TO,
 301                  'user:profilefield_text_value' => 'Hello',
 302              ], 'admin'],
 303              'Filter by textarea profile field' => ['user:profilefield_textarea', [
 304                  'user:profilefield_textarea_operator' => text::IS_EQUAL_TO,
 305                  'user:profilefield_textarea_value' => 'Goodbye',
 306              ], 'testuser'],
 307              'Filter by textarea profile field (empty)' => ['user:profilefield_textarea', [
 308                  'user:profilefield_textarea_operator' => text::DOES_NOT_CONTAIN,
 309                  'user:profilefield_textarea_value' => 'Goodbye',
 310              ], 'admin'],
 311          ];
 312      }
 313  
 314      /**
 315       * Test filtering report by custom profile fields
 316       *
 317       * @param string $filtername
 318       * @param array $filtervalues
 319       * @param string $expectmatchuser
 320       *
 321       * @dataProvider custom_report_filter_provider
 322       */
 323      public function test_custom_report_filter(string $filtername, array $filtervalues, string $expectmatchuser): void {
 324          $this->resetAfterTest();
 325  
 326          $userprofilefields = $this->generate_userprofilefields();
 327  
 328          // Create test subject with user profile fields content.
 329          $user = $this->getDataGenerator()->create_user([
 330              'username' => 'testuser',
 331              'profile_field_checkbox' => true,
 332              'profile_field_datetime' => '2021-12-09',
 333              'profile_field_menu' => 'Dog',
 334              'profile_field_Social' => '12345',
 335              'profile_field_text' => 'Hello',
 336              'profile_field_textarea' => 'Goodbye',
 337          ]);
 338  
 339          /** @var core_reportbuilder_generator $generator */
 340          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 341  
 342          // Create report containing single column, and given filter.
 343          $report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
 344          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']);
 345  
 346          // Add filter, set it's values.
 347          $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
 348          $content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
 349  
 350          $this->assertCount(1, $content);
 351          $this->assertEquals($expectmatchuser, reset($content[0]));
 352      }
 353  
 354      /**
 355       * Stress test user datasource using profile fields
 356       *
 357       * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
 358       */
 359      public function test_stress_datasource(): void {
 360          if (!PHPUNIT_LONGTEST) {
 361              $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
 362          }
 363  
 364          $this->resetAfterTest();
 365  
 366          $userprofilefields = $this->generate_userprofilefields();
 367          $user = $this->getDataGenerator()->create_user([
 368              'profile_field_checkbox' => true,
 369              'profile_field_datetime' => '2021-12-09',
 370              'profile_field_menu' => 'Dog',
 371              'profile_field_Social' => '12345',
 372              'profile_field_text' => 'Hello',
 373              'profile_field_textarea' => 'Goodbye',
 374          ]);
 375  
 376          $this->datasource_stress_test_columns(users::class);
 377          $this->datasource_stress_test_columns_aggregation(users::class);
 378          $this->datasource_stress_test_conditions(users::class, 'user:idnumber');
 379      }
 380  }