Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]

   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              $out .= fread($tmp['fp'], $curr_read);
 175              $count -= $curr_read;
 176              $this->_position += $curr_read;
 177  
 178              if ($this->_position == $this->_length) {
 179                  if ($count) {
 180                      $this->_ateof = true;
 181                      break;
 182                  } else {
 183                      $tmp['p'] += $curr_read;
 184                  }
 185              } elseif ($count) {
 186                  if (!isset($this->_data[++$this->_datapos])) {
 187                      return false;
 188                  }
 189                  $tmp = &$this->_data[$this->_datapos];
 190                  rewind($tmp['fp']);
 191                  $tmp['p'] = 0;
 192              } else {
 193                  $tmp['p'] += $curr_read;
 194              }
 195          }
 196  
 197          return $out;
 198      }
 199  
 200      /**
 201       * @see streamWrapper::stream_write()
 202       *
 203       * @param string $data
 204       *
 205       * @return integer
 206       */
 207      public function stream_write($data)
 208      {
 209          $tmp = &$this->_data[$this->_datapos];
 210  
 211          $oldlen = $tmp['l'];
 212          $res = fwrite($tmp['fp'], $data);
 213          if ($res === false) {
 214              return false;
 215          }
 216  
 217          $tmp['p'] = ftell($tmp['fp']);
 218          if ($tmp['p'] > $oldlen) {
 219              $tmp['l'] = $tmp['p'];
 220              $this->_length += ($tmp['l'] - $oldlen);
 221          }
 222  
 223          return $res;
 224      }
 225  
 226      /**
 227       * @see streamWrapper::stream_tell()
 228       *
 229       * @return integer
 230       */
 231      public function stream_tell()
 232      {
 233          return $this->_position;
 234      }
 235  
 236      /**
 237       * @see streamWrapper::stream_eof()
 238       *
 239       * @return boolean
 240       */
 241      public function stream_eof()
 242      {
 243          return $this->_ateof;
 244      }
 245  
 246      /**
 247       * @see streamWrapper::stream_stat()
 248       *
 249       * @return array
 250       */
 251      public function stream_stat()
 252      {
 253          return array(
 254              'dev' => 0,
 255              'ino' => 0,
 256              'mode' => 0,
 257              'nlink' => 0,
 258              'uid' => 0,
 259              'gid' => 0,
 260              'rdev' => 0,
 261              'size' => $this->_length,
 262              'atime' => 0,
 263              'mtime' => 0,
 264              'ctime' => 0,
 265              'blksize' => 0,
 266              'blocks' => 0
 267          );
 268      }
 269  
 270      /**
 271       * @see streamWrapper::stream_seek()
 272       *
 273       * @param integer $offset
 274       * @param integer $whence  SEEK_SET, SEEK_CUR, or SEEK_END
 275       *
 276       * @return boolean
 277       */
 278      public function stream_seek($offset, $whence)
 279      {
 280          $oldpos = $this->_position;
 281          $this->_ateof = false;
 282  
 283          switch ($whence) {
 284          case SEEK_SET:
 285              $offset = $offset;
 286              break;
 287  
 288          case SEEK_CUR:
 289              $offset = $this->_position + $offset;
 290              break;
 291  
 292          case SEEK_END:
 293              $offset = $this->_length + $offset;
 294              break;
 295  
 296          default:
 297              return false;
 298          }
 299  
 300          $count = $this->_position = min($this->_length, $offset);
 301  
 302          foreach ($this->_data as $key => $val) {
 303              if ($count < $val['l']) {
 304                  $this->_datapos = $key;
 305                  $val['p'] = $count;
 306                  fseek($val['fp'], $count, SEEK_SET);
 307                  break;
 308              }
 309              $count -= $val['l'];
 310          }
 311  
 312          return ($oldpos != $this->_position);
 313      }
 314  
 315  }