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  
   3  namespace Moodle;
   4  
   5  use stdClass;
   6  
   7  /**
   8   * Class
   9   */
  10  class H5peditorFile {
  11    private $result, $field, $interface;
  12    public $type, $name, $path, $mime, $size;
  13  
  14    /**
  15     * Constructor. Process data for file uploaded through the editor.
  16     */
  17    function __construct($interface) {
  18      $field = filter_input(INPUT_POST, 'field', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
  19  
  20      // Check for file upload.
  21      if ($field === NULL || empty($_FILES) || !isset($_FILES['file'])) {
  22        return;
  23      }
  24  
  25      $this->interface = $interface;
  26  
  27      // Create a new result object.
  28      $this->result = new stdClass();
  29  
  30      // Get the field.
  31      $this->field = json_decode($field);
  32  
  33      // Handle temporarily uploaded form file
  34      if (function_exists('finfo_file')) {
  35        $finfo = finfo_open(FILEINFO_MIME_TYPE);
  36        $this->type = finfo_file($finfo, $_FILES['file']['tmp_name']);
  37        finfo_close($finfo);
  38      }
  39      elseif (function_exists('mime_content_type')) {
  40        // Deprecated, only when finfo isn't available.
  41        $this->type = mime_content_type($_FILES['file']['tmp_name']);
  42      }
  43      else {
  44        $this->type = $_FILES['file']['type'];
  45      }
  46  
  47      $this->extension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
  48      $this->size = $_FILES['file']['size'];
  49    }
  50  
  51    /**
  52     * Indicates if an uploaded file was found or not.
  53     *
  54     * @return boolean
  55     */
  56    public function isLoaded() {
  57      return is_object($this->result);
  58    }
  59  
  60    /**
  61     * Check current file up agains mime types and extensions in the given list.
  62     *
  63     * @param array $mimes List to check against.
  64     * @return boolean
  65     */
  66    public function check($mimes) {
  67      $ext = strtolower($this->extension);
  68      foreach ($mimes as $mime => $extension) {
  69        if (is_array($extension)) {
  70          // Multiple extensions
  71          if (in_array($ext, $extension)) {
  72            $this->type = $mime;
  73            return TRUE;
  74          }
  75        }
  76        elseif (/*$this->type === $mime && */$ext === $extension) {
  77          // TODO: Either remove everything that has to do with mime types, or make it work
  78          // Currently we're experiencing trouble with mime types on different servers...
  79          $this->type = $mime;
  80          return TRUE;
  81        }
  82      }
  83      return FALSE;
  84    }
  85  
  86    /**
  87     * Validate the file.
  88     *
  89     * @return boolean
  90     */
  91    public function validate() {
  92      if (isset($this->result->error)) {
  93        return FALSE;
  94      }
  95  
  96      // Check for field type.
  97      if (!isset($this->field->type)) {
  98        $this->result->error = $this->interface->t('Unable to get field type.');
  99        return FALSE;
 100      }
 101  
 102      $whitelist = explode(' ', $this->interface->getWhitelist(
 103        FALSE,
 104        H5PCore::$defaultContentWhitelist,
 105        H5PCore::$defaultLibraryWhitelistExtras
 106      ));
 107  
 108      // Check if mime type is allowed.
 109      $isValidMime = !isset($this->field->mimes) || in_array($this->type, $this->field->mimes);
 110      $isPhp = substr($this->extension, 0, 3) === 'php';
 111      $isWhitelisted = in_array(strtolower($this->extension), $whitelist);
 112      if (!$isValidMime || !$isWhitelisted || $isPhp) {
 113        $this->result->error = $this->interface->t("File type isn't allowed.");
 114        return FALSE;
 115      }
 116  
 117      // Type specific validations.
 118      switch ($this->field->type) {
 119        default:
 120          $this->result->error = $this->interface->t('Invalid field type.');
 121          return FALSE;
 122  
 123        case 'image':
 124          $allowed = array(
 125            'image/png' => 'png',
 126            'image/jpeg' => array('jpg', 'jpeg'),
 127            'image/gif' => 'gif',
 128          );
 129          if (!$this->check($allowed)) {
 130            $this->result->error = $this->interface->t('Invalid image file format. Use jpg, png or gif.');
 131            return FALSE;
 132          }
 133  
 134          // Image size from temp file
 135          $image = @getimagesize($_FILES['file']['tmp_name']);
 136  
 137          if (!$image) {
 138            $this->result->error = $this->interface->t('File is not an image.');
 139            return FALSE;
 140          }
 141  
 142          $this->result->width = $image[0];
 143          $this->result->height = $image[1];
 144          $this->result->mime = $this->type;
 145          break;
 146  
 147        case 'audio':
 148          $allowed = array(
 149            'audio/mpeg' => 'mp3',
 150            'audio/mp3' => 'mp3',
 151            'audio/mp4' => 'm4a',
 152            'audio/x-wav' => 'wav',
 153            'audio/wav' => 'wav',
 154            //'application/ogg' => 'ogg',
 155            'audio/ogg' => 'ogg',
 156            //'video/ogg' => 'ogg',
 157          );
 158          if (!$this->check($allowed)) {
 159            $this->result->error = $this->interface->t('Invalid audio file format. Use mp3 or wav.');
 160            return FALSE;
 161  
 162          }
 163  
 164          $this->result->mime = $this->type;
 165          break;
 166  
 167        case 'video':
 168          $allowed = array(
 169            'video/mp4' => 'mp4',
 170            'video/webm' => 'webm',
 171           // 'application/ogg' => 'ogv',
 172            'video/ogg' => 'ogv',
 173          );
 174          if (!$this->check($allowed)) {
 175            $this->result->error = $this->interface->t('Invalid video file format. Use mp4 or webm.');
 176            return FALSE;
 177          }
 178  
 179          $this->result->mime = $this->type;
 180          break;
 181  
 182        case 'file':
 183          // TODO: Try to get file extension for type and check that it matches the current extension.
 184          $this->result->mime = $this->type;
 185      }
 186  
 187      return TRUE;
 188    }
 189  
 190    /**
 191     * Get the type of the current file.
 192     *
 193     * @return string
 194     */
 195    public function getType() {
 196      return $this->field->type;
 197    }
 198  
 199    /**
 200     * Get the name of the current file.
 201     *
 202     * @return string
 203     */
 204    public function getName() {
 205      static $name;
 206  
 207      if (empty($name)) {
 208        $name = uniqid($this->field->name . '-');
 209  
 210        $matches = array();
 211        preg_match('/([a-z0-9]{1,})$/i', $_FILES['file']['name'], $matches);
 212        if (isset($matches[0])) {
 213          $name .= '.' . $matches[0];
 214        }
 215      }
 216  
 217      return $name;
 218    }
 219  
 220    /**
 221     * Get result from file processing.
 222     */
 223    public function getResult() {
 224      return json_encode($this->result);
 225    }
 226  
 227    /**
 228     * Print result from file processing.
 229     */
 230    public function printResult() {
 231      $this->result->path = $this->getType() . 's/' . $this->getName() . '#tmp';
 232  
 233      // text/plain is used to support IE
 234      header('Cache-Control: no-cache');
 235      header('Content-Type: text/plain; charset=utf-8');
 236  
 237      print $this->getResult();
 238    }
 239  }