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