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]

   1  <?php
   2  /**
   3   * Copyright 2009-2017 Horde LLC (http://www.horde.org/)
   4   *
   5   * See the enclosed file LICENSE for license information (BSD). If you
   6   * did not receive this file, see http://www.horde.org/licenses/bsd.
   7   *
   8   * @category  Horde
   9   * @copyright 2009-2017 Horde LLC
  10   * @license   http://www.horde.org/licenses/bsd BSD
  11   * @package   Stream_Wrapper
  12   */
  13  
  14  /**
  15   * A stream wrapper that will combine multiple strings/streams into a single
  16   * stream.
  17   *
  18   * @author    Michael Slusarz <slusarz@horde.org>
  19   * @category  Horde
  20   * @copyright 2009-2017 Horde LLC
  21   * @license   http://www.horde.org/licenses/bsd BSD
  22   * @package   Stream_Wrapper
  23   */
  24  class Horde_Stream_Wrapper_Combine
  25  {
  26      /**/
  27      const WRAPPER_NAME = 'horde-stream-wrapper-combine';
  28  
  29      /**
  30       * Context.
  31       *
  32       * @var resource
  33       */
  34      public $context;
  35  
  36      /**
  37       * Array that holds the various streams.
  38       *
  39       * @var array
  40       */
  41      protected $_data = array();
  42  
  43      /**
  44       * The combined length of the stream.
  45       *
  46       * @var integer
  47       */
  48      protected $_length = 0;
  49  
  50      /**
  51       * The current position in the string.
  52       *
  53       * @var integer
  54       */
  55      protected $_position = 0;
  56  
  57      /**
  58       * The current position in the data array.
  59       *
  60       * @var integer
  61       */
  62      protected $_datapos = 0;
  63  
  64      /**
  65       * Have we reached EOF?
  66       *
  67       * @var boolean
  68       */
  69      protected $_ateof = false;
  70  
  71      /**
  72       * Unique ID tracker for the streams.
  73       *
  74       * @var integer
  75       */
  76      private static $_id = 0;
  77  
  78      /**
  79       * Create a stream from multiple data sources.
  80       *
  81       * @since 2.1.0
  82       *
  83       * @param array $data  An array of strings and/or streams to combine into
  84       *                     a single stream.
  85       *
  86       * @return resource  A PHP stream.
  87       */
  88      public static function getStream($data)
  89      {
  90          if (!self::$_id) {
  91              stream_wrapper_register(self::WRAPPER_NAME, __CLASS__);
  92          }
  93  
  94          return fopen(
  95              self::WRAPPER_NAME . '://' . ++self::$_id,
  96              'wb',
  97              false,
  98              stream_context_create(array(
  99                  self::WRAPPER_NAME => array(
 100                      'data' => $data
 101                  )
 102              ))
 103          );
 104      }
 105      /**
 106       * @see streamWrapper::stream_open()
 107       *
 108       * @param string $path
 109       * @param string $mode
 110       * @param integer $options
 111       * @param string &$opened_path
 112       *
 113       * @throws Exception
 114       */
 115      public function stream_open($path, $mode, $options, &$opened_path)
 116      {
 117          $opts = stream_context_get_options($this->context);
 118  
 119          if (isset($opts[self::WRAPPER_NAME]['data'])) {
 120              $data = $opts[self::WRAPPER_NAME]['data'];
 121          } elseif (isset($opts['horde-combine']['data'])) {
 122              // @deprecated
 123              $data = $opts['horde-combine']['data']->getData();
 124          } else {
 125              throw new Exception('Use ' . __CLASS__ . '::getStream() to initialize the stream.');
 126          }
 127  
 128          foreach ($data as $val) {
 129              if (is_string($val)) {
 130                  $fp = fopen('php://temp', 'r+');
 131                  fwrite($fp, $val);
 132              } else {
 133                  $fp = $val;
 134              }
 135  
 136              fseek($fp, 0, SEEK_END);
 137              $length = ftell($fp);
 138              rewind($fp);
 139  
 140              $this->_data[] = array(
 141                  'fp' => $fp,
 142                  'l' => $length,
 143                  'p' => 0
 144              );
 145  
 146              $this->_length += $length;
 147          }
 148  
 149          return true;
 150      }
 151  
 152      /**
 153       * @see streamWrapper::stream_read()
 154       *
 155       * @param integer $count
 156       *
 157       * @return mixed
 158       */
 159      public function stream_read($count)
 160      {
 161          if ($this->stream_eof()) {
 162              return false;
 163          }
 164  
 165          $out = '';
 166          $tmp = &$this->_data[$this->_datapos];
 167  
 168          while ($count) {
 169              if (!is_resource($tmp['fp'])) {
 170                  return false;
 171              }
 172  
 173              $curr_read = min($count, $tmp['l'] - $tmp['p']);
 174              if ($curr_read > 0) {
 175                  $out .= fread($tmp['fp'], $curr_read);
 176                  $count -= $curr_read;
 177                  $this->_position += $curr_read;
 178              }
 179  
 180              if ($this->_position == $this->_length) {
 181                  if ($count) {
 182                      $this->_ateof = true;
 183                      break;
 184                  } else {
 185                      $tmp['p'] += $curr_read;
 186                  }
 187              } elseif ($count) {
 188                  if (!isset($this->_data[++$this->_datapos])) {
 189                      return false;
 190                  }
 191                  $tmp = &$this->_data[$this->_datapos];
 192                  rewind($tmp['fp']);
 193                  $tmp['p'] = 0;
 194              } else {
 195                  $tmp['p'] += $curr_read;
 196              }
 197          }
 198  
 199          return $out;
 200      }
 201  
 202      /**
 203       * @see streamWrapper::stream_write()
 204       *
 205       * @param string $data
 206       *
 207       * @return integer
 208       */
 209      public function stream_write($data)
 210      {
 211          $tmp = &$this->_data[$this->_datapos];
 212  
 213          $oldlen = $tmp['l'];
 214          $res = fwrite($tmp['fp'], $data);
 215          if ($res === false) {
 216              return false;
 217          }
 218  
 219          $tmp['p'] = ftell($tmp['fp']);
 220          if ($tmp['p'] > $oldlen) {
 221              $tmp['l'] = $tmp['p'];
 222              $this->_length += ($tmp['l'] - $oldlen);
 223          }
 224  
 225          return $res;
 226      }
 227  
 228      /**
 229       * @see streamWrapper::stream_tell()
 230       *
 231       * @return integer
 232       */
 233      public function stream_tell()
 234      {
 235          return $this->_position;
 236      }
 237  
 238      /**
 239       * @see streamWrapper::stream_eof()
 240       *
 241       * @return boolean
 242       */
 243      public function stream_eof()
 244      {
 245          return $this->_ateof;
 246      }
 247  
 248      /**
 249       * @see streamWrapper::stream_stat()
 250       *
 251       * @return array
 252       */
 253      public function stream_stat()
 254      {
 255          return array(
 256              'dev' => 0,
 257              'ino' => 0,
 258              'mode' => 0,
 259              'nlink' => 0,
 260              'uid' => 0,
 261              'gid' => 0,
 262              'rdev' => 0,
 263              'size' => $this->_length,
 264              'atime' => 0,
 265              'mtime' => 0,
 266              'ctime' => 0,
 267              'blksize' => 0,
 268              'blocks' => 0
 269          );
 270      }
 271  
 272      /**
 273       * @see streamWrapper::stream_seek()
 274       *
 275       * @param integer $offset
 276       * @param integer $whence  SEEK_SET, SEEK_CUR, or SEEK_END
 277       *
 278       * @return boolean
 279       */
 280      public function stream_seek($offset, $whence)
 281      {
 282          $oldpos = $this->_position;
 283          $this->_ateof = false;
 284  
 285          switch ($whence) {
 286          case SEEK_SET:
 287              $offset = $offset;
 288              break;
 289  
 290          case SEEK_CUR:
 291              $offset = $this->_position + $offset;
 292              break;
 293  
 294          case SEEK_END:
 295              $offset = $this->_length + $offset;
 296              break;
 297  
 298          default:
 299              return false;
 300          }
 301  
 302          $count = $this->_position = min($this->_length, $offset);
 303  
 304          foreach ($this->_data as $key => $val) {
 305              if ($count < $val['l']) {
 306                  $this->_datapos = $key;
 307                  $val['p'] = $count;
 308                  fseek($val['fp'], $count, SEEK_SET);
 309                  break;
 310              }
 311              $count -= $val['l'];
 312          }
 313  
 314          return ($oldpos != $this->_position);
 315      }
 316  
 317  }