3 namespace Drupal\Core\StreamWrapper;
6 * Defines a Drupal stream wrapper base class for local files.
8 * This class provides a complete stream wrapper implementation. URIs such as
9 * "public://example.txt" are expanded to a normal filesystem path such as
10 * "sites/default/files/example.txt" and then PHP filesystem functions are
13 * Drupal\Core\StreamWrapper\LocalStream implementations need to implement at least the
14 * getDirectoryPath() and getExternalUrl() methods.
16 abstract class LocalStream implements StreamWrapperInterface {
18 * Stream context resource.
25 * A generic resource handle.
29 public $handle = NULL;
32 * Instance URI (stream).
34 * A stream is referenced as "scheme://target".
43 public static function getType() {
44 return StreamWrapperInterface::NORMAL;
48 * Gets the path that the wrapper is responsible for.
50 * @todo Review this method name in D8 per https://www.drupal.org/node/701358.
53 * String specifying the path.
55 abstract public function getDirectoryPath();
60 public function setUri($uri) {
67 public function getUri() {
72 * Returns the local writable target of the resource within the stream.
74 * This function should be used in place of calls to realpath() or similar
75 * functions when attempting to determine the location of a file. While
76 * functions like realpath() may return the location of a read-only file, this
77 * method may return a URI or path suitable for writing that is completely
78 * separate from the URI used for reading.
84 * Returns a string representing a location suitable for writing of a file,
85 * or FALSE if unable to write to the file such as with read-only streams.
87 protected function getTarget($uri = NULL) {
92 list(, $target) = explode('://', $uri, 2);
94 // Remove erroneous leading or trailing, forward-slashes and backslashes.
95 return trim($target, '\/');
101 public function realpath() {
102 return $this->getLocalPath();
106 * Returns the canonical absolute path of the URI, if possible.
109 * (optional) The stream wrapper URI to be converted to a canonical
110 * absolute path. This may point to a directory or another type of file.
112 * @return string|bool
113 * If $uri is not set, returns the canonical absolute path of the URI
114 * previously set by the
115 * Drupal\Core\StreamWrapper\StreamWrapperInterface::setUri() function.
116 * If $uri is set and valid for this class, returns its canonical absolute
117 * path, as determined by the realpath() function. If $uri is set but not
118 * valid, returns FALSE.
120 protected function getLocalPath($uri = NULL) {
124 $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
126 // In PHPUnit tests, the base path for local streams may be a virtual
127 // filesystem stream wrapper URI, in which case this local stream acts like
128 // a proxy. realpath() is not supported by vfsStream, because a virtual
129 // file system does not have a real filepath.
130 if (strpos($path, 'vfs://') === 0) {
134 $realpath = realpath($path);
136 // This file does not yet exist.
137 $realpath = realpath(dirname($path)) . '/' . drupal_basename($path);
139 $directory = realpath($this->getDirectoryPath());
140 if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {
147 * Support for fopen(), file_get_contents(), file_put_contents() etc.
150 * A string containing the URI to the file to open.
152 * The file mode ("r", "wb" etc.).
153 * @param int $options
154 * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
155 * @param string $opened_path
156 * A string containing the path actually opened.
159 * Returns TRUE if file was opened successfully.
161 * @see http://php.net/manual/streamwrapper.stream-open.php
163 public function stream_open($uri, $mode, $options, &$opened_path) {
165 $path = $this->getLocalPath();
166 $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
168 if ((bool) $this->handle && $options & STREAM_USE_PATH) {
169 $opened_path = $path;
172 return (bool) $this->handle;
176 * Support for flock().
178 * @param int $operation
179 * One of the following:
180 * - LOCK_SH to acquire a shared lock (reader).
181 * - LOCK_EX to acquire an exclusive lock (writer).
182 * - LOCK_UN to release a lock (shared or exclusive).
183 * - LOCK_NB if you don't want flock() to block while locking (not
184 * supported on Windows).
187 * Always returns TRUE at the present time.
189 * @see http://php.net/manual/streamwrapper.stream-lock.php
191 public function stream_lock($operation) {
192 if (in_array($operation, [LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB])) {
193 return flock($this->handle, $operation);
200 * Support for fread(), file_get_contents() etc.
203 * Maximum number of bytes to be read.
205 * @return string|bool
206 * The string that was read, or FALSE in case of an error.
208 * @see http://php.net/manual/streamwrapper.stream-read.php
210 public function stream_read($count) {
211 return fread($this->handle, $count);
215 * Support for fwrite(), file_put_contents() etc.
217 * @param string $data
218 * The string to be written.
221 * The number of bytes written.
223 * @see http://php.net/manual/streamwrapper.stream-write.php
225 public function stream_write($data) {
226 return fwrite($this->handle, $data);
230 * Support for feof().
233 * TRUE if end-of-file has been reached.
235 * @see http://php.net/manual/streamwrapper.stream-eof.php
237 public function stream_eof() {
238 return feof($this->handle);
244 public function stream_seek($offset, $whence = SEEK_SET) {
245 // fseek returns 0 on success and -1 on a failure.
246 // stream_seek 1 on success and 0 on a failure.
247 return !fseek($this->handle, $offset, $whence);
251 * Support for fflush().
254 * TRUE if data was successfully stored (or there was no data to store).
256 * @see http://php.net/manual/streamwrapper.stream-flush.php
258 public function stream_flush() {
259 return fflush($this->handle);
263 * Support for ftell().
266 * The current offset in bytes from the beginning of file.
268 * @see http://php.net/manual/streamwrapper.stream-tell.php
270 public function stream_tell() {
271 return ftell($this->handle);
275 * Support for fstat().
278 * An array with file status, or FALSE in case of an error - see fstat()
279 * for a description of this array.
281 * @see http://php.net/manual/streamwrapper.stream-stat.php
283 public function stream_stat() {
284 return fstat($this->handle);
288 * Support for fclose().
291 * TRUE if stream was successfully closed.
293 * @see http://php.net/manual/streamwrapper.stream-close.php
295 public function stream_close() {
296 return fclose($this->handle);
302 public function stream_cast($cast_as) {
303 return $this->handle ? $this->handle : FALSE;
309 public function stream_metadata($uri, $option, $value) {
310 $target = $this->getLocalPath($uri);
313 case STREAM_META_TOUCH:
314 if (!empty($value)) {
315 $return = touch($target, $value[0], $value[1]);
318 $return = touch($target);
322 case STREAM_META_OWNER_NAME:
323 case STREAM_META_OWNER:
324 $return = chown($target, $value);
327 case STREAM_META_GROUP_NAME:
328 case STREAM_META_GROUP:
329 $return = chgrp($target, $value);
332 case STREAM_META_ACCESS:
333 $return = chmod($target, $value);
337 // For convenience clear the file status cache of the underlying file,
338 // since metadata operations are often followed by file status checks.
339 clearstatcache(TRUE, $target);
347 * Since Windows systems do not allow it and it is not needed for most use
348 * cases anyway, this method is not supported on local files and will trigger
349 * an error and return false. If needed, custom subclasses can provide
350 * OS-specific implementations for advanced use cases.
352 public function stream_set_option($option, $arg1, $arg2) {
353 trigger_error('stream_set_option() not supported for local file based stream wrappers', E_USER_WARNING);
360 public function stream_truncate($new_size) {
361 return ftruncate($this->handle, $new_size);
365 * Support for unlink().
368 * A string containing the URI to the resource to delete.
371 * TRUE if resource was successfully deleted.
373 * @see http://php.net/manual/streamwrapper.unlink.php
375 public function unlink($uri) {
377 return drupal_unlink($this->getLocalPath());
381 * Support for rename().
383 * @param string $from_uri,
384 * The URI to the file to rename.
385 * @param string $to_uri
386 * The new URI for file.
389 * TRUE if file was successfully renamed.
391 * @see http://php.net/manual/streamwrapper.rename.php
393 public function rename($from_uri, $to_uri) {
394 return rename($this->getLocalPath($from_uri), $this->getLocalPath($to_uri));
398 * Gets the name of the directory from a given path.
400 * This method is usually accessed through drupal_dirname(), which wraps
401 * around the PHP dirname() function because it does not support stream
408 * A string containing the directory name.
410 * @see drupal_dirname()
412 public function dirname($uri = NULL) {
413 list($scheme) = explode('://', $uri, 2);
414 $target = $this->getTarget($uri);
415 $dirname = dirname($target);
417 if ($dirname == '.') {
421 return $scheme . '://' . $dirname;
425 * Support for mkdir().
428 * A string containing the URI to the directory to create.
430 * Permission flags - see mkdir().
431 * @param int $options
432 * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
435 * TRUE if directory was successfully created.
437 * @see http://php.net/manual/streamwrapper.mkdir.php
439 public function mkdir($uri, $mode, $options) {
441 $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);
443 // $this->getLocalPath() fails if $uri has multiple levels of directories
444 // that do not yet exist.
445 $localpath = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
448 $localpath = $this->getLocalPath($uri);
450 if ($options & STREAM_REPORT_ERRORS) {
451 return drupal_mkdir($localpath, $mode, $recursive);
454 return @drupal_mkdir($localpath, $mode, $recursive);
459 * Support for rmdir().
462 * A string containing the URI to the directory to delete.
463 * @param int $options
464 * A bit mask of STREAM_REPORT_ERRORS.
467 * TRUE if directory was successfully removed.
469 * @see http://php.net/manual/streamwrapper.rmdir.php
471 public function rmdir($uri, $options) {
473 if ($options & STREAM_REPORT_ERRORS) {
474 return drupal_rmdir($this->getLocalPath());
477 return @drupal_rmdir($this->getLocalPath());
482 * Support for stat().
485 * A string containing the URI to get information about.
487 * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
490 * An array with file status, or FALSE in case of an error - see fstat()
491 * for a description of this array.
493 * @see http://php.net/manual/streamwrapper.url-stat.php
495 public function url_stat($uri, $flags) {
497 $path = $this->getLocalPath();
498 // Suppress warnings if requested or if the file or directory does not
499 // exist. This is consistent with PHP's plain filesystem stream wrapper.
500 if ($flags & STREAM_URL_STAT_QUIET || !file_exists($path)) {
509 * Support for opendir().
512 * A string containing the URI to the directory to open.
513 * @param int $options
514 * Unknown (parameter is not documented in PHP Manual).
519 * @see http://php.net/manual/streamwrapper.dir-opendir.php
521 public function dir_opendir($uri, $options) {
523 $this->handle = opendir($this->getLocalPath());
525 return (bool) $this->handle;
529 * Support for readdir().
532 * The next filename, or FALSE if there are no more files in the directory.
534 * @see http://php.net/manual/streamwrapper.dir-readdir.php
536 public function dir_readdir() {
537 return readdir($this->handle);
541 * Support for rewinddir().
546 * @see http://php.net/manual/streamwrapper.dir-rewinddir.php
548 public function dir_rewinddir() {
549 rewinddir($this->handle);
550 // We do not really have a way to signal a failure as rewinddir() does not
551 // have a return value and there is no way to read a directory handler
552 // without advancing to the next file.
557 * Support for closedir().
562 * @see http://php.net/manual/streamwrapper.dir-closedir.php
564 public function dir_closedir() {
565 closedir($this->handle);
566 // We do not really have a way to signal a failure as closedir() does not
567 // have a return value.