| : | Javascript | : | PHP Index | : | MySQL | : |
| : | Source | : | : | Explanation | : | : | Example | : | : | Todo | : | : | Feedback | : |
This class facilitates listing the contents of directories (recursively)
Added: 2008-08-02 12:41:07
<?
/******
* You may use and/or modify this script as long as you:
* 1. Keep my name & webpage mentioned
* 2. Don't use it for commercial purposes
*
* If you want to use this script without complying to the rules above, please contact me first at: marty@excudo.net
*
* Author: Martijn Korse
* Website: http://devshed.excudo.net
*
* Date: 2008-08-02 12:41:07
***/
/**
* This class facilitates listing the contents of directories.
* It features:
* - optional recursiveness
* - sorting of output by
* - + name
* - + extension
* - + size
* - + time (of modification)
* - output control
*
* It has been written in PHP compatibel code, since PHP5 already provides an easier way (though
* not as extensive as this one) to deal with directory-listing
*
* The final output will be an array (multidimensional when recursiveness is switched on) that looks like this:
* array {
* "dirs" => array() // contains all the directories
* "files" => array() // contains all the files
* }
* When a directory has a subdirectory, this array will look the same and can be found under the 'list' key.
* The complete array of files and directories look like this:
*
* dirs:
* array (
* "name" => name of the directory
* "modified" => unix-timestamp when it was last modified
* "accessed" => unix-timestamp when it was last accessed
* "size" => total size of the directory in bytes, which is a sum of all the files in it (not recursive!)
* "files" => total amount of files in it
"list" => array( etc )
* )
* files:
* array (
* "name" => name of the directory
* "ext" => file extension
* "modified" => unix-timestamp when it was last modified
* "accessed" => unix-timestamp when it was last accessed
* "size" => total size of the directory in bytes, which is a sum of all the files in it (not recursive!)
* )
*
* @todo: support for multiple parameters
* @todo: add size of subdirectories to size of own directory
*/
class ListDir
{
/**
* Hold the path to the directory that we want to list
* This value will change when we recurse!
*
* @access private
* @var string
*/
var $root;
/**
*
* @access private
* @var boolean
*/
var $rootIsDir;
/**
*
* @access public
* @var boolean
*/
var $sane;
/**
*
* @access private
* @var array
*/
var $content = array(); // array
/**
* PHP4 style constructor
*
* @param string $path the path to the directory we want to list
*/
function ListDir($path)
{
// pointing the current root to the one given.
$this->setRoot($path);
}
/**
* Setter for the root-variable
*
* @param string $path
*/
function setRoot($path)
{
// check if the path exists
if (file_exists($path))
{
$this->sane = True;
$this->root = $path;
$this->rootIsDir = is_dir($path) ? True : False;
}
else
{
$this->sane = False;
}
}
/**
* Lists the content of the currently set root (@see constructor)
*
* @param string $params parameters indicating how to sort
* @param bool $recursive indicates whether the function should recurse or not
* @param array $parentDir a reference to the parent-dir. this shouldnt be set from
* outside. it will be filled during recursion so we
* can update the parentdir with info about the files
* @see parseParams()
* @return array
*/
function listContent($params = "", $recursive = true, $parentDir = null)
{
$outputArr = array("dirs"=>array(), "files"=>array());
if (!empty($params))
$order = $this->parseParam($params);
if ($handle = opendir($this->root))
{
$dirIndex = 0;
$fileIndex = 0;
while (false !== ($file = readdir($handle)))
{
if ($file != "." && $file != "..")
{
if (is_dir($this->root."/".$file))
{
$statArr = stat($this->root."/".$file);
if ($recursive)
{
// advancing the root to the next dir
$this->setRoot($this->root."/".$file);
$outputArr['dirs'][$dirIndex] = array(
"name" => $file,
"modified" => $statArr['mtime'],
"accessed" => $statArr['atime'],
"size" => 0,
"files" => 0,
);
$outputArr['dirs'][$dirIndex]['list'] = $this->listContent($params, $recursive, $outputArr['dirs'][$dirIndex]);
$dirIndex++;
// going back to the previous path, since we exited the recursion
$this->setRoot($this->root."/../");
}
else
{
$outputArr['dirs'][$dirIndex++] = array(
"name" => $file,
"modified" => $statArr['mtime'],
"accessed" => $statArr['atime'],
"size" => null,
"files" => null,
"list" => null
);
}
}
else
{
// a file. we add it
$this->addFile($file, $outputArr['files'][]);
// and update the dir it is in with some information
$this->updateDir($parentDir, $outputArr['files'][$fileIndex]);
$fileIndex++;
}
}
}
closedir($handle);
}
if (!empty($params))
{
// parameters were passed, so we sort the output
$this->sortArray($outputArr, $params);
}
return $outputArr;
}
/**
* Adds a file to the stack and fills its properties
*
* @param string $file The name of the file
* @param null $arrIndex Reference to the place in the stack where the array of information about the file needs to be stored
* @return void
*/
function addFile($file, &$arrIndex)
{
$statArr = stat($this->root."/".$file);
// root = $this->root
$arrIndex = array(
"name" => $file,
"ext" => substr(strrchr($file, "."), 1),
"size" => $statArr['size'],
"modified" => $statArr['mtime'],
"accessed" => $statArr['atime'],
);
}
/**
* Updates a directory-array using a file-array for information
*
* @param array $dirArr The dir that we want to update (which is the directory in which the file (fileArr) is located
* @param array $fileArr An array with information about a file
* @return void
*/
function updateDir(&$dirArr, $fileArr)
{
if (!is_null($dirArr))
{
$dirArr['size'] += $fileArr['size'];
$dirArr['files']++;
}
}
/**
* Lists all the files in a directory (so, without directories)
* Because directories are not considered, this is not recursive
*
* @param string $params Sorting parameters
* @see parseParams()
* @return array
*/
function listFiles($params = "")
{
$content = $this->listContent($params, False, null);
return $content['files'];
}
/**
* Lists all the directories in a directory (so, without files)
* For uniformity, this method is also non-recursive (like listFiles). Use listContent
* if you need recursiveness
*
* @param string $params Sorting parameters
* @see parseParams()
* @return array
*/
function listDirs($params = "")
{
$content = $this->listContent($params, False, null);
return $content['dirs'];
}
/**
* Parses the parameter string that some methods (listContent(), listFiles(), listDirs(), sortArray()) (can) take
* as argument.
* The format should be "type direction [, type direction [, type direction]]"
* Example: NAME DESC, EXT ASC
* Where 'type' can be:
* - NAME to sort by filename
* - EXT to sort by extension
* - SIZE to sort by filesize
* - TIME to sort by time of last modification
* And 'direction' can be:
* - ASC to order ascending (default) (so, A->Z or 0->999)
* - DESC to order descending (so, Z->A or 999->0)
*
* NOTE: although this function can already parse multiple sorting options, the sortArray() function cannot handle them
* so, only use one 'type direction' for now.
*
* @param string $params The parameter string
* @return array
*
* @see sortArray()
* @see listContent()
* @see listFiles()
* @see listDirs()
*/
function parseParam($params)
{
if (preg_match("/,/", $params))
$paramArr = preg_split("/,( )*/", $params);
else
$paramArr = array($params);
foreach ($paramArr AS $value)
{
if (preg_match("/( )/", $value))
list($orderType, $orderDirection) = preg_split("/( )+/", $value);
else
list($orderType, $orderDirection) = array(trim($value), "ASC");
$order[$orderType] = $orderDirection;
}
return $order;
}
/**
* Sorts an array according to parameters passed by the user
*
* NOTE: only 1 soting option can be given for the moment. But in case of equal occurences ...
* - When sorting by name, it will automatically sort by extension too in the same direction (asc/desc)
* - When sorting by ext, it will automatically sort by name too in the same direction (asc/desc)
*
* @param array $arr The array that needs to be sorted (containting files and/or dirs)
* @param string $params
* @return void
*
*/
function sortArray(&$arr, $params)
{
$orderArray = $this->parseParam($params);
if (count($orderArray) == 1)
{
foreach ($orderArray AS $type => $direction)
{
$direction = strtoupper($direction);
switch(strtoupper($type))
{
case "NAME" :
$this->sortBy($arr['files'], "name", $direction);
$this->sortBy($arr['dirs'], "name", $direction);
break;
case "EXT" :
$this->sortBy($arr['files'], "ext", $direction);
$this->sortBy($arr['dirs'], "name", $direction);
break;
case "SIZE" :
$this->sortBy($arr['files'], "size", $direction);
$this->sortBy($arr['dirs'], "size", $direction);
break;
case "TIME" :
$this->sortBy($arr['files'], "modified", $direction);
$this->sortBy($arr['dirs'], "modified", $direction);
break;
default:
die("unsupported sort ".$type);
}
}
}
else
{
die("multiple sorts are not supported (yet)");
}
}
/**
* Manages what kind of callback function for usort() to use
*
* @see sortArray()
*
* @param array $arr Array that needs to be sorted
*
*/
function sortBy(&$arr, $type, $direction)
{
switch ($type)
{
case "size" :
usort($arr, array(get_class($this), "fileCompareSize"));
break;
case "ext" :
usort($arr, array(get_class($this), "fileCompareExt"));
break;
case "modified" :
usort($arr, array(get_class($this), "fileCompareModified"));
break;
default : /* name */
usort($arr, array(get_class($this), "fileCompareName"));
break;
}
// if it is indicated the sorting has to be 'descending', we simply reverse the array
if ($direction == "DESC")
{
$arr = array_reverse($arr, false);
}
}
/**
* comparison functions
* these all return 0, -1 or 1; this value is used by PHP's usort() to sort the array accordingly
*/
/**
* Compares the filename of two file-arrays
* When two names are identical, they are further compared by extention
*
* @see compareArr()
* @see sortBy()
*
* @return int (0 / -1 / 1)
*/
function fileCompareName($a, $b)
{
if (isset($this) && is_object($this))
$result = $this->compareArr($a, $b, "name");
else
$result = ListDir::compareArr($a, $b, "name");
if ($result === 0)
{
$result = $this->compareArr($a, $b, "ext");
}
return $result;
}
/**
* Compares the file-extention time of two file-arrays
* When two names are identical, they are further compared by filename
*
* @see compareArr()
* @see sortBy()
*
* @return int (0 / -1 / 1)
*/
function fileCompareExt($a, $b)
{
if (isset($this) && is_object($this))
$result = $this->compareArr($a, $b, "ext");
else
$result = ListDir::compareArr($a, $b, "ext");
if ($result === 0)
{
$result = $this->compareArr($a, $b, "name");
}
return $result;
}
/**
* Compares the size of two file/dir-arrays
*
* @see compareInt()
* @see sortBy()
*
* @return int (0 / -1 / 1)
*/
function fileCompareSize($a, $b)
{
if (isset($this) && is_object($this))
return $this->compareInt(intval($a['size']), intval($b['size']));
else
return ListDir::compareInt(intval($a['size']), intval($b['size']));
}
/**
* Compares the modification time of two file/dir-arrays
*
* @see compareInt()
* @see sortBy()
*
* @return int (0 / -1 / 1)
*/
function fileCompareModified($a, $b)
{
if (isset($this) && is_object($this))
return $this->compareInt(intval($a['modified']), intval($b['modified']));
else
return ListDir::compareInt(intval($a['modified']), intval($b['modified']));
}
/**
* Compares two integers
*
* @see fileCompareSize()
* @see fileCompareModified()
*
* @return int (0 / -1 / 1)
*/
function compareInt($x, $y)
{
if ($x == $y) {
return 0;
}
return ($x < $y) ? -1 : 1;
}
/**
* Compares specific values (indicated by $key) of two arrays based on the alphabet
*
* @see fileCompareName()
* @see fileCompareExt()
*
* @return int (0 / -1 / 1)
*/
function compareArr($x, $y, $key)
{
if ($x[$key] == $y[$key]) {
return 0;
}
$tempArr = array($y[$key], $x[$key]);
sort($tempArr);
reset($tempArr);
return ($x[$key] == $tempArr[0]) ? -1 : 1;
}
/**
* You can pass a (sorted) array to this function (of the type that gets outputted by listContent()
* You can pass different functions for handling the output of a file and a directory. Those functions will be passed 3 parameters:
* 1. The file or directory array (as described at the top of this file)
* 2. The current position of that array. Say, we are in a loop containing 5 file-arrays and we're passing the 2nd one to the custom
* function, this argument will have the value 2
* 3. The total amount of arrays that will be passed during that loop. In the example of (2.) that would have been 5.
* => 2. & 3. allow you to do something special, for example, at the beginning and end of the loop by comparing these numbers. That way you
* can, for example, open and close an unsorted list (<ul></ul>)
*
* @param array $arr array, like the one outputted by listContent()
* @param string|array $dirFunc string: the name of the function that should handle the output of a directory
* array: the name of the class and the (static) method that should handle the output of a directory
* @param string|array $fileFunc string: the name of the function that should handle the output of a file
* array: the name of the class and the (static) method that should handle the output of a file
*
* @access public
*
* @see listContent()
*
* @return void
*/
function showOutput($arr, $dirFunc, $fileFunc)
{
if (isset($arr['dirs']))
{
$totalDir = count($arr['dirs']);
$cursorDir = 1;
foreach ($arr['dirs'] AS $dirArr)
{
call_user_func($dirFunc, $dirArr, $cursorDir++, $totalDir);
if (is_array($dirArr['list']) && count($dirArr['list']) > 0)
{
$this->showOutput($dirArr['list'], $dirFunc, $fileFunc);
}
}
}
else
{
$totalDir = 0;
}
if (isset($arr['files']))
{
$totalFiles = count($arr['files']);
$cursorFiles = 1;
foreach ($arr['files'] AS $fileArr)
{
call_user_func($fileFunc, $fileArr, $cursorFiles++, $totalFiles, $totalDir);
}
}
}
}