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: foreach ($masks as $mask) {
165: $mask = rtrim(strtr($mask, '\\', '/'), '/');
166: $prefix = '';
167: if ($mask === '') {
168: continue;
169:
170: } elseif ($mask === '*') {
171: return NULL;
172:
173: } elseif ($mask[0] === '/') {
174: $mask = ltrim($mask, '/');
175: $prefix = '(?<=^/)';
176: }
177: $pattern[] = $prefix . strtr(preg_quote($mask, '#'),
178: array('\*\*' => '.*', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-'));
179: }
180: return $pattern ? '#/(' . implode('|', $pattern) . ')\z#i' : NULL;
181: }
182:
183:
184:
185:
186:
187: 188: 189: 190:
191: public function getIterator()
192: {
193: if (!$this->paths) {
194: throw new InvalidStateException('Call in() or from() to specify directory to search.');
195:
196: } elseif (count($this->paths) === 1) {
197: return $this->buildIterator($this->paths[0]);
198:
199: } else {
200: $iterator = new AppendIterator();
201: $iterator->append($workaround = new ArrayIterator(array('workaround PHP bugs #49104, #63077')));
202: foreach ($this->paths as $path) {
203: $iterator->append($this->buildIterator($path));
204: }
205: unset($workaround[0]);
206: return $iterator;
207: }
208: }
209:
210:
211: 212: 213: 214: 215:
216: private function buildIterator($path)
217: {
218: if (PHP_VERSION_ID < 50301) {
219: $iterator = new RecursiveDirectoryIteratorFixed($path);
220: } else {
221: $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
222: }
223:
224: if ($this->exclude) {
225: $filters = $this->exclude;
226: $iterator = new NRecursiveCallbackFilterIterator($iterator, create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('filters'=>$filters)).'-1], EXTR_REFS);
227: if (!$file->isDot() && !$file->isFile()) {
228: foreach ($filters as $filter) {
229: if (!call_user_func($filter, $file)) {
230: return FALSE;
231: }
232: }
233: }
234: return TRUE;
235: '));
236: }
237:
238: if ($this->maxDepth !== 0) {
239: $iterator = new RecursiveIteratorIterator($iterator, $this->order);
240: $iterator->setMaxDepth($this->maxDepth);
241: }
242:
243: if ($this->groups) {
244: $groups = $this->groups;
245: $iterator = new NCallbackFilterIterator($iterator, create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('groups'=>$groups)).'-1], EXTR_REFS);
246: foreach ($groups as $filters) {
247: foreach ($filters as $filter) {
248: if (!call_user_func($filter, $file)) {
249: continue 2;
250: }
251: }
252: return TRUE;
253: }
254: return FALSE;
255: '));
256: }
257:
258: return $iterator;
259: }
260:
261:
262:
263:
264:
265: 266: 267: 268: 269: 270:
271: public function exclude($masks)
272: {
273: if (!is_array($masks)) {
274: $masks = func_get_args();
275: }
276: $pattern = self::buildPattern($masks);
277: if ($pattern) {
278: $this->filter(create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('pattern'=>$pattern)).'-1], EXTR_REFS);
279: return !preg_match($pattern, \'/\' . strtr($file->getSubPathName(), \'\\\\\', \'/\'));
280: '));
281: }
282: return $this;
283: }
284:
285:
286: 287: 288: 289: 290:
291: public function filter($callback)
292: {
293: $this->cursor[] = $callback;
294: return $this;
295: }
296:
297:
298: 299: 300: 301: 302:
303: public function limitDepth($depth)
304: {
305: $this->maxDepth = $depth;
306: return $this;
307: }
308:
309:
310: 311: 312: 313: 314: 315:
316: public function size($operator, $size = NULL)
317: {
318: if (func_num_args() === 1) {
319: if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?\z#i', $operator, $matches)) {
320: throw new InvalidArgumentException('Invalid size predicate format.');
321: }
322: list(, $operator, $size, $unit) = $matches;
323: static $units = array('' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9);
324: $size *= $units[strtolower($unit)];
325: $operator = $operator ? $operator : '=';
326: }
327: return $this->filter(create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('operator'=>$operator,'size'=> $size)).'-1], EXTR_REFS);
328: return Finder::compare($file->getSize(), $operator, $size);
329: '));
330: }
331:
332:
333: 334: 335: 336: 337: 338:
339: public function date($operator, $date = NULL)
340: {
341: if (func_num_args() === 1) {
342: if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)\z#i', $operator, $matches)) {
343: throw new InvalidArgumentException('Invalid date predicate format.');
344: }
345: list(, $operator, $date) = $matches;
346: $operator = $operator ? $operator : '=';
347: }
348: $date = DateTime53::from($date)->format('U');
349: return $this->filter(create_function('$file', 'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('operator'=>$operator,'date'=> $date)).'-1], EXTR_REFS);
350: return Finder::compare($file->getMTime(), $operator, $date);
351: '));
352: }
353:
354:
355: 356: 357: 358: 359: 360:
361: public static function compare($l, $operator, $r)
362: {
363: switch ($operator) {
364: case '>':
365: return $l > $r;
366: case '>=':
367: return $l >= $r;
368: case '<':
369: return $l < $r;
370: case '<=':
371: return $l <= $r;
372: case '=':
373: case '==':
374: return $l == $r;
375: case '!':
376: case '!=':
377: case '<>':
378: return $l != $r;
379: default:
380: throw new InvalidArgumentException("Unknown operator $operator.");
381: }
382: }
383:
384: }
385:
386:
387: if (PHP_VERSION_ID < 50301) {
388:
389: class RecursiveDirectoryIteratorFixed extends RecursiveDirectoryIterator
390: {
391: function hasChildren()
392: {
393: return parent::hasChildren(TRUE);
394: }
395: }
396: }
397: