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 310 and 401] [Versions 311 and 401] [Versions 39 and 401]

   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  /**
  18   * Testing the H5P API.
  19   *
  20   * @package    core_h5p
  21   * @category   test
  22   * @copyright  2020 Sara Arjona <sara@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  declare(strict_types = 1);
  27  
  28  namespace core_h5p;
  29  
  30  use stdClass;
  31  
  32  defined('MOODLE_INTERNAL') || die();
  33  
  34  /**
  35   * Test class covering the H5P API.
  36   *
  37   * @package    core_h5p
  38   * @copyright  2020 Sara Arjona <sara@moodle.com>
  39   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   * @coversDefaultClass \core_h5p\api
  41   */
  42  class api_test extends \advanced_testcase {
  43  
  44      /**
  45       * Test the behaviour of delete_library().
  46       *
  47       * @dataProvider  delete_library_provider
  48       * @param  string $libraryname          Machine name of the library to delete.
  49       * @param  int    $expectedh5p          Total of H5P contents expected after deleting the library.
  50       * @param  int    $expectedlibraries    Total of H5P libraries expected after deleting the library.
  51       * @param  int    $expectedcontents     Total of H5P content_libraries expected after deleting the library.
  52       * @param  int    $expecteddependencies Total of H5P library dependencies expected after deleting the library.
  53       */
  54      public function test_delete_library(string $libraryname, int $expectedh5p, int $expectedlibraries,
  55              int $expectedcontents, int $expecteddependencies): void {
  56          global $DB;
  57  
  58          $this->setRunTestInSeparateProcess(true);
  59          $this->resetAfterTest();
  60  
  61          // Generate h5p related data.
  62          $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
  63          $generator->generate_h5p_data();
  64          $generator->create_library_record('H5P.TestingLibrary', 'TestingLibrary', 1, 0);
  65  
  66          // Check the current content in H5P tables is the expected.
  67          $counth5p = $DB->count_records('h5p');
  68          $counth5plibraries = $DB->count_records('h5p_libraries');
  69          $counth5pcontents = $DB->count_records('h5p_contents_libraries');
  70          $counth5pdependencies = $DB->count_records('h5p_library_dependencies');
  71  
  72          $this->assertSame(1, $counth5p);
  73          $this->assertSame(7, $counth5plibraries);
  74          $this->assertSame(5, $counth5pcontents);
  75          $this->assertSame(7, $counth5pdependencies);
  76  
  77          // Delete this library.
  78          $factory = new factory();
  79          $library = $DB->get_record('h5p_libraries', ['machinename' => $libraryname]);
  80          if ($library) {
  81              api::delete_library($factory, $library);
  82          }
  83  
  84          // Check the expected libraries and content have been removed.
  85          $counth5p = $DB->count_records('h5p');
  86          $counth5plibraries = $DB->count_records('h5p_libraries');
  87          $counth5pcontents = $DB->count_records('h5p_contents_libraries');
  88          $counth5pdependencies = $DB->count_records('h5p_library_dependencies');
  89  
  90          $this->assertSame($expectedh5p, $counth5p);
  91          $this->assertSame($expectedlibraries, $counth5plibraries);
  92          $this->assertSame($expectedcontents, $counth5pcontents);
  93          $this->assertSame($expecteddependencies, $counth5pdependencies);
  94      }
  95  
  96      /**
  97       * Data provider for test_delete_library().
  98       *
  99       * @return array
 100       */
 101      public function delete_library_provider(): array {
 102          return [
 103              'Delete MainLibrary' => [
 104                  'MainLibrary',
 105                  0,
 106                  6,
 107                  0,
 108                  4,
 109              ],
 110              'Delete Library1' => [
 111                  'Library1',
 112                  0,
 113                  5,
 114                  0,
 115                  1,
 116              ],
 117              'Delete Library2' => [
 118                  'Library2',
 119                  0,
 120                  4,
 121                  0,
 122                  1,
 123              ],
 124              'Delete Library3' => [
 125                  'Library3',
 126                  0,
 127                  4,
 128                  0,
 129                  0,
 130              ],
 131              'Delete Library4' => [
 132                  'Library4',
 133                  0,
 134                  4,
 135                  0,
 136                  1,
 137              ],
 138              'Delete Library5' => [
 139                  'Library5',
 140                  0,
 141                  3,
 142                  0,
 143                  0,
 144              ],
 145              'Delete a library without dependencies' => [
 146                  'H5P.TestingLibrary',
 147                  1,
 148                  6,
 149                  5,
 150                  7,
 151              ],
 152              'Delete unexisting library' => [
 153                  'LibraryX',
 154                  1,
 155                  7,
 156                  5,
 157                  7,
 158              ],
 159          ];
 160      }
 161  
 162      /**
 163       * Test the behaviour of get_dependent_libraries().
 164       *
 165       * @dataProvider  get_dependent_libraries_provider
 166       * @param  string $libraryname     Machine name of the library to delete.
 167       * @param  int    $expectedvalue   Total of H5P required libraries expected.
 168       */
 169      public function test_get_dependent_libraries(string $libraryname, int $expectedvalue): void {
 170          global $DB;
 171  
 172          $this->resetAfterTest();
 173  
 174          // Generate h5p related data.
 175          $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
 176          $generator->generate_h5p_data();
 177          $generator->create_library_record('H5P.TestingLibrary', 'TestingLibrary', 1, 0);
 178  
 179          // Get required libraries.
 180          $library = $DB->get_record('h5p_libraries', ['machinename' => $libraryname], 'id');
 181          if ($library) {
 182              $libraries = api::get_dependent_libraries((int)$library->id);
 183          } else {
 184              $libraries = [];
 185          }
 186  
 187          $this->assertCount($expectedvalue, $libraries);
 188      }
 189  
 190      /**
 191       * Data provider for test_get_dependent_libraries().
 192       *
 193       * @return array
 194       */
 195      public function get_dependent_libraries_provider(): array {
 196          return [
 197              'Main library of a content' => [
 198                  'MainLibrary',
 199                  0,
 200              ],
 201              'Library1' => [
 202                  'Library1',
 203                  1,
 204              ],
 205              'Library2' => [
 206                  'Library2',
 207                  2,
 208              ],
 209              'Library without dependencies' => [
 210                  'H5P.TestingLibrary',
 211                  0,
 212              ],
 213              'Unexisting library' => [
 214                  'LibraryX',
 215                  0,
 216              ],
 217          ];
 218      }
 219  
 220      /**
 221       * Test the behaviour of get_library().
 222       *
 223       * @dataProvider  get_library_provider
 224       * @param  string $libraryname     Machine name of the library to delete.
 225       * @param  bool   $emptyexpected   Wether the expected result is empty or not.
 226       */
 227      public function test_get_library(string $libraryname, bool $emptyexpected): void {
 228          global $DB;
 229  
 230          $this->resetAfterTest();
 231  
 232          // Generate h5p related data.
 233          $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
 234          $generator->generate_h5p_data();
 235          $generator->create_library_record('H5P.TestingLibrary', 'TestingLibrary', 1, 0);
 236  
 237          // Get the library identifier.
 238          $library = $DB->get_record('h5p_libraries', ['machinename' => $libraryname], 'id');
 239          if ($library) {
 240              $result = api::get_library((int)$library->id);
 241          } else {
 242              $result = null;
 243          }
 244  
 245          if ($emptyexpected) {
 246              $this->assertEmpty($result);
 247          } else {
 248              $this->assertEquals($library->id, $result->id);
 249              $this->assertEquals($libraryname, $result->machinename);
 250          }
 251  
 252      }
 253  
 254      /**
 255       * Data provider for test_get_library().
 256       *
 257       * @return array
 258       */
 259      public function get_library_provider(): array {
 260          return [
 261              'Main library of a content' => [
 262                  'MainLibrary',
 263                  false,
 264              ],
 265              'Library1' => [
 266                  'Library1',
 267                  false,
 268              ],
 269              'Library without dependencies' => [
 270                  'H5P.TestingLibrary',
 271                  false,
 272              ],
 273              'Unexisting library' => [
 274                  'LibraryX',
 275                  true,
 276              ],
 277          ];
 278      }
 279  
 280      /**
 281       * Test the behaviour of get_content_from_pluginfile_url().
 282       */
 283      public function test_get_content_from_pluginfile_url(): void {
 284          $this->setRunTestInSeparateProcess(true);
 285          $this->resetAfterTest();
 286          $factory = new factory();
 287  
 288          // Create the H5P data.
 289          $filename = 'find-the-words.h5p';
 290          $path = __DIR__ . '/fixtures/' . $filename;
 291          $fakefile = helper::create_fake_stored_file_from_path($path);
 292          $config = (object)[
 293              'frame' => 1,
 294              'export' => 1,
 295              'embed' => 0,
 296              'copyright' => 0,
 297          ];
 298  
 299          // Get URL for this H5P content file.
 300          $syscontext = \context_system::instance();
 301          $url = \moodle_url::make_pluginfile_url(
 302              $syscontext->id,
 303              \core_h5p\file_storage::COMPONENT,
 304              'unittest',
 305              $fakefile->get_itemid(),
 306              '/',
 307              $filename
 308          );
 309  
 310          // Scenario 1: Get the H5P for this URL and check there isn't any existing H5P (because it hasn't been saved).
 311          list($newfile, $h5p) = api::get_content_from_pluginfile_url($url->out());
 312          $this->assertEquals($fakefile->get_pathnamehash(), $newfile->get_pathnamehash());
 313          $this->assertEquals($fakefile->get_contenthash(), $newfile->get_contenthash());
 314          $this->assertFalse($h5p);
 315  
 316          // Scenario 2: Save the H5P and check now the H5P is exactly the same as the original one.
 317          $h5pid = helper::save_h5p($factory, $fakefile, $config);
 318          list($newfile, $h5p) = api::get_content_from_pluginfile_url($url->out());
 319  
 320          $this->assertEquals($h5pid, $h5p->id);
 321          $this->assertEquals($fakefile->get_pathnamehash(), $h5p->pathnamehash);
 322          $this->assertEquals($fakefile->get_contenthash(), $h5p->contenthash);
 323  
 324          // Scenario 3: Get the H5P for an unexisting H5P file.
 325          $url = \moodle_url::make_pluginfile_url(
 326              $syscontext->id,
 327              \core_h5p\file_storage::COMPONENT,
 328              'unittest',
 329              $fakefile->get_itemid(),
 330              '/',
 331              'unexisting.h5p'
 332          );
 333          list($newfile, $h5p) = api::get_content_from_pluginfile_url($url->out());
 334          $this->assertFalse($newfile);
 335          $this->assertFalse($h5p);
 336      }
 337  
 338      /**
 339       * Test the behaviour of get_original_content_from_pluginfile_url().
 340       *
 341       * @covers ::get_original_content_from_pluginfile_url
 342       */
 343      public function test_get_original_content_from_pluginfile_url(): void {
 344          $this->setRunTestInSeparateProcess(true);
 345          $this->resetAfterTest();
 346          $this->setAdminUser();
 347  
 348          $factory = new factory();
 349          $syscontext = \context_system::instance();
 350  
 351          // Create the original file.
 352          $filename = 'greeting-card.h5p';
 353          $path = __DIR__ . '/fixtures/' . $filename;
 354          $originalfile = helper::create_fake_stored_file_from_path($path);
 355          $originalfilerecord = [
 356              'contextid' => $originalfile->get_contextid(),
 357              'component' => $originalfile->get_component(),
 358              'filearea'  => $originalfile->get_filearea(),
 359              'itemid'    => $originalfile->get_itemid(),
 360              'filepath'  => $originalfile->get_filepath(),
 361              'filename'  => $originalfile->get_filename(),
 362          ];
 363  
 364          $config = (object)[
 365              'frame' => 1,
 366              'export' => 1,
 367              'embed' => 0,
 368              'copyright' => 0,
 369          ];
 370  
 371          $originalurl = \moodle_url::make_pluginfile_url(
 372              $originalfile->get_contextid(),
 373              $originalfile->get_component(),
 374              $originalfile->get_filearea(),
 375              $originalfile->get_itemid(),
 376              $originalfile->get_filepath(),
 377              $originalfile->get_filename()
 378          );
 379  
 380          // Create a reference to the original file.
 381          $reffilerecord = [
 382              'contextid' => $syscontext->id,
 383              'component' => 'core',
 384              'filearea'  => 'phpunit',
 385              'itemid'    => 0,
 386              'filepath'  => '/',
 387              'filename'  => $filename
 388          ];
 389  
 390          $fs = get_file_storage();
 391          $ref = $fs->pack_reference($originalfilerecord);
 392          $repos = \repository::get_instances(['type' => 'user']);
 393          $userrepository = reset($repos);
 394          $referencedfile = $fs->create_file_from_reference($reffilerecord, $userrepository->id, $ref);
 395          $this->assertEquals($referencedfile->get_contenthash(), $originalfile->get_contenthash());
 396  
 397          $referencedurl = \moodle_url::make_pluginfile_url(
 398              $syscontext->id,
 399              'core',
 400              'phpunit',
 401              0,
 402              '/',
 403              $filename
 404          );
 405  
 406          // Scenario 1: Original file (without any reference).
 407          $originalh5pid = helper::save_h5p($factory, $originalfile, $config);
 408          list($source, $h5p, $file) = api::get_original_content_from_pluginfile_url($originalurl->out());
 409          $this->assertEquals($originalfile->get_pathnamehash(), $source->get_pathnamehash());
 410          $this->assertEquals($originalfile->get_contenthash(), $source->get_contenthash());
 411          $this->assertEquals($originalh5pid, $h5p->id);
 412          $this->assertFalse($file);
 413  
 414          // Scenario 2: Referenced file (alias to originalfile).
 415          list($source, $h5p, $file) = api::get_original_content_from_pluginfile_url($referencedurl->out());
 416          $this->assertEquals($originalfile->get_pathnamehash(), $source->get_pathnamehash());
 417          $this->assertEquals($originalfile->get_contenthash(), $source->get_contenthash());
 418          $this->assertEquals($originalfile->get_contenthash(), $source->get_contenthash());
 419          $this->assertEquals($originalh5pid, $h5p->id);
 420          $this->assertEquals($referencedfile->get_pathnamehash(), $file->get_pathnamehash());
 421          $this->assertEquals($referencedfile->get_contenthash(), $file->get_contenthash());
 422          $this->assertEquals($referencedfile->get_contenthash(), $file->get_contenthash());
 423  
 424          // Scenario 3: Unexisting file.
 425          $unexistingurl = \moodle_url::make_pluginfile_url(
 426              $syscontext->id,
 427              'core',
 428              'phpunit',
 429              0,
 430              '/',
 431              'unexisting.h5p'
 432          );
 433          list($source, $h5p, $file) = api::get_original_content_from_pluginfile_url($unexistingurl->out());
 434          $this->assertFalse($source);
 435          $this->assertFalse($h5p);
 436          $this->assertFalse($file);
 437      }
 438  
 439      /**
 440       * Test the behaviour of can_edit_content().
 441       *
 442       * @covers ::can_edit_content
 443       * @dataProvider can_edit_content_provider
 444       *
 445       * @param string $currentuser User who will call the method.
 446       * @param string $fileauthor Author of the file to check.
 447       * @param string $filecomponent Component of the file to check.
 448       * @param bool $expected Expected result after calling the can_edit_content method.
 449       * @param string $filearea Area of the file to check.
 450       *
 451       * @return void
 452       */
 453      public function test_can_edit_content(string $currentuser, string $fileauthor, string $filecomponent, bool $expected,
 454              $filearea = 'unittest'): void {
 455          global $USER, $DB;
 456  
 457          $this->setRunTestInSeparateProcess(true);
 458          $this->resetAfterTest();
 459  
 460          // Create course.
 461          $course = $this->getDataGenerator()->create_course();
 462          $context = \context_course::instance($course->id);
 463  
 464          // Create some users.
 465          $this->setAdminUser();
 466          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
 467          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 468          $users = [
 469              'admin' => $USER,
 470              'teacher' => $teacher,
 471              'student' => $student,
 472          ];
 473  
 474          // Set current user.
 475          if ($currentuser !== 'admin') {
 476              $this->setUser($users[$currentuser]);
 477          }
 478  
 479          $itemid = rand();
 480          if ($filearea === 'post') {
 481              // Create a forum and add a discussion.
 482              $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
 483  
 484              $record = new stdClass();
 485              $record->course = $course->id;
 486              $record->userid = $users[$fileauthor]->id;
 487              $record->forum = $forum->id;
 488              $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
 489              $post = $DB->get_record('forum_posts', ['discussion' => $discussion->id]);
 490              $itemid = $post->id;
 491          }
 492  
 493          // Create the file.
 494          $filename = 'greeting-card.h5p';
 495          $path = __DIR__ . '/fixtures/' . $filename;
 496          if ($filecomponent === 'contentbank') {
 497              $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
 498              $contents = $generator->generate_contentbank_data(
 499                  'contenttype_h5p',
 500                  1,
 501                  (int)$users[$fileauthor]->id,
 502                  $context,
 503                  true,
 504                  $path
 505              );
 506              $content = array_shift($contents);
 507              $file = $content->get_file();
 508          } else {
 509              $filerecord = [
 510                  'contextid' => $context->id,
 511                  'component' => $filecomponent,
 512                  'filearea'  => $filearea,
 513                  'itemid'    => $itemid,
 514                  'filepath'  => '/',
 515                  'filename'  => basename($path),
 516                  'userid'    => $users[$fileauthor]->id,
 517              ];
 518              $fs = get_file_storage();
 519              $file = $fs->create_file_from_pathname($filerecord, $path);
 520          }
 521  
 522          // Check if the currentuser can edit the file.
 523          $result = api::can_edit_content($file);
 524          $this->assertEquals($expected, $result);
 525      }
 526  
 527      /**
 528       * Data provider for test_can_edit_content().
 529       *
 530       * @return array
 531       */
 532      public function can_edit_content_provider(): array {
 533          return [
 534              // Component = user.
 535              'user: Admin user is author' => [
 536                  'currentuser' => 'admin',
 537                  'fileauthor' => 'admin',
 538                  'filecomponent' => 'user',
 539                  'expected' => true,
 540              ],
 541              'user: Admin user, teacher is author' => [
 542                  'currentuser' => 'admin',
 543                  'fileauthor' => 'teacher',
 544                  'filecomponent' => 'user',
 545                  'expected' => false,
 546              ],
 547              'user: Teacher user, teacher is author' => [
 548                  'currentuser' => 'teacher',
 549                  'fileauthor' => 'teacher',
 550                  'filecomponent' => 'user',
 551                  'expected' => true,
 552              ],
 553              'user: Teacher user, admin is author' => [
 554                  'currentuser' => 'teacher',
 555                  'fileauthor' => 'admin',
 556                  'filecomponent' => 'user',
 557                  'expected' => false,
 558              ],
 559              'user: Student user, student is author' => [
 560                  'currentuser' => 'student',
 561                  'fileauthor' => 'student',
 562                  'filecomponent' => 'user',
 563                  'expected' => true,
 564              ],
 565              'user: Student user, teacher is author' => [
 566                  'currentuser' => 'student',
 567                  'fileauthor' => 'teacher',
 568                  'filecomponent' => 'user',
 569                  'expected' => false,
 570              ],
 571  
 572              // Component = mod_h5pactivity.
 573              'mod_h5pactivity: Admin user is author' => [
 574                  'currentuser' => 'admin',
 575                  'fileauthor' => 'admin',
 576                  'filecomponent' => 'mod_h5pactivity',
 577                  'expected' => true,
 578              ],
 579              'mod_h5pactivity: Admin user, teacher is author' => [
 580                  'currentuser' => 'admin',
 581                  'fileauthor' => 'teacher',
 582                  'filecomponent' => 'mod_h5pactivity',
 583                  'expected' => true,
 584              ],
 585              'mod_h5pactivity: Teacher user, teacher is author' => [
 586                  'currentuser' => 'teacher',
 587                  'fileauthor' => 'teacher',
 588                  'filecomponent' => 'mod_h5pactivity',
 589                  'expected' => true,
 590              ],
 591              'mod_h5pactivity: Teacher user, admin is author' => [
 592                  'currentuser' => 'teacher',
 593                  'fileauthor' => 'admin',
 594                  'filecomponent' => 'mod_h5pactivity',
 595                  'expected' => true,
 596              ],
 597              'mod_h5pactivity: Student user, student is author' => [
 598                  'currentuser' => 'student',
 599                  'fileauthor' => 'student',
 600                  'filecomponent' => 'mod_h5pactivity',
 601                  'expected' => false,
 602              ],
 603              'mod_h5pactivity: Student user, teacher is author' => [
 604                  'currentuser' => 'student',
 605                  'fileauthor' => 'teacher',
 606                  'filecomponent' => 'mod_h5pactivity',
 607                  'expected' => false,
 608              ],
 609  
 610              // Component = mod_book.
 611              'mod_book: Admin user is author' => [
 612                  'currentuser' => 'admin',
 613                  'fileauthor' => 'admin',
 614                  'filecomponent' => 'mod_book',
 615                  'expected' => true,
 616              ],
 617              'mod_book: Admin user, teacher is author' => [
 618                  'currentuser' => 'admin',
 619                  'fileauthor' => 'teacher',
 620                  'filecomponent' => 'mod_book',
 621                  'expected' => true,
 622              ],
 623  
 624              // Component = mod_forum.
 625              'mod_forum: Admin user is author' => [
 626                  'currentuser' => 'admin',
 627                  'fileauthor' => 'admin',
 628                  'filecomponent' => 'mod_forum',
 629                  'expected' => true,
 630              ],
 631              'mod_forum: Admin user, teacher is author' => [
 632                  'currentuser' => 'admin',
 633                  'fileauthor' => 'teacher',
 634                  'filecomponent' => 'mod_forum',
 635                  'expected' => true,
 636              ],
 637              'mod_forum: Teacher user, admin is author' => [
 638                  'currentuser' => 'teacher',
 639                  'fileauthor' => 'admin',
 640                  'filecomponent' => 'mod_forum',
 641                  'expected' => true,
 642              ],
 643              'mod_forum: Student user, teacher is author' => [
 644                  'currentuser' => 'student',
 645                  'fileauthor' => 'teacher',
 646                  'filecomponent' => 'mod_forum',
 647                  'expected' => false,
 648              ],
 649              'mod_forum/post: Admin user is author' => [
 650                  'currentuser' => 'admin',
 651                  'fileauthor' => 'admin',
 652                  'filecomponent' => 'mod_forum',
 653                  'expected' => true,
 654                  'filearea' => 'post',
 655              ],
 656              'mod_forum/post: Teacher user, admin is author' => [
 657                  'currentuser' => 'teacher',
 658                  'fileauthor' => 'admin',
 659                  'filecomponent' => 'mod_forum',
 660                  'expected' => true,
 661                  'filearea' => 'post',
 662              ],
 663              'mod_forum/post: Student user, teacher is author' => [
 664                  'currentuser' => 'student',
 665                  'fileauthor' => 'teacher',
 666                  'filecomponent' => 'mod_forum',
 667                  'expected' => false,
 668                  'filearea' => 'post',
 669              ],
 670  
 671              // Component = block_html.
 672              'block_html: Admin user is author' => [
 673                  'currentuser' => 'admin',
 674                  'fileauthor' => 'admin',
 675                  'filecomponent' => 'block_html',
 676                  'expected' => true,
 677              ],
 678              'block_html: Admin user, teacher is author' => [
 679                  'currentuser' => 'admin',
 680                  'fileauthor' => 'teacher',
 681                  'filecomponent' => 'block_html',
 682                  'expected' => true,
 683              ],
 684  
 685              // Component = contentbank.
 686              'contentbank: Admin user is author' => [
 687                  'currentuser' => 'admin',
 688                  'fileauthor' => 'admin',
 689                  'filecomponent' => 'contentbank',
 690                  'expected' => true,
 691              ],
 692              'contentbank: Admin user, teacher is author' => [
 693                  'currentuser' => 'admin',
 694                  'fileauthor' => 'teacher',
 695                  'filecomponent' => 'contentbank',
 696                  'expected' => true,
 697              ],
 698              'contentbank: Teacher user, teacher is author' => [
 699                  'currentuser' => 'teacher',
 700                  'fileauthor' => 'teacher',
 701                  'filecomponent' => 'contentbank',
 702                  'expected' => true,
 703              ],
 704              'contentbank: Teacher user, admin is author' => [
 705                  'currentuser' => 'teacher',
 706                  'fileauthor' => 'admin',
 707                  'filecomponent' => 'contentbank',
 708                  'expected' => false,
 709              ],
 710              'contentbank: Student user, student is author' => [
 711                  'currentuser' => 'student',
 712                  'fileauthor' => 'student',
 713                  'filecomponent' => 'contentbank',
 714                  'expected' => false,
 715              ],
 716              'contentbank: Student user, teacher is author' => [
 717                  'currentuser' => 'student',
 718                  'fileauthor' => 'teacher',
 719                  'filecomponent' => 'contentbank',
 720                  'expected' => false,
 721              ],
 722  
 723              // Unexisting components.
 724              'Unexisting component' => [
 725                  'currentuser' => 'admin',
 726                  'fileauthor' => 'admin',
 727                  'filecomponent' => 'unexisting_component',
 728                  'expected' => false,
 729              ],
 730              'Unexisting module activity' => [
 731                  'currentuser' => 'admin',
 732                  'fileauthor' => 'admin',
 733                  'filecomponent' => 'mod_unexisting',
 734                  'expected' => false,
 735              ],
 736              'Unexisting block' => [
 737                  'currentuser' => 'admin',
 738                  'fileauthor' => 'admin',
 739                  'filecomponent' => 'block_unexisting',
 740                  'expected' => false,
 741              ],
 742          ];
 743      }
 744  
 745      /**
 746       * Test the behaviour of create_content_from_pluginfile_url().
 747       */
 748      public function test_create_content_from_pluginfile_url(): void {
 749          global $DB;
 750  
 751          $this->setRunTestInSeparateProcess(true);
 752          $this->resetAfterTest();
 753          $factory = new factory();
 754  
 755          // Create the H5P data.
 756          $filename = 'find-the-words.h5p';
 757          $path = __DIR__ . '/fixtures/' . $filename;
 758          $fakefile = helper::create_fake_stored_file_from_path($path);
 759          $config = (object)[
 760              'frame' => 1,
 761              'export' => 1,
 762              'embed' => 0,
 763              'copyright' => 0,
 764          ];
 765  
 766          // Get URL for this H5P content file.
 767          $syscontext = \context_system::instance();
 768          $url = \moodle_url::make_pluginfile_url(
 769              $syscontext->id,
 770              \core_h5p\file_storage::COMPONENT,
 771              'unittest',
 772              $fakefile->get_itemid(),
 773              '/',
 774              $filename
 775          );
 776  
 777          // Scenario 1: Create the H5P from this URL and check the content is exactly the same as the fake file.
 778          $messages = new \stdClass();
 779          list($newfile, $h5pid) = api::create_content_from_pluginfile_url($url->out(), $config, $factory, $messages);
 780          $this->assertNotFalse($h5pid);
 781          $h5p = $DB->get_record('h5p', ['id' => $h5pid]);
 782          $this->assertEquals($fakefile->get_pathnamehash(), $h5p->pathnamehash);
 783          $this->assertEquals($fakefile->get_contenthash(), $h5p->contenthash);
 784          $this->assertTrue(empty($messages->error));
 785          $this->assertTrue(empty($messages->info));
 786  
 787          // Scenario 2: Create the H5P for an unexisting H5P file.
 788          $url = \moodle_url::make_pluginfile_url(
 789              $syscontext->id,
 790              \core_h5p\file_storage::COMPONENT,
 791              'unittest',
 792              $fakefile->get_itemid(),
 793              '/',
 794              'unexisting.h5p'
 795          );
 796          list($newfile, $h5p) = api::create_content_from_pluginfile_url($url->out(), $config, $factory, $messages);
 797          $this->assertFalse($newfile);
 798          $this->assertFalse($h5p);
 799          $this->assertTrue(empty($messages->error));
 800          $this->assertTrue(empty($messages->info));
 801      }
 802  
 803      /**
 804       * Test the behaviour of delete_content_from_pluginfile_url().
 805       */
 806      public function test_delete_content_from_pluginfile_url(): void {
 807          global $DB;
 808  
 809          $this->setRunTestInSeparateProcess(true);
 810          $this->resetAfterTest();
 811          $factory = new factory();
 812  
 813          // Create the H5P data.
 814          $filename = 'find-the-words.h5p';
 815          $path = __DIR__ . '/fixtures/' . $filename;
 816          $fakefile = helper::create_fake_stored_file_from_path($path);
 817          $config = (object)[
 818              'frame' => 1,
 819              'export' => 1,
 820              'embed' => 0,
 821              'copyright' => 0,
 822          ];
 823  
 824          // Get URL for this H5P content file.
 825          $syscontext = \context_system::instance();
 826          $url = \moodle_url::make_pluginfile_url(
 827              $syscontext->id,
 828              \core_h5p\file_storage::COMPONENT,
 829              'unittest',
 830              $fakefile->get_itemid(),
 831              '/',
 832              $filename
 833          );
 834  
 835          // Scenario 1: Try to remove the H5P content for an undeployed file.
 836          list($newfile, $h5p) = api::get_content_from_pluginfile_url($url->out());
 837          $this->assertEquals(0, $DB->count_records('h5p'));
 838          api::delete_content_from_pluginfile_url($url->out(), $factory);
 839          $this->assertEquals(0, $DB->count_records('h5p'));
 840  
 841          // Scenario 2: Deploy an H5P from this URL, check it's created, remove it and check it has been removed as expected.
 842          $this->assertEquals(0, $DB->count_records('h5p'));
 843  
 844          $messages = new \stdClass();
 845          list($newfile, $h5pid) = api::create_content_from_pluginfile_url($url->out(), $config, $factory, $messages);
 846          $this->assertEquals(1, $DB->count_records('h5p'));
 847  
 848          api::delete_content_from_pluginfile_url($url->out(), $factory);
 849          $this->assertEquals(0, $DB->count_records('h5p'));
 850  
 851          // Scenario 3: Try to remove the H5P for an unexisting H5P URL.
 852          $url = \moodle_url::make_pluginfile_url(
 853              $syscontext->id,
 854              \core_h5p\file_storage::COMPONENT,
 855              'unittest',
 856              $fakefile->get_itemid(),
 857              '/',
 858              'unexisting.h5p'
 859          );
 860          $this->assertEquals(0, $DB->count_records('h5p'));
 861          api::delete_content_from_pluginfile_url($url->out(), $factory);
 862          $this->assertEquals(0, $DB->count_records('h5p'));
 863      }
 864  
 865      /**
 866       * Test the behaviour of get_export_info_from_context_id().
 867       */
 868      public function test_get_export_info_from_context_id(): void {
 869          global $DB;
 870  
 871          $this->setRunTestInSeparateProcess(true);
 872          $this->resetAfterTest();
 873          $factory = new factory();
 874  
 875          // Create the H5P data.
 876          $filename = 'find-the-words.h5p';
 877          $syscontext = \context_system::instance();
 878  
 879          // Test scenario 1: H5P exists and deployed.
 880          $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
 881          $fakeexportfile = $generator->create_export_file($filename,
 882              $syscontext->id,
 883              \core_h5p\file_storage::COMPONENT,
 884              \core_h5p\file_storage::EXPORT_FILEAREA);
 885  
 886          $exportfile = api::get_export_info_from_context_id($syscontext->id,
 887              $factory,
 888              \core_h5p\file_storage::COMPONENT,
 889              \core_h5p\file_storage::EXPORT_FILEAREA);
 890          $this->assertEquals($fakeexportfile['filename'], $exportfile['filename']);
 891          $this->assertEquals($fakeexportfile['filepath'], $exportfile['filepath']);
 892          $this->assertEquals($fakeexportfile['filesize'], $exportfile['filesize']);
 893          $this->assertEquals($fakeexportfile['timemodified'], $exportfile['timemodified']);
 894          $this->assertEquals($fakeexportfile['fileurl'], $exportfile['fileurl']);
 895  
 896          // Test scenario 2: H5P exist, deployed but the content has changed.
 897          // We need to change the contenthash to simulate the H5P file was changed.
 898          $h5pfile = $DB->get_record('h5p', []);
 899          $h5pfile->contenthash = sha1('testedit');
 900          $DB->update_record('h5p', $h5pfile);
 901          $exportfile = api::get_export_info_from_context_id($syscontext->id,
 902              $factory,
 903              \core_h5p\file_storage::COMPONENT,
 904              \core_h5p\file_storage::EXPORT_FILEAREA);
 905          $this->assertNull($exportfile);
 906  
 907          // Tests scenario 3: H5P is not deployed.
 908          // We need to delete the H5P record to simulate the H5P was not deployed.
 909          $DB->delete_records('h5p', ['id' => $h5pfile->id]);
 910          $exportfile = api::get_export_info_from_context_id($syscontext->id,
 911              $factory,
 912              \core_h5p\file_storage::COMPONENT,
 913              \core_h5p\file_storage::EXPORT_FILEAREA);
 914          $this->assertNull($exportfile);
 915      }
 916  
 917      /**
 918       * Test the behaviour of set_library_enabled().
 919       *
 920       * @covers ::set_library_enabled
 921       * @dataProvider set_library_enabled_provider
 922       *
 923       * @param string $libraryname Library name to enable/disable.
 924       * @param string $action Action to be done with the library. Supported values: enable, disable.
 925       * @param int $expected Expected value for the enabled library field. -1 will be passed if the library doesn't exist.
 926       */
 927      public function test_set_library_enabled(string $libraryname, string $action, int $expected): void {
 928          global $DB;
 929  
 930          $this->resetAfterTest();
 931  
 932          // Create libraries.
 933          $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
 934          $generator->generate_h5p_data();
 935  
 936          // Check by default the library is enabled.
 937          $library = $DB->get_record('h5p_libraries', ['machinename' => $libraryname]);
 938          if ($expected >= 0) {
 939              $this->assertEquals(1, $library->enabled);
 940              $libraryid = (int) $library->id;
 941          } else {
 942              // Unexisting library. Set libraryid to some unexisting id.
 943              $libraryid = -1;
 944              $this->expectException('dml_missing_record_exception');
 945          }
 946  
 947          \core_h5p\api::set_library_enabled($libraryid, ($action == 'enable'));
 948  
 949          // Check the value of the "enabled" field after calling enable/disable method.
 950          $libraries = $DB->get_records('h5p_libraries');
 951          foreach ($libraries as $libraryid => $library) {
 952              if ($library->machinename == $libraryname) {
 953                  $this->assertEquals($expected, $library->enabled);
 954              } else {
 955                  // Check that only $libraryname has been enabled/disabled.
 956                  $this->assertEquals(1, $library->enabled);
 957              }
 958          }
 959      }
 960  
 961      /**
 962       * Data provider for test_set_library_enabled().
 963       *
 964       * @return array
 965       */
 966      public function set_library_enabled_provider(): array {
 967          return [
 968              'Disable existing library' => [
 969                  'libraryname' => 'MainLibrary',
 970                  'action' => 'disable',
 971                  'expected' => 0,
 972              ],
 973              'Enable existing library' => [
 974                  'libraryname' => 'MainLibrary',
 975                  'action' => 'enable',
 976                  'expected' => 1,
 977              ],
 978              'Disable existing library (not main)' => [
 979                  'libraryname' => 'Library1',
 980                  'action' => 'disable',
 981                  'expected' => 0,
 982              ],
 983              'Enable existing library (not main)' => [
 984                  'libraryname' => 'Library1',
 985                  'action' => 'enable',
 986                  'expected' => 1,
 987              ],
 988              'Disable existing library (not runnable)' => [
 989                  'libraryname' => 'Library3',
 990                  'action' => 'disable',
 991                  'expected' => 1, // Not runnable libraries can't be disabled.
 992              ],
 993              'Enable existing library (not runnable)' => [
 994                  'libraryname' => 'Library3',
 995                  'action' => 'enable',
 996                  'expected' => 1,
 997              ],
 998              'Enable unexisting library' => [
 999                  'libraryname' => 'Unexisting library',
1000                  'action' => 'enable',
1001                  'expected' => -1,
1002              ],
1003              'Disable unexisting library' => [
1004                  'libraryname' => 'Unexisting library',
1005                  'action' => 'disable',
1006                  'expected' => -1,
1007              ],
1008          ];
1009      }
1010  
1011      /**
1012       * Test the behaviour of is_library_enabled().
1013       *
1014       * @covers ::is_library_enabled
1015       * @dataProvider is_library_enabled_provider
1016       *
1017       * @param string $libraryname Library name to check.
1018       * @param bool $expected Expected result after calling the method.
1019       * @param bool $exception Exception expected or not.
1020       * @param bool $useid Whether to use id for calling is_library_enabled method.
1021       * @param bool $uselibraryname Whether to use libraryname for calling is_library_enabled method.
1022       */
1023      public function test_is_library_enabled(string $libraryname, bool $expected, bool $exception = false,
1024          bool $useid = false, bool $uselibraryname = true): void {
1025          global $DB;
1026  
1027          $this->resetAfterTest();
1028  
1029          // Create the following libraries:
1030          // - H5P.Lib1: 1 version enabled, 1 version disabled.
1031          // - H5P.Lib2: 2 versions enabled.
1032          // - H5P.Lib3: 2 versions disabled.
1033          // - H5P.Lib4: 1 version disabled.
1034          // - H5P.Lib5: 1 version enabled.
1035          $generator = $this->getDataGenerator()->get_plugin_generator('core_h5p');
1036          $libraries = [
1037              'H5P.Lib1.1' => $generator->create_library_record('H5P.Lib1', 'Lib1', 1, 1, 0, '', null, null, null, false),
1038              'H5P.Lib1.2' => $generator->create_library_record('H5P.Lib1', 'Lib1', 1, 2),
1039              'H5P.Lib2.1' => $generator->create_library_record('H5P.Lib2', 'Lib2', 2, 1),
1040              'H5P.Lib2.2' => $generator->create_library_record('H5P.Lib2', 'Lib2', 2, 2),
1041              'H5P.Lib3.1' => $generator->create_library_record('H5P.Lib3', 'Lib3', 3, 1, 0, '', null, null, null, false),
1042              'H5P.Lib3.2' => $generator->create_library_record('H5P.Lib3', 'Lib3', 3, 2, 0, '', null, null, null, false),
1043              'H5P.Lib4.1' => $generator->create_library_record('H5P.Lib4', 'Lib4', 4, 1, 0, '', null, null, null, false),
1044              'H5P.Lib5.1' => $generator->create_library_record('H5P.Lib5', 'Lib5', 5, 1),
1045          ];
1046  
1047          $countenabledlibraries = $DB->count_records('h5p_libraries', ['enabled' => 1]);
1048          $this->assertEquals(4, $countenabledlibraries);
1049  
1050          if ($useid) {
1051              $librarydata = ['id' => $libraries[$libraryname]->id];
1052          } else if ($uselibraryname) {
1053              $librarydata = ['machinename' => $libraryname];
1054          } else {
1055              $librarydata = ['invalid' => true];
1056          }
1057  
1058          if ($exception) {
1059              $this->expectException(\moodle_exception::class);
1060          }
1061  
1062          $result = api::is_library_enabled((object) $librarydata);
1063          $this->assertEquals($expected, $result);
1064      }
1065  
1066      /**
1067       * Data provider for test_is_library_enabled().
1068       *
1069       * @return array
1070       */
1071      public function is_library_enabled_provider(): array {
1072          return [
1073              'Library with 2 versions, one of them disabled' => [
1074                  'libraryname' => 'H5P.Lib1',
1075                  'expected' => false,
1076              ],
1077              'Library with 2 versions, all enabled' => [
1078                  'libraryname' => 'H5P.Lib2',
1079                  'expected' => true,
1080              ],
1081              'Library with 2 versions, all disabled' => [
1082                  'libraryname' => 'H5P.Lib3',
1083                  'expected' => false,
1084              ],
1085              'Library with only one version, disabled' => [
1086                  'libraryname' => 'H5P.Lib4',
1087                  'expected' => false,
1088              ],
1089              'Library with only one version, enabled' => [
1090                  'libraryname' => 'H5P.Lib5',
1091                  'expected' => true,
1092              ],
1093              'Library with 2 versions, one of them disabled (using id) - 1.1 (disabled)' => [
1094                  'libraryname' => 'H5P.Lib1.1',
1095                  'expected' => false,
1096                  'exception' => false,
1097                  'useid' => true,
1098              ],
1099              'Library with 2 versions, one of them disabled (using id) - 1.2 (enabled)' => [
1100                  'libraryname' => 'H5P.Lib1.2',
1101                  'expected' => true,
1102                  'exception' => false,
1103                  'useid' => true,
1104              ],
1105              'Library with 2 versions, all enabled (using id) - 2.1' => [
1106                  'libraryname' => 'H5P.Lib2.1',
1107                  'expected' => true,
1108                  'exception' => false,
1109                  'useid' => true,
1110              ],
1111              'Library with 2 versions, all enabled (using id) - 2.2' => [
1112                  'libraryname' => 'H5P.Lib2.2',
1113                  'expected' => true,
1114                  'exception' => false,
1115                  'useid' => true,
1116              ],
1117              'Library with 2 versions, all disabled (using id) - 3.1' => [
1118                  'libraryname' => 'H5P.Lib3.1',
1119                  'expected' => false,
1120                  'exception' => false,
1121                  'useid' => true,
1122              ],
1123              'Library with 2 versions, all disabled (using id) - 3.2' => [
1124                  'libraryname' => 'H5P.Lib3.2',
1125                  'expected' => false,
1126                  'exception' => false,
1127                  'useid' => true,
1128              ],
1129              'Library with only one version, disabled (using id)' => [
1130                  'libraryname' => 'H5P.Lib4.1',
1131                  'expected' => false,
1132                  'exception' => false,
1133                  'useid' => true,
1134              ],
1135              'Library with only one version, enabled (using id)' => [
1136                  'libraryname' => 'H5P.Lib5.1',
1137                  'expected' => true,
1138                  'exception' => false,
1139                  'useid' => true,
1140              ],
1141              'Unexisting library' => [
1142                  'libraryname' => 'H5P.Unexisting',
1143                  'expected' => true,
1144              ],
1145              'Missing required parameters' => [
1146                  'libraryname' => 'H5P.Unexisting',
1147                  'expected' => false,
1148                  'exception' => true,
1149                  'useid' => false,
1150                  'uselibraryname' => false,
1151              ],
1152          ];
1153      }
1154  
1155      /**
1156       * Test the behaviour of is_valid_package().
1157       * @runInSeparateProcess
1158       *
1159       * @covers ::is_valid_package
1160       * @dataProvider is_valid_package_provider
1161       *
1162       * @param string $filename The H5P content to validate.
1163       * @param bool $expected Expected result after calling the method.
1164       * @param bool $isadmin Whether the user calling the method will be admin or not.
1165       * @param bool $onlyupdatelibs Whether new libraries can be installed or only the existing ones can be updated.
1166       * @param bool $skipcontent Should the content be skipped (so only the libraries will be saved)?
1167       */
1168      public function test_is_valid_package(string $filename, bool $expected, bool $isadmin = false, bool $onlyupdatelibs = false,
1169              bool $skipcontent = false): void {
1170          global $USER;
1171  
1172          $this->resetAfterTest();
1173  
1174          if ($isadmin) {
1175              $this->setAdminUser();
1176              $user = $USER;
1177          } else {
1178              // Create a user.
1179              $user = $this->getDataGenerator()->create_user();
1180              $this->setUser($user);
1181          }
1182  
1183          // Prepare the file.
1184          $path = __DIR__ . $filename;
1185          $file = helper::create_fake_stored_file_from_path($path, (int)$user->id);
1186  
1187          // Check if the H5P content is valid or not.
1188          $result = api::is_valid_package($file, $onlyupdatelibs, $skipcontent);
1189          $this->assertEquals($expected, $result);
1190      }
1191  
1192      /**
1193       * Data provider for test_is_valid_package().
1194       *
1195       * @return array
1196       */
1197      public function is_valid_package_provider(): array {
1198          return [
1199              'Valid H5P file (as admin)' => [
1200                  'filename' => '/fixtures/greeting-card.h5p',
1201                  'expected' => true,
1202                  'isadmin' => true,
1203              ],
1204              'Valid H5P file (as user) without library update and checking content' => [
1205                  'filename' => '/fixtures/greeting-card.h5p',
1206                  'expected' => false, // Libraries are missing and user hasn't the right permissions to upload them.
1207                  'isadmin' => false,
1208                  'onlyupdatelibs' => false,
1209                  'skipcontent' => false,
1210              ],
1211              'Valid H5P file (as user) with library update and checking content' => [
1212                  'filename' => '/fixtures/greeting-card.h5p',
1213                  'expected' => false, // Libraries are missing and user hasn't the right permissions to upload them.
1214                  'isadmin' => false,
1215                  'onlyupdatelibs' => true,
1216                  'skipcontent' => false,
1217              ],
1218              'Valid H5P file (as user) without library update and skipping content' => [
1219                  'filename' => '/fixtures/greeting-card.h5p',
1220                  'expected' => true, // Content check is skipped so the package will be considered valid.
1221                  'isadmin' => false,
1222                  'onlyupdatelibs' => false,
1223                  'skipcontent' => true,
1224              ],
1225              'Valid H5P file (as user) with library update and skipping content' => [
1226                  'filename' => '/fixtures/greeting-card.h5p',
1227                  'expected' => true, // Content check is skipped so the package will be considered valid.
1228                  'isadmin' => false,
1229                  'onlyupdatelibs' => true,
1230                  'skipcontent' => true,
1231              ],
1232              'Invalid H5P file (as admin)' => [
1233                  'filename' => '/fixtures/h5ptest.zip',
1234                  'expected' => false,
1235                  'isadmin' => true,
1236              ],
1237              'Invalid H5P file (as user)' => [
1238                  'filename' => '/fixtures/h5ptest.zip',
1239                  'expected' => false,
1240                  'isadmin' => false,
1241              ],
1242              'Invalid H5P file (as user) skipping content' => [
1243                  'filename' => '/fixtures/h5ptest.zip',
1244                  'expected' => true, // Content check is skipped so the package will be considered valid.
1245                  'isadmin' => false,
1246                  'onlyupdatelibs' => false,
1247                  'skipcontent' => true,
1248              ],
1249          ];
1250      }
1251  }