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 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  use core_files_external;
  20  use core_files\external\delete\draft;
  21  use core_files\external\get\unused_draft;
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  global $CFG;
  26  
  27  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  28  require_once($CFG->dirroot . '/files/externallib.php');
  29  
  30  /**
  31   * PHPunit tests for external files API.
  32   *
  33   * @package    core_files
  34   * @category   external
  35   * @copyright  2013 Ankit Agarwal
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   * @since Moodle 2.6
  38   */
  39  class externallib_test extends \advanced_testcase {
  40  
  41      /*
  42       * Test core_files_external::upload().
  43       */
  44  
  45      public function test_upload() {
  46          global $USER;
  47  
  48          $this->resetAfterTest();
  49          $this->setAdminUser();
  50          $context = \context_user::instance($USER->id);
  51          $contextid = $context->id;
  52          $component = "user";
  53          $filearea = "draft";
  54          $itemid = 0;
  55          $filepath = "/";
  56          $filename = "Simple.txt";
  57          $filecontent = base64_encode("Let us create a nice simple file");
  58          $contextlevel = null;
  59          $instanceid = null;
  60          $browser = get_file_browser();
  61  
  62          // Make sure no file exists.
  63          $file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
  64          $this->assertEmpty($file);
  65  
  66          // Call the api to create a file.
  67          $fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
  68                  $filename, $filecontent, $contextlevel, $instanceid);
  69          $fileinfo = \external_api::clean_returnvalue(core_files_external::upload_returns(), $fileinfo);
  70          // Get the created draft item id.
  71          $itemid = $fileinfo['itemid'];
  72  
  73          // Make sure the file was created.
  74          $file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
  75          $this->assertNotEmpty($file);
  76  
  77          // Make sure no file exists.
  78          $filename = "Simple2.txt";
  79          $file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
  80          $this->assertEmpty($file);
  81  
  82          // Call the api to create a file.
  83          $fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid,
  84                  $filepath, $filename, $filecontent, $contextlevel, $instanceid);
  85          $fileinfo = \external_api::clean_returnvalue(core_files_external::upload_returns(), $fileinfo);
  86          $file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
  87          $this->assertNotEmpty($file);
  88  
  89          // Let us try creating a file using contextlevel and instance id.
  90          $filename = "Simple5.txt";
  91          $contextid = 0;
  92          $contextlevel = "user";
  93          $instanceid = $USER->id;
  94          $file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
  95          $this->assertEmpty($file);
  96          $fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
  97                  $filename, $filecontent, $contextlevel, $instanceid);
  98          $fileinfo = \external_api::clean_returnvalue(core_files_external::upload_returns(), $fileinfo);
  99          $file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
 100          $this->assertNotEmpty($file);
 101  
 102          // Make sure the same file cannot be created again.
 103          $this->expectException("moodle_exception");
 104          core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath,
 105                  $filename, $filecontent, $contextlevel, $instanceid);
 106      }
 107  
 108      /*
 109       * Make sure only user component is allowed in  core_files_external::upload().
 110       */
 111      public function test_upload_param_component() {
 112          global $USER;
 113  
 114          $this->resetAfterTest();
 115          $this->setAdminUser();
 116          $context = \context_user::instance($USER->id);
 117          $contextid = $context->id;
 118          $component = "backup";
 119          $filearea = "draft";
 120          $itemid = 0;
 121          $filepath = "/";
 122          $filename = "Simple3.txt";
 123          $filecontent = base64_encode("Let us create a nice simple file");
 124          $contextlevel = null;
 125          $instanceid = null;
 126  
 127          // Make sure exception is thrown.
 128          $this->expectException("coding_exception");
 129          core_files_external::upload($contextid, $component, $filearea, $itemid,
 130                  $filepath, $filename, $filecontent, $contextlevel, $instanceid);
 131      }
 132  
 133      /*
 134       * Make sure only draft areas are allowed in  core_files_external::upload().
 135       */
 136      public function test_upload_param_area() {
 137          global $USER;
 138  
 139          $this->resetAfterTest();
 140          $this->setAdminUser();
 141          $context = \context_user::instance($USER->id);
 142          $contextid = $context->id;
 143          $component = "user";
 144          $filearea = "draft";
 145          $itemid = file_get_unused_draft_itemid();
 146          $filepath = "/";
 147          $filename = "Simple4.txt";
 148          $filecontent = base64_encode("Let us create a nice simple file");
 149          $contextlevel = null;
 150          $instanceid = null;
 151  
 152          // Make sure the file is created.
 153          $fileinfo = core_files_external::upload($contextid, $component, $filearea, $itemid, $filepath, $filename, $filecontent,
 154              'user', $USER->id);
 155          $fileinfo = \external_api::clean_returnvalue(core_files_external::upload_returns(), $fileinfo);
 156          $browser = get_file_browser();
 157          $file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename);
 158          $this->assertNotEmpty($file);
 159      }
 160  
 161      /**
 162       * Test getting a list of files with and without a context ID.
 163       */
 164      public function test_get_files() {
 165          global $USER, $DB;
 166  
 167          $this->resetAfterTest();
 168  
 169          // Set the current user to be the administrator.
 170          $this->setAdminUser();
 171          $USER->email = 'test@example.com';
 172  
 173          // Create a course.
 174          $course = $this->getDataGenerator()->create_course();
 175          $record = new \stdClass();
 176          $record->course = $course->id;
 177          $record->name = "Mod data upload test";
 178          $record->intro = "Some intro of some sort";
 179  
 180          // Create a database module.
 181          $module = $this->getDataGenerator()->create_module('data', $record);
 182  
 183          // Create a new field in the database activity.
 184          $field = data_get_field_new('file', $module);
 185          // Add more detail about the field.
 186          $fielddetail = new \stdClass();
 187          $fielddetail->d = $module->id;
 188          $fielddetail->mode = 'add';
 189          $fielddetail->type = 'file';
 190          $fielddetail->sesskey = sesskey();
 191          $fielddetail->name = 'Upload file';
 192          $fielddetail->description = 'Some description';
 193          $fielddetail->param3 = '0';
 194  
 195          $field->define_field($fielddetail);
 196          $field->insert_field();
 197          $recordid = data_add_record($module);
 198  
 199          // File information for the database module record.
 200          $datacontent = array();
 201          $datacontent['fieldid'] = $field->field->id;
 202          $datacontent['recordid'] = $recordid;
 203          $datacontent['content'] = 'Simple4.txt';
 204  
 205          // Insert the information about the file.
 206          $contentid = $DB->insert_record('data_content', $datacontent);
 207          // Required information for uploading a file.
 208          $context = \context_module::instance($module->cmid);
 209          $usercontext = \context_user::instance($USER->id);
 210          $component = 'mod_data';
 211          $filearea = 'content';
 212          $itemid = $contentid;
 213          $filename = $datacontent['content'];
 214          $filecontent = base64_encode("Let us create a nice simple file.");
 215  
 216          $filerecord = array();
 217          $filerecord['contextid'] = $context->id;
 218          $filerecord['component'] = $component;
 219          $filerecord['filearea'] = $filearea;
 220          $filerecord['itemid'] = $itemid;
 221          $filerecord['filepath'] = '/';
 222          $filerecord['filename'] = $filename;
 223  
 224          // Create an area to upload the file.
 225          $fs = get_file_storage();
 226          // Create a file from the string that we made earlier.
 227          $file = $fs->create_file_from_string($filerecord, $filecontent);
 228          $timemodified = $file->get_timemodified();
 229          $timecreated = $file->get_timemodified();
 230          $filesize = $file->get_filesize();
 231  
 232          // Use the web service function to return the information about the file that we just uploaded.
 233          // The first time is with a valid context ID.
 234          $filename = '';
 235          $testfilelisting = core_files_external::get_files($context->id, $component, $filearea, $itemid, '/', $filename);
 236          $testfilelisting = \external_api::clean_returnvalue(core_files_external::get_files_returns(), $testfilelisting);
 237  
 238          // With the information that we have provided we should get an object exactly like the one below.
 239          $coursecontext = \context_course::instance($course->id);
 240          $testdata = array();
 241          $testdata['parents'] = array();
 242          $testdata['parents']['0'] = array('contextid' => 1,
 243                                            'component' => null,
 244                                            'filearea' => null,
 245                                            'itemid' => null,
 246                                            'filepath' => null,
 247                                            'filename' => 'System');
 248          $testdata['parents']['1'] = array('contextid' => 3,
 249                                            'component' => null,
 250                                            'filearea' => null,
 251                                            'itemid' => null,
 252                                            'filepath' => null,
 253                                            'filename' => get_string('defaultcategoryname'));
 254          $testdata['parents']['2'] = array('contextid' => $coursecontext->id,
 255                                            'component' => null,
 256                                            'filearea' => null,
 257                                            'itemid' => null,
 258                                            'filepath' => null,
 259                                            'filename' => 'Test course 1');
 260          $testdata['parents']['3'] = array('contextid' => $context->id,
 261                                            'component' => null,
 262                                            'filearea' => null,
 263                                            'itemid' => null,
 264                                            'filepath' => null,
 265                                            'filename' => 'Mod data upload test (Database)');
 266          $testdata['parents']['4'] = array('contextid' => $context->id,
 267                                            'component' => 'mod_data',
 268                                            'filearea' => 'content',
 269                                            'itemid' => null,
 270                                            'filepath' => null,
 271                                            'filename' => 'Fields');
 272          $testdata['files'] = array();
 273          $testdata['files']['0'] = array('contextid' => $context->id,
 274                                          'component' => 'mod_data',
 275                                          'filearea' => 'content',
 276                                          'itemid' => $itemid,
 277                                          'filepath' => '/',
 278                                          'filename' => 'Simple4.txt',
 279                                          'url' => 'https://www.example.com/moodle/pluginfile.php/'.$context->id.'/mod_data/content/'.$itemid.'/Simple4.txt',
 280                                          'isdir' => false,
 281                                          'timemodified' => $timemodified,
 282                                          'timecreated' => $timecreated,
 283                                          'filesize' => $filesize,
 284                                          'author' => null,
 285                                          'license' => null
 286                                          );
 287          // Make sure that they are the same.
 288          $this->assertEquals($testdata, $testfilelisting);
 289  
 290          // Try again but without the context. Minus one signals the function to use other variables to obtain the context.
 291          $nocontext = -1;
 292          $modified = 0;
 293          // Context level and instance ID are used to determine what the context is.
 294          $contextlevel = 'module';
 295          $instanceid = $module->cmid;
 296          $testfilelisting = core_files_external::get_files($nocontext, $component, $filearea, $itemid, '/', $filename, $modified, $contextlevel, $instanceid);
 297          $testfilelisting = \external_api::clean_returnvalue(core_files_external::get_files_returns(), $testfilelisting);
 298  
 299          $this->assertEquals($testfilelisting, $testdata);
 300      }
 301  
 302      /**
 303       * Test delete draft files
 304       */
 305      public function test_delete_draft_files() {
 306          global $USER;
 307  
 308          $this->resetAfterTest();
 309          $this->setAdminUser();
 310  
 311          // Add files to user draft area.
 312          $draftitemid = file_get_unused_draft_itemid();
 313          $context = \context_user::instance($USER->id);
 314          $filerecordinline = array(
 315              'contextid' => $context->id,
 316              'component' => 'user',
 317              'filearea'  => 'draft',
 318              'itemid'    => $draftitemid,
 319              'filepath'  => '/',
 320              'filename'  => 'faketxt.txt',
 321          );
 322          $fs = get_file_storage();
 323          $fs->create_file_from_string($filerecordinline, 'fake txt contents 1.');
 324  
 325          // Now create a folder with a file inside.
 326          $fs->create_directory($context->id, 'user', 'draft', $draftitemid, '/fakefolder/');
 327          $filerecordinline['filepath'] = '/fakefolder/';
 328          $filerecordinline['filename'] = 'fakeimage.png';
 329          $fs->create_file_from_string($filerecordinline, 'img...');
 330  
 331          // Check two files were created (one file and one directory).
 332          $files = core_files_external::get_files($context->id, 'user', 'draft', $draftitemid, '/', '');
 333          $files = \external_api::clean_returnvalue(core_files_external::get_files_returns(), $files);
 334          $this->assertCount(2, $files['files']);
 335  
 336          // Check the folder has one file.
 337          $files = core_files_external::get_files($context->id, 'user', 'draft', $draftitemid, '/fakefolder/', '');
 338          $files = \external_api::clean_returnvalue(core_files_external::get_files_returns(), $files);
 339          $this->assertCount(1, $files['files']);
 340  
 341          // Delete a file and a folder.
 342          $filestodelete = [
 343              ['filepath' => '/', 'filename' => 'faketxt.txt'],
 344              ['filepath' => '/fakefolder/', 'filename' => ''],
 345          ];
 346          $paths = draft::execute($draftitemid, $filestodelete);
 347          $paths = \external_api::clean_returnvalue(draft::execute_returns(), $paths);
 348  
 349          // Check everything was deleted.
 350          $files = core_files_external::get_files($context->id, 'user', 'draft', $draftitemid, '/', '');
 351          $files = \external_api::clean_returnvalue(core_files_external::get_files_returns(), $files);
 352          $this->assertCount(0, $files['files']);
 353      }
 354  
 355      /**
 356       * Test get_unused_draft_itemid.
 357       */
 358      public function test_get_unused_draft_itemid() {
 359          global $USER;
 360  
 361          $this->resetAfterTest();
 362          $this->setAdminUser();
 363  
 364          // Add files to user draft area.
 365          $result = unused_draft::execute();
 366          $result = \external_api::clean_returnvalue(unused_draft::execute_returns(), $result);
 367  
 368          $filerecordinline = [
 369              'contextid' => $result['contextid'],
 370              'component' => $result['component'],
 371              'filearea'  => $result['filearea'],
 372              'itemid'    => $result['itemid'],
 373              'filepath'  => '/',
 374              'filename'  => 'faketxt.txt',
 375          ];
 376          $fs = get_file_storage();
 377          $fs->create_file_from_string($filerecordinline, 'fake txt contents 1.');
 378  
 379          // Now create a folder with a file inside.
 380          $fs->create_directory($result['contextid'], $result['component'], $result['filearea'], $result['itemid'], '/fakefolder/');
 381          $filerecordinline['filepath'] = '/fakefolder/';
 382          $filerecordinline['filename'] = 'fakeimage.png';
 383          $fs->create_file_from_string($filerecordinline, 'img...');
 384  
 385          $context = \context_user::instance($USER->id);
 386          // Check two files were created (one file and one directory).
 387          $files = core_files_external::get_files($context->id, 'user', 'draft', $result['itemid'], '/', '');
 388          $files = \external_api::clean_returnvalue(core_files_external::get_files_returns(), $files);
 389          $this->assertCount(2, $files['files']);
 390      }
 391  }