5 * Copyright 2018 Andy Street <andy@street.me.uk>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
28 * A PHP stream wrapper with the ability to peek at the first read.
30 * Note: This class is used to workaround an issue with the `XMLReader` class.
31 * It should not be called by code outside of libgpx.
35 * @author Andy Street <andy@street.me.uk>
41 * All instances of this class with open streams.
45 public static $instances = [];
48 * The handle to the stream to read from.
55 * The stream path where data is loaded from.
62 * Cache of the first successful read from the stream.
66 protected $first_read;
69 * The context for this stream.
71 * Unused but set by PHP so included for completeness.
78 * Remove the protocol from a path.
80 * @param string $path The path including the protocol for this stream wrapper.
81 * @return string The path without the protocol.
83 protected function stripProtocol(string $path)
85 $pos = strpos($path, ':');
86 return ($pos === false ? null : substr($path, $pos + 3));
92 * @param string $path The path to open.
93 * @param string $mode The mode used to open the stream.
94 * @param int $options Streams API flags.
95 * @param string $opened_path The path that was actually opened.
96 * @return boolean If it was possible to open a stream.
98 public function stream_open(
104 $this->path = $this->stripProtocol($path);
105 if ($this->path === null) return false;
107 if (!in_array($mode, ['r', 'rb'])) return false;
109 $this->handle = fopen($this->path, $mode);
110 if (!is_resource($this->handle)) return false;
111 self::$instances[] = $this;
119 * @return boolean If the stream was closed successfully.
121 public function stream_close()
123 foreach (self::$instances as $key => $value) {
124 if ($value === $this) {
125 unset(self::$instances[$key]);
129 return fclose($this->handle);
133 * Check if the pointer is at the end of the stream.
135 * @return boolean If the pointer is at the end of the stream.
137 public function stream_eof()
139 return feof($this->handle);
143 * Read data from the stream.
145 * @param int $count The number of bytes to read.
146 * @return string The bytes read or false on failure.
148 public function stream_read(int $count)
150 $data = fread($this->handle, $count);
151 if ($data !== false && $this->first_read === null)
152 $this->first_read = $data;
157 * Fetch file information.
159 * @param string $path The path to check.
160 * @param int $flags PHP streams API flags.
161 * @return array A `stat` compatible array.
163 public function url_stat(string $path, int $flags)
165 $path = $this->stripProtocol($path);
166 if ($path === null) return 0;
171 * Fetch the contents of the first read from a stream.
173 * @param string $path The path to peek at.
174 * @return string|null The first read or null if not available.
176 public static function peek(string $path)
180 foreach (self::$instances as $i) {
181 if ($i->path == $path) {
186 if ($instance instanceof PeekStream && is_string($instance->first_read)) {
187 $result = $instance->first_read;
188 $instance->first_read = false;