Namespaces

  • 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
  • Namespace
  • 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:  */
 11: 
 12: namespace Nette\Utils;
 13: 
 14: use Nette,
 15:     RecursiveIteratorIterator;
 16: 
 17: 
 18: /**
 19:  * Finder allows searching through directory trees using iterator.
 20:  *
 21:  * <code>
 22:  * Finder::findFiles('*.php')
 23:  *     ->size('> 10kB')
 24:  *     ->from('.')
 25:  *     ->exclude('temp');
 26:  * </code>
 27:  *
 28:  * @author     David Grudl
 29:  */
 30: class Finder extends Nette\Object implements \IteratorAggregate
 31: {
 32:     /** @var array */
 33:     private $paths = array();
 34: 
 35:     /** @var array of filters */
 36:     private $groups;
 37: 
 38:     /** @var filter for recursive traversing */
 39:     private $exclude = array();
 40: 
 41:     /** @var int */
 42:     private $order = RecursiveIteratorIterator::SELF_FIRST;
 43: 
 44:     /** @var int */
 45:     private $maxDepth = -1;
 46: 
 47:     /** @var array */
 48:     private $cursor;
 49: 
 50: 
 51:     /**
 52:      * Begins search for files matching mask and all directories.
 53:      * @param  mixed
 54:      * @return Finder
 55:      */
 56:     public static function find($mask)
 57:     {
 58:         if (!is_array($mask)) {
 59:             $mask = func_get_args();
 60:         }
 61:         $finder = new static;
 62:         return $finder->select(array(), 'isDir')->select($mask, 'isFile');
 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 static;
 77:         return $finder->select($mask, 'isFile');
 78:     }
 79: 
 80: 
 81:     /**
 82:      * Begins search for directories matching mask.
 83:      * @param  mixed
 84:      * @return Finder
 85:      */
 86:     public static function findDirectories($mask)
 87:     {
 88:         if (!is_array($mask)) {
 89:             $mask = func_get_args();
 90:         }
 91:         $finder = new static;
 92:         return $finder->select($mask, 'isDir');
 93:     }
 94: 
 95: 
 96:     /**
 97:      * Creates filtering group by mask & type selector.
 98:      * @param  array
 99:      * @param  string
100:      * @return self
101:      */
102:     private function select($masks, $type)
103:     {
104:         $this->cursor = & $this->groups[];
105:         $pattern = self::buildPattern($masks);
106:         if ($type || $pattern) {
107:             $this->filter(function($file) use ($type, $pattern) {
108:                 return !$file->isDot()
109:                     && (!$type || $file->$type())
110:                     && (!$pattern || preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/')));
111:             });
112:         }
113:         return $this;
114:     }
115: 
116: 
117:     /**
118:      * Searchs in the given folder(s).
119:      * @param  string|array
120:      * @return self
121:      */
122:     public function in($path)
123:     {
124:         if (!is_array($path)) {
125:             $path = func_get_args();
126:         }
127:         $this->maxDepth = 0;
128:         return $this->from($path);
129:     }
130: 
131: 
132:     /**
133:      * Searchs recursively from the given folder(s).
134:      * @param  string|array
135:      * @return self
136:      */
137:     public function from($path)
138:     {
139:         if ($this->paths) {
140:             throw new Nette\InvalidStateException('Directory to search has already been specified.');
141:         }
142:         if (!is_array($path)) {
143:             $path = func_get_args();
144:         }
145:         $this->paths = $path;
146:         $this->cursor = & $this->exclude;
147:         return $this;
148:     }
149: 
150: 
151:     /**
152:      * Shows folder content prior to the folder.
153:      * @return self
154:      */
155:     public function childFirst()
156:     {
157:         $this->order = RecursiveIteratorIterator::CHILD_FIRST;
158:         return $this;
159:     }
160: 
161: 
162:     /**
163:      * Converts Finder pattern to regular expression.
164:      * @param  array
165:      * @return string
166:      */
167:     private static function buildPattern($masks)
168:     {
169:         $pattern = array();
170:         // TODO: accept regexp
171:         foreach ($masks as $mask) {
172:             $mask = rtrim(strtr($mask, '\\', '/'), '/');
173:             $prefix = '';
174:             if ($mask === '') {
175:                 continue;
176: 
177:             } elseif ($mask === '*') {
178:                 return NULL;
179: 
180:             } elseif ($mask[0] === '/') { // absolute fixing
181:                 $mask = ltrim($mask, '/');
182:                 $prefix = '(?<=^/)';
183:             }
184:             $pattern[] = $prefix . strtr(preg_quote($mask, '#'),
185:                 array('\*\*' => '.*', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-'));
186:         }
187:         return $pattern ? '#/(' . implode('|', $pattern) . ')\z#i' : NULL;
188:     }
189: 
190: 
191:     /********************* iterator generator ****************d*g**/
192: 
193: 
194:     /**
195:      * Returns iterator.
196:      * @return \Iterator
197:      */
198:     public function getIterator()
199:     {
200:         if (!$this->paths) {
201:             throw new Nette\InvalidStateException('Call in() or from() to specify directory to search.');
202: 
203:         } elseif (count($this->paths) === 1) {
204:             return $this->buildIterator($this->paths[0]);
205: 
206:         } else {
207:             $iterator = new \AppendIterator();
208:             $iterator->append($workaround = new \ArrayIterator(array('workaround PHP bugs #49104, #63077')));
209:             foreach ($this->paths as $path) {
210:                 $iterator->append($this->buildIterator($path));
211:             }
212:             unset($workaround[0]);
213:             return $iterator;
214:         }
215:     }
216: 
217: 
218:     /**
219:      * Returns per-path iterator.
220:      * @param  string
221:      * @return \Iterator
222:      */
223:     private function buildIterator($path)
224:     {
225:         if (PHP_VERSION_ID < 50301) {
226:             $iterator = new Nette\Utils\RecursiveDirectoryIteratorFixed($path);
227:         } else {
228:             $iterator = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
229:         }
230: 
231:         if ($this->exclude) {
232:             $filters = $this->exclude;
233:             $iterator = new Nette\Iterators\RecursiveFilter($iterator, function($file) use ($filters) {
234:                 if (!$file->isDot() && !$file->isFile()) {
235:                     foreach ($filters as $filter) {
236:                         if (!call_user_func($filter, $file)) {
237:                             return FALSE;
238:                         }
239:                     }
240:                 }
241:                 return TRUE;
242:             });
243:         }
244: 
245:         if ($this->maxDepth !== 0) {
246:             $iterator = new RecursiveIteratorIterator($iterator, $this->order);
247:             $iterator->setMaxDepth($this->maxDepth);
248:         }
249: 
250:         if ($this->groups) {
251:             $groups = $this->groups;
252:             $iterator = new Nette\Iterators\Filter($iterator, function($file) use ($groups) {
253:                 foreach ($groups as $filters) {
254:                     foreach ($filters as $filter) {
255:                         if (!call_user_func($filter, $file)) {
256:                             continue 2;
257:                         }
258:                     }
259:                     return TRUE;
260:                 }
261:                 return FALSE;
262:             });
263:         }
264: 
265:         return $iterator;
266:     }
267: 
268: 
269:     /********************* filtering ****************d*g**/
270: 
271: 
272:     /**
273:      * Restricts the search using mask.
274:      * Excludes directories from recursive traversing.
275:      * @param  mixed
276:      * @return self
277:      */
278:     public function exclude($masks)
279:     {
280:         if (!is_array($masks)) {
281:             $masks = func_get_args();
282:         }
283:         $pattern = self::buildPattern($masks);
284:         if ($pattern) {
285:             $this->filter(function($file) use ($pattern) {
286:                 return !preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/'));
287:             });
288:         }
289:         return $this;
290:     }
291: 
292: 
293:     /**
294:      * Restricts the search using callback.
295:      * @param  callable
296:      * @return self
297:      */
298:     public function filter($callback)
299:     {
300:         $this->cursor[] = $callback;
301:         return $this;
302:     }
303: 
304: 
305:     /**
306:      * Limits recursion level.
307:      * @param  int
308:      * @return self
309:      */
310:     public function limitDepth($depth)
311:     {
312:         $this->maxDepth = $depth;
313:         return $this;
314:     }
315: 
316: 
317:     /**
318:      * Restricts the search by size.
319:      * @param  string  "[operator] [size] [unit]" example: >=10kB
320:      * @param  int
321:      * @return self
322:      */
323:     public function size($operator, $size = NULL)
324:     {
325:         if (func_num_args() === 1) { // in $operator is predicate
326:             if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?\z#i', $operator, $matches)) {
327:                 throw new Nette\InvalidArgumentException('Invalid size predicate format.');
328:             }
329:             list(, $operator, $size, $unit) = $matches;
330:             static $units = array('' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9);
331:             $size *= $units[strtolower($unit)];
332:             $operator = $operator ? $operator : '=';
333:         }
334:         return $this->filter(function($file) use ($operator, $size) {
335:             return Finder::compare($file->getSize(), $operator, $size);
336:         });
337:     }
338: 
339: 
340:     /**
341:      * Restricts the search by modified time.
342:      * @param  string  "[operator] [date]" example: >1978-01-23
343:      * @param  mixed
344:      * @return self
345:      */
346:     public function date($operator, $date = NULL)
347:     {
348:         if (func_num_args() === 1) { // in $operator is predicate
349:             if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)\z#i', $operator, $matches)) {
350:                 throw new Nette\InvalidArgumentException('Invalid date predicate format.');
351:             }
352:             list(, $operator, $date) = $matches;
353:             $operator = $operator ? $operator : '=';
354:         }
355:         $date = Nette\DateTime::from($date)->format('U');
356:         return $this->filter(function($file) use ($operator, $date) {
357:             return Finder::compare($file->getMTime(), $operator, $date);
358:         });
359:     }
360: 
361: 
362:     /**
363:      * Compares two values.
364:      * @param  mixed
365:      * @param  mixed
366:      * @return bool
367:      */
368:     public static function compare($l, $operator, $r)
369:     {
370:         switch ($operator) {
371:             case '>':
372:                 return $l > $r;
373:             case '>=':
374:                 return $l >= $r;
375:             case '<':
376:                 return $l < $r;
377:             case '<=':
378:                 return $l <= $r;
379:             case '=':
380:             case '==':
381:                 return $l == $r;
382:             case '!':
383:             case '!=':
384:             case '<>':
385:                 return $l != $r;
386:             default:
387:                 throw new Nette\InvalidArgumentException("Unknown operator $operator.");
388:         }
389:     }
390: 
391: }
392: 
393: 
394: if (PHP_VERSION_ID < 50301) {
395:     /** @internal */
396:     class RecursiveDirectoryIteratorFixed extends \RecursiveDirectoryIterator
397:     {
398:         function hasChildren()
399:         {
400:             return parent::hasChildren(TRUE);
401:         }
402:     }
403: }
404: 
Nette Framework 2.0.12 API API documentation generated by ApiGen 2.8.0