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:      * Begins search for files matching mask and all directories.
 51:      * @param  mixed
 52:      * @return Finder
 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:      * Begins search for files matching mask.
 66:      * @param  mixed
 67:      * @return Finder
 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:      * Begins search for directories matching mask.
 81:      * @param  mixed
 82:      * @return Finder
 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:      * Creates filtering group by mask & type selector.
 96:      * @param  array
 97:      * @param  string
 98:      * @return self
 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:      * Searchs in the given folder(s).
117:      * @param  string|array
118:      * @return self
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:      * Searchs recursively from the given folder(s).
132:      * @param  string|array
133:      * @return self
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:      * Shows folder content prior to the folder.
151:      * @return self
152:      */
153:     public function childFirst()
154:     {
155:         $this->order = RecursiveIteratorIterator::CHILD_FIRST;
156:         return $this;
157:     }
158: 
159: 
160:     /**
161:      * Converts Finder pattern to regular expression.
162:      * @param  array
163:      * @return string
164:      */
165:     private static function buildPattern($masks)
166:     {
167:         $pattern = array();
168:         // TODO: accept regexp
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] === '/') { // absolute fixing
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:     /********************* iterator generator ****************d*g**/
190: 
191: 
192:     /**
193:      * Returns iterator.
194:      * @return Iterator
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:      * Returns per-path iterator.
218:      * @param  string
219:      * @return Iterator
220:      */
221:     private function buildIterator($path)
222:     {
223:         if (PHP_VERSION_ID < 50301) {
224:             $iterator = new RecursiveDirectoryIteratorFixed($path);
225:         } else {
226:             $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
227:         }
228: 
229:         if ($this->exclude) {
230:             $filters = $this->exclude;
231:             $iterator = new NRecursiveCallbackFilterIterator($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 NCallbackFilterIterator($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:     /********************* filtering ****************d*g**/
268: 
269: 
270:     /**
271:      * Restricts the search using mask.
272:      * Excludes directories from recursive traversing.
273:      * @param  mixed
274:      * @return self
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:      * Restricts the search using callback.
293:      * @param  callable
294:      * @return self
295:      */
296:     public function filter($callback)
297:     {
298:         $this->cursor[] = $callback;
299:         return $this;
300:     }
301: 
302: 
303:     /**
304:      * Limits recursion level.
305:      * @param  int
306:      * @return self
307:      */
308:     public function limitDepth($depth)
309:     {
310:         $this->maxDepth = $depth;
311:         return $this;
312:     }
313: 
314: 
315:     /**
316:      * Restricts the search by size.
317:      * @param  string  "[operator] [size] [unit]" example: >=10kB
318:      * @param  int
319:      * @return self
320:      */
321:     public function size($operator, $size = NULL)
322:     {
323:         if (func_num_args() === 1) { // in $operator is predicate
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 Finder::compare($file->getSize(), $operator, $size);
334:         '));
335:     }
336: 
337: 
338:     /**
339:      * Restricts the search by modified time.
340:      * @param  string  "[operator] [date]" example: >1978-01-23
341:      * @param  mixed
342:      * @return self
343:      */
344:     public function date($operator, $date = NULL)
345:     {
346:         if (func_num_args() === 1) { // in $operator is predicate
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 = DateTime53::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 Finder::compare($file->getMTime(), $operator, $date);
356:         '));
357:     }
358: 
359: 
360:     /**
361:      * Compares two values.
362:      * @param  mixed
363:      * @param  mixed
364:      * @return bool
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:     /** @internal */
394:     class RecursiveDirectoryIteratorFixed extends RecursiveDirectoryIterator
395:     {
396:         function hasChildren()
397:         {
398:             return parent::hasChildren(TRUE);
399:         }
400:     }
401: }
402: 
Nette Framework 2.0.13 (for PHP 5.2, un-prefixed) API API documentation generated by ApiGen 2.8.0