Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

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