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] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core_files;
  18  
  19  /**
  20   * PHPUnit tests for conversion persistent.
  21   *
  22   * @package    core_files
  23   * @copyright  2017 Andrew nicols <andrew@nicols.co.uk>
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  class conversion_test extends \advanced_testcase {
  27  
  28      /**
  29       * Helper to create a stored file object with the given supplied content.
  30       *
  31       * @param   string $filecontent The content of the mocked file
  32       * @param   string $filename The file name to use in the stored_file
  33       * @param   string $filerecord Any overrides to the filerecord
  34       * @return  stored_file
  35       */
  36      protected function create_stored_file($filecontent = 'content', $filename = 'testfile.txt', $filerecord = []) {
  37          $filerecord = array_merge([
  38                  'contextid' => \context_system::instance()->id,
  39                  'component' => 'core',
  40                  'filearea'  => 'unittest',
  41                  'itemid'    => 0,
  42                  'filepath'  => '/',
  43                  'filename'  => $filename,
  44              ], $filerecord);
  45  
  46          $fs = get_file_storage();
  47          $file = $fs->create_file_from_string($filerecord, $filecontent);
  48  
  49          return $file;
  50      }
  51  
  52      /**
  53       * Ensure that get_conversions_for_file returns an existing conversion
  54       * record with matching sourcefileid and targetformat.
  55       */
  56      public function test_get_conversions_for_file_existing_conversion_incomplete() {
  57          $this->resetAfterTest();
  58  
  59          $sourcefile = $this->create_stored_file();
  60  
  61          $existing = new conversion(0, (object) [
  62                  'sourcefileid' => $sourcefile->get_id(),
  63                  'targetformat' => 'pdf',
  64              ]);
  65          $existing->create();
  66  
  67          $conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
  68  
  69          $this->assertCount(1, $conversions);
  70  
  71          $conversion = array_shift($conversions);
  72          $conversionfile = $conversion->get_sourcefile();
  73  
  74          $this->assertEquals($sourcefile->get_id(), $conversionfile->get_id());
  75          $this->assertFalse($conversion->get_destfile());
  76      }
  77  
  78      /**
  79       * Ensure that get_conversions_for_file returns an existing conversion
  80       * record with matching sourcefileid and targetformat when a file with the same
  81       * contenthash is uploaded several times.
  82       *
  83       * @covers \core_files\conversion::get_conversions_for_file
  84       */
  85      public function test_get_conversions_for_multiple_files_existing_conversion_incomplete() {
  86          $this->resetAfterTest();
  87  
  88          // Create a bunch of files with the same content.
  89          for ($i = 0; $i < 5; $i++) {
  90              $sourcefiles[] = $this->create_stored_file('test content', 'testfile' . $i . '.txt');
  91          }
  92  
  93          // Use only one file for the conversion.
  94          // Pick some file in the middle.
  95          $sourcefile = $sourcefiles[count($sourcefiles) - 2];
  96  
  97          $existing = new conversion(0, (object) [
  98              'sourcefileid' => $sourcefile->get_id(),
  99              'targetformat' => 'pdf',
 100          ]);
 101          $existing->create();
 102  
 103          $conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
 104          $this->assertCount(1, $conversions);
 105  
 106          $conversion = array_shift($conversions);
 107          $conversionfile = $conversion->get_sourcefile();
 108  
 109          $this->assertEquals($sourcefile->get_id(), $conversionfile->get_id());
 110          $this->assertFalse($conversion->get_destfile());
 111  
 112          // Check that getting the conversion for a different file record with the same contenthash
 113          // returns the same conversion as above.
 114          $conversions = conversion::get_conversions_for_file($sourcefiles[count($sourcefiles) - 1], 'pdf');
 115          $this->assertCount(1, $conversions);
 116  
 117          $conversion = array_shift($conversions);
 118          $conversionfile = $conversion->get_sourcefile();
 119  
 120          $this->assertEquals($existing->get('id'), $conversion->get('id'));
 121          $this->assertEquals($sourcefile->get_id(), $conversionfile->get_id());
 122          $this->assertFalse($conversion->get_destfile());
 123      }
 124  
 125      /**
 126       * Ensure that get_conversions_for_file returns an existing conversion
 127       * record with matching sourcefileid and targetformat when a second
 128       * conversion to a different format exists.
 129       */
 130      public function test_get_conversions_for_file_existing_conversion_multiple_formats_incomplete() {
 131          $this->resetAfterTest();
 132  
 133          $sourcefile = $this->create_stored_file();
 134  
 135          $existing = new conversion(0, (object) [
 136                  'sourcefileid' => $sourcefile->get_id(),
 137                  'targetformat' => 'pdf',
 138              ]);
 139          $existing->create();
 140  
 141          $second = new conversion(0, (object) [
 142                  'sourcefileid' => $sourcefile->get_id(),
 143                  'targetformat' => 'doc',
 144              ]);
 145          $second->create();
 146  
 147          $conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
 148  
 149          $this->assertCount(1, $conversions);
 150  
 151          $conversion = array_shift($conversions);
 152          $conversionfile = $conversion->get_sourcefile();
 153  
 154          $this->assertEquals($sourcefile->get_id(), $conversionfile->get_id());
 155          $this->assertFalse($conversion->get_destfile());
 156      }
 157  
 158      /**
 159       * Ensure that get_conversions_for_file returns an existing conversion
 160       * record with matching sourcefileid and targetformat.
 161       */
 162      public function test_get_conversions_for_file_existing_conversion_complete() {
 163          $this->resetAfterTest();
 164  
 165          $sourcefile = $this->create_stored_file();
 166          $destfile = $this->create_stored_file(
 167              'example content',
 168              $sourcefile->get_contenthash(),
 169              [
 170                  'component' => 'core',
 171                  'filearea' => 'documentconversion',
 172                  'filepath' => '/pdf/',
 173              ]);
 174  
 175          $existing = new conversion(0, (object) [
 176                  'sourcefileid' => $sourcefile->get_id(),
 177                  'targetformat' => 'pdf',
 178                  'destfileid' => $destfile->get_id(),
 179              ]);
 180          $existing->create();
 181  
 182          $conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
 183  
 184          // Only one file should be returned.
 185          $this->assertCount(1, $conversions);
 186  
 187          $conversion = array_shift($conversions);
 188  
 189          $this->assertEquals($existing->get('id'), $conversion->get('id'));
 190          $this->assertEquals($sourcefile->get_id(), $conversion->get_sourcefile()->get_id());
 191          $this->assertEquals($destfile->get_id(), $conversion->get_destfile()->get_id());
 192      }
 193  
 194      /**
 195       * Ensure that get_conversions_for_file returns an existing conversion
 196       * record with matching sourcefileid and targetformat.
 197       */
 198      public function test_get_conversions_for_file_existing_conversion_multiple_formats_complete() {
 199          $this->resetAfterTest();
 200  
 201          $sourcefile = $this->create_stored_file();
 202          $destfile = $this->create_stored_file(
 203              'example content',
 204              $sourcefile->get_contenthash(),
 205              [
 206                  'component' => 'core',
 207                  'filearea' => 'documentconversion',
 208                  'filepath' => '/pdf/',
 209              ]);
 210  
 211          $existing = new conversion(0, (object) [
 212                  'sourcefileid' => $sourcefile->get_id(),
 213                  'targetformat' => 'pdf',
 214                  'destfileid' => $destfile->get_id(),
 215              ]);
 216          $existing->create();
 217  
 218          $second = new conversion(0, (object) [
 219                  'sourcefileid' => $sourcefile->get_id(),
 220                  'targetformat' => 'doc',
 221              ]);
 222          $second->create();
 223  
 224          $conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
 225  
 226          // Only one file should be returned.
 227          $this->assertCount(1, $conversions);
 228  
 229          $conversion = array_shift($conversions);
 230  
 231          $this->assertEquals($sourcefile->get_id(), $conversion->get_sourcefile()->get_id());
 232          $this->assertEquals($destfile->get_id(), $conversion->get_destfile()->get_id());
 233      }
 234  
 235      /**
 236       * Ensure that get_conversions_for_file returns an existing conversion
 237       * record does not exist, but the file has previously been converted.
 238       */
 239      public function test_get_conversions_for_file_existing_target() {
 240          $this->resetAfterTest();
 241  
 242          $sourcefile = $this->create_stored_file();
 243          $destfile = $this->create_stored_file(
 244              'example content',
 245              $sourcefile->get_contenthash(),
 246              [
 247                  'component' => 'core',
 248                  'filearea' => 'documentconversion',
 249                  'filepath' => '/pdf/',
 250              ]);
 251  
 252          $conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
 253  
 254          $this->assertCount(1, $conversions);
 255  
 256          $conversion = array_shift($conversions);
 257          $conversionsource = $conversion->get_sourcefile();
 258          $this->assertEquals($sourcefile->get_id(), $conversionsource->get_id());
 259          $conversiondest = $conversion->get_destfile();
 260          $this->assertEquals($destfile->get_id(), $conversiondest->get_id());
 261      }
 262  
 263      /**
 264       * Ensure that set_sourcefile sets the correct fileid.
 265       */
 266      public function test_set_sourcefile() {
 267          $this->resetAfterTest();
 268  
 269          $sourcefile = $this->create_stored_file();
 270          $conversion = new conversion(0, (object) []);
 271  
 272          $conversion->set_sourcefile($sourcefile);
 273  
 274          $this->assertEquals($sourcefile->get_id(), $conversion->get('sourcefileid'));
 275          $this->assertNull($conversion->get('destfileid'));
 276      }
 277  
 278      /**
 279       * Ensure that store_destfile_from_path stores the file as expected.
 280       */
 281      public function test_store_destfile_from_path() {
 282          $this->resetAfterTest();
 283  
 284          $sourcefile = $this->create_stored_file();
 285          $conversion = new conversion(0, (object) [
 286              'sourcefileid' => $sourcefile->get_id(),
 287              'targetformat' => 'pdf',
 288          ]);
 289  
 290          $fixture = __FILE__;
 291          $conversion->store_destfile_from_path($fixture);
 292  
 293          $destfile = $conversion->get_destfile();
 294          $this->assertEquals(file_get_contents($fixture), $destfile->get_content());
 295      }
 296  
 297      /**
 298       * Ensure that store_destfile_from_path stores the file as expected.
 299       */
 300      public function test_store_destfile_from_path_delete_existing() {
 301          $this->resetAfterTest();
 302  
 303          $sourcefile = $this->create_stored_file();
 304          $conversion = new conversion(0, (object) [
 305              'sourcefileid' => $sourcefile->get_id(),
 306              'targetformat' => 'pdf',
 307          ]);
 308  
 309          $record = [
 310              'contextid' => \context_system::instance()->id,
 311              'component' => 'core',
 312              'filearea'  => 'documentconversion',
 313              'itemid'    => 0,
 314              'filepath'  => '/pdf/',
 315          ];
 316          $existingfile = $this->create_stored_file('foo', $sourcefile->get_contenthash(), $record);
 317  
 318          $fixture = __FILE__;
 319          $conversion->store_destfile_from_path($fixture);
 320  
 321          $destfile = $conversion->get_destfile();
 322          $this->assertEquals(file_get_contents($fixture), $destfile->get_content());
 323      }
 324  
 325      /**
 326       * Ensure that store_destfile_from_path stores the file as expected.
 327       */
 328      public function test_store_destfile_from_string() {
 329          $this->resetAfterTest();
 330  
 331          $sourcefile = $this->create_stored_file();
 332          $conversion = new conversion(0, (object) [
 333              'sourcefileid' => $sourcefile->get_id(),
 334              'targetformat' => 'pdf',
 335          ]);
 336  
 337          $fixture = 'Example content';
 338          $conversion->store_destfile_from_string($fixture);
 339  
 340          $destfile = $conversion->get_destfile();
 341          $this->assertEquals($fixture, $destfile->get_content());
 342      }
 343  
 344      /**
 345       * Ensure that store_destfile_from_string stores the file as expected when
 346       * an existing destfile is found.
 347       */
 348      public function test_store_destfile_from_string_delete_existing() {
 349          $this->resetAfterTest();
 350  
 351          $sourcefile = $this->create_stored_file();
 352          $conversion = new conversion(0, (object) [
 353              'sourcefileid' => $sourcefile->get_id(),
 354              'targetformat' => 'pdf',
 355          ]);
 356  
 357          $record = [
 358              'contextid' => \context_system::instance()->id,
 359              'component' => 'core',
 360              'filearea'  => 'documentconversion',
 361              'itemid'    => 0,
 362              'filepath'  => '/pdf/',
 363          ];
 364          $existingfile = $this->create_stored_file('foo', $sourcefile->get_contenthash(), $record);
 365  
 366          $fixture = 'Example content';
 367          $conversion->store_destfile_from_string($fixture);
 368  
 369          $destfile = $conversion->get_destfile();
 370          $this->assertEquals($fixture, $destfile->get_content());
 371      }
 372  
 373      /**
 374       * Ensure that the get_status functions cast the status to integer correctly.
 375       */
 376      public function test_get_status() {
 377          $conversion = new conversion(0, (object) [
 378              'status' => (string) 1,
 379          ]);
 380  
 381          $this->assertIsInt($conversion->get('status'));
 382      }
 383  
 384      /**
 385       * Ensure that get_converter_instance returns false when no converter is set.
 386       */
 387      public function test_get_converter_instance_none_set() {
 388          $conversion = new conversion(0, (object) []);
 389          $this->assertFalse($conversion->get_converter_instance());
 390      }
 391  
 392      /**
 393       * Ensure that get_converter_instance returns false when no valid converter is set.
 394       */
 395      public function test_get_converter_instance_invalid_set() {
 396          $conversion = new conversion(0, (object) [
 397              'converter' => '\\fileconverter_not_a_valid_converter\\converter',
 398          ]);
 399          $this->assertFalse($conversion->get_converter_instance());
 400      }
 401  
 402      /**
 403       * Ensure that get_converter_instance returns an instance when a valid converter is set.
 404       */
 405      public function test_get_converter_instance_valid_set() {
 406          $conversion = new conversion(0, (object) [
 407              'converter' => \fileconverter_unoconv\converter::class,
 408          ]);
 409          $this->assertInstanceOf(\fileconverter_unoconv\converter::class, $conversion->get_converter_instance());
 410      }
 411  
 412      /**
 413       * Test that all old conversion records are removed periodically.
 414       */
 415      public function test_remove_old_conversion_records_old() {
 416          $this->resetAfterTest();
 417          global $DB;
 418  
 419          $sourcefile = $this->create_stored_file();
 420          $conversion = new conversion(0, (object) [
 421                  'sourcefileid' => $sourcefile->get_id(),
 422                  'targetformat' => 'pdf',
 423              ]);
 424          $conversion->create();
 425          $DB->set_field(conversion::TABLE, 'timemodified', time() - YEARSECS);
 426  
 427          conversion::remove_old_conversion_records();
 428  
 429          $this->assertEquals(0, $DB->count_records(conversion::TABLE));
 430      }
 431  
 432      /**
 433       * Test that all old conversion records are removed periodically.
 434       */
 435      public function test_remove_old_conversion_records_young() {
 436          $this->resetAfterTest();
 437          global $DB;
 438  
 439          $sourcefile = $this->create_stored_file();
 440          $conversion = new conversion(0, (object) [
 441                  'sourcefileid' => $sourcefile->get_id(),
 442                  'targetformat' => 'pdf',
 443              ]);
 444          $conversion->create();
 445          $DB->set_field(conversion::TABLE, 'timemodified', time() - DAYSECS);
 446  
 447          conversion::remove_old_conversion_records();
 448  
 449          $this->assertEquals(1, $DB->count_records(conversion::TABLE));
 450      }
 451  
 452      /**
 453       * Test orphan records are removed.
 454       */
 455      public function test_remove_orphan_records() {
 456          global $DB;
 457          $this->resetAfterTest();
 458  
 459          $sf1 = $this->create_stored_file('1', '1');
 460          $sf2 = $this->create_stored_file('2', '2');
 461          $sf3 = $this->create_stored_file('3', '3');
 462          $c1 = new conversion(0, (object) ['sourcefileid' => $sf1->get_id(), 'targetformat' => 'pdf']);
 463          $c1->create();
 464          $c2 = new conversion(0, (object) ['sourcefileid' => $sf2->get_id(), 'targetformat' => 'pdf']);
 465          $c2->create();
 466          $c3 = new conversion(0, (object) ['sourcefileid' => $sf3->get_id(), 'targetformat' => 'pdf']);
 467          $c3->create();
 468  
 469          $this->assertTrue(conversion::record_exists($c1->get('id')));
 470          $this->assertTrue(conversion::record_exists($c2->get('id')));
 471          $this->assertTrue(conversion::record_exists($c3->get('id')));
 472  
 473          // Nothing should happen here.
 474          conversion::remove_orphan_records();
 475          $this->assertTrue(conversion::record_exists($c1->get('id')));
 476          $this->assertTrue(conversion::record_exists($c2->get('id')));
 477          $this->assertTrue(conversion::record_exists($c3->get('id')));
 478  
 479          // Delete file #2.
 480          $sf2->delete();
 481          conversion::remove_orphan_records();
 482          $this->assertTrue(conversion::record_exists($c1->get('id')));
 483          $this->assertFalse(conversion::record_exists($c2->get('id')));
 484          $this->assertTrue(conversion::record_exists($c3->get('id')));
 485  
 486          // Delete file #1, #3.
 487          $sf1->delete();
 488          $sf3->delete();
 489          conversion::remove_orphan_records();
 490          $this->assertFalse(conversion::record_exists($c1->get('id')));
 491          $this->assertFalse(conversion::record_exists($c2->get('id')));
 492          $this->assertFalse(conversion::record_exists($c3->get('id')));
 493      }
 494  }