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_files\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, number, select, text};
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  global $CFG;
  31  require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
  32  
  33  /**
  34   * Unit tests for files datasource
  35   *
  36   * @package     core_files
  37   * @covers      \core_files\reportbuilder\datasource\files
  38   * @copyright   2022 Paul Holden <paulh@moodle.com>
  39   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class files_test extends core_reportbuilder_testcase {
  42  
  43      /**
  44       * Test default datasource
  45       */
  46      public function test_datasource_default(): void {
  47          $this->resetAfterTest();
  48  
  49          $user = $this->getDataGenerator()->create_user();
  50          $usercontext = context_user::instance($user->id);
  51  
  52          $this->setUser($user);
  53  
  54          $course = $this->getDataGenerator()->create_course();
  55          $coursecontext = context_course::instance($course->id);
  56  
  57          $this->generate_test_files($coursecontext);
  58  
  59          /** @var core_reportbuilder_generator $generator */
  60          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  61          $report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 1]);
  62  
  63          $content = $this->get_custom_report_content($report->get('id'));
  64          $content = $this->filter_custom_report_content($content, static function(array $row): bool {
  65              return $row['c0_contextid'] !== 'System';
  66          }, 'c0_contextid');
  67  
  68          $this->assertCount(2, $content);
  69  
  70          // First row (course summary file).
  71          [$contextname, $userfullname, $filename, $mimetype, $filesize, $timecreated] = array_values($content[0]);
  72  
  73          $this->assertEquals($coursecontext->get_context_name(), $contextname);
  74          $this->assertEquals(fullname($user), $userfullname);
  75          $this->assertEquals('Hello.txt', $filename);
  76          $this->assertEquals('Text file', $mimetype);
  77          $this->assertEquals("5\xc2\xa0bytes", $filesize);
  78          $this->assertNotEmpty($timecreated);
  79  
  80          // Second row (user draft file).
  81          [$contextname, $userfullname, $filename, $mimetype, $filesize, $timecreated] = array_values($content[1]);
  82  
  83          $this->assertEquals($usercontext->get_context_name(), $contextname);
  84          $this->assertEquals(fullname($user), $userfullname);
  85          $this->assertEquals('Hello.txt', $filename);
  86          $this->assertEquals('Text file', $mimetype);
  87          $this->assertEquals("5\xc2\xa0bytes", $filesize);
  88          $this->assertNotEmpty($timecreated);
  89      }
  90  
  91      /**
  92       * Test datasource columns that aren't added by default
  93       */
  94      public function test_datasource_non_default_columns(): void {
  95          $this->resetAfterTest();
  96          $this->setAdminUser();
  97  
  98          $user = $this->getDataGenerator()->create_user();
  99          $usercontext = context_user::instance($user->id);
 100  
 101          $this->setUser($user);
 102  
 103          $course = $this->getDataGenerator()->create_course();
 104          $coursecontext = context_course::instance($course->id);
 105  
 106          $draftitemid = $this->generate_test_files($coursecontext);
 107  
 108          /** @var core_reportbuilder_generator $generator */
 109          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 110          $report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 0]);
 111  
 112          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:contexturl']);
 113          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:path']);
 114          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:author']);
 115          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:license']);
 116          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:component']);
 117          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:area']);
 118          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:itemid']);
 119  
 120          $content = $this->get_custom_report_content($report->get('id'));
 121          $content = $this->filter_custom_report_content($content, static function(array $row): bool {
 122              return stripos($row['c0_contextid'], 'System') === false;
 123          }, 'c0_contextid');
 124  
 125          // There should be two entries (directory & file) for each context.
 126          $this->assertEquals([
 127              [
 128                  "<a href=\"{$coursecontext->get_url()}\">{$coursecontext->get_context_name()}</a>",
 129                  '/',
 130                  null,
 131                  '',
 132                  'course',
 133                  'summary',
 134                  0,
 135              ],
 136              [
 137                  "<a href=\"{$coursecontext->get_url()}\">{$coursecontext->get_context_name()}</a>",
 138                  '/',
 139                  null,
 140                  '',
 141                  'course',
 142                  'summary',
 143                  0,
 144              ],
 145              [
 146                  "<a href=\"{$usercontext->get_url()}\">{$usercontext->get_context_name()}</a>",
 147                  '/',
 148                  null,
 149                  '',
 150                  'user',
 151                  'draft',
 152                  $draftitemid,
 153              ],
 154              [
 155                  "<a href=\"{$usercontext->get_url()}\">{$usercontext->get_context_name()}</a>",
 156                  '/',
 157                  null,
 158                  '',
 159                  'user',
 160                  'draft',
 161                  $draftitemid,
 162              ],
 163          ], array_map('array_values', $content));
 164      }
 165  
 166      /**
 167       * Data provider for {@see test_datasource_filters}
 168       *
 169       * @return array[]
 170       */
 171      public function datasource_filters_provider(): array {
 172          return [
 173              // File.
 174              'Filter directory' => ['file:directory', [
 175                  'file:directory_operator' => boolean_select::CHECKED,
 176              ], 2],
 177              'Filter draft' => ['file:draft', [
 178                  'file:draft_operator' => boolean_select::CHECKED,
 179              ], 2],
 180              'Filter name' => ['file:name', [
 181                  'file:name_operator' => text::IS_EQUAL_TO,
 182                  'file:name_value' => 'Hello.txt',
 183              ], 2],
 184              'Filter size' => ['file:size', [
 185                  'file:size_operator' => number::GREATER_THAN,
 186                  'file:size_value1' => 2,
 187              ], 2],
 188              'Filter license' => ['file:license', [
 189                  'file:license_operator' => select::EQUAL_TO,
 190                  'file:license_value' => 'unknown',
 191              ], 4],
 192              'Filter license (non match)' => ['file:license', [
 193                  'file:license_operator' => select::EQUAL_TO,
 194                  'file:license_value' => 'public',
 195              ], 0],
 196              'Filter time created' => ['file:timecreated', [
 197                  'file:timecreated_operator' => date::DATE_RANGE,
 198                  'file:timecreated_from' => 1622502000,
 199              ], 4],
 200              'Filter time created (non match)' => ['file:timecreated', [
 201                  'file:timecreated_operator' => date::DATE_RANGE,
 202                  'file:timecreated_to' => 1622502000,
 203              ], 0],
 204  
 205              // User (just to check the join).
 206              'Filter user' => ['user:username', [
 207                  'user:username_operator' => text::IS_EQUAL_TO,
 208                  'user:username_value' => 'alfie',
 209              ], 4],
 210              'Filter user (no match)' => ['user:username', [
 211                  'user:username_operator' => text::IS_EQUAL_TO,
 212                  'user:username_value' => 'lionel',
 213              ], 0],
 214          ];
 215      }
 216  
 217      /**
 218       * Test datasource filters
 219       *
 220       * @param string $filtername
 221       * @param array $filtervalues
 222       * @param int $expectmatchcount
 223       *
 224       * @dataProvider datasource_filters_provider
 225       */
 226      public function test_datasource_filters(
 227          string $filtername,
 228          array $filtervalues,
 229          int $expectmatchcount
 230      ): void {
 231          $this->resetAfterTest();
 232          $this->setAdminUser();
 233  
 234          $user = $this->getDataGenerator()->create_user(['username' => 'alfie']);
 235          $this->setUser($user);
 236  
 237          $course = $this->getDataGenerator()->create_course();
 238          $coursecontext = context_course::instance($course->id);
 239  
 240          $this->generate_test_files($coursecontext);
 241  
 242          /** @var core_reportbuilder_generator $generator */
 243          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 244  
 245          // Create report containing single column, and given filter.
 246          $report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 0]);
 247          $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:context']);
 248  
 249          // Add filter, set it's values.
 250          $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
 251          $content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
 252          $content = $this->filter_custom_report_content($content, static function(array $row): bool {
 253              return stripos($row['c0_contextid'], 'System') === false;
 254          }, 'c0_contextid');
 255  
 256          $this->assertCount($expectmatchcount, $content);
 257      }
 258  
 259      /**
 260       * Stress test datasource
 261       *
 262       * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
 263       */
 264      public function test_stress_datasource(): void {
 265          if (!PHPUNIT_LONGTEST) {
 266              $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
 267          }
 268  
 269          $this->resetAfterTest();
 270          $this->setAdminUser();
 271  
 272          $course = $this->getDataGenerator()->create_course();
 273          $coursecontext = context_course::instance($course->id);
 274  
 275          $this->generate_test_files($coursecontext);
 276  
 277          $this->datasource_stress_test_columns(files::class);
 278          $this->datasource_stress_test_columns_aggregation(files::class);
 279          $this->datasource_stress_test_conditions(files::class, 'file:path');
 280      }
 281  
 282      /**
 283       * Ensuring report content only includes files we have explicitly created within the test, and ordering them
 284       *
 285       * @param array $content
 286       * @param callable $callback
 287       * @param string $sortfield
 288       * @return array
 289       */
 290      protected function filter_custom_report_content(array $content, callable $callback, string $sortfield): array {
 291          $content = array_filter($content, $callback);
 292          core_collator::asort_array_of_arrays_by_key($content, $sortfield);
 293          return array_values($content);
 294      }
 295  
 296      /**
 297       * Helper method to generate some test files for reporting on
 298       *
 299       * @param context_course $context
 300       * @return int Draft item ID
 301       */
 302      protected function generate_test_files(context_course $context): int {
 303          global $USER;
 304  
 305          $draftitemid = file_get_unused_draft_itemid();
 306  
 307          // Populate user draft.
 308          get_file_storage()->create_file_from_string([
 309              'contextid' => context_user::instance($USER->id)->id,
 310              'userid' => $USER->id,
 311              'component' => 'user',
 312              'filearea' => 'draft',
 313              'itemid' => $draftitemid,
 314              'filepath' => '/',
 315              'filename' => 'Hello.txt',
 316          ], 'Hello');
 317  
 318          // Save draft to course summary file area.
 319          file_save_draft_area_files($draftitemid, $context->id, 'course', 'summary', 0);
 320  
 321          return $draftitemid;
 322      }
 323  }