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
  • PHP

Classes

  • Arrays
  • Finder
  • Html
  • Json
  • LimitedScope
  • MimeTypeDetector
  • Neon
  • NeonEntity
  • Paginator
  • Strings
  • Tokenizer
  • Validators

Exceptions

  • AssertionException
  • JsonException
  • NeonException
  • RegexpException
  • TokenizerException
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  *
  6:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  * @package Nette\Utils
 11:  */
 12: 
 13: 
 14: 
 15: /**
 16:  * Finder allows searching through directory trees using iterator.
 17:  *
 18:  * <code>
 19:  * Finder::findFiles('*.php')
 20:  *     ->size('> 10kB')
 21:  *     ->from('.')
 22:  *     ->exclude('temp');
 23:  * </code>
 24:  *
 25:  * @author     David Grudl
 26:  * @package Nette\Utils
 27:  */
 28: class Finder extends Object implements IteratorAggregate
 29: {
 30:     /** @var array */
 31:     private $paths = array();
 32: 
 33:     /** @var array of filters */
 34:     private $groups;
 35: 
 36:     /** @var filter for recursive traversing */
 37:     private $exclude = array();
 38: 
 39:     /** @var int */
 40:     private $order = RecursiveIteratorIterator::SELF_FIRST;
 41: 
 42:     /** @var int */
 43:     private $maxDepth = -1;
 44: 
 45:     /** @var array */
 46:     private $cursor;
 47: 
 48: 
 49: 
 50:     /**
 51:      * Begins search for files matching mask and all directories.
 52:      * @param  mixed
 53:      * @return Finder
 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:      * Begins search for files matching mask.
 68:      * @param  mixed
 69:      * @return Finder
 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:      * Begins search for directories matching mask.
 84:      * @param  mixed
 85:      * @return Finder
 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:      * Creates filtering group by mask & type selector.
100:      * @param  array
101:      * @param  string
102:      * @return Finder  provides a fluent interface
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:      * Searchs in the given folder(s).
122:      * @param  string|array
123:      * @return Finder  provides a fluent interface
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:      * Searchs recursively from the given folder(s).
138:      * @param  string|array
139:      * @return Finder  provides a fluent interface
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:      * Shows folder content prior to the folder.
158:      * @return Finder  provides a fluent interface
159:      */
160:     public function childFirst()
161:     {
162:         $this->order = RecursiveIteratorIterator::CHILD_FIRST;
163:         return $this;
164:     }
165: 
166: 
167: 
168:     /**
169:      * Converts Finder pattern to regular expression.
170:      * @param  array
171:      * @return string
172:      */
173:     private static function buildPattern($masks)
174:     {
175:         $pattern = array();
176:         // TODO: accept regexp
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] === '/') { // absolute fixing
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:     /********************* iterator generator ****************d*g**/
199: 
200: 
201: 
202:     /**
203:      * Returns iterator.
204:      * @return Iterator
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:      * Returns per-path iterator.
229:      * @param  string
230:      * @return Iterator
231:      */
232:     private function buildIterator($path)
233:     {
234:         if (PHP_VERSION_ID < 50301) {
235:             $iterator = new RecursiveDirectoryIteratorFixed($path);
236:         } else {
237:             $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
238:         }
239: 
240:         if ($this->exclude) {
241:             $filters = $this->exclude;
242:             $iterator = new NRecursiveCallbackFilterIterator($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 NCallbackFilterIterator($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:     /********************* filtering ****************d*g**/
280: 
281: 
282: 
283:     /**
284:      * Restricts the search using mask.
285:      * Excludes directories from recursive traversing.
286:      * @param  mixed
287:      * @return Finder  provides a fluent interface
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:      * Restricts the search using callback.
307:      * @param  callable
308:      * @return Finder  provides a fluent interface
309:      */
310:     public function filter($callback)
311:     {
312:         $this->cursor[] = $callback;
313:         return $this;
314:     }
315: 
316: 
317: 
318:     /**
319:      * Limits recursion level.
320:      * @param  int
321:      * @return Finder  provides a fluent interface
322:      */
323:     public function limitDepth($depth)
324:     {
325:         $this->maxDepth = $depth;
326:         return $this;
327:     }
328: 
329: 
330: 
331:     /**
332:      * Restricts the search by size.
333:      * @param  string  "[operator] [size] [unit]" example: >=10kB
334:      * @param  int
335:      * @return Finder  provides a fluent interface
336:      */
337:     public function size($operator, $size = NULL)
338:     {
339:         if (func_num_args() === 1) { // in $operator is predicate
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 Finder::compare($file->getSize(), $operator, $size);
350:         '));
351:     }
352: 
353: 
354: 
355:     /**
356:      * Restricts the search by modified time.
357:      * @param  string  "[operator] [date]" example: >1978-01-23
358:      * @param  mixed
359:      * @return Finder  provides a fluent interface
360:      */
361:     public function date($operator, $date = NULL)
362:     {
363:         if (func_num_args() === 1) { // in $operator is predicate
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 = DateTime53::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 Finder::compare($file->getMTime(), $operator, $date);
373:         '));
374:     }
375: 
376: 
377: 
378:     /**
379:      * Compares two values.
380:      * @param  mixed
381:      * @param  mixed
382:      * @return bool
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:     /** @internal */
412:     class RecursiveDirectoryIteratorFixed extends RecursiveDirectoryIterator
413:     {
414:         function hasChildren()
415:         {
416:             return parent::hasChildren(TRUE);
417:         }
418:     }
419: }
420: 
Nette Framework 2.0.8 (for PHP 5.2, un-prefixed) API API documentation generated by ApiGen 2.8.0