1 : <?php
2 : /**
3 : * Stream wrapper to mock file system requests.
4 : *
5 : * @package bovigo_vfs
6 : * @version $Id: vfsStreamWrapper.php 214 2010-10-07 20:57:57Z google@frankkleine.de $
7 : */
8 : /**
9 : * @ignore
10 : */
11 : require_once dirname(__FILE__) . '/vfsStreamDirectory.php';
12 : require_once dirname(__FILE__) . '/vfsStreamFile.php';
13 : require_once dirname(__FILE__) . '/vfsStreamException.php';
14 : /**
15 : * Stream wrapper to mock file system requests.
16 : *
17 : * @package bovigo_vfs
18 : */
19 : class vfsStreamWrapper
20 : {
21 : /**
22 : * open file for reading
23 : */
24 : const READ = 'r';
25 : /**
26 : * truncate file
27 : */
28 : const TRUNCATE = 'w';
29 : /**
30 : * set file pointer to end, append new data
31 : */
32 : const APPEND = 'a';
33 : /**
34 : * set file pointer to start, overwrite existing data
35 : */
36 : const WRITE = 'x';
37 : /**
38 : * file mode: read only
39 : */
40 : const READONLY = 0;
41 : /**
42 : * file mode: write only
43 : */
44 : const WRITEONLY = 1;
45 : /**
46 : * file mode: read and write
47 : */
48 : const ALL = 2;
49 : /**
50 : * switch whether class has already been registered as stream wrapper or not
51 : *
52 : * @var bool
53 : */
54 : protected static $registered = false;
55 : /**
56 : * root content
57 : *
58 : * @var vfsStreamContent
59 : */
60 : protected static $root;
61 : /**
62 : * file mode: read only, write only, all
63 : *
64 : * @var int
65 : */
66 : protected $mode;
67 : /**
68 : * shortcut to file container
69 : *
70 : * @var vfsStreamFile
71 : */
72 : protected $content;
73 : /**
74 : * shortcut to directory container
75 : *
76 : * @var vfsStreamDirectory
77 : */
78 : protected $dir;
79 : /**
80 : * shortcut to directory container iterator
81 : *
82 : * @var vfsStreamDirectory
83 : */
84 : protected $dirIterator;
85 :
86 : /**
87 : * method to register the stream wrapper
88 : *
89 : * Please be aware that a call to this method will reset the root element
90 : * to null.
91 : * If the stream is already registered the method returns silently. If there
92 : * is already another stream wrapper registered for the scheme used by
93 : * vfsStream a vfsStreamException will be thrown.
94 : *
95 : * @throws vfsStreamException
96 : */
97 : public static function register()
98 : {
99 98 : self::$root = null;
100 98 : if (true === self::$registered) {
101 95 : return;
102 : }
103 :
104 3 : if (@stream_wrapper_register(vfsStream::SCHEME, __CLASS__) === false) {
105 1 : throw new vfsStreamException('A handler has already been registered for the ' . vfsStream::SCHEME . ' protocol.');
106 : }
107 :
108 2 : self::$registered = true;
109 2 : }
110 :
111 : /**
112 : * sets the root content
113 : *
114 : * @param vfsStreamContent $root
115 : */
116 : public static function setRoot(vfsStreamContent $root)
117 : {
118 93 : self::$root = $root;
119 93 : }
120 :
121 : /**
122 : * returns the root content
123 : *
124 : * @return vfsStreamContent
125 : */
126 : public static function getRoot()
127 : {
128 19 : return self::$root;
129 : }
130 :
131 : /**
132 : * returns content for given path
133 : *
134 : * @param string $path
135 : * @return vfsStreamContent
136 : */
137 : protected function getContent($path)
138 : {
139 84 : if (null === self::$root) {
140 7 : return null;
141 : }
142 :
143 79 : if (self::$root->getName() === $path) {
144 58 : return self::$root;
145 : }
146 :
147 61 : if (self::$root->hasChild($path) === true) {
148 49 : return self::$root->getChild($path);
149 : }
150 :
151 32 : return null;
152 : }
153 :
154 : /**
155 : * returns content for given path but only when it is of given type
156 : *
157 : * @param string $path
158 : * @param int $type
159 : * @return vfsStreamContent
160 : */
161 : protected function getContentOfType($path, $type)
162 : {
163 39 : $content = $this->getContent($path);
164 39 : if (null !== $content && $content->getType() === $type) {
165 34 : return $content;
166 : }
167 :
168 18 : return null;
169 : }
170 :
171 : /**
172 : * splits path into its dirname and the basename
173 : *
174 : * @param string $path
175 : * @return array
176 : */
177 : protected function splitPath($path)
178 : {
179 40 : $lastSlashPos = strrpos($path, '/');
180 40 : if (false === $lastSlashPos) {
181 16 : return array('dirname' => '', 'basename' => $path);
182 : }
183 :
184 38 : return array('dirname' => substr($path, 0, $lastSlashPos),
185 38 : 'basename' => substr($path, $lastSlashPos + 1)
186 38 : );
187 : }
188 :
189 : /**
190 : * helper method to resolve a path from /foo/bar/. to /foo/bar
191 : *
192 : * @param string $path
193 : * @return string
194 : */
195 : protected function resolvePath($path)
196 : {
197 70 : $newPath = array();
198 70 : foreach (explode('/', $path) as $pathPart) {
199 70 : if ('.' !== $pathPart) {
200 70 : if ('..' !== $pathPart) {
201 70 : $newPath[] = $pathPart;
202 70 : } else {
203 1 : array_pop($newPath);
204 : }
205 70 : }
206 70 : }
207 :
208 70 : return implode('/', $newPath);
209 : }
210 :
211 : /**
212 : * open the stream
213 : *
214 : * @param string $path the path to open
215 : * @param string $mode mode for opening
216 : * @param string $options options for opening
217 : * @param string $opened_path full path that was actually opened
218 : * @return bool
219 : */
220 : public function stream_open($path, $mode, $options, $opened_path)
221 : {
222 25 : $extended = ((strstr($mode, '+') !== false) ? (true) : (false));
223 25 : $mode = str_replace(array('b', '+'), '', $mode);
224 25 : if (in_array($mode, array('r', 'w', 'a', 'x')) === false) {
225 1 : if (!($options & STREAM_REPORT_ERRORS)) {
226 1 : trigger_error('Illegal mode ' . $mode . ', use r, w, a or x, flavoured with b and/or +', E_USER_WARNING);
227 1 : }
228 :
229 1 : return false;
230 : }
231 :
232 24 : $this->mode = $this->calculateMode($mode, $extended);
233 24 : $path = $this->resolvePath(vfsStream::path($path));
234 24 : $this->content = $this->getContentOfType($path, vfsStreamContent::TYPE_FILE);
235 24 : if (null !== $this->content) {
236 17 : if (self::WRITE === $mode) {
237 1 : if (!($options & STREAM_REPORT_ERRORS)) {
238 1 : trigger_error('File ' . $path . ' already exists, can not open with mode x', E_USER_WARNING);
239 1 : }
240 :
241 1 : return false;
242 : }
243 :
244 17 : $this->content->seek(0, SEEK_SET);
245 17 : if (self::TRUNCATE === $mode && $this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === true) {
246 4 : $this->content->setContent(''); // truncate
247 17 : } elseif (self::APPEND === $mode) {
248 2 : $this->content->seek(0, SEEK_END);
249 2 : }
250 :
251 17 : return true;
252 : }
253 :
254 14 : $names = $this->splitPath($path);
255 14 : if (empty($names['dirname']) === true) {
256 3 : if (!($options & STREAM_REPORT_ERRORS)) {
257 3 : trigger_error('File ' . $names['basename'] . ' does not exist', E_USER_WARNING);
258 2 : }
259 :
260 2 : return false;
261 : }
262 :
263 13 : $dir = $this->getContentOfType($names['dirname'], vfsStreamContent::TYPE_DIR);
264 13 : if (null === $dir) {
265 0 : if (!($options & STREAM_REPORT_ERRORS)) {
266 0 : trigger_error('Directory ' . $names['dirname'] . ' does not exist', E_USER_WARNING);
267 0 : }
268 :
269 0 : return false;
270 13 : } elseif ($dir->hasChild($names['basename']) === true) {
271 2 : if (!($options & STREAM_REPORT_ERRORS)) {
272 2 : trigger_error('Directory ' . $names['dirname'] . ' already contains a director named ' . $names['basename'], E_USER_WARNING);
273 2 : }
274 :
275 2 : return false;
276 : }
277 :
278 11 : if (self::READ === $mode) {
279 2 : if (!($options & STREAM_REPORT_ERRORS)) {
280 2 : trigger_error('Can not open non-existing file ' . $path . ' for reading', E_USER_WARNING);
281 2 : }
282 :
283 2 : return false;
284 : }
285 :
286 9 : if ($dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
287 1 : if (!($options & STREAM_REPORT_ERRORS)) {
288 1 : trigger_error('Can not create new file in non-writable path ' . $names['dirname'], E_USER_WARNING);
289 1 : }
290 :
291 1 : return false;
292 : }
293 :
294 8 : $this->content = vfsStream::newFile($names['basename'])->at($dir);
295 8 : return true;
296 : }
297 :
298 : /**
299 : * calculates the file mode
300 : *
301 : * @param string $mode opening mode: r, w, a or x
302 : * @param bool $extended true if + was set with opening mode
303 : * @return int
304 : */
305 : protected function calculateMode($mode, $extended)
306 : {
307 24 : if (true === $extended) {
308 1 : return self::ALL;
309 : }
310 :
311 23 : if (self::READ === $mode) {
312 18 : return self::READONLY;
313 : }
314 :
315 14 : return self::WRITEONLY;
316 : }
317 :
318 : /**
319 : * closes the stream
320 : */
321 : public function stream_close()
322 : {
323 : // nothing to do
324 20 : }
325 :
326 : /**
327 : * read the stream up to $count bytes
328 : *
329 : * @param int $count amount of bytes to read
330 : * @return string
331 : */
332 : public function stream_read($count)
333 : {
334 15 : if (self::WRITEONLY === $this->mode) {
335 3 : return '';
336 : }
337 :
338 15 : if ($this->content->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
339 1 : return '';
340 : }
341 :
342 14 : return $this->content->read($count);
343 : }
344 :
345 : /**
346 : * writes data into the stream
347 : *
348 : * @param string $data
349 : * @return int amount of bytes written
350 : */
351 : public function stream_write($data)
352 : {
353 14 : if (self::READONLY === $this->mode) {
354 1 : return 0;
355 : }
356 :
357 13 : if ($this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
358 1 : return 0;
359 : }
360 :
361 12 : return $this->content->write($data);
362 : }
363 :
364 : /**
365 : * checks whether stream is at end of file
366 : *
367 : * @return bool
368 : */
369 : public function stream_eof()
370 : {
371 15 : return $this->content->eof();
372 : }
373 :
374 : /**
375 : * returns the current position of the stream
376 : *
377 : * @return int
378 : */
379 : public function stream_tell()
380 : {
381 5 : return $this->content->getBytesRead();
382 : }
383 :
384 : /**
385 : * seeks to the given offset
386 : *
387 : * @param int $offset
388 : * @param int $whence
389 : * @return bool
390 : */
391 : public function stream_seek($offset, $whence)
392 : {
393 5 : return $this->content->seek($offset, $whence);
394 : }
395 :
396 : /**
397 : * flushes unstored data into storage
398 : *
399 : * @return bool
400 : */
401 : public function stream_flush()
402 : {
403 20 : return true;
404 : }
405 :
406 : /**
407 : * returns status of stream
408 : *
409 : * @return array
410 : */
411 : public function stream_stat()
412 : {
413 15 : $fileStat = array('dev' => 0,
414 15 : 'ino' => 0,
415 15 : 'mode' => $this->content->getType() | $this->content->getPermissions(),
416 15 : 'nlink' => 0,
417 15 : 'uid' => $this->content->getUser(),
418 15 : 'gid' => $this->content->getGroup(),
419 15 : 'rdev' => 0,
420 15 : 'size' => $this->content->size(),
421 15 : 'atime' => $this->content->filemtime(),
422 15 : 'mtime' => $this->content->filemtime(),
423 15 : 'ctime' => $this->content->filemtime(),
424 15 : 'blksize' => -1,
425 0 : 'blocks' => -1
426 15 : );
427 15 : return array_merge(array_values($fileStat), $fileStat);
428 : }
429 :
430 : /**
431 : * remove the data under the given path
432 : *
433 : * @param string $path
434 : * @return bool
435 : */
436 : public function unlink($path)
437 : {
438 8 : $realPath = $this->resolvePath(vfsStream::path($path));
439 8 : $content = $this->getContent($realPath);
440 8 : if (null === $content || $content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
441 3 : return false;
442 : }
443 :
444 6 : if (self::$root->getName() === $realPath) {
445 : // delete root? very brave. :)
446 1 : self::$root = null;
447 1 : clearstatcache();
448 1 : return true;
449 : }
450 :
451 6 : $names = $this->splitPath($realPath);
452 6 : $content = $this->getContent($names['dirname']);
453 6 : if ($content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
454 1 : return false;
455 : }
456 :
457 5 : clearstatcache();
458 5 : return $content->removeChild($names['basename']);
459 : }
460 :
461 : /**
462 : * rename from one path to another
463 : *
464 : * @param string $path_from
465 : * @param string $path_to
466 : * @return bool
467 : * @author Benoit Aubuchon
468 : */
469 : public function rename($path_from, $path_to)
470 : {
471 8 : $srcRealPath = $this->resolvePath(vfsStream::path($path_from));
472 8 : $dstRealPath = vfsStream::path($path_to);
473 8 : $srcContent = $this->getContent($srcRealPath);
474 8 : if (null == $srcContent) {
475 2 : trigger_error(' No such file or directory', E_USER_WARNING);
476 1 : return false;
477 : }
478 :
479 6 : $dstContent = clone $srcContent;
480 6 : $dstNames = $this->splitPath($dstRealPath);
481 : // Renaming the filename
482 6 : $dstContent->rename($dstNames['basename']);
483 : // Copying to the destination
484 6 : $dstParentContent = $this->getContent($dstNames['dirname']);
485 6 : if (null == $dstParentContent) {
486 1 : trigger_error('No such file or directory', E_USER_WARNING);
487 0 : return false;
488 : }
489 :
490 5 : if ($dstParentContent->getType() !== vfsStreamContent::TYPE_DIR) {
491 1 : trigger_error('Target is not a directory', E_USER_WARNING);
492 0 : return false;
493 : }
494 :
495 4 : $dstParentContent->addChild($dstContent);
496 : // Removing the source
497 4 : return $this->unlink($path_from);
498 : }
499 :
500 : /**
501 : * creates a new directory
502 : *
503 : * @param string $path
504 : * @param int $mode
505 : * @param int $options
506 : * @return bool
507 : */
508 : public function mkdir($path, $mode, $options)
509 : {
510 18 : $umask = vfsStream::umask();
511 18 : if (0 < $umask) {
512 4 : $permissions = $mode & ~$umask;
513 4 : } else {
514 14 : $permissions = $mode;
515 : }
516 :
517 18 : $path = vfsStream::path($path);
518 18 : if (null === self::$root) {
519 3 : self::$root = vfsStream::newDirectory($path, $permissions);
520 3 : return true;
521 : }
522 :
523 15 : $maxDepth = count(explode('/', $path));
524 15 : $names = $this->splitPath($path);
525 15 : $newDirs = $names['basename'];
526 15 : $dir = null;
527 15 : $i = 0;
528 15 : while ($dir === null && $i < $maxDepth) {
529 15 : $dir = $this->getContent($names['dirname']);
530 15 : $names = $this->splitPath($names['dirname']);
531 15 : $newDirs = $names['basename'] . '/' . $newDirs;
532 15 : $i++;
533 15 : }
534 :
535 : if (null === $dir
536 15 : || $dir->getType() !== vfsStreamContent::TYPE_DIR
537 15 : || $dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
538 5 : return false;
539 : }
540 :
541 10 : $newDirs = str_replace($dir->getName() . '/', '', $newDirs);
542 10 : $recursive = ((STREAM_MKDIR_RECURSIVE & $options) !== 0) ? (true) : (false);
543 10 : if (strpos($newDirs, '/') !== false && false === $recursive) {
544 1 : return false;
545 : }
546 :
547 10 : vfsStream::newDirectory($newDirs, $permissions)->at($dir);
548 10 : return true;
549 : }
550 :
551 : /**
552 : * removes a directory
553 : *
554 : * @param string $path
555 : * @param int $options
556 : * @return bool
557 : * @todo consider $options with STREAM_MKDIR_RECURSIVE
558 : */
559 : public function rmdir($path, $options)
560 : {
561 8 : $path = $this->resolvePath(vfsStream::path($path));
562 8 : $child = $this->getContentOfType($path, vfsStreamContent::TYPE_DIR);
563 8 : if (null === $child) {
564 3 : return false;
565 : }
566 :
567 : // can only remove empty directories
568 5 : if (count($child->getChildren()) > 0) {
569 1 : return false;
570 : }
571 :
572 4 : if (self::$root->getName() === $path) {
573 : // delete root? very brave. :)
574 1 : self::$root = null;
575 1 : clearstatcache();
576 1 : return true;
577 : }
578 :
579 3 : $names = $this->splitPath($path);
580 3 : $dir = $this->getContentOfType($names['dirname'], vfsStreamContent::TYPE_DIR);
581 3 : if ($dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
582 1 : return false;
583 : }
584 :
585 2 : clearstatcache();
586 2 : return $dir->removeChild($child->getName());
587 : }
588 :
589 : /**
590 : * opens a directory
591 : *
592 : * @param string $path
593 : * @param int $options
594 : * @return bool
595 : */
596 : public function dir_opendir($path, $options)
597 : {
598 7 : $path = $this->resolvePath(vfsStream::path($path));
599 7 : $this->dir = $this->getContentOfType($path, vfsStreamContent::TYPE_DIR);
600 7 : if (null === $this->dir || $this->dir->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
601 2 : return false;
602 : }
603 :
604 5 : $this->dirIterator = $this->dir->getIterator();
605 5 : return true;
606 : }
607 :
608 : /**
609 : * reads directory contents
610 : *
611 : * @return string
612 : */
613 : public function dir_readdir()
614 : {
615 5 : $dir = $this->dirIterator->current();
616 5 : if (null === $dir) {
617 4 : return false;
618 : }
619 :
620 5 : $this->dirIterator->next();
621 5 : return $dir->getName();
622 : }
623 :
624 : /**
625 : * reset directory iteration
626 : *
627 : * @return bool
628 : */
629 : public function dir_rewinddir()
630 : {
631 3 : return $this->dirIterator->rewind();
632 : }
633 :
634 : /**
635 : * closes directory
636 : *
637 : * @return bool
638 : */
639 : public function dir_closedir()
640 : {
641 5 : $this->dirIterator = null;
642 5 : return true;
643 : }
644 :
645 : /**
646 : * returns status of url
647 : *
648 : * @param string $path path of url to return status for
649 : * @param ? $flags flags set by the stream API
650 : * @return array
651 : */
652 : public function url_stat($path, $flags)
653 : {
654 31 : $path = $this->resolvePath(vfsStream::path($path));
655 31 : $content = $this->getContent($path);
656 31 : if (null === $content) {
657 13 : if (!($flags & STREAM_URL_STAT_QUIET)) {
658 0 : trigger_error(' No such file or directory', E_USER_WARNING);
659 0 : }
660 13 : return false;
661 :
662 : }
663 :
664 28 : $fileStat = array('dev' => 0,
665 28 : 'ino' => 0,
666 28 : 'mode' => $content->getType() | $content->getPermissions(),
667 28 : 'nlink' => 0,
668 28 : 'uid' => $content->getUser(),
669 28 : 'gid' => $content->getGroup(),
670 28 : 'rdev' => 0,
671 28 : 'size' => $content->size(),
672 28 : 'atime' => $content->filemtime(),
673 28 : 'mtime' => $content->filemtime(),
674 28 : 'ctime' => $content->filemtime(),
675 28 : 'blksize' => -1,
676 0 : 'blocks' => -1
677 28 : );
678 28 : return array_merge(array_values($fileStat), $fileStat);
679 : }
680 : }
|