[TASK] Removes extra empty lines
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / FileStreamWrapper.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests;
3
4 /**
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * Stream wrapper for the file:// protocol
19 *
20 * Implementation details:
21 * Due to the nature of PHP, it is not possible to switch to the default handler
22 * other then restoring the default handler for file:// and registering it again
23 * around each call.
24 * It is important that the default handler is restored to allow autoloading (including)
25 * of files during the test run.
26 * For each method allowed to pass paths, the passed path is checked against the
27 * the list of paths to overlay and rewritten if needed.
28 *
29 * = Usage =
30 * <code title="Add use statements">
31 * use org\bovigo\vfs\vfsStream;
32 * use org\bovigo\vfs\visitor\vfsStreamStructureVisitor;
33 * </code>
34 *
35 * <code title="Usage in test">
36 * $root = \org\bovigo\vfs\vfsStream::setup('root');
37 * $subfolder = \org\bovigo\vfs\vfsStream::newDirectory('fileadmin');
38 * $root->addChild($subfolder);
39 * // Load fixture files and folders from disk
40 * \org\bovigo\vfs\vfsStream::copyFromFileSystem(__DIR__ . '/Fixture/Files', $subfolder, 1024*1024);
41 * FileStreamWrapper::init(PATH_site);
42 * FileStreamWrapper::registerOverlayPath('fileadmin', 'vfs://root/fileadmin');
43 *
44 * // Use file functions as usual
45 * mkdir(PATH_site . 'fileadmin/test/');
46 * $file = PATH_site . 'fileadmin/test/Foo.bar';
47 * file_put_contents($file, 'Baz');
48 * $content = file_get_contents($file);
49 * $this->assertSame('Baz', $content);
50 *
51 * $this->assertEqual(**array(file system structure as array**), vfsStream::inspect(new vfsStreamStructureVisitor())->getStructure());
52 *
53 * FileStreamWrapper::destroy();
54 * </code>
55 *
56 * @see http://www.php.net/manual/en/class.streamwrapper.php
57 */
58 class FileStreamWrapper
59 {
60 /**
61 * @var resource
62 */
63 protected $dirHandle = null;
64
65 /**
66 * @var resource
67 */
68 protected $fileHandle = null;
69
70 /**
71 * Switch whether class has already been registered as stream wrapper or not
72 *
73 * @type bool
74 */
75 protected static $registered = false;
76
77 /**
78 * Array of paths to overlay
79 *
80 * @var array
81 */
82 protected static $overlayPaths = array();
83
84 /**
85 * The first part of each (absolute) path that shall be ignored
86 *
87 * @var string
88 */
89 protected static $rootPath = '';
90
91 /**
92 * Initialize the stream wrapper with a root path and register itself
93 *
94 * @param $rootPath
95 * @return void
96 */
97 public static function init($rootPath)
98 {
99 self::$rootPath = rtrim(str_replace('\\', '/', $rootPath), '/') . '/';
100 self::register();
101 }
102
103 /**
104 * Unregister the stream wrapper and reset all static members to their default values
105 * @return void
106 */
107 public static function destroy()
108 {
109 self::$overlayPaths = array();
110 self::$rootPath = '';
111 if (self::$registered) {
112 self::restore();
113 }
114 }
115
116 /**
117 * Register a path relative to the root path (set in init) to be overlaid
118 *
119 * @param string $overlay Relative path to the root folder
120 * @param string $replace The path that should replace the overlay path
121 * @param bool $createFolder TRUE of the folder should be created (mkdir)
122 * @return void
123 */
124 public static function registerOverlayPath($overlay, $replace, $createFolder = true)
125 {
126 $overlay = trim(str_replace('\\', '/', $overlay), '/') . '/';
127 $replace = rtrim(str_replace('\\', '/', $replace), '/') . '/';
128 self::$overlayPaths[$overlay] = $replace;
129 if ($createFolder) {
130 mkdir($replace);
131 }
132 }
133
134 /**
135 * Checks and overlays a path
136 *
137 * @param string $path The path to check
138 * @return string The potentially overlaid path
139 */
140 protected static function overlayPath($path)
141 {
142 $path = str_replace('\\', '/', $path);
143 $hasOverlay = false;
144 if (strpos($path, self::$rootPath) !== 0) {
145 // Path is not below root path, ignore it
146 return $path;
147 }
148
149 $newPath = ltrim(substr($path, strlen(self::$rootPath)), '/');
150 foreach (self::$overlayPaths as $overlay => $replace) {
151 if (strpos($newPath, $overlay) === 0) {
152 $newPath = $replace . substr($newPath, strlen($overlay));
153 $hasOverlay = true;
154 break;
155 }
156 }
157 return $hasOverlay ? $newPath : $path;
158 }
159
160 /**
161 * Method to register the stream wrapper
162 *
163 * If the stream is already registered the method returns silently. If there
164 * is already another stream wrapper registered for the scheme used by
165 * file:// scheme a \BadFunctionCallException will be thrown.
166 *
167 * @throws \BadFunctionCallException
168 * @return void
169 */
170 protected static function register()
171 {
172 if (self::$registered) {
173 return;
174 }
175
176 if (@stream_wrapper_unregister('file') === false) {
177 throw new \BadFunctionCallException('Cannot unregister file:// stream wrapper.', 1396340331);
178 }
179 if (@stream_wrapper_register('file', __CLASS__) === false) {
180 throw new \BadFunctionCallException('A handler has already been registered for the file:// scheme.', 1396340332);
181 }
182
183 self::$registered = true;
184 }
185
186 /**
187 * Restore the file handler
188 *
189 * @return void
190 */
191 protected static function restore()
192 {
193 if (!self::$registered) {
194 return;
195 }
196 if (@stream_wrapper_restore('file') === false) {
197 throw new \BadFunctionCallException('Cannot restore the default file:// stream handler.', 1396340333);
198 }
199 self::$registered = false;
200 }
201
202 /*
203 * The following list of functions is implemented as of
204 * @see http://www.php.net/manual/en/streamwrapper.dir-closedir.php
205 */
206
207 /**
208 * Close the directory
209 *
210 * @return bool
211 */
212 public function dir_closedir()
213 {
214 if ($this->dirHandle === null) {
215 return false;
216 } else {
217 self::restore();
218 closedir($this->dirHandle);
219 self::register();
220 $this->dirHandle = null;
221 return true;
222 }
223 }
224
225 /**
226 * Opens a directory for reading
227 *
228 * @param string $path
229 * @param int $options
230 * @return bool
231 */
232 public function dir_opendir($path, $options = 0)
233 {
234 if ($this->dirHandle !== null) {
235 return false;
236 }
237 self::restore();
238 $path = self::overlayPath($path);
239 $this->dirHandle = opendir($path);
240 self::register();
241 return $this->dirHandle !== false;
242 }
243
244 /**
245 * Read a single filename of a directory
246 *
247 * @return string|bool
248 */
249 public function dir_readdir()
250 {
251 if ($this->dirHandle === null) {
252 return false;
253 }
254 self::restore();
255 $success = readdir($this->dirHandle);
256 self::register();
257 return $success;
258 }
259
260 /**
261 * Reset directory name pointer
262 *
263 * @return bool
264 */
265 public function dir_rewinddir()
266 {
267 if ($this->dirHandle === null) {
268 return false;
269 }
270 self::restore();
271 rewinddir($this->dirHandle);
272 self::register();
273 return true;
274 }
275
276 /**
277 * Create a directory
278 *
279 * @param string $path
280 * @param int $mode
281 * @param int $options
282 * @return bool
283 */
284 public function mkdir($path, $mode, $options = 0)
285 {
286 self::restore();
287 $path = self::overlayPath($path);
288 $success = mkdir($path, $mode, (bool)($options & STREAM_MKDIR_RECURSIVE));
289 self::register();
290 return $success;
291 }
292
293 /**
294 * Rename a file
295 *
296 * @param string $pathFrom
297 * @param string $pathTo
298 * @return bool
299 */
300 public function rename($pathFrom, $pathTo)
301 {
302 self::restore();
303 $pathFrom = self::overlayPath($pathFrom);
304 $pathTo = self::overlayPath($pathTo);
305 $success = rename($pathFrom, $pathTo);
306 self::register();
307 return $success;
308 }
309
310 /**
311 * Remove a directory
312 *
313 * @param string $path
314 * @return bool
315 */
316 public function rmdir($path)
317 {
318 self::restore();
319 $path = self::overlayPath($path);
320 $success = rmdir($path);
321 self::register();
322 return $success;
323 }
324
325 /**
326 * Retrieve the underlying resource
327 *
328 * @param int $castAs Can be STREAM_CAST_FOR_SELECT when stream_select()
329 * is calling stream_cast() or STREAM_CAST_AS_STREAM when stream_cast()
330 * is called for other uses.
331 * @return resource|bool
332 */
333 public function stream_cast($castAs)
334 {
335 if ($this->fileHandle !== null && $castAs & STREAM_CAST_AS_STREAM) {
336 return $this->fileHandle;
337 } else {
338 return false;
339 }
340 }
341
342 /**
343 * Close a file
344 *
345 */
346 public function stream_close()
347 {
348 self::restore();
349 if ($this->fileHandle !== null) {
350 fclose($this->fileHandle);
351 $this->fileHandle = null;
352 }
353 self::register();
354 }
355
356 /**
357 * Test for end-of-file on a file pointer
358 *
359 * @return bool
360 */
361 public function stream_eof()
362 {
363 if ($this->fileHandle === null) {
364 return false;
365 }
366 self::restore();
367 $success = feof($this->fileHandle);
368 self::register();
369 return $success;
370 }
371
372 /**
373 * Flush the output
374 *
375 * @return bool
376 */
377 public function stream_flush()
378 {
379 if ($this->fileHandle === null) {
380 return false;
381 }
382 self::restore();
383 $success = fflush($this->fileHandle);
384 self::register();
385 return $success;
386 }
387
388 /**
389 * Advisory file locking
390 *
391 * @param int $operation
392 * @return bool
393 */
394 public function stream_lock($operation)
395 {
396 if ($this->fileHandle === null) {
397 return false;
398 }
399 self::restore();
400 $success = flock($this->fileHandle, $operation);
401 self::register();
402 return $success;
403 }
404
405 /**
406 * Change file options
407 *
408 * @param string $path
409 * @param int $options
410 * @param mixed $value
411 * @return bool
412 */
413 public function stream_metadata($path, $options, $value)
414 {
415 self::restore();
416 $path = self::overlayPath($path);
417 switch ($options) {
418 case STREAM_META_TOUCH:
419 $success = touch($path, $value[0], $value[1]);
420 break;
421 case STREAM_META_OWNER_NAME:
422 // Fall through
423 case STREAM_META_OWNER:
424 $success = chown($path, $value);
425 break;
426 case STREAM_META_GROUP_NAME:
427 // Fall through
428 case STREAM_META_GROUP:
429 $success = chgrp($path, $value);
430 break;
431 case STREAM_META_ACCESS:
432 $success = chmod($path, $value);
433 break;
434 default:
435 $success = false;
436 }
437 self::register();
438 return $success;
439 }
440
441 /**
442 * Open a file
443 *
444 * @param string $path
445 * @param string $mode
446 * @param int $options
447 * @param string &$opened_path
448 * @return bool
449 */
450 public function stream_open($path, $mode, $options, &$opened_path)
451 {
452 if ($this->fileHandle !== null) {
453 return false;
454 }
455 self::restore();
456 $path = self::overlayPath($path);
457 $this->fileHandle = fopen($path, $mode, (bool)($options & STREAM_USE_PATH));
458 self::register();
459 return $this->fileHandle !== false;
460 }
461
462 /**
463 * Read from a file
464 *
465 * @param int $length
466 * @return string
467 */
468 public function stream_read($length)
469 {
470 if ($this->fileHandle === null) {
471 return false;
472 }
473 self::restore();
474 $content = fread($this->fileHandle, $length);
475 self::register();
476 return $content;
477 }
478
479 /**
480 * Seek to specific location in a stream
481 *
482 * @param int $offset
483 * @param int $whence = SEEK_SET
484 * @return bool
485 */
486 public function stream_seek($offset, $whence = SEEK_SET)
487 {
488 if ($this->fileHandle === null) {
489 return false;
490 }
491 self::restore();
492 $success = fseek($this->fileHandle, $offset, $whence);
493 self::register();
494 return $success;
495 }
496
497 /**
498 * Change stream options (not implemented)
499 *
500 * @param int $option
501 * @param int $arg1
502 * @param int $arg2
503 * @return bool
504 */
505 public function stream_set_option($option, $arg1, $arg2)
506 {
507 return false;
508 }
509
510 /**
511 * Retrieve information about a file resource
512 *
513 * @return array
514 */
515 public function stream_stat()
516 {
517 if ($this->fileHandle === null) {
518 return false;
519 }
520 self::restore();
521 $stats = fstat($this->fileHandle);
522 self::register();
523 return $stats;
524 }
525
526 /**
527 * Retrieve the current position of a stream
528 *
529 * @return int
530 */
531 public function stream_tell()
532 {
533 if ($this->fileHandle === null) {
534 return false;
535 }
536 self::restore();
537 $position = ftell($this->fileHandle);
538 self::register();
539 return $position;
540 }
541
542 /**
543 * Truncates a file to the given size
544 *
545 * @param int $size Truncate to this size
546 * @return bool
547 */
548 public function stream_truncate($size)
549 {
550 if ($this->fileHandle === null) {
551 return false;
552 }
553 self::restore();
554 $success = ftruncate($this->fileHandle, $size);
555 self::register();
556 return $success;
557 }
558
559 /**
560 * Write to stream
561 *
562 * @param string $data
563 * @return int
564 */
565 public function stream_write($data)
566 {
567 if ($this->fileHandle === null) {
568 return false;
569 }
570 self::restore();
571 $length = fwrite($this->fileHandle, $data);
572 self::register();
573 return $length;
574 }
575
576 /**
577 * Unlink a file
578 *
579 * @param string $path
580 * @return bool
581 */
582 public function unlink($path)
583 {
584 self::restore();
585 $path = self::overlayPath($path);
586 $success = unlink($path);
587 self::register();
588 return $success;
589 }
590
591 /**
592 * Retrieve information about a file
593 *
594 * @param string $path
595 * @param int $flags
596 * @return array
597 */
598 public function url_stat($path, $flags)
599 {
600 self::restore();
601 $path = self::overlayPath($path);
602 if ($flags & STREAM_URL_STAT_LINK) {
603 $information = @lstat($path);
604 } else {
605 $information = @stat($path);
606 }
607 self::register();
608 return $information;
609 }
610 }