/lib/horde/framework/Horde/Stream/Wrapper/Combine.php

https://github.com/markn86/moodle · PHP · 315 lines · 162 code · 38 blank · 115 comment · 19 complexity · 819f32e45d5813a919c4d63b055e45ea MD5 · raw file

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