Packages

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • none

Classes

Exceptions

  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6:  * @package Nette\Utils
  7:  */
  8: 
  9: 
 10: 
 11: /**
 12:  * Finder allows searching through directory trees using iterator.
 13:  *
 14:  * <code>
 15:  * NFinder::findFiles('*.php')
 16:  *     ->size('> 10kB')
 17:  *     ->from('.')
 18:  *     ->exclude('temp');
 19:  * </code>
 20:  *
 21:  * @author     David Grudl
 22:  * @package Nette\Utils
 23:  */
 24: class NFinder extends NObject implements IteratorAggregate
 25: {
 26:     /** @var array */
 27:     private $paths = array();
 28: 
 29:     /** @var array of filters */
 30:     private $groups;
 31: 
 32:     /** @var filter for recursive traversing */
 33:     private $exclude = array();
 34: 
 35:     /** @var int */
 36:     private $order = RecursiveIteratorIterator::SELF_FIRST;
 37: 
 38:     /** @var int */
 39:     private $maxDepth = -1;
 40: 
 41:     /** @var array */
 42:     private $cursor;
 43: 
 44: 
 45:     /**
 46:      * Begins search for files matching mask and all directories.
 47:      * @param  mixed
 48:      * @return NFinder
 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:      * Begins search for files matching mask.
 62:      * @param  mixed
 63:      * @return NFinder
 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:      * Begins search for directories matching mask.
 77:      * @param  mixed
 78:      * @return NFinder
 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:      * Creates filtering group by mask & type selector.
 92:      * @param  array
 93:      * @param  string
 94:      * @return self
 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:      * Searchs in the given folder(s).
113:      * @param  string|array
114:      * @return self
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:      * Searchs recursively from the given folder(s).
128:      * @param  string|array
129:      * @return self
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:      * Shows folder content prior to the folder.
147:      * @return self
148:      */
149:     public function childFirst()
150:     {
151:         $this->order = RecursiveIteratorIterator::CHILD_FIRST;
152:         return $this;
153:     }
154: 
155: 
156:     /**
157:      * Converts Finder pattern to regular expression.
158:      * @param  array
159:      * @return string
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] === '/') { // absolute fixing
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:     /********************* iterator generator ****************d*g**/
185: 
186: 
187:     /**
188:      * Returns iterator.
189:      * @return Iterator
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:      * Returns per-path iterator.
213:      * @param  string
214:      * @return Iterator
215:      */
216:     private function buildIterator($path)
217:     {
218:         if (PHP_VERSION_ID < 50301) {
219:             $iterator = new NRecursiveDirectoryIteratorFixed($path);
220:         } else {
221:             $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
222:         }
223: 
224:         if ($this->exclude) {
225:             $filters = $this->exclude;
226:             $iterator = new NNRecursiveCallbackFilterIterator($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 NNCallbackFilterIterator($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:     /********************* filtering ****************d*g**/
263: 
264: 
265:     /**
266:      * Restricts the search using mask.
267:      * Excludes directories from recursive traversing.
268:      * @param  mixed
269:      * @return self
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:      * Restricts the search using callback.
288:      * @param  callable
289:      * @return self
290:      */
291:     public function filter($callback)
292:     {
293:         $this->cursor[] = $callback;
294:         return $this;
295:     }
296: 
297: 
298:     /**
299:      * Limits recursion level.
300:      * @param  int
301:      * @return self
302:      */
303:     public function limitDepth($depth)
304:     {
305:         $this->maxDepth = $depth;
306:         return $this;
307:     }
308: 
309: 
310:     /**
311:      * Restricts the search by size.
312:      * @param  string  "[operator] [size] [unit]" example: >=10kB
313:      * @param  int
314:      * @return self
315:      */
316:     public function size($operator, $size = NULL)
317:     {
318:         if (func_num_args() === 1) { // in $operator is predicate
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 NFinder::compare($file->getSize(), $operator, $size);
329:         '));
330:     }
331: 
332: 
333:     /**
334:      * Restricts the search by modified time.
335:      * @param  string  "[operator] [date]" example: >1978-01-23
336:      * @param  mixed
337:      * @return self
338:      */
339:     public function date($operator, $date = NULL)
340:     {
341:         if (func_num_args() === 1) { // in $operator is predicate
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 = NDateTime53::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 NFinder::compare($file->getMTime(), $operator, $date);
351:         '));
352:     }
353: 
354: 
355:     /**
356:      * Compares two values.
357:      * @param  mixed
358:      * @param  mixed
359:      * @return bool
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:     /** @internal */
389:     class NRecursiveDirectoryIteratorFixed extends RecursiveDirectoryIterator
390:     {
391:         function hasChildren()
392:         {
393:             return parent::hasChildren(TRUE);
394:         }
395:     }
396: }
397: 
Nette Framework 2.0.18 (for PHP 5.2, prefixed) API documentation generated by ApiGen 2.8.0