Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   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 MongoDB\BSON\UTCDateTime;
  21  use stdClass;
  22  use Throwable;
  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      public function __destruct()
  61      {
  62          /* This destructor is a workaround for PHP trying to use the stream well
  63           * after all objects have been destructed. This can cause autoloading
  64           * issues and possibly segmentation faults during PHP shutdown. */
  65          $this->stream = null;
  66      }
  67  
  68      /**
  69       * Return the stream's file document.
  70       *
  71       * @return stdClass
  72       */
  73      public function getFile()
  74      {
  75          return $this->stream->getFile();
  76      }
  77  
  78      /**
  79       * Register the GridFS stream wrapper.
  80       *
  81       * @param string $protocol Protocol to use for stream_wrapper_register()
  82       */
  83      public static function register($protocol = 'gridfs')
  84      {
  85          if (in_array($protocol, stream_get_wrappers())) {
  86              stream_wrapper_unregister($protocol);
  87          }
  88  
  89          stream_wrapper_register($protocol, static::class, STREAM_IS_URL);
  90      }
  91  
  92      /**
  93       * Closes the stream.
  94       *
  95       * @see http://php.net/manual/en/streamwrapper.stream-close.php
  96       */
  97      public function stream_close()
  98      {
  99          if (! $this->stream) {
 100              return;
 101          }
 102  
 103          $this->stream->close();
 104      }
 105  
 106      /**
 107       * Returns whether the file pointer is at the end of the stream.
 108       *
 109       * @see http://php.net/manual/en/streamwrapper.stream-eof.php
 110       * @return boolean
 111       */
 112      public function stream_eof()
 113      {
 114          if (! $this->stream instanceof ReadableStream) {
 115              return false;
 116          }
 117  
 118          return $this->stream->isEOF();
 119      }
 120  
 121      /**
 122       * Opens the stream.
 123       *
 124       * @see http://php.net/manual/en/streamwrapper.stream-open.php
 125       * @param string  $path       Path to the file resource
 126       * @param string  $mode       Mode used to open the file (only "r" and "w" are supported)
 127       * @param integer $options    Additional flags set by the streams API
 128       * @param string  $openedPath Not used
 129       * @return boolean
 130       */
 131      public function stream_open($path, $mode, $options, &$openedPath)
 132      {
 133          $this->initProtocol($path);
 134          $this->mode = $mode;
 135  
 136          if ($mode === 'r') {
 137              return $this->initReadableStream();
 138          }
 139  
 140          if ($mode === 'w') {
 141              return $this->initWritableStream();
 142          }
 143  
 144          return false;
 145      }
 146  
 147      /**
 148       * Read bytes from the stream.
 149       *
 150       * Note: this method may return a string smaller than the requested length
 151       * if data is not available to be read.
 152       *
 153       * @see http://php.net/manual/en/streamwrapper.stream-read.php
 154       * @param integer $length Number of bytes to read
 155       * @return string
 156       */
 157      public function stream_read($length)
 158      {
 159          if (! $this->stream instanceof ReadableStream) {
 160              return '';
 161          }
 162  
 163          try {
 164              return $this->stream->readBytes($length);
 165          } catch (Throwable $e) {
 166              trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), E_USER_WARNING);
 167  
 168              return false;
 169          }
 170      }
 171  
 172      /**
 173       * Return the current position of the stream.
 174       *
 175       * @see http://php.net/manual/en/streamwrapper.stream-seek.php
 176       * @param integer $offset Stream offset to seek to
 177       * @param integer $whence One of SEEK_SET, SEEK_CUR, or SEEK_END
 178       * @return boolean True if the position was updated and false otherwise
 179       */
 180      public function stream_seek($offset, $whence = SEEK_SET)
 181      {
 182          $size = $this->stream->getSize();
 183  
 184          if ($whence === SEEK_CUR) {
 185              $offset += $this->stream->tell();
 186          }
 187  
 188          if ($whence === SEEK_END) {
 189              $offset += $size;
 190          }
 191  
 192          // WritableStreams are always positioned at the end of the stream
 193          if ($this->stream instanceof WritableStream) {
 194              return $offset === $size;
 195          }
 196  
 197          if ($offset < 0 || $offset > $size) {
 198              return false;
 199          }
 200  
 201          $this->stream->seek($offset);
 202  
 203          return true;
 204      }
 205  
 206      /**
 207       * Return information about the stream.
 208       *
 209       * @see http://php.net/manual/en/streamwrapper.stream-stat.php
 210       * @return array
 211       */
 212      public function stream_stat()
 213      {
 214          $stat = $this->getStatTemplate();
 215  
 216          $stat[2] = $stat['mode'] = $this->stream instanceof ReadableStream
 217              ? 0100444  // S_IFREG & S_IRUSR & S_IRGRP & S_IROTH
 218              : 0100222; // S_IFREG & S_IWUSR & S_IWGRP & S_IWOTH
 219          $stat[7] = $stat['size'] = $this->stream->getSize();
 220  
 221          $file = $this->stream->getFile();
 222  
 223          if (isset($file->uploadDate) && $file->uploadDate instanceof UTCDateTime) {
 224              $timestamp = $file->uploadDate->toDateTime()->getTimestamp();
 225              $stat[9] = $stat['mtime'] = $timestamp;
 226              $stat[10] = $stat['ctime'] = $timestamp;
 227          }
 228  
 229          if (isset($file->chunkSize) && is_integer($file->chunkSize)) {
 230              $stat[11] = $stat['blksize'] = $file->chunkSize;
 231          }
 232  
 233          return $stat;
 234      }
 235  
 236      /**
 237       * Return the current position of the stream.
 238       *
 239       * @see http://php.net/manual/en/streamwrapper.stream-tell.php
 240       * @return integer The current position of the stream
 241       */
 242      public function stream_tell()
 243      {
 244          return $this->stream->tell();
 245      }
 246  
 247      /**
 248       * Write bytes to the stream.
 249       *
 250       * @see http://php.net/manual/en/streamwrapper.stream-write.php
 251       * @param string $data Data to write
 252       * @return integer The number of bytes written
 253       */
 254      public function stream_write($data)
 255      {
 256          if (! $this->stream instanceof WritableStream) {
 257              return 0;
 258          }
 259  
 260          try {
 261              return $this->stream->writeBytes($data);
 262          } catch (Throwable $e) {
 263              trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), E_USER_WARNING);
 264  
 265              return false;
 266          }
 267      }
 268  
 269      /**
 270       * Returns a stat template with default values.
 271       *
 272       * @return array
 273       */
 274      private function getStatTemplate()
 275      {
 276          return [
 277              // phpcs:disable Squiz.Arrays.ArrayDeclaration.IndexNoNewline
 278              0  => 0,  'dev'     => 0,
 279              1  => 0,  'ino'     => 0,
 280              2  => 0,  'mode'    => 0,
 281              3  => 0,  'nlink'   => 0,
 282              4  => 0,  'uid'     => 0,
 283              5  => 0,  'gid'     => 0,
 284              6  => -1, 'rdev'    => -1,
 285              7  => 0,  'size'    => 0,
 286              8  => 0,  'atime'   => 0,
 287              9  => 0,  'mtime'   => 0,
 288              10 => 0,  'ctime'   => 0,
 289              11 => -1, 'blksize' => -1,
 290              12 => -1, 'blocks'  => -1,
 291              // phpcs:enable
 292          ];
 293      }
 294  
 295      /**
 296       * Initialize the protocol from the given path.
 297       *
 298       * @see StreamWrapper::stream_open()
 299       * @param string $path
 300       */
 301      private function initProtocol($path)
 302      {
 303          $parts = explode('://', $path, 2);
 304          $this->protocol = $parts[0] ?: 'gridfs';
 305      }
 306  
 307      /**
 308       * Initialize the internal stream for reading.
 309       *
 310       * @see StreamWrapper::stream_open()
 311       * @return boolean
 312       */
 313      private function initReadableStream()
 314      {
 315          $context = stream_context_get_options($this->context);
 316  
 317          $this->stream = new ReadableStream(
 318              $context[$this->protocol]['collectionWrapper'],
 319              $context[$this->protocol]['file']
 320          );
 321  
 322          return true;
 323      }
 324  
 325      /**
 326       * Initialize the internal stream for writing.
 327       *
 328       * @see StreamWrapper::stream_open()
 329       * @return boolean
 330       */
 331      private function initWritableStream()
 332      {
 333          $context = stream_context_get_options($this->context);
 334  
 335          $this->stream = new WritableStream(
 336              $context[$this->protocol]['collectionWrapper'],
 337              $context[$this->protocol]['filename'],
 338              $context[$this->protocol]['options']
 339          );
 340  
 341          return true;
 342      }
 343  }