LocalStream.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. <?php
  2. namespace Drupal\Core\StreamWrapper;
  3. /**
  4. * Defines a Drupal stream wrapper base class for local files.
  5. *
  6. * This class provides a complete stream wrapper implementation. URIs such as
  7. * "public://example.txt" are expanded to a normal filesystem path such as
  8. * "sites/default/files/example.txt" and then PHP filesystem functions are
  9. * invoked.
  10. *
  11. * Drupal\Core\StreamWrapper\LocalStream implementations need to implement at least the
  12. * getDirectoryPath() and getExternalUrl() methods.
  13. */
  14. abstract class LocalStream implements StreamWrapperInterface {
  15. /**
  16. * Stream context resource.
  17. *
  18. * @var resource
  19. */
  20. public $context;
  21. /**
  22. * A generic resource handle.
  23. *
  24. * @var resource
  25. */
  26. public $handle = NULL;
  27. /**
  28. * Instance URI (stream).
  29. *
  30. * A stream is referenced as "scheme://target".
  31. *
  32. * @var string
  33. */
  34. protected $uri;
  35. /**
  36. * {@inheritdoc}
  37. */
  38. public static function getType() {
  39. return StreamWrapperInterface::NORMAL;
  40. }
  41. /**
  42. * Gets the path that the wrapper is responsible for.
  43. *
  44. * @todo Review this method name in D8 per https://www.drupal.org/node/701358.
  45. *
  46. * @return string
  47. * String specifying the path.
  48. */
  49. abstract public function getDirectoryPath();
  50. /**
  51. * {@inheritdoc}
  52. */
  53. public function setUri($uri) {
  54. $this->uri = $uri;
  55. }
  56. /**
  57. * {@inheritdoc}
  58. */
  59. public function getUri() {
  60. return $this->uri;
  61. }
  62. /**
  63. * Returns the local writable target of the resource within the stream.
  64. *
  65. * This function should be used in place of calls to realpath() or similar
  66. * functions when attempting to determine the location of a file. While
  67. * functions like realpath() may return the location of a read-only file, this
  68. * method may return a URI or path suitable for writing that is completely
  69. * separate from the URI used for reading.
  70. *
  71. * @param string $uri
  72. * Optional URI.
  73. *
  74. * @return string|bool
  75. * Returns a string representing a location suitable for writing of a file,
  76. * or FALSE if unable to write to the file such as with read-only streams.
  77. */
  78. protected function getTarget($uri = NULL) {
  79. if (!isset($uri)) {
  80. $uri = $this->uri;
  81. }
  82. list(, $target) = explode('://', $uri, 2);
  83. // Remove erroneous leading or trailing, forward-slashes and backslashes.
  84. return trim($target, '\/');
  85. }
  86. /**
  87. * {@inheritdoc}
  88. */
  89. public function realpath() {
  90. return $this->getLocalPath();
  91. }
  92. /**
  93. * Returns the canonical absolute path of the URI, if possible.
  94. *
  95. * @param string $uri
  96. * (optional) The stream wrapper URI to be converted to a canonical
  97. * absolute path. This may point to a directory or another type of file.
  98. *
  99. * @return string|bool
  100. * If $uri is not set, returns the canonical absolute path of the URI
  101. * previously set by the
  102. * Drupal\Core\StreamWrapper\StreamWrapperInterface::setUri() function.
  103. * If $uri is set and valid for this class, returns its canonical absolute
  104. * path, as determined by the realpath() function. If $uri is set but not
  105. * valid, returns FALSE.
  106. */
  107. protected function getLocalPath($uri = NULL) {
  108. if (!isset($uri)) {
  109. $uri = $this->uri;
  110. }
  111. $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
  112. // In PHPUnit tests, the base path for local streams may be a virtual
  113. // filesystem stream wrapper URI, in which case this local stream acts like
  114. // a proxy. realpath() is not supported by vfsStream, because a virtual
  115. // file system does not have a real filepath.
  116. if (strpos($path, 'vfs://') === 0) {
  117. return $path;
  118. }
  119. $realpath = realpath($path);
  120. if (!$realpath) {
  121. // This file does not yet exist.
  122. $realpath = realpath(dirname($path)) . '/' . \Drupal::service('file_system')->basename($path);
  123. }
  124. $directory = realpath($this->getDirectoryPath());
  125. if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {
  126. return FALSE;
  127. }
  128. return $realpath;
  129. }
  130. /**
  131. * Support for fopen(), file_get_contents(), file_put_contents() etc.
  132. *
  133. * @param string $uri
  134. * A string containing the URI to the file to open.
  135. * @param int $mode
  136. * The file mode ("r", "wb" etc.).
  137. * @param int $options
  138. * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
  139. * @param string $opened_path
  140. * A string containing the path actually opened.
  141. *
  142. * @return bool
  143. * Returns TRUE if file was opened successfully.
  144. *
  145. * @see http://php.net/manual/streamwrapper.stream-open.php
  146. */
  147. public function stream_open($uri, $mode, $options, &$opened_path) {
  148. $this->uri = $uri;
  149. $path = $this->getLocalPath();
  150. $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
  151. if ((bool) $this->handle && $options & STREAM_USE_PATH) {
  152. $opened_path = $path;
  153. }
  154. return (bool) $this->handle;
  155. }
  156. /**
  157. * Support for flock().
  158. *
  159. * @param int $operation
  160. * One of the following:
  161. * - LOCK_SH to acquire a shared lock (reader).
  162. * - LOCK_EX to acquire an exclusive lock (writer).
  163. * - LOCK_UN to release a lock (shared or exclusive).
  164. * - LOCK_NB if you don't want flock() to block while locking (not
  165. * supported on Windows).
  166. *
  167. * @return bool
  168. * Always returns TRUE at the present time.
  169. *
  170. * @see http://php.net/manual/streamwrapper.stream-lock.php
  171. */
  172. public function stream_lock($operation) {
  173. if (in_array($operation, [LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB])) {
  174. return flock($this->handle, $operation);
  175. }
  176. return TRUE;
  177. }
  178. /**
  179. * Support for fread(), file_get_contents() etc.
  180. *
  181. * @param int $count
  182. * Maximum number of bytes to be read.
  183. *
  184. * @return string|bool
  185. * The string that was read, or FALSE in case of an error.
  186. *
  187. * @see http://php.net/manual/streamwrapper.stream-read.php
  188. */
  189. public function stream_read($count) {
  190. return fread($this->handle, $count);
  191. }
  192. /**
  193. * Support for fwrite(), file_put_contents() etc.
  194. *
  195. * @param string $data
  196. * The string to be written.
  197. *
  198. * @return int
  199. * The number of bytes written.
  200. *
  201. * @see http://php.net/manual/streamwrapper.stream-write.php
  202. */
  203. public function stream_write($data) {
  204. return fwrite($this->handle, $data);
  205. }
  206. /**
  207. * Support for feof().
  208. *
  209. * @return bool
  210. * TRUE if end-of-file has been reached.
  211. *
  212. * @see http://php.net/manual/streamwrapper.stream-eof.php
  213. */
  214. public function stream_eof() {
  215. return feof($this->handle);
  216. }
  217. /**
  218. * {@inheritdoc}
  219. */
  220. public function stream_seek($offset, $whence = SEEK_SET) {
  221. // fseek returns 0 on success and -1 on a failure.
  222. // stream_seek 1 on success and 0 on a failure.
  223. return !fseek($this->handle, $offset, $whence);
  224. }
  225. /**
  226. * Support for fflush().
  227. *
  228. * @return bool
  229. * TRUE if data was successfully stored (or there was no data to store).
  230. *
  231. * @see http://php.net/manual/streamwrapper.stream-flush.php
  232. */
  233. public function stream_flush() {
  234. return fflush($this->handle);
  235. }
  236. /**
  237. * Support for ftell().
  238. *
  239. * @return bool
  240. * The current offset in bytes from the beginning of file.
  241. *
  242. * @see http://php.net/manual/streamwrapper.stream-tell.php
  243. */
  244. public function stream_tell() {
  245. return ftell($this->handle);
  246. }
  247. /**
  248. * Support for fstat().
  249. *
  250. * @return bool
  251. * An array with file status, or FALSE in case of an error - see fstat()
  252. * for a description of this array.
  253. *
  254. * @see http://php.net/manual/streamwrapper.stream-stat.php
  255. */
  256. public function stream_stat() {
  257. return fstat($this->handle);
  258. }
  259. /**
  260. * Support for fclose().
  261. *
  262. * @return bool
  263. * TRUE if stream was successfully closed.
  264. *
  265. * @see http://php.net/manual/streamwrapper.stream-close.php
  266. */
  267. public function stream_close() {
  268. return fclose($this->handle);
  269. }
  270. /**
  271. * {@inheritdoc}
  272. */
  273. public function stream_cast($cast_as) {
  274. return $this->handle ? $this->handle : FALSE;
  275. }
  276. /**
  277. * {@inheritdoc}
  278. */
  279. public function stream_metadata($uri, $option, $value) {
  280. $target = $this->getLocalPath($uri);
  281. $return = FALSE;
  282. switch ($option) {
  283. case STREAM_META_TOUCH:
  284. if (!empty($value)) {
  285. $return = touch($target, $value[0], $value[1]);
  286. }
  287. else {
  288. $return = touch($target);
  289. }
  290. break;
  291. case STREAM_META_OWNER_NAME:
  292. case STREAM_META_OWNER:
  293. $return = chown($target, $value);
  294. break;
  295. case STREAM_META_GROUP_NAME:
  296. case STREAM_META_GROUP:
  297. $return = chgrp($target, $value);
  298. break;
  299. case STREAM_META_ACCESS:
  300. $return = chmod($target, $value);
  301. break;
  302. }
  303. if ($return) {
  304. // For convenience clear the file status cache of the underlying file,
  305. // since metadata operations are often followed by file status checks.
  306. clearstatcache(TRUE, $target);
  307. }
  308. return $return;
  309. }
  310. /**
  311. * {@inheritdoc}
  312. *
  313. * Since Windows systems do not allow it and it is not needed for most use
  314. * cases anyway, this method is not supported on local files and will trigger
  315. * an error and return false. If needed, custom subclasses can provide
  316. * OS-specific implementations for advanced use cases.
  317. */
  318. public function stream_set_option($option, $arg1, $arg2) {
  319. trigger_error('stream_set_option() not supported for local file based stream wrappers', E_USER_WARNING);
  320. return FALSE;
  321. }
  322. /**
  323. * {@inheritdoc}
  324. */
  325. public function stream_truncate($new_size) {
  326. return ftruncate($this->handle, $new_size);
  327. }
  328. /**
  329. * Support for unlink().
  330. *
  331. * @param string $uri
  332. * A string containing the URI to the resource to delete.
  333. *
  334. * @return bool
  335. * TRUE if resource was successfully deleted.
  336. *
  337. * @see http://php.net/manual/streamwrapper.unlink.php
  338. */
  339. public function unlink($uri) {
  340. $this->uri = $uri;
  341. return $this->getFileSystem()->unlink($this->getLocalPath());
  342. }
  343. /**
  344. * Support for rename().
  345. *
  346. * @param string $from_uri
  347. * The URI to the file to rename.
  348. * @param string $to_uri
  349. * The new URI for file.
  350. *
  351. * @return bool
  352. * TRUE if file was successfully renamed.
  353. *
  354. * @see http://php.net/manual/streamwrapper.rename.php
  355. */
  356. public function rename($from_uri, $to_uri) {
  357. return rename($this->getLocalPath($from_uri), $this->getLocalPath($to_uri));
  358. }
  359. /**
  360. * Gets the name of the directory from a given path.
  361. *
  362. * This method is usually accessed through
  363. * \Drupal\Core\File\FileSystemInterface::dirname(), which wraps around the
  364. * PHP dirname() function because it does not support stream wrappers.
  365. *
  366. * @param string $uri
  367. * A URI or path.
  368. *
  369. * @return string
  370. * A string containing the directory name.
  371. *
  372. * @see \Drupal\Core\File\FileSystemInterface::dirname()
  373. */
  374. public function dirname($uri = NULL) {
  375. list($scheme) = explode('://', $uri, 2);
  376. $target = $this->getTarget($uri);
  377. $dirname = dirname($target);
  378. if ($dirname == '.') {
  379. $dirname = '';
  380. }
  381. return $scheme . '://' . $dirname;
  382. }
  383. /**
  384. * Support for mkdir().
  385. *
  386. * @param string $uri
  387. * A string containing the URI to the directory to create.
  388. * @param int $mode
  389. * Permission flags - see mkdir().
  390. * @param int $options
  391. * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
  392. *
  393. * @return bool
  394. * TRUE if directory was successfully created.
  395. *
  396. * @see http://php.net/manual/streamwrapper.mkdir.php
  397. */
  398. public function mkdir($uri, $mode, $options) {
  399. $this->uri = $uri;
  400. $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);
  401. if ($recursive) {
  402. // $this->getLocalPath() fails if $uri has multiple levels of directories
  403. // that do not yet exist.
  404. $localpath = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
  405. }
  406. else {
  407. $localpath = $this->getLocalPath($uri);
  408. }
  409. /** @var \Drupal\Core\File\FileSystemInterface $file_system */
  410. $file_system = \Drupal::service('file_system');
  411. if ($options & STREAM_REPORT_ERRORS) {
  412. return $file_system->mkdir($localpath, $mode, $recursive);
  413. }
  414. else {
  415. return @$file_system->mkdir($localpath, $mode, $recursive);
  416. }
  417. }
  418. /**
  419. * Support for rmdir().
  420. *
  421. * @param string $uri
  422. * A string containing the URI to the directory to delete.
  423. * @param int $options
  424. * A bit mask of STREAM_REPORT_ERRORS.
  425. *
  426. * @return bool
  427. * TRUE if directory was successfully removed.
  428. *
  429. * @see http://php.net/manual/streamwrapper.rmdir.php
  430. */
  431. public function rmdir($uri, $options) {
  432. $this->uri = $uri;
  433. /** @var \Drupal\Core\File\FileSystemInterface $file_system */
  434. $file_system = \Drupal::service('file_system');
  435. if ($options & STREAM_REPORT_ERRORS) {
  436. return $file_system->rmdir($this->getLocalPath());
  437. }
  438. else {
  439. return @$file_system->rmdir($this->getLocalPath());
  440. }
  441. }
  442. /**
  443. * Support for stat().
  444. *
  445. * @param string $uri
  446. * A string containing the URI to get information about.
  447. * @param int $flags
  448. * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
  449. *
  450. * @return array
  451. * An array with file status, or FALSE in case of an error - see fstat()
  452. * for a description of this array.
  453. *
  454. * @see http://php.net/manual/streamwrapper.url-stat.php
  455. */
  456. public function url_stat($uri, $flags) {
  457. $this->uri = $uri;
  458. $path = $this->getLocalPath();
  459. // Suppress warnings if requested or if the file or directory does not
  460. // exist. This is consistent with PHP's plain filesystem stream wrapper.
  461. if ($flags & STREAM_URL_STAT_QUIET || !file_exists($path)) {
  462. return @stat($path);
  463. }
  464. else {
  465. return stat($path);
  466. }
  467. }
  468. /**
  469. * Support for opendir().
  470. *
  471. * @param string $uri
  472. * A string containing the URI to the directory to open.
  473. * @param int $options
  474. * Unknown (parameter is not documented in PHP Manual).
  475. *
  476. * @return bool
  477. * TRUE on success.
  478. *
  479. * @see http://php.net/manual/streamwrapper.dir-opendir.php
  480. */
  481. public function dir_opendir($uri, $options) {
  482. $this->uri = $uri;
  483. $this->handle = opendir($this->getLocalPath());
  484. return (bool) $this->handle;
  485. }
  486. /**
  487. * Support for readdir().
  488. *
  489. * @return string
  490. * The next filename, or FALSE if there are no more files in the directory.
  491. *
  492. * @see http://php.net/manual/streamwrapper.dir-readdir.php
  493. */
  494. public function dir_readdir() {
  495. return readdir($this->handle);
  496. }
  497. /**
  498. * Support for rewinddir().
  499. *
  500. * @return bool
  501. * TRUE on success.
  502. *
  503. * @see http://php.net/manual/streamwrapper.dir-rewinddir.php
  504. */
  505. public function dir_rewinddir() {
  506. rewinddir($this->handle);
  507. // We do not really have a way to signal a failure as rewinddir() does not
  508. // have a return value and there is no way to read a directory handler
  509. // without advancing to the next file.
  510. return TRUE;
  511. }
  512. /**
  513. * Support for closedir().
  514. *
  515. * @return bool
  516. * TRUE on success.
  517. *
  518. * @see http://php.net/manual/streamwrapper.dir-closedir.php
  519. */
  520. public function dir_closedir() {
  521. closedir($this->handle);
  522. // We do not really have a way to signal a failure as closedir() does not
  523. // have a return value.
  524. return TRUE;
  525. }
  526. /**
  527. * Returns file system service.
  528. *
  529. * @return \Drupal\Core\File\FileSystemInterface
  530. * The file system service.
  531. */
  532. private function getFileSystem() {
  533. return \Drupal::service('file_system');
  534. }
  535. }