* @category Horde * @copyright 2009-2017 Horde LLC * @license http://www.horde.org/licenses/bsd BSD * @package Stream_Wrapper */ class Horde_Stream_Wrapper_Combine { /**/ const WRAPPER_NAME = 'horde-stream-wrapper-combine'; /** * Context. * * @var resource */ public $context; /** * Array that holds the various streams. * * @var array */ protected $_data = array(); /** * The combined length of the stream. * * @var integer */ protected $_length = 0; /** * The current position in the string. * * @var integer */ protected $_position = 0; /** * The current position in the data array. * * @var integer */ protected $_datapos = 0; /** * Have we reached EOF? * * @var boolean */ protected $_ateof = false; /** * Unique ID tracker for the streams. * * @var integer */ private static $_id = 0; /** * Create a stream from multiple data sources. * * @since 2.1.0 * * @param array $data An array of strings and/or streams to combine into * a single stream. * * @return resource A PHP stream. */ public static function getStream($data) { if (!self::$_id) { stream_wrapper_register(self::WRAPPER_NAME, __CLASS__); } return fopen( self::WRAPPER_NAME . '://' . ++self::$_id, 'wb', false, stream_context_create(array( self::WRAPPER_NAME => array( 'data' => $data ) )) ); } /** * @see streamWrapper::stream_open() * * @param string $path * @param string $mode * @param integer $options * @param string &$opened_path * * @throws Exception */ public function stream_open($path, $mode, $options, &$opened_path) { $opts = stream_context_get_options($this->context); if (isset($opts[self::WRAPPER_NAME]['data'])) { $data = $opts[self::WRAPPER_NAME]['data']; } elseif (isset($opts['horde-combine']['data'])) { // @deprecated $data = $opts['horde-combine']['data']->getData(); } else { throw new Exception('Use ' . __CLASS__ . '::getStream() to initialize the stream.'); } foreach ($data as $val) { if (is_string($val)) { $fp = fopen('php://temp', 'r+'); fwrite($fp, $val); } else { $fp = $val; } fseek($fp, 0, SEEK_END); $length = ftell($fp); rewind($fp); $this->_data[] = array( 'fp' => $fp, 'l' => $length, 'p' => 0 ); $this->_length += $length; } return true; } /** * @see streamWrapper::stream_read() * * @param integer $count * * @return mixed */ public function stream_read($count) { if ($this->stream_eof()) { return false; } $out = ''; $tmp = &$this->_data[$this->_datapos]; while ($count) { if (!is_resource($tmp['fp'])) { return false; } $curr_read = min($count, $tmp['l'] - $tmp['p']); $out .= fread($tmp['fp'], $curr_read); $count -= $curr_read; $this->_position += $curr_read; if ($this->_position == $this->_length) { if ($count) { $this->_ateof = true; break; } else { $tmp['p'] += $curr_read; } } elseif ($count) { if (!isset($this->_data[++$this->_datapos])) { return false; } $tmp = &$this->_data[$this->_datapos]; rewind($tmp['fp']); $tmp['p'] = 0; } else { $tmp['p'] += $curr_read; } } return $out; } /** * @see streamWrapper::stream_write() * * @param string $data * * @return integer */ public function stream_write($data) { $tmp = &$this->_data[$this->_datapos]; $oldlen = $tmp['l']; $res = fwrite($tmp['fp'], $data); if ($res === false) { return false; } $tmp['p'] = ftell($tmp['fp']); if ($tmp['p'] > $oldlen) { $tmp['l'] = $tmp['p']; $this->_length += ($tmp['l'] - $oldlen); } return $res; } /** * @see streamWrapper::stream_tell() * * @return integer */ public function stream_tell() { return $this->_position; } /** * @see streamWrapper::stream_eof() * * @return boolean */ public function stream_eof() { return $this->_ateof; } /** * @see streamWrapper::stream_stat() * * @return array */ public function stream_stat() { return array( 'dev' => 0, 'ino' => 0, 'mode' => 0, 'nlink' => 0, 'uid' => 0, 'gid' => 0, 'rdev' => 0, 'size' => $this->_length, 'atime' => 0, 'mtime' => 0, 'ctime' => 0, 'blksize' => 0, 'blocks' => 0 ); } /** * @see streamWrapper::stream_seek() * * @param integer $offset * @param integer $whence SEEK_SET, SEEK_CUR, or SEEK_END * * @return boolean */ public function stream_seek($offset, $whence) { $oldpos = $this->_position; $this->_ateof = false; switch ($whence) { case SEEK_SET: $offset = $offset; break; case SEEK_CUR: $offset = $this->_position + $offset; break; case SEEK_END: $offset = $this->_length + $offset; break; default: return false; } $count = $this->_position = min($this->_length, $offset); foreach ($this->_data as $key => $val) { if ($count < $val['l']) { $this->_datapos = $key; $val['p'] = $count; fseek($val['fp'], $count, SEEK_SET); break; } $count -= $val['l']; } return ($oldpos != $this->_position); } }