Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401]

   1  <?php
   2  /*
   3   * Copyright 2016-2017 MongoDB, Inc.
   4   *
   5   * Licensed under the Apache License, Version 2.0 (the "License");
   6   * you may not use this file except in compliance with the License.
   7   * You may obtain a copy of the License at
   8   *
   9   *   http://www.apache.org/licenses/LICENSE-2.0
  10   *
  11   * Unless required by applicable law or agreed to in writing, software
  12   * distributed under the License is distributed on an "AS IS" BASIS,
  13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14   * See the License for the specific language governing permissions and
  15   * limitations under the License.
  16   */
  17  
  18  namespace MongoDB\GridFS;
  19  
  20  use Exception;
  21  use MongoDB\BSON\UTCDateTime;
  22  use stdClass;
  23  use function explode;
  24  use function get_class;
  25  use function in_array;
  26  use function is_integer;
  27  use function sprintf;
  28  use function stream_context_get_options;
  29  use function stream_get_wrappers;
  30  use function stream_wrapper_register;
  31  use function stream_wrapper_unregister;
  32  use function trigger_error;
  33  use const E_USER_WARNING;
  34  use const SEEK_CUR;
  35  use const SEEK_END;
  36  use const SEEK_SET;
  37  use const STREAM_IS_URL;
  38  
  39  /**
  40   * Stream wrapper for reading and writing a GridFS file.
  41   *
  42   * @internal
  43   * @see Bucket::openUploadStream()
  44   * @see Bucket::openDownloadStream()
  45   */
  46  class StreamWrapper
  47  {
  48      /** @var resource|null Stream context (set by PHP) */
  49      public $context;
  50  
  51      /** @var string|null */
  52      private $mode;
  53  
  54      /** @var string|null */
  55      private $protocol;
  56  
  57      /** @var ReadableStream|WritableStream|null */
  58      private $stream;
  59  
  60      /**
  61       * Return the stream's file document.
  62       *
  63       * @return stdClass
  64       */
  65      public function getFile()
  66      {
  67          return $this->stream->getFile();
  68      }
  69  
  70      /**
  71       * Register the GridFS stream wrapper.
  72       *
  73       * @param string $protocol Protocol to use for stream_wrapper_register()
  74       */
  75      public static function register($protocol = 'gridfs')
  76      {
  77          if (in_array($protocol, stream_get_wrappers())) {
  78              stream_wrapper_unregister($protocol);
  79          }
  80  
  81          stream_wrapper_register($protocol, static::class, STREAM_IS_URL);
  82      }
  83  
  84      /**
  85       * Closes the stream.
  86       *
  87       * @see http://php.net/manual/en/streamwrapper.stream-close.php
  88       */
  89      public function stream_close()
  90      {
  91          $this->stream->close();
  92      }
  93  
  94      /**
  95       * Returns whether the file pointer is at the end of the stream.
  96       *
  97       * @see http://php.net/manual/en/streamwrapper.stream-eof.php
  98       * @return boolean
  99       */
 100      public function stream_eof()
 101      {
 102          if (! $this->stream instanceof ReadableStream) {
 103              return false;
 104          }
 105  
 106          return $this->stream->isEOF();
 107      }
 108  
 109      /**
 110       * Opens the stream.
 111       *
 112       * @see http://php.net/manual/en/streamwrapper.stream-open.php
 113       * @param string  $path       Path to the file resource
 114       * @param string  $mode       Mode used to open the file (only "r" and "w" are supported)
 115       * @param integer $options    Additional flags set by the streams API
 116       * @param string  $openedPath Not used
 117       * @return boolean
 118       */
 119      public function stream_open($path, $mode, $options, &$openedPath)
 120      {
 121          $this->initProtocol($path);
 122          $this->mode = $mode;
 123  
 124          if ($mode === 'r') {
 125              return $this->initReadableStream();
 126          }
 127  
 128          if ($mode === 'w') {
 129              return $this->initWritableStream();
 130          }
 131  
 132          return false;
 133      }
 134  
 135      /**
 136       * Read bytes from the stream.
 137       *
 138       * Note: this method may return a string smaller than the requested length
 139       * if data is not available to be read.
 140       *
 141       * @see http://php.net/manual/en/streamwrapper.stream-read.php
 142       * @param integer $length Number of bytes to read
 143       * @return string
 144       */
 145      public function stream_read($length)
 146      {
 147          if (! $this->stream instanceof ReadableStream) {
 148              return '';
 149          }
 150  
 151          try {
 152              return $this->stream->readBytes($length);
 153          } catch (Exception $e) {
 154              trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), E_USER_WARNING);
 155  
 156              return false;
 157          }
 158      }
 159  
 160      /**
 161       * Return the current position of the stream.
 162       *
 163       * @see http://php.net/manual/en/streamwrapper.stream-seek.php
 164       * @param integer $offset Stream offset to seek to
 165       * @param integer $whence One of SEEK_SET, SEEK_CUR, or SEEK_END
 166       * @return boolean True if the position was updated and false otherwise
 167       */
 168      public function stream_seek($offset, $whence = SEEK_SET)
 169      {
 170          $size = $this->stream->getSize();
 171  
 172          if ($whence === SEEK_CUR) {
 173              $offset += $this->stream->tell();
 174          }
 175  
 176          if ($whence === SEEK_END) {
 177              $offset += $size;
 178          }
 179  
 180          // WritableStreams are always positioned at the end of the stream
 181          if ($this->stream instanceof WritableStream) {
 182              return $offset === $size;
 183          }
 184  
 185          if ($offset < 0 || $offset > $size) {
 186              return false;
 187          }
 188  
 189          $this->stream->seek($offset);
 190  
 191          return true;
 192      }
 193  
 194      /**
 195       * Return information about the stream.
 196       *
 197       * @see http://php.net/manual/en/streamwrapper.stream-stat.php
 198       * @return array
 199       */
 200      public function stream_stat()
 201      {
 202          $stat = $this->getStatTemplate();
 203  
 204          $stat[2] = $stat['mode'] = $this->stream instanceof ReadableStream
 205              ? 0100444  // S_IFREG & S_IRUSR & S_IRGRP & S_IROTH
 206              : 0100222; // S_IFREG & S_IWUSR & S_IWGRP & S_IWOTH
 207          $stat[7] = $stat['size'] = $this->stream->getSize();
 208  
 209          $file = $this->stream->getFile();
 210  
 211          if (isset($file->uploadDate) && $file->uploadDate instanceof UTCDateTime) {
 212              $timestamp = $file->uploadDate->toDateTime()->getTimestamp();
 213              $stat[9] = $stat['mtime'] = $timestamp;
 214              $stat[10] = $stat['ctime'] = $timestamp;
 215          }
 216  
 217          if (isset($file->chunkSize) && is_integer($file->chunkSize)) {
 218              $stat[11] = $stat['blksize'] = $file->chunkSize;
 219          }
 220  
 221          return $stat;
 222      }
 223  
 224      /**
 225       * Return the current position of the stream.
 226       *
 227       * @see http://php.net/manual/en/streamwrapper.stream-tell.php
 228       * @return integer The current position of the stream
 229       */
 230      public function stream_tell()
 231      {
 232          return $this->stream->tell();
 233      }
 234  
 235      /**
 236       * Write bytes to the stream.
 237       *
 238       * @see http://php.net/manual/en/streamwrapper.stream-write.php
 239       * @param string $data Data to write
 240       * @return integer The number of bytes written
 241       */
 242      public function stream_write($data)
 243      {
 244          if (! $this->stream instanceof WritableStream) {
 245              return 0;
 246          }
 247  
 248          try {
 249              return $this->stream->writeBytes($data);
 250          } catch (Exception $e) {
 251              trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), E_USER_WARNING);
 252  
 253              return false;
 254          }
 255      }
 256  
 257      /**
 258       * Returns a stat template with default values.
 259       *
 260       * @return array
 261       */
 262      private function getStatTemplate()
 263      {
 264          return [
 265              // phpcs:disable Squiz.Arrays.ArrayDeclaration.IndexNoNewline
 266              0  => 0,  'dev'     => 0,
 267              1  => 0,  'ino'     => 0,
 268              2  => 0,  'mode'    => 0,
 269              3  => 0,  'nlink'   => 0,
 270              4  => 0,  'uid'     => 0,
 271              5  => 0,  'gid'     => 0,
 272              6  => -1, 'rdev'    => -1,
 273              7  => 0,  'size'    => 0,
 274              8  => 0,  'atime'   => 0,
 275              9  => 0,  'mtime'   => 0,
 276              10 => 0,  'ctime'   => 0,
 277              11 => -1, 'blksize' => -1,
 278              12 => -1, 'blocks'  => -1,
 279              // phpcs:enable
 280          ];
 281      }
 282  
 283      /**
 284       * Initialize the protocol from the given path.
 285       *
 286       * @see StreamWrapper::stream_open()
 287       * @param string $path
 288       */
 289      private function initProtocol($path)
 290      {
 291          $parts = explode('://', $path, 2);
 292          $this->protocol = $parts[0] ?: 'gridfs';
 293      }
 294  
 295      /**
 296       * Initialize the internal stream for reading.
 297       *
 298       * @see StreamWrapper::stream_open()
 299       * @return boolean
 300       */
 301      private function initReadableStream()
 302      {
 303          $context = stream_context_get_options($this->context);
 304  
 305          $this->stream = new ReadableStream(
 306              $context[$this->protocol]['collectionWrapper'],
 307              $context[$this->protocol]['file']
 308          );
 309  
 310          return true;
 311      }
 312  
 313      /**
 314       * Initialize the internal stream for writing.
 315       *
 316       * @see StreamWrapper::stream_open()
 317       * @return boolean
 318       */
 319      private function initWritableStream()
 320      {
 321          $context = stream_context_get_options($this->context);
 322  
 323          $this->stream = new WritableStream(
 324              $context[$this->protocol]['collectionWrapper'],
 325              $context[$this->protocol]['filename'],
 326              $context[$this->protocol]['options']
 327          );
 328  
 329          return true;
 330      }
 331  }