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