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