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