1: <?php
2:
3: 4: 5: 6: 7:
8:
9:
10:
11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
24: class Finder extends Object implements IteratorAggregate
25: {
26:
27: private $paths = array();
28:
29:
30: private $groups;
31:
32:
33: private $exclude = array();
34:
35:
36: private $order = RecursiveIteratorIterator::SELF_FIRST;
37:
38:
39: private $maxDepth = -1;
40:
41:
42: private $cursor;
43:
44:
45: 46: 47: 48: 49:
50: public static function find($mask)
51: {
52: if (!is_array($mask)) {
53: $mask = func_get_args();
54: }
55: $finder = new self;
56: return $finder->select(array(), 'isDir')->select($mask, 'isFile');
57: }
58:
59:
60: 61: 62: 63: 64:
65: public static function findFiles($mask)
66: {
67: if (!is_array($mask)) {
68: $mask = func_get_args();
69: }
70: $finder = new self;
71: return $finder->select($mask, 'isFile');
72: }
73:
74:
75: 76: 77: 78: 79:
80: public static function findDirectories($mask)
81: {
82: if (!is_array($mask)) {
83: $mask = func_get_args();
84: }
85: $finder = new self;
86: return $finder->select($mask, 'isDir');
87: }
88:
89:
90: 91: 92: 93: 94: 95:
96: private function select($masks, $type)
97: {
98: $this->cursor = & $this->groups[];
99: $pattern = self::buildPattern($masks);
100: if ($type || $pattern) {
101: $this->filter(create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('type'=>$type,'pattern'=> $pattern)).'-1], EXTR_REFS);
102: return !$file->isDot()
103: && (!$type || $file->$type())
104: && (!$pattern || preg_match($pattern, \'/\' . strtr($file->getSubPathName(), \'\\\\\', \'/\')));
105: '));
106: }
107: return $this;
108: }
109:
110:
111: 112: 113: 114: 115:
116: public function in($path)
117: {
118: if (!is_array($path)) {
119: $path = func_get_args();
120: }
121: $this->maxDepth = 0;
122: return $this->from($path);
123: }
124:
125:
126: 127: 128: 129: 130:
131: public function from($path)
132: {
133: if ($this->paths) {
134: throw new InvalidStateException('Directory to search has already been specified.');
135: }
136: if (!is_array($path)) {
137: $path = func_get_args();
138: }
139: $this->paths = $path;
140: $this->cursor = & $this->exclude;
141: return $this;
142: }
143:
144:
145: 146: 147: 148:
149: public function childFirst()
150: {
151: $this->order = RecursiveIteratorIterator::CHILD_FIRST;
152: return $this;
153: }
154:
155:
156: 157: 158: 159: 160:
161: private static function buildPattern($masks)
162: {
163: $pattern = array();
164:
165: foreach ($masks as $mask) {
166: $mask = rtrim(strtr($mask, '\\', '/'), '/');
167: $prefix = '';
168: if ($mask === '') {
169: continue;
170:
171: } elseif ($mask === '*') {
172: return NULL;
173:
174: } elseif ($mask[0] === '/') {
175: $mask = ltrim($mask, '/');
176: $prefix = '(?<=^/)';
177: }
178: $pattern[] = $prefix . strtr(preg_quote($mask, '#'),
179: array('\*\*' => '.*', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-'));
180: }
181: return $pattern ? '#/(' . implode('|', $pattern) . ')\z#i' : NULL;
182: }
183:
184:
185:
186:
187:
188: 189: 190: 191:
192: public function getIterator()
193: {
194: if (!$this->paths) {
195: throw new InvalidStateException('Call in() or from() to specify directory to search.');
196:
197: } elseif (count($this->paths) === 1) {
198: return $this->buildIterator($this->paths[0]);
199:
200: } else {
201: $iterator = new AppendIterator();
202: $iterator->append($workaround = new ArrayIterator(array('workaround PHP bugs #49104, #63077')));
203: foreach ($this->paths as $path) {
204: $iterator->append($this->buildIterator($path));
205: }
206: unset($workaround[0]);
207: return $iterator;
208: }
209: }
210:
211:
212: 213: 214: 215: 216:
217: private function buildIterator($path)
218: {
219: if (PHP_VERSION_ID < 50301) {
220: $iterator = new RecursiveDirectoryIteratorFixed($path);
221: } else {
222: $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
223: }
224:
225: if ($this->exclude) {
226: $filters = $this->exclude;
227: $iterator = new NRecursiveCallbackFilterIterator($iterator, create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('filters'=>$filters)).'-1], EXTR_REFS);
228: if (!$file->isDot() && !$file->isFile()) {
229: foreach ($filters as $filter) {
230: if (!call_user_func($filter, $file)) {
231: return FALSE;
232: }
233: }
234: }
235: return TRUE;
236: '));
237: }
238:
239: if ($this->maxDepth !== 0) {
240: $iterator = new RecursiveIteratorIterator($iterator, $this->order);
241: $iterator->setMaxDepth($this->maxDepth);
242: }
243:
244: if ($this->groups) {
245: $groups = $this->groups;
246: $iterator = new NCallbackFilterIterator($iterator, create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('groups'=>$groups)).'-1], EXTR_REFS);
247: foreach ($groups as $filters) {
248: foreach ($filters as $filter) {
249: if (!call_user_func($filter, $file)) {
250: continue 2;
251: }
252: }
253: return TRUE;
254: }
255: return FALSE;
256: '));
257: }
258:
259: return $iterator;
260: }
261:
262:
263:
264:
265:
266: 267: 268: 269: 270: 271:
272: public function exclude($masks)
273: {
274: if (!is_array($masks)) {
275: $masks = func_get_args();
276: }
277: $pattern = self::buildPattern($masks);
278: if ($pattern) {
279: $this->filter(create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('pattern'=>$pattern)).'-1], EXTR_REFS);
280: return !preg_match($pattern, \'/\' . strtr($file->getSubPathName(), \'\\\\\', \'/\'));
281: '));
282: }
283: return $this;
284: }
285:
286:
287: 288: 289: 290: 291:
292: public function filter($callback)
293: {
294: $this->cursor[] = $callback;
295: return $this;
296: }
297:
298:
299: 300: 301: 302: 303:
304: public function limitDepth($depth)
305: {
306: $this->maxDepth = $depth;
307: return $this;
308: }
309:
310:
311: 312: 313: 314: 315: 316:
317: public function size($operator, $size = NULL)
318: {
319: if (func_num_args() === 1) {
320: if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?\z#i', $operator, $matches)) {
321: throw new InvalidArgumentException('Invalid size predicate format.');
322: }
323: list(, $operator, $size, $unit) = $matches;
324: static $units = array('' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9);
325: $size *= $units[strtolower($unit)];
326: $operator = $operator ? $operator : '=';
327: }
328: return $this->filter(create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('operator'=>$operator,'size'=> $size)).'-1], EXTR_REFS);
329: return Finder::compare($file->getSize(), $operator, $size);
330: '));
331: }
332:
333:
334: 335: 336: 337: 338: 339:
340: public function date($operator, $date = NULL)
341: {
342: if (func_num_args() === 1) {
343: if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)\z#i', $operator, $matches)) {
344: throw new InvalidArgumentException('Invalid date predicate format.');
345: }
346: list(, $operator, $date) = $matches;
347: $operator = $operator ? $operator : '=';
348: }
349: $date = DateTime53::from($date)->format('U');
350: return $this->filter(create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('operator'=>$operator,'date'=> $date)).'-1], EXTR_REFS);
351: return Finder::compare($file->getMTime(), $operator, $date);
352: '));
353: }
354:
355:
356: 357: 358: 359: 360: 361:
362: public static function compare($l, $operator, $r)
363: {
364: switch ($operator) {
365: case '>':
366: return $l > $r;
367: case '>=':
368: return $l >= $r;
369: case '<':
370: return $l < $r;
371: case '<=':
372: return $l <= $r;
373: case '=':
374: case '==':
375: return $l == $r;
376: case '!':
377: case '!=':
378: case '<>':
379: return $l != $r;
380: default:
381: throw new InvalidArgumentException("Unknown operator $operator.");
382: }
383: }
384:
385: }
386:
387:
388: if (PHP_VERSION_ID < 50301) {
389:
390: class RecursiveDirectoryIteratorFixed extends RecursiveDirectoryIterator
391: {
392: function hasChildren()
393: {
394: return parent::hasChildren(TRUE);
395: }
396: }
397: }
398: