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 401 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_tag\reportbuilder\datasource;
  20  
  21  use context_course;
  22  use context_user;
  23  use core_collator;
  24  use core_reportbuilder_generator;
  25  use core_reportbuilder_testcase;
  26  use core_reportbuilder\local\filters\{boolean_select, date, select};
  27  use core_reportbuilder\local\filters\tags as tags_filter;
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  global $CFG;
  32  require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
  33  
  34  /**
  35   * Unit tests for tags datasource
  36   *
  37   * @package     core_tag
  38   * @covers      \core_tag\reportbuilder\datasource\tags
  39   * @copyright   2022 Paul Holden <paulh@moodle.com>
  40   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   */
  42  class tags_test extends core_reportbuilder_testcase {
  43  
  44      /**
  45       * Test default datasource
  46       */
  47      public function test_datasource_default(): void {
  48          $this->resetAfterTest();
  49  
  50          $course = $this->getDataGenerator()->create_course(['tags' => ['Horses']]);
  51          $coursecontext = context_course::instance($course->id);
  52  
  53          $user = $this->getDataGenerator()->create_user(['interests' => ['Pies']]);
  54          $usercontext = context_user::instance($user->id);
  55  
  56          /** @var core_reportbuilder_generator $generator */
  57          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  58          $report = $generator->create_report(['name' => 'Notes', 'source' => tags::class, 'default' => 1]);
  59  
  60          $content = $this->get_custom_report_content($report->get('id'));
  61          $this->assertCount(2, $content);
  62  
  63          // Consistent order (course, user), just in case.
  64          core_collator::asort_array_of_arrays_by_key($content, 'c3_contextid');
  65          $content = array_values($content);
  66  
  67          // Default columns are collection, tag name, tag standard, instance context.
  68          [$courserow, $userrow] = array_map('array_values', $content);
  69  
  70          $this->assertEquals('Default collection', $courserow[0]);
  71          $this->assertStringContainsString('Horses', $courserow[1]);
  72          $this->assertEquals('No', $courserow[2]);
  73          $this->assertEquals($coursecontext->get_context_name(), $courserow[3]);
  74  
  75          $this->assertEquals('Default collection', $userrow[0]);
  76          $this->assertStringContainsString('Pies', $userrow[1]);
  77          $this->assertEquals('No', $courserow[2]);
  78          $this->assertEquals($usercontext->get_context_name(), $userrow[3]);
  79      }
  80  
  81      /**
  82       * Test datasource columns that aren't added by default
  83       */
  84      public function test_datasource_non_default_columns(): void {
  85          $this->resetAfterTest();
  86  
  87          $this->getDataGenerator()->create_tag(['name' => 'Horses', 'description' => 'Neigh', 'flag' => 2]);
  88          $course = $this->getDataGenerator()->create_course(['tags' => ['Horses']]);
  89          $coursecontext = context_course::instance($course->id);
  90  
  91          /** @var core_reportbuilder_generator $generator */
  92          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  93          $report = $generator->create_report(['name' => 'Notes', 'source' => tags::class, 'default' => 0]);
  94  
  95          // Collection.
  96          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'collection:default']);
  97          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'collection:component']);
  98          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'collection:searchable']);
  99          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'collection:customurl']);
 100  
 101          // Tag.
 102          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:name']);
 103          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:description']);
 104          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:flagged']);
 105          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:timemodified']);
 106  
 107          // Instance.
 108          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:contexturl']);
 109          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:area']);
 110          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:component']);
 111          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:itemtype']);
 112          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:itemid']);
 113          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:timecreated']);
 114          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:timemodified']);
 115  
 116          $content = $this->get_custom_report_content($report->get('id'));
 117          $this->assertCount(1, $content);
 118  
 119          $courserow = array_values($content[0]);
 120  
 121          // Collection.
 122          $this->assertEquals('Yes', $courserow[0]);
 123          $this->assertEmpty($courserow[1]);
 124          $this->assertEquals('Yes', $courserow[2]);
 125          $this->assertEmpty($courserow[3]);
 126  
 127          // Tag.
 128          $this->assertEquals('Horses', $courserow[4]);
 129          $this->assertEquals('<div class="text_to_html">Neigh</div>', $courserow[5]);
 130          $this->assertEquals('Yes', $courserow[6]);
 131          $this->assertNotEmpty($courserow[7]);
 132  
 133          // Instance.
 134          $this->assertEquals('<a href="' . $coursecontext->get_url()  . '">' . $coursecontext->get_context_name()  . '</a>',
 135              $courserow[8]);
 136          $this->assertEquals('Courses', $courserow[9]);
 137          $this->assertEquals('core', $courserow[10]);
 138          $this->assertEquals('course', $courserow[11]);
 139          $this->assertEquals($course->id, $courserow[12]);
 140          $this->assertNotEmpty($courserow[13]);
 141          $this->assertNotEmpty($courserow[14]);
 142      }
 143  
 144      /**
 145       * Data provider for {@see test_datasource_filters}
 146       *
 147       * @return array[]
 148       */
 149      public function datasource_filters_provider(): array {
 150          return [
 151              // Collection.
 152              'Filter collection name' => ['collection:name', [
 153                  'collection:name_operator' => select::NOT_EQUAL_TO,
 154                  'collection:name_value' => -1,
 155              ], true],
 156              'Filter collection default' => ['collection:default', [
 157                  'collection:default_operator' => boolean_select::CHECKED,
 158              ], true],
 159              'Filter collection default (no match)' => ['collection:default', [
 160                  'collection:default_operator' => boolean_select::NOT_CHECKED,
 161              ], false],
 162              'Filter collection searchable' => ['collection:searchable', [
 163                  'collection:searchable_operator' => boolean_select::CHECKED,
 164              ], true],
 165              'Filter collection searchable (no match)' => ['collection:searchable', [
 166                  'collection:searchable_operator' => boolean_select::NOT_CHECKED,
 167              ], false],
 168  
 169              // Tag.
 170              'Filter tag name equal to' => ['tag:name', [
 171                  'tag:name_operator' => tags_filter::EQUAL_TO,
 172                  'tag:name_value' => [-1],
 173              ], false],
 174              'Filter tag name not equal to' => ['tag:name', [
 175                  'tag:name_operator' => tags_filter::NOT_EQUAL_TO,
 176                  'tag:name_value' => [-1],
 177              ], true],
 178              'Filter tag name empty' => ['tag:name', [
 179                  'tag:name_operator' => tags_filter::EMPTY,
 180              ], false],
 181              'Filter tag name not empty' => ['tag:name', [
 182                  'tag:name_operator' => tags_filter::NOT_EMPTY,
 183              ], true],
 184              'Filter tag standard' => ['tag:standard', [
 185                  'tag:standard_operator' => boolean_select::NOT_CHECKED,
 186              ], true],
 187              'Filter tag standard (no match)' => ['tag:standard', [
 188                  'tag:standard_operator' => boolean_select::CHECKED,
 189              ], false],
 190              'Filter tag flagged' => ['tag:flagged', [
 191                  'tag:flagged_operator' => boolean_select::CHECKED,
 192              ], true],
 193              'Filter tag flagged (no match)' => ['tag:flagged', [
 194                  'tag:flagged_operator' => boolean_select::NOT_CHECKED,
 195              ], false],
 196              'Filter tag time modified' => ['tag:timemodified', [
 197                  'tag:timemodified_operator' => date::DATE_RANGE,
 198                  'tag:timemodified_from' => 1622502000,
 199              ], true],
 200              'Filter tag time modified (no match)' => ['tag:timemodified', [
 201                  'tag:timemodified_operator' => date::DATE_RANGE,
 202                  'tag:timemodified_to' => 1622502000,
 203              ], false],
 204  
 205              // Instance.
 206              'Filter instance tag area' => ['instance:area', [
 207                  'instance:area_operator' => select::EQUAL_TO,
 208                  'instance:area_value' => 'core/course',
 209              ], true],
 210              'Filter instance tag area (no match)' => ['instance:area', [
 211                  'instance:area_operator' => select::NOT_EQUAL_TO,
 212                  'instance:area_value' => 'core/course',
 213              ], false],
 214              'Filter instance time created' => ['instance:timecreated', [
 215                  'instance:timecreated_operator' => date::DATE_RANGE,
 216                  'instance:timecreated_from' => 1622502000,
 217              ], true],
 218              'Filter instance time created (no match)' => ['instance:timecreated', [
 219                  'instance:timecreated_operator' => date::DATE_RANGE,
 220                  'instance:timecreated_to' => 1622502000,
 221              ], false],
 222              'Filter instance time modified' => ['instance:timemodified', [
 223                  'instance:timemodified_operator' => date::DATE_RANGE,
 224                  'instance:timemodified_from' => 1622502000,
 225              ], true],
 226              'Filter instance time modified (no match)' => ['instance:timemodified', [
 227                  'instance:timemodified_operator' => date::DATE_RANGE,
 228                  'instance:timemodified_to' => 1622502000,
 229              ], false],
 230          ];
 231      }
 232  
 233      /**
 234       * Test datasource filters
 235       *
 236       * @param string $filtername
 237       * @param array $filtervalues
 238       * @param bool $expectmatch
 239       *
 240       * @dataProvider datasource_filters_provider
 241       */
 242      public function test_datasource_filters(
 243          string $filtername,
 244          array $filtervalues,
 245          bool $expectmatch
 246      ): void {
 247          $this->resetAfterTest();
 248  
 249          $this->getDataGenerator()->create_tag(['name' => 'Horses', 'flag' => 2]);
 250          $this->getDataGenerator()->create_course(['tags' => ['Horses']]);
 251  
 252          /** @var core_reportbuilder_generator $generator */
 253          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 254  
 255          // Create report containing single tag name, and given filter.
 256          $report = $generator->create_report(['name' => 'Tasks', 'source' => tags::class, 'default' => 0]);
 257          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:name']);
 258  
 259          // Add filter, set it's values.
 260          $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
 261          $content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
 262  
 263          if ($expectmatch) {
 264              $this->assertCount(1, $content);
 265              $this->assertEquals('Horses', reset($content[0]));
 266          } else {
 267              $this->assertEmpty($content);
 268          }
 269      }
 270  
 271      /**
 272       * Stress test datasource
 273       *
 274       * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
 275       */
 276      public function test_stress_datasource(): void {
 277          if (!PHPUNIT_LONGTEST) {
 278              $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
 279          }
 280  
 281          $this->resetAfterTest();
 282  
 283          $this->getDataGenerator()->create_course(['tags' => ['Horses']]);
 284  
 285          $this->datasource_stress_test_columns(tags::class);
 286          $this->datasource_stress_test_columns_aggregation(tags::class);
 287          $this->datasource_stress_test_conditions(tags::class, 'tag:name');
 288      }
 289  }