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_course\reportbuilder\datasource;
  20  
  21  use core_customfield_generator;
  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 component datasources
  37   *
  38   * @package     core_course
  39   * @covers      \core_course\reportbuilder\datasource\courses
  40   * @copyright   2021 Paul Holden <paulh@moodle.com>
  41   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class courses_test extends core_reportbuilder_testcase {
  44  
  45      /**
  46       * Test default datasource
  47       */
  48      public function test_datasource_default(): void {
  49          $this->resetAfterTest();
  50  
  51          // Test subject.
  52          $category = $this->getDataGenerator()->create_category(['name' => 'My cats']);
  53          $course = $this->getDataGenerator()->create_course([
  54              'category' => $category->id,
  55              'fullname' => 'All about cats',
  56              'shortname' => 'C101',
  57              'idnumber' => 'CAT101'
  58          ]);
  59  
  60          /** @var core_reportbuilder_generator $generator */
  61          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  62          $report = $generator->create_report(['name' => 'Courses', 'source' => courses::class, 'default' => 1]);
  63  
  64          $content = $this->get_custom_report_content($report->get('id'));
  65          $this->assertCount(1, $content);
  66  
  67          $contentrow = array_values($content[0]);
  68  
  69          $this->assertEquals([
  70              $category->get_formatted_name(),
  71              $course->shortname,
  72              $course->fullname,
  73              $course->idnumber,
  74          ], $contentrow);
  75      }
  76  
  77      /**
  78       * Test datasource columns that aren't added by default
  79       */
  80      public function test_datasource_non_default_columns(): void {
  81          $this->resetAfterTest();
  82  
  83          $category = $this->getDataGenerator()->create_category([
  84              'name' => 'Animals',
  85              'idnumber' => 'CAT101',
  86              'description' => 'Category description',
  87          ]);
  88          $course = $this->getDataGenerator()->create_course([
  89              'category' => $category->id,
  90              'fullname' => 'Cats',
  91              'summary' => 'Course description',
  92          ]);
  93  
  94          /** @var core_reportbuilder_generator $generator */
  95          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  96          $report = $generator->create_report(['name' => 'Courses', 'source' => courses::class, 'default' => 0]);
  97  
  98          // Category.
  99          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:path']);
 100          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:idnumber']);
 101          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:description']);
 102  
 103          // Course.
 104          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:coursefullnamewithlink']);
 105          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:courseshortnamewithlink']);
 106          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:courseidnumberewithlink']);
 107          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:summary']);
 108          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:format']);
 109          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:startdate']);
 110          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:enddate']);
 111          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:visible']);
 112          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:groupmode']);
 113          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:groupmodeforce']);
 114          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:lang']);
 115          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:calendartype']);
 116          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:theme']);
 117          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:enablecompletion']);
 118          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:downloadcontent']);
 119  
 120          $content = $this->get_custom_report_content($report->get('id'));
 121          $this->assertCount(1, $content);
 122  
 123          $courserow = array_values($content[0]);
 124  
 125          // Category.
 126          $this->assertEquals($category->get_nested_name(false), $courserow[0]);
 127          $this->assertEquals($category->idnumber, $courserow[1]);
 128          $this->assertEquals(format_text($category->description, $category->descriptionformat), $courserow[2]);
 129  
 130          // Course.
 131          $this->assertStringContainsString($course->fullname, $courserow[3]);
 132          $this->assertStringContainsString($course->shortname, $courserow[4]);
 133          $this->assertStringContainsString($course->idnumber, $courserow[5]);
 134          $this->assertEquals(format_text($course->summary, $course->summaryformat), $courserow[6]);
 135          $this->assertEquals('Topics format', $courserow[7]);
 136          $this->assertEquals(userdate($course->startdate), $courserow[8]);
 137          $this->assertEmpty($courserow[9]);
 138          $this->assertEquals('Yes', $courserow[10]);
 139          $this->assertEquals('No groups', $courserow[11]);
 140          $this->assertEquals('No', $courserow[12]);
 141          $this->assertEmpty($courserow[13]);
 142          $this->assertEmpty($courserow[14]);
 143          $this->assertEmpty($courserow[15]);
 144          $this->assertEquals('No', $courserow[16]);
 145          $this->assertEmpty($courserow[17]);
 146      }
 147  
 148      /**
 149       * Tests courses datasource using multilang filters
 150       */
 151      public function test_courses_datasource_multilang_filters(): void {
 152          $this->resetAfterTest();
 153  
 154          filter_set_global_state('multilang', TEXTFILTER_ON);
 155          filter_set_applies_to_strings('multilang', true);
 156  
 157          // Test subject.
 158          $category = $this->getDataGenerator()->create_category([
 159              'name' => '<span class="multilang" lang="en">Cat (en)</span><span class="multilang" lang="es">Cat (es)</span>',
 160          ]);
 161          $course = $this->getDataGenerator()->create_course([
 162              'category' => $category->id,
 163              'fullname' => '<span class="multilang" lang="en">Crs (en)</span><span class="multilang" lang="es">Crs (es)</span>',
 164          ]);
 165  
 166          /** @var core_reportbuilder_generator $generator */
 167          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 168  
 169          // Create a report containing columns that support multilang content.
 170          $report = $generator->create_report(['name' => 'Courses', 'source' => courses::class, 'default' => 0]);
 171          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:name']);
 172          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname']);
 173          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:coursefullnamewithlink']);
 174  
 175          $content = $this->get_custom_report_content($report->get('id'));
 176          $this->assertCount(1, $content);
 177  
 178          $contentrow = array_values(reset($content));
 179          $this->assertEquals([
 180              'Cat (en)',
 181              'Crs (en)',
 182              '<a href="' . (string) course_get_url($course->id) . '">Crs (en)</a>',
 183          ], $contentrow);
 184      }
 185  
 186      /**
 187       * Data provider for {@see test_datasource_filters}
 188       *
 189       * @return array[]
 190       */
 191      public function datasource_filters_provider(): array {
 192          return [
 193              // Category.
 194              'Filter category' => ['course_category:name', [
 195                  'course_category:name_operator' => select::EQUAL_TO,
 196                  'course_category:name_value' => -1,
 197              ], false],
 198              'Filter category idnumber' => ['course_category:idnumber', [
 199                  'course_category:idnumber_operator' => text::IS_EQUAL_TO,
 200                  'course_category:idnumber_value' => 'CAT101',
 201              ], true],
 202              'Filter category idnumber (no match)' => ['course_category:idnumber', [
 203                  'course_category:idnumber_operator' => text::CONTAINS,
 204                  'course_category:idnumber_value' => 'FRUIT',
 205              ], false],
 206  
 207              // Course.
 208              'Filter course' => ['course:courseselector', [
 209                  'course:courseselector_values' => [-1],
 210              ], false],
 211              'Filter course fullname' => ['course:fullname', [
 212                  'course:fullname_operator' => text::IS_EQUAL_TO,
 213                  'course:fullname_value' => 'Equine',
 214              ], true],
 215              'Filter course fullname (no match)' => ['course:fullname', [
 216                  'course:fullname_operator' => text::IS_EQUAL_TO,
 217                  'course:fullname_value' => 'Foxes',
 218              ], false],
 219              'Filter course shortname' => ['course:shortname', [
 220                  'course:shortname_operator' => text::IS_EQUAL_TO,
 221                  'course:shortname_value' => 'EQ101',
 222              ], true],
 223              'Filter course shortname (no match)' => ['course:shortname', [
 224                  'course:shortname_operator' => text::IS_EQUAL_TO,
 225                  'course:shortname_value' => 'FX101',
 226              ], false],
 227              'Filter course idnumber' => ['course:idnumber', [
 228                  'course:idnumber_operator' => text::IS_EQUAL_TO,
 229                  'course:idnumber_value' => 'E-101AB',
 230              ], true],
 231              'Filter course idnumber (no match)' => ['course:idnumber', [
 232                  'course:idnumber_operator' => text::IS_EQUAL_TO,
 233                  'course:idnumber_value' => 'F-101XT',
 234              ], false],
 235              'Filter course format' => ['course:format', [
 236                  'course:format_operator' => select::EQUAL_TO,
 237                  'course:format_value' => 'topics',
 238              ], true],
 239              'Filter course format (no match)' => ['course:format', [
 240                  'course:format_operator' => select::EQUAL_TO,
 241                  'course:format_value' => 'weekly',
 242              ], false],
 243              'Filter course startdate' => ['course:startdate', [
 244                  'course:startdate_operator' => date::DATE_RANGE,
 245                  'course:startdate_from' => 1622502000,
 246              ], true],
 247              'Filter course startdate (no match)' => ['course:startdate', [
 248                  'course:startdate_operator' => date::DATE_RANGE,
 249                  'course:startdate_to' => 1622502000,
 250              ], false],
 251              'Filter course enddate' => ['course:enddate', [
 252                  'course:enddate_operator' => date::DATE_EMPTY,
 253              ], true],
 254              'Filter course enddate (no match)' => ['course:enddate', [
 255                  'course:enddate_operator' => date::DATE_NOT_EMPTY,
 256              ], false],
 257              'Filter course visible' => ['course:visible', [
 258                  'course:visible_operator' => boolean_select::CHECKED,
 259              ], true],
 260              'Filter course visible (no match)' => ['course:visible', [
 261                  'course:visible_operator' => boolean_select::NOT_CHECKED,
 262              ], false],
 263              'Filter course groupmode' => ['course:groupmode', [
 264                  'course:groupmode_operator' => select::EQUAL_TO,
 265                  'course:groupmode_value' => 0, // No groups.
 266              ], true],
 267              'Filter course groupmode (no match)' => ['course:groupmode', [
 268                  'course:groupmode_operator' => select::EQUAL_TO,
 269                  'course:groupmode_value' => 1, // Separate groups.
 270              ], false],
 271              'Filter course groupmodeforce' => ['course:groupmodeforce', [
 272                  'course:groupmodeforce_operator' => boolean_select::NOT_CHECKED,
 273              ], true],
 274              'Filter course groupmodeforce (no match)' => ['course:groupmodeforce', [
 275                  'course:groupmodeforce_operator' => boolean_select::CHECKED,
 276              ], false],
 277              'Filter course lang' => ['course:lang', [
 278                  'course:lang_operator' => select::EQUAL_TO,
 279                  'course:lang_value' => 'en',
 280              ], true],
 281              'Filter course lang (no match)' => ['course:lang', [
 282                  'course:lang_operator' => select::EQUAL_TO,
 283                  'course:lang_value' => 'de',
 284              ], false],
 285              'Filter course calendartype' => ['course:calendartype', [
 286                  'course:calendartype_operator' => select::EQUAL_TO,
 287                  'course:calendartype_value' => 'gregorian',
 288              ], true],
 289              'Filter course calendartype (no match)' => ['course:calendartype', [
 290                  'course:calendartype_operator' => select::EQUAL_TO,
 291                  'course:calendartype_value' => 'hijri',
 292              ], false],
 293              'Filter course theme' => ['course:theme', [
 294                  'course:theme_operator' => select::EQUAL_TO,
 295                  'course:theme_value' => 'boost',
 296              ], true],
 297              'Filter course theme (no match)' => ['course:theme', [
 298                  'course:theme_operator' => select::EQUAL_TO,
 299                  'course:theme_value' => 'classic',
 300              ], false],
 301              'Filter course enablecompletion' => ['course:enablecompletion', [
 302                  'course:enablecompletion_operator' => boolean_select::NOT_CHECKED,
 303              ], true],
 304              'Filter course enablecompletion (no match)' => ['course:enablecompletion', [
 305                  'course:enablecompletion_operator' => boolean_select::CHECKED,
 306              ], false],
 307              'Filter course downloadcontent' => ['course:downloadcontent', [
 308                  'course:downloadcontent_operator' => boolean_select::CHECKED,
 309              ], true],
 310              'Filter course downloadcontent (no match)' => ['course:downloadcontent', [
 311                  'course:downloadcontent_operator' => boolean_select::NOT_CHECKED,
 312              ], false],
 313          ];
 314      }
 315  
 316      /**
 317       * Test datasource filters
 318       *
 319       * @param string $filtername
 320       * @param array $filtervalues
 321       * @param bool $expectmatch
 322       *
 323       * @dataProvider datasource_filters_provider
 324       */
 325      public function test_datasource_filters(string $filtername, array $filtervalues, bool $expectmatch): void {
 326          $this->resetAfterTest();
 327  
 328          $category = $this->getDataGenerator()->create_category(['name' => 'Animals', 'idnumber' => 'CAT101']);
 329          $course = $this->getDataGenerator()->create_course([
 330              'category' => $category->id,
 331              'fullname' => 'Equine',
 332              'shortname' => 'EQ101',
 333              'idnumber' => 'E-101AB',
 334              'lang' => 'en',
 335              'calendartype' => 'gregorian',
 336              'theme' => 'boost',
 337              'downloadcontent' => 1,
 338          ]);
 339  
 340          /** @var core_reportbuilder_generator $generator */
 341          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 342  
 343          // Create report containing single column, and given filter.
 344          $report = $generator->create_report(['name' => 'Tasks', 'source' => courses::class, 'default' => 0]);
 345          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname']);
 346  
 347          // Add filter, set it's values.
 348          $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
 349          user_filter_manager::set($report->get('id'), $filtervalues);
 350  
 351          $content = $this->get_custom_report_content($report->get('id'));
 352  
 353          if ($expectmatch) {
 354              $this->assertCount(1, $content);
 355              $this->assertEquals($course->fullname, reset($content[0]));
 356          } else {
 357              $this->assertEmpty($content);
 358          }
 359      }
 360  
 361      /**
 362       * Stress test datasource
 363       *
 364       * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
 365       */
 366      public function test_stress_datasource(): void {
 367          if (!PHPUNIT_LONGTEST) {
 368              $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
 369          }
 370  
 371          $this->resetAfterTest();
 372  
 373          /** @var core_customfield_generator $generator */
 374          $generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
 375          $customfieldcategory = $generator->create_category();
 376          $generator->create_field(['categoryid' => $customfieldcategory->get('id'), 'shortname' => 'hi']);
 377  
 378          $category = $this->getDataGenerator()->create_category();
 379          $course = $this->getDataGenerator()->create_course(['category' => $category->id, 'customfield_hi' => 'Hello']);
 380  
 381          $this->datasource_stress_test_columns(courses::class);
 382          $this->datasource_stress_test_columns_aggregation(courses::class);
 383          $this->datasource_stress_test_conditions(courses::class, 'course:idnumber');
 384      }
 385  }