Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
76.32% covered (warning)
76.32%
29 / 38
60.00% covered (warning)
60.00%
6 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
FSFileBackendList
78.38% covered (warning)
78.38%
29 / 37
60.00% covered (warning)
60.00%
6 / 10
19.92
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 initIterator
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 key
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 current
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 next
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
2.50
 rewind
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
2.50
 valid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getLastError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 filterViaNext
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRelPath
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 * @ingroup FileBackend
20 */
21
22namespace Wikimedia\FileBackend\FileIteration;
23
24use DirectoryIterator;
25use FilesystemIterator;
26use Iterator;
27use RecursiveDirectoryIterator;
28use RecursiveIteratorIterator;
29use UnexpectedValueException;
30use Wikimedia\FileBackend\FileBackendError;
31
32/**
33 * Wrapper around RecursiveDirectoryIterator/DirectoryIterator that
34 * catches exception or does any custom behavior that we may want.
35 * Do not use this class from places outside FSFileBackend.
36 *
37 * @ingroup FileBackend
38 */
39abstract class FSFileBackendList implements Iterator {
40    /** @var Iterator|null */
41    protected $iter;
42    /** @var string */
43    protected $lastError;
44
45    /** @var int */
46    protected $suffixStart;
47
48    /** @var int */
49    protected $pos = 0;
50
51    /** @var array */
52    protected $params = [];
53
54    /**
55     * @param string $dir File system directory
56     * @param array $params
57     */
58    public function __construct( $dir, array $params ) {
59        $path = realpath( $dir ); // normalize
60        if ( $path === false ) {
61            $path = $dir;
62        }
63        $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
64        $this->params = $params;
65
66        try {
67            $this->iter = $this->initIterator( $path );
68        } catch ( UnexpectedValueException $e ) {
69            $this->iter = null; // bad permissions? deleted?
70            $this->lastError = $e->getMessage();
71        }
72    }
73
74    /**
75     * Return an appropriate iterator object to wrap
76     *
77     * @param string $dir File system directory
78     * @return Iterator
79     * @throws UnexpectedValueException
80     */
81    protected function initIterator( $dir ) {
82        if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
83            # Get an iterator that will get direct sub-nodes
84            return new DirectoryIterator( $dir );
85        } else { // recursive
86            # Get an iterator that will return leaf nodes (non-directories)
87            # RecursiveDirectoryIterator extends FilesystemIterator.
88            # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
89            $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
90
91            return new RecursiveIteratorIterator(
92                new RecursiveDirectoryIterator( $dir, $flags ),
93                RecursiveIteratorIterator::CHILD_FIRST // include dirs
94            );
95        }
96    }
97
98    /**
99     * @see Iterator::key()
100     * @return int
101     */
102    public function key(): int {
103        return $this->pos;
104    }
105
106    /**
107     * @see Iterator::current()
108     * @return string|false
109     */
110    #[\ReturnTypeWillChange]
111    public function current() {
112        return $this->getRelPath( $this->iter->current()->getPathname() );
113    }
114
115    /**
116     * @see Iterator::next()
117     * @throws FileBackendError
118     */
119    public function next(): void {
120        try {
121            $this->iter->next();
122            $this->filterViaNext();
123        } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
124            $this->lastError = $e->getMessage();
125
126            throw new FileBackendError( "File iterator gave UnexpectedValueException." );
127        }
128        ++$this->pos;
129    }
130
131    /**
132     * @see Iterator::rewind()
133     * @throws FileBackendError
134     */
135    public function rewind(): void {
136        $this->pos = 0;
137        try {
138            $this->iter->rewind();
139            $this->filterViaNext();
140        } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
141            $this->lastError = $e->getMessage();
142
143            throw new FileBackendError( "File iterator gave UnexpectedValueException." );
144        }
145    }
146
147    /**
148     * @see Iterator::valid()
149     * @return bool
150     */
151    public function valid(): bool {
152        return $this->iter && $this->iter->valid();
153    }
154
155    /**
156     * @return string|null The last caught exception message
157     */
158    public function getLastError() {
159        return $this->lastError;
160    }
161
162    /**
163     * Filter out items by advancing to the next ones
164     */
165    protected function filterViaNext() {
166    }
167
168    /**
169     * Return only the relative path and normalize slashes to FileBackend-style.
170     * Uses the "real path" since the suffix is based upon that.
171     *
172     * @param string $dir
173     * @return string
174     */
175    protected function getRelPath( $dir ) {
176        $path = realpath( $dir );
177        if ( $path === false ) {
178            $path = $dir;
179        }
180
181        return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
182    }
183}
184
185/** @deprecated class alias since 1.43 */
186class_alias( FSFileBackendList::class, 'FSFileBackendList' );