mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-29 18:18:56 +01:00
739 lines
20 KiB
PHP
739 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* eGroupWare API: Find Iterator - classes to iterate over large directory trees
|
|
* This is a rewrite of the original egw_vfs::find function, original author
|
|
* Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
*
|
|
* @link http://www.egroupware.org
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @package api
|
|
* @subpackage vfs
|
|
* @author Andreas Stoeckel
|
|
* @copyright (c) 2010 by Andreas Stoeckel
|
|
* @version $Id$
|
|
*/
|
|
|
|
define("FILTER_NOFOLLOW", 2); //Display the element but do not follow
|
|
define("FILTER_OK", 1); //The filter has passed
|
|
define("FILTER_DISPLAY", -1); //The element filtered should not be returned to the caller of find, it should not be displayed
|
|
define("FILTER_ALL", -2); //The element didn't pass the filter
|
|
|
|
function translate_path($path, $isurl) {
|
|
return $isurl ? $path : egw_vfs::PREFIX.parse_url($path, PHP_URL_PATH);
|
|
}
|
|
|
|
class egw_find_file
|
|
{
|
|
private $isurl = false;
|
|
private $need_mime = false;
|
|
private $base_path = "";
|
|
|
|
private $stat = null;
|
|
private $mime = null;
|
|
private $props = null;
|
|
private $result = null;
|
|
|
|
public $filename = ''; //< The filename including the file path, e.g. /home/user/example.file
|
|
public $path = ''; //< The filename, e.g. example.file
|
|
public $name = ''; //< The file path, e.g. /home/user/
|
|
public $isdot = false;
|
|
public $filterstate = FILTER_ALL;
|
|
|
|
public function __construct($path, $isurl, $need_mime)
|
|
{
|
|
//Copy the given parameters
|
|
$this->isurl = $isurl;
|
|
$this->need_mime = $need_mime;
|
|
$this->pair_result = $pair_result;
|
|
$this->base_path = $path;
|
|
|
|
//Set filename, path and path + name as this information will most likely
|
|
//be needed
|
|
$paths = $this->isurl ? explode('/', $path) : explode('/', parse_url($path, PHP_URL_PATH));
|
|
$this->name = implode('/',$paths);
|
|
$this->filename = array_pop($paths);
|
|
$this->path = implode('/',$paths).'/';
|
|
$this->is_dot = ($this->filename == '.') || ($this->filename == '..');
|
|
}
|
|
|
|
public function get_stat()
|
|
{
|
|
//Check whether the stat data has already been created, if not, create it
|
|
if ($this->stat === null)
|
|
{
|
|
$this->stat = $this->isurl ? lstat($this->base_path) :
|
|
egw_vfs::url_stat($this->base_path, STREAM_URL_STAT_LINK);
|
|
|
|
//Remove numerical indices 0-12
|
|
$this->stat = array_slice($this->stat, 13);
|
|
};
|
|
|
|
return $this->stat;
|
|
}
|
|
|
|
public function get_mime()
|
|
{
|
|
//Check if the mime data has already been read, if not, read it
|
|
if ($this->mime === null)
|
|
$res['mime'] = egw_vfs::mime_content_type($this->base_path);
|
|
|
|
return $this->mime;
|
|
}
|
|
|
|
public function get_props()
|
|
{
|
|
//Check whether props has already been read, if not, read it
|
|
if ($this->props === null)
|
|
{
|
|
$this->props = array(
|
|
"isfile" => is_file($this->base_path),
|
|
"isdir" => is_dir($this->base_path),
|
|
"islink" => is_link($this->base_path),
|
|
);
|
|
|
|
if ($this->props['islink'])
|
|
$this->props['linktarget'] = readlink($this->base_path);
|
|
}
|
|
|
|
return $this->props;
|
|
}
|
|
|
|
public function get_result($pair_result)
|
|
{
|
|
//Check whether the result has already been produced, if not, produce it
|
|
if ($this->result === null)
|
|
{
|
|
//Check whether only the file name/path should be returned or the complete
|
|
//resultset
|
|
if ($pair_result)
|
|
{
|
|
//Copy the state and add some custom information
|
|
$this->result = $this->get_stat();
|
|
$this->result['path'] = $this->name;
|
|
$this->result['name'] = $this->filename;
|
|
if ($this->need_mime)
|
|
$this->result['mime'] = $this->get_mime();
|
|
}
|
|
else
|
|
{
|
|
$this->result = $this->name;
|
|
}
|
|
}
|
|
|
|
return $this->result;
|
|
}
|
|
|
|
public function translate_path()
|
|
{
|
|
return translate_path($this->name, $this->isurl);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* egw_directory_iterator_filter filters files based on the options set supplied during
|
|
* construction. It is used by the RecursiveDirectoryIteratorWrapper and
|
|
* FindIterator classes. Filtering is performed by calling the "filter" function
|
|
* and passing an url_state array.
|
|
*/
|
|
class egw_directory_iterator_filter
|
|
{
|
|
//Parsed options
|
|
private $hidden = false;
|
|
private $type = null;
|
|
private $name_preg = null;
|
|
private $path_preg = null;
|
|
private $curkey = 0;
|
|
private $lcur = null;
|
|
private $uid = null;
|
|
private $gid = null;
|
|
private $ctime = null;
|
|
private $mtime = null;
|
|
private $cmin = null;
|
|
private $mmin = null;
|
|
private $size = null;
|
|
private $empty = true;
|
|
private $follow = false;
|
|
private $mime = null;
|
|
|
|
/*
|
|
* check_num is internally used to compare strings/numerical values.
|
|
* @param(value is an integer parameter)
|
|
* @param(argument may be a string or an integer. If argument is an integer
|
|
* the result gets true if argument equals value. If argument is a string, the
|
|
* first char, which may be '+' or '-' defines whether the result gets true if
|
|
* value is greater than argument (+) or when it is smaller than argument (-))
|
|
*/
|
|
private static function check_num($value, $argument)
|
|
{
|
|
if (is_int($argument) && $argument >= 0 || $argument[0] != '-' && $argument[0] != '+')
|
|
return $value == $argument;
|
|
|
|
if ($argument < 0)
|
|
return $value < abs($argument);
|
|
|
|
return $value > (int) substr($argument,1);
|
|
}
|
|
|
|
public function __construct($options)
|
|
{
|
|
//Preprocess the parameters
|
|
$this->hidden = isset($options['hidden']) ? $options['hidden'] : false;
|
|
$this->type = isset($options['type']) ? $options['type'] : null;
|
|
$this->name_preg = isset($options['name_preg']) ? $options['name_preg'] : null;
|
|
$this->path_preg = isset($options['path_preg']) ? $options['path_preg'] : null;
|
|
|
|
$this->cmin = isset($options['cmin']) ? $options['cmin'] : null;
|
|
$this->mmin = isset($options['mmin']) ? $options['mmin'] : null;
|
|
$this->ctime = isset($options['ctime']) ? $options['ctime'] : null;
|
|
$this->mtime = isset($options['mtime']) ? $options['mtime'] : null;
|
|
|
|
$this->empty = isset($options['empty']) ? $options['empty'] : true;
|
|
$this->size = isset($options['size']) ? $options['size'] : null;
|
|
$this->follow = isset($options['follow']) ? $options['follow'] : false;
|
|
|
|
$this->mime = isset($options['mime']) ? $options['mime'] : null;
|
|
|
|
//Convert the (probably) given path/name filters to regular expressions
|
|
if (isset($options['name']) && !isset($options['name_preg'])) // change from simple *,? wildcards to preg regular expression once
|
|
$this->name_preg = '/^'.str_replace(array('\\?','\\*'),
|
|
array('.{1}','.*'), preg_quote($options['name'])).'$/i';
|
|
|
|
if (isset($options['path']) && !isset($options['preg_path'])) // change from simple *,? wildcards to preg regular expression once
|
|
$this->path_preg = '/^'.str_replace(array('\\?','\\*'),
|
|
array('.{1}','.*'), preg_quote($options['path'])).'$/i';
|
|
|
|
//Translate username to uid
|
|
if (!isset($options['uid']))
|
|
{
|
|
if (isset($options['user']))
|
|
{
|
|
$this->uid = $GLOBALS['egw']->accounts->name2id($options['user'],'account_lid','u');
|
|
}
|
|
elseif (isset($options['nouser']))
|
|
{
|
|
$this->uid = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->uid = $options['uid'];
|
|
}
|
|
|
|
//Translate groupid to gid
|
|
if (!isset($options['gid']))
|
|
{
|
|
if (isset($options['group']))
|
|
{
|
|
$this->gid = abs($GLOBALS['egw']->accounts->name2id($options['group'],'account_lid','g'));
|
|
}
|
|
elseif (isset($options['nogroup']))
|
|
{
|
|
$this->gid = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->gid = $options['group'];
|
|
}
|
|
}
|
|
|
|
public function filter($findfile)
|
|
{
|
|
if ($findfile)
|
|
{
|
|
//Exclude directory and parent directory
|
|
if ($findfile->isdot)
|
|
return FILTER_ALL;
|
|
|
|
//Exclude hidden files
|
|
if (!$this->hidden && (($findfile->filename[0] == '.') || ($findfile->filename == 'Thumbs.db')))
|
|
return FILTER_ALL;
|
|
|
|
//Read the file properties (like (is link etc.))
|
|
$props = $findfile->get_props();
|
|
|
|
//If this is a directory, we probably need to keep this entry to crawl
|
|
//for files inside this directory. That's what FILTER_DISPLAY is for
|
|
if ($props['isdir'] && (!($props['islink']) || $this->follow))
|
|
$dir_filter = FILTER_DISPLAY;
|
|
else
|
|
$dir_filter = FILTER_ALL;
|
|
|
|
//Exclude files/directories based on the given regular expression
|
|
if ($this->name_preg && !preg_match($this->name_preg, $findfile->filename) ||
|
|
$this->path_preg && !preg_match($this->path_preg, $findfile->path))
|
|
return $dir_filter;
|
|
|
|
//Exclude directories/symlinks/files
|
|
if ($this->type && ($this->type == 'd') == !($props['isdir'] && !$props['islink']) ||
|
|
(($this->type == 'F') && ($props['isdir'] || $props['islink'])))
|
|
return $dir_filter;
|
|
|
|
if (($this->cmin !== null) || ($this->mmin !== null) ||
|
|
($this->ctime !== null) || ($this->mtime !== null) ||
|
|
($this->size !== null) || (!$this->empty) ||
|
|
($this->gid !== null) || ($this->uid !== null))
|
|
{
|
|
//Check user/group
|
|
if (($this->gid !== null) && $stat['gid'] != $options['gid'] ||
|
|
($this->uid !== null) && $stat['uid'] != $options['uid'])
|
|
{
|
|
return FILTER_ALL; // wrong user or group
|
|
}
|
|
|
|
//Retrieve the file stat
|
|
$stat = $this->get_stat();
|
|
|
|
//Excludes files where the creation/last modification timestamps is greater than
|
|
//the given time
|
|
if ($this->cmin && !self::check_num(round((time()-$stat['ctime'])/60), $this->cmin) ||
|
|
$this->mmin && !self::check_num(round((time()-$stat['mtime'])/60), $this->mmin) ||
|
|
$this->ctime && !self::check_num(round((time()-$stat['ctime'])/86400), $this->ctime) ||
|
|
$this->mtime && !self::check_num(round((time()-$stat['mtime'])/86400), $this->mtime))
|
|
{
|
|
return $dir_filter;
|
|
}
|
|
|
|
//Check filesize
|
|
if (($this->size !== null) && !self::check_num($stat['size'], $this->size) ||
|
|
(!$this->empty && $stat['size'] <= 0)) //TODO Check this
|
|
{
|
|
return $dir_filter;
|
|
}
|
|
}
|
|
|
|
//Check the mime type and subtype. As loading the mime type takes the
|
|
//longest time, it is postponed as long as possible
|
|
if ($this->mime !== null) {
|
|
$mime = $findfile->get_mime();
|
|
if ($options['mime'] != $mime)
|
|
{
|
|
list($type, $subtype) = explode('/', $this->mime);
|
|
|
|
// no subtype (eg. 'image') --> check only the main type
|
|
if ($sub_type || substr($this->mime, 0, strlen($type)+1) != $type.'/')
|
|
return $dir_filter; // wrong mime-type
|
|
}
|
|
}
|
|
|
|
//Return FILTER_OK, if the filters have passed
|
|
return FILTER_OK;
|
|
}
|
|
else
|
|
{
|
|
return FILTER_ALL;
|
|
}
|
|
}
|
|
}
|
|
|
|
class egw_recursive_directory_iterator_wrapper implements RecursiveIterator
|
|
{
|
|
private $base = "";
|
|
private $exec = null;
|
|
private $exec_params = null;
|
|
private $filter = null;
|
|
|
|
private $elements = null;
|
|
private $index = 0;
|
|
private $iterator = null;
|
|
private $follow = false;
|
|
private $sortfunc;
|
|
private $sort = false;
|
|
private $url = false;
|
|
private $needmime = false;
|
|
|
|
private function fillelemarray()
|
|
{
|
|
//Fill the "elements" array with all filestates
|
|
$this->elements = array();
|
|
|
|
//Loop through each element of the recursive directory iterator
|
|
foreach($this->iterator as $key => $value)
|
|
{
|
|
//Create a new egw_find_file class for each entry and filter it
|
|
$findfile = new egw_find_file(
|
|
$this->iterator->getPath().'/'.$this->iterator->getFilename(),
|
|
$this->url, $this->needmime);
|
|
|
|
//Filter the element
|
|
$filterresult = $this->filter->filter($findfile);
|
|
if ($filterresult >= FILTER_DISPLAY)
|
|
{
|
|
$findfile->filterstate = $filterresult;
|
|
$this->elements[] = $findfile;
|
|
}
|
|
}
|
|
|
|
//Sort the elements
|
|
if ($this->sort)
|
|
{
|
|
usort($this->elements, $this->sortfunc);
|
|
}
|
|
}
|
|
|
|
public function __construct($iterator, &$filter, $follow, $url, $needmime,
|
|
$sort, $sortfunc)
|
|
{
|
|
$this->filter = $filter;
|
|
$this->iterator = $iterator;
|
|
if ($this->iterator->valid())
|
|
{
|
|
$this->base = $this->iterator->getPath();
|
|
}
|
|
$this->follow = $follow;
|
|
$this->sortfunc = $sortfunc;
|
|
$this->sort = $sort;
|
|
$this->url = $url;
|
|
$this->needmime = $needmime;
|
|
|
|
//Get the element array. As this array only contains the current directory,
|
|
//and subdirectories are first loaded when they are accessed, a lot of memory
|
|
//is saved.
|
|
$this->fillelemarray();
|
|
}
|
|
|
|
public function valid()
|
|
{
|
|
return $this->index < count($this->elements);
|
|
}
|
|
|
|
public function next()
|
|
{
|
|
$this->index++;
|
|
}
|
|
|
|
public function current()
|
|
{
|
|
return $this->elements[$this->index];
|
|
}
|
|
|
|
public function key()
|
|
{
|
|
return $this->elements[$this->index]['name'];
|
|
}
|
|
|
|
public function hasChildren()
|
|
{
|
|
$props = &$this->elements[$this->index]->get_props();
|
|
|
|
//Only follow symlinks if we were ought to do so
|
|
if ($props['isdir'] && !$props['isdot'] && (!$props['islink'] || $this->follow))
|
|
{
|
|
//Check whether the link points on a file
|
|
if ($props['islink'])
|
|
{
|
|
$info = new SplFileInfo($props['linktarget']);
|
|
//Only follow the symlink if the symlink points to a directory.
|
|
//TODO: Recursion prevention?
|
|
return ($info->isDir());
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function getChildren()
|
|
{
|
|
$it = new RecursiveDirectoryIterator($this->elements[$this->index]->translate_path());
|
|
return new egw_recursive_directory_iterator_wrapper($it, $this->filter,
|
|
$this->follow, $this->url, $this->needmime, $this->sort,
|
|
$this->sortfunc, false);
|
|
}
|
|
|
|
public function rewind()
|
|
{
|
|
$this->index = 0;
|
|
}
|
|
}
|
|
|
|
class egw_find_iterator implements Iterator
|
|
{
|
|
private $filter = null;
|
|
private $follow = false;
|
|
private $iterator_stack = array();
|
|
private $base = array();
|
|
private $base_index = 0;
|
|
private $current_cache = null;
|
|
private $needs_next = false;
|
|
private $rewind = false;
|
|
private $pairs = false;
|
|
|
|
private $numerical_sort = false;
|
|
private $dirsontop = true;
|
|
private $sort = 1;
|
|
private $order = null;
|
|
|
|
private $index = 0;
|
|
private $lower_limit = 0;
|
|
private $upper_limit = null;
|
|
private $mindepth = null;
|
|
private $maxdepth = null;
|
|
private $depth = false;
|
|
private $url = false;
|
|
private $needmime = false;
|
|
|
|
private function push_root_iterator()
|
|
{
|
|
$root_iterator = new egw_recursive_directory_iterator_wrapper(
|
|
new RecursiveDirectoryIterator($this->base[$this->base_index]),
|
|
$this->filter, $this->follow, $this->url, $this->needmime,
|
|
($this->order !== null) || $this->dirsontop, array($this, "sort"), true);
|
|
array_push($this->iterator_stack, $root_iterator);
|
|
}
|
|
|
|
public function sort($a, $b)
|
|
{
|
|
//TODO restore, make it much faster :-)
|
|
|
|
/*if ($this->dirsontop)
|
|
{
|
|
if (($a['isdir'] != $b['isdir']) && ($a['isdir'] || $b['isdir']))
|
|
return $a['isdir'] ? -1:1;
|
|
}
|
|
|
|
if ($this->numerical_sort)
|
|
{
|
|
$res = $a[$this->order] == $b[$this->order] ? 0 : ($a[$this->order] > $b[$this->order] ? -1:1);
|
|
}
|
|
else
|
|
{
|
|
$res = strcasecmp($a[$this->order], $b[$this->order]);
|
|
}
|
|
|
|
//Always use name as second sort criteria
|
|
if (($res == 0) && ($this->order != 'name'))
|
|
{
|
|
$res = strcasecmp($a['name'], $b['name']);
|
|
}*/
|
|
|
|
return 0; //$res * $this->sort;
|
|
}
|
|
|
|
private function cur_iterator()
|
|
{
|
|
$s = count($this->iterator_stack);
|
|
if ($s > 0)
|
|
{
|
|
return $this->iterator_stack[$s - 1];
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function output_directory(&$findfile)
|
|
{
|
|
//If the element is visible, set $current_cache properly and return
|
|
if ($findfile->filterstate >= FILTER_OK)
|
|
{
|
|
$this->index++;
|
|
if (($this->index-1 >= $this->lower_limit) &&
|
|
(($this->mindepth === null) || count($this->iterator_stack) >= $this->mindepth))
|
|
{
|
|
$this->current_cache = $findfile->get_result($this->pairs);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function prefetch_current()
|
|
{
|
|
//If needs_next is true, a new element has been requested by calling the
|
|
//"next()" function
|
|
if ($this->needs_next)
|
|
{
|
|
//Reset $current_cache
|
|
$this->needs_next = false;
|
|
$this->current_cache = null;
|
|
$this->rewind = false;
|
|
|
|
$cur = $this->cur_iterator();
|
|
while (($cur != null) && (!$this->upper_limit || ($this->index <= $this->upper_limit)))
|
|
{
|
|
$skip = false;
|
|
if ($cur->valid())
|
|
{
|
|
//Get a copy of the current file state
|
|
$findfile = $cur->current();
|
|
|
|
//Add the children elements, if some exist
|
|
if ($cur->hasChildren() && !($findfile->filterstate == FILTER_NOFOLLOW))
|
|
{
|
|
if (($this->maxdepth === null) || count($this->iterator_stack) < $this->maxdepth)
|
|
{
|
|
array_push($this->iterator_stack, $cur->getChildren());
|
|
$skip = $this->depth;
|
|
}
|
|
}
|
|
|
|
//Step the current element one further and get the (probably) new iterator
|
|
if (!$skip)
|
|
$cur->next();
|
|
$cur = $this->cur_iterator();
|
|
|
|
if (!$skip && $this->output_directory($findfile))
|
|
break;
|
|
}
|
|
|
|
//If the current iterator doesn't contain any valid element, we're at the
|
|
//end of the current directory. Switch to the last directory
|
|
if (!$cur->valid())
|
|
{
|
|
array_pop($this->iterator_stack);
|
|
$cur = $this->cur_iterator();
|
|
//If cur = null we're at the end of the current directory tree.
|
|
//Check whether there was another directory specified
|
|
if ($cur === null)
|
|
{
|
|
//Increment the base dir array index
|
|
$this->base_index++;
|
|
if ($this->base_index < count($this->base))
|
|
{
|
|
$this->push_root_iterator();
|
|
$cur = $this->cur_iterator();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//If the contents of the directory were listed before the directory itself
|
|
//output the directory name when it was popped from the iterator stack
|
|
if ($this->depth && $cur && $cur->valid())
|
|
{
|
|
$findfile = $cur->current();
|
|
$cur->next();
|
|
if ($this->output_directory($findfile))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private function parseoptions(&$options)
|
|
{
|
|
//If options is not set, make it an empty array
|
|
if (!$options)
|
|
$options = array();
|
|
|
|
//Create the filter object (passed to other objects by reference) and
|
|
//parse some other options relevant for this base class
|
|
$this->filter = new egw_directory_iterator_filter($options);
|
|
$this->follow = isset($options['follow']) ? $options['follow'] : false;
|
|
$this->mindepth = isset($options['mindepth']) ? $options['mindepth'] : null;
|
|
$this->maxdepth = isset($options['maxdepth']) ? $options['maxdepth'] : null;
|
|
$this->depth = isset($options['depth']) ? $options['depth'] : false;
|
|
$this->dirsontop = isset($options['dirsontop']) ? (boolean)$options['dirsontop'] :
|
|
(isset($options['maxdepth']) && $options['maxdepth']>0);
|
|
$this->sort = isset($options['sort']) ? (($options['sort'] == 'ASC') ? 1 : -1) : 1;
|
|
$this->order = isset($options['order']) ? $options['order'] : null;
|
|
|
|
$this->numerical_sort = ($this->order == 'size' || $this->order == 'uid' ||
|
|
$this->order == 'gid' || $this->order == 'mode' || $this->order == 'ctime' ||
|
|
$this->order == 'mtime');
|
|
|
|
$this->url = isset($options['url']) ? (boolean)$options['url'] : false;
|
|
|
|
$this->needmime = isset($options['need_mime']) ? $options['need_mime'] : false;
|
|
$this->needmime = $this->needmime || isset($options['mime']) || ($this->order == 'mime');
|
|
|
|
//Retrieve the limit parameters
|
|
if (isset($options['limit']))
|
|
{
|
|
$args = explode(',', $options['limit']);
|
|
$count = $args[0];
|
|
if (isset($args[1]))
|
|
{
|
|
$this->lower_limit = $args[1];
|
|
}
|
|
$this->upper_limit = $this->lower_limit + $count;
|
|
}
|
|
}
|
|
|
|
public function __construct($base, $options=null, $pairs = false)
|
|
{
|
|
//_debug_array($options);
|
|
$this->pairs = $pairs;
|
|
|
|
$this->parseoptions($options);
|
|
|
|
//Make sure that base is an array with numeric keys
|
|
if (is_array($base))
|
|
{
|
|
$this->base = array_values($base);
|
|
}
|
|
else
|
|
{
|
|
$this->base[] = $base;
|
|
}
|
|
|
|
//Add the default url prefix to every path if url is not set.
|
|
foreach($this->base as $key => $path)
|
|
$this->base[$key] = translate_path($path, $this->url);
|
|
|
|
$this->rewind();
|
|
}
|
|
|
|
public function current()
|
|
{
|
|
$this->prefetch_current();
|
|
return $this->current_cache;
|
|
}
|
|
|
|
public function key()
|
|
{
|
|
$this->prefetch_current();
|
|
if ($this->pairs)
|
|
{
|
|
return $this->current_cache->path;
|
|
}
|
|
else
|
|
{
|
|
return $this->index - 1 - $this->lower_limit;
|
|
}
|
|
}
|
|
|
|
public function next()
|
|
{
|
|
$this->needs_next = true;
|
|
$this->prefetch_current();
|
|
}
|
|
|
|
public function valid()
|
|
{
|
|
if (!$this->upper_limit || ($this->index <= $this->upper_limit))
|
|
{
|
|
$this->prefetch_current();
|
|
return $this->current_cache != null;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function rewind()
|
|
{
|
|
if (!$this->rewind)
|
|
{
|
|
//Empty the iterator stack
|
|
$this->iterator_stack = array();
|
|
$this->current_cache = null;
|
|
$this->needs_next = true;
|
|
$this->index = 0;
|
|
$this->base_index = 0;
|
|
$this->rewind = true;
|
|
|
|
//Create the root directory iterator
|
|
$this->push_root_iterator();
|
|
}
|
|
}
|
|
}
|
|
|