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