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 Finder extends Object 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(NCFix::$vars['.NCFix::uses(array('type'=>$type,'pattern'=> $pattern)).'], 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) . ')$#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: foreach ($this->paths as $path) {
217: $iterator->append($this->buildIterator($path));
218: }
219: return $iterator;
220: }
221: }
222:
223:
224:
225: 226: 227: 228: 229:
230: private function buildIterator($path)
231: {
232: if (PHP_VERSION_ID < 50301) {
233: $iterator = new RecursiveDirectoryIteratorFixed($path);
234: } else {
235: $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
236: }
237:
238: if ($this->exclude) {
239: $filters = $this->exclude;
240: $iterator = new NRecursiveCallbackFilterIterator($iterator, create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('filters'=>$filters)).'], EXTR_REFS);
241: if (!$file->isDot() && !$file->isFile()) {
242: foreach ($filters as $filter) {
243: if (!call_user_func($filter, $file)) {
244: return FALSE;
245: }
246: }
247: }
248: return TRUE;
249: '));
250: }
251:
252: if ($this->maxDepth !== 0) {
253: $iterator = new RecursiveIteratorIterator($iterator, $this->order);
254: $iterator->setMaxDepth($this->maxDepth);
255: }
256:
257: if ($this->groups) {
258: $groups = $this->groups;
259: $iterator = new NCallbackFilterIterator($iterator, create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('groups'=>$groups)).'], EXTR_REFS);
260: foreach ($groups as $filters) {
261: foreach ($filters as $filter) {
262: if (!call_user_func($filter, $file)) {
263: continue 2;
264: }
265: }
266: return TRUE;
267: }
268: return FALSE;
269: '));
270: }
271:
272: return $iterator;
273: }
274:
275:
276:
277:
278:
279:
280:
281: 282: 283: 284: 285: 286:
287: public function exclude($masks)
288: {
289: if (!is_array($masks)) {
290: $masks = func_get_args();
291: }
292: $pattern = self::buildPattern($masks);
293: if ($pattern) {
294: $this->filter(create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('pattern'=>$pattern)).'], EXTR_REFS);
295: return !preg_match($pattern, \'/\' . strtr($file->getSubPathName(), \'\\\\\', \'/\'));
296: '));
297: }
298: return $this;
299: }
300:
301:
302:
303: 304: 305: 306: 307:
308: public function filter($callback)
309: {
310: $this->cursor[] = $callback;
311: return $this;
312: }
313:
314:
315:
316: 317: 318: 319: 320:
321: public function limitDepth($depth)
322: {
323: $this->maxDepth = $depth;
324: return $this;
325: }
326:
327:
328:
329: 330: 331: 332: 333: 334:
335: public function size($operator, $size = NULL)
336: {
337: if (func_num_args() === 1) {
338: if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#i', $operator, $matches)) {
339: throw new InvalidArgumentException('Invalid size predicate format.');
340: }
341: list(, $operator, $size, $unit) = $matches;
342: static $units = array('' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9);
343: $size *= $units[strtolower($unit)];
344: $operator = $operator ? $operator : '=';
345: }
346: return $this->filter(create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('operator'=>$operator,'size'=> $size)).'], EXTR_REFS);
347: return Finder::compare($file->getSize(), $operator, $size);
348: '));
349: }
350:
351:
352:
353: 354: 355: 356: 357: 358:
359: public function date($operator, $date = NULL)
360: {
361: if (func_num_args() === 1) {
362: if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#i', $operator, $matches)) {
363: throw new InvalidArgumentException('Invalid date predicate format.');
364: }
365: list(, $operator, $date) = $matches;
366: $operator = $operator ? $operator : '=';
367: }
368: $date = DateTime53::from($date)->format('U');
369: return $this->filter(create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('operator'=>$operator,'date'=> $date)).'], EXTR_REFS);
370: return Finder::compare($file->getMTime(), $operator, $date);
371: '));
372: }
373:
374:
375:
376: 377: 378: 379: 380: 381:
382: public static function compare($l, $operator, $r)
383: {
384: switch ($operator) {
385: case '>':
386: return $l > $r;
387: case '>=':
388: return $l >= $r;
389: case '<':
390: return $l < $r;
391: case '<=':
392: return $l <= $r;
393: case '=':
394: case '==':
395: return $l == $r;
396: case '!':
397: case '!=':
398: case '<>':
399: return $l != $r;
400: }
401: throw new InvalidArgumentException("Unknown operator $operator.");
402: }
403:
404: }
405:
406:
407:
408: if (PHP_VERSION_ID < 50301) {
409:
410: class RecursiveDirectoryIteratorFixed extends RecursiveDirectoryIterator
411: {
412: function hasChildren()
413: {
414: return parent::hasChildren(TRUE);
415: }
416: }
417: }
418: