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.
<?php
/**
 * Copyright 2009-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file LICENSE for license information (BSD). If you
 * did not receive this file, see http://www.horde.org/licenses/bsd.
 *
 * @category  Horde
 * @copyright 2009-2017 Horde LLC
 * @license   http://www.horde.org/licenses/bsd BSD
 * @package   Stream_Wrapper
 */

/**
 * A stream wrapper that will combine multiple strings/streams into a single
 * stream.
 *
 * @author    Michael Slusarz <slusarz@horde.org>
 * @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']);
> if ($curr_read > 0) {
$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); } }