Seditio Source
Root |
./othercms/dotclear-2.22/inc/libs/clearbricks/filemanager/class.filemanager.php
<?php
/**
 * @class filemanager
 * @brief Files management class
 *
 * @package Clearbricks
 * @subpackage Filemanager
 *
 * @copyright Olivier Meunier & Association Dotclear
 * @copyright GPL-2.0-only
 */
class filemanager
{
    public
$root;                                               ///< string: Files manager root path
   
public $root_url;                                           ///< string: Files manager root URL
   
protected $pwd;                                             ///< string: Working (current) director
   
protected $exclude_list    = [];                            ///< array: Array of regexps defining excluded items
   
protected $exclude_pattern = '';                            ///< string: Files exclusion regexp pattern
   
public $dir                = ['dirs' => [], 'files' => []]; ///< array: Current directory content array

    /**
     * Constructor
     *
     * New filemanage istance. Note that filemanage is a jail in given root
     * path. You won't be able to access files outside {@link $root} path with
     * the object's methods.
     *
     * @param string    $root        Root path
     * @param string    $root_url        Root URL
     */
   
public function __construct($root, $root_url = '')
    {
       
$this->root     = $this->pwd     = path::real($root);
       
$this->root_url = $root_url;

        if (!
preg_match('#/$#', (string) $this->root_url)) {
           
$this->root_url = $this->root_url . '/';
        }

        if (!
$this->root) {
            throw new
Exception('Invalid root directory.');
        }
    }

   
/**
     * Change directory
     *
     * Changes working directory. $dir is relative to instance {@link $root}
     * directory.
     *
     * @param string    $dir            Directory
     */
   
public function chdir($dir)
    {
       
$realdir = path::real($this->root . '/' . path::clean($dir));
        if (!
$realdir || !is_dir($realdir)) {
            throw new
Exception('Invalid directory.');
        }

        if (
$this->isExclude($realdir)) {
            throw new
Exception('Directory is excluded.');
        }

       
$this->pwd = $realdir;
    }

   
/**
     * Get working directory
     *
     * Returns working directory path.
     *
     * @return string
     */
   
public function getPwd()
    {
        return
$this->pwd;
    }

   
/**
     * Current directory is writable
     *
     * @return boolean    true if working directory is writable
     */
   
public function writable()
    {
        if (!
$this->pwd) {
            return
false;
        }

        return
is_writable($this->pwd);
    }

   
/**
     * Add exclusion
     *
     * Appends an exclusion to exclusions list. $f should be a regexp.
     *
     * @see $exclude_list
     * @param array|string    $f            Exclusion regexp
     */
   
public function addExclusion($f)
    {
        if (
is_array($f)) {
            foreach (
$f as $v) {
                if ((
$V = path::real($v)) !== false) {
                   
$this->exclude_list[] = $V;
                }
            }
        } elseif ((
$F = path::real($f)) !== false) {
           
$this->exclude_list[] = $F;
        }
    }

   
/**
     * Path is excluded
     *
     * Returns true if path (file or directory) $f is excluded. $f path is
     * relative to {@link $root} path.
     *
     * @see $exclude_list
     * @param string    $f            Path to match
     * @return boolean
     */
   
protected function isExclude($f)
    {
        foreach (
$this->exclude_list as $v) {
            if (
strpos($f, $v) === 0) {
                return
true;
            }
        }

        return
false;
    }

   
/**
     * File is excluded
     *
     * Returns true if file $f is excluded. $f path is relative to {@link $root}
     * path.
     *
     * @see $exclude_pattern
     * @param string    $f            File to match
     * @return boolean
     */
   
protected function isFileExclude($f)
    {
        if (!
$this->exclude_pattern) {
            return
false;
        }

        return
preg_match($this->exclude_pattern, (string) $f);
    }

   
/**
     * Item in jail
     *
     * Returns true if file or directory $f is in jail (ie. not outside the
     * {@link $root} directory).
     *
     * @param string    $f            Path to match
     * @return boolean
     */
   
protected function inJail($f)
    {
       
$f = path::real($f);

        if (
$f !== false) {
            return
preg_match('|^' . preg_quote($this->root, '|') . '|', (string) $f);
        }

        return
false;
    }

   
/**
     * File in files
     *
     * Returns true if file $f is in files array of {@link $dir}.
     *
     * @param string    $f            File to match
     * @return boolean
     */
   
public function inFiles($f)
    {
        foreach (
$this->dir['files'] as $v) {
            if (
$v->relname == $f) {
                return
true;
            }
        }

        return
false;
    }

   
/**
     * Directory list
     *
     * Creates list of items in working directory and append it to {@link $dir}
     *
     * @uses sortHandler(), fileItem
     */
   
public function getDir()
    {
       
$dir = path::clean($this->pwd);

       
$dh = @opendir($dir);

        if (
$dh === false) {
            throw new
Exception('Unable to read directory.');
        }

       
$d_res = $f_res = [];

        while ((
$file = readdir($dh)) !== false) {
           
$fname = $dir . '/' . $file;

            if (
$this->inJail($fname) && !$this->isExclude($fname)) {
                if (
is_dir($fname) && $file != '.') {
                   
$tmp = new fileItem($fname, $this->root, $this->root_url);
                    if (
$file == '..') {
                       
$tmp->parent = true;
                    }
                   
$d_res[] = $tmp;
                }

                if (
is_file($fname) && strpos($file, '.') !== 0 && !$this->isFileExclude($file)) {
                   
$f_res[] = new fileItem($fname, $this->root, $this->root_url);
                }
            }
        }
       
closedir($dh);

       
$this->dir = ['dirs' => $d_res, 'files' => $f_res];
       
usort($this->dir['dirs'], [$this, 'sortHandler']);
       
usort($this->dir['files'], [$this, 'sortHandler']);
    }

   
/**
     * Root directories
     *
     * Returns an array of directory under {@link $root} directory.
     *
     * @uses fileItem
     * @return array
     */
   
public function getRootDirs()
    {
       
$d = files::getDirList($this->root);

       
$dir = [];

        foreach (
$d['dirs'] as $v) {
           
$dir[] = new fileItem($v, $this->root, $this->root_url);
        }

        return
$dir;
    }

   
/**
     * Upload file
     *
     * Move <var>$tmp</var> file to its final destination <var>$dest</var> and
     * returns the destination file path.
     * <var>$dest</var> should be in jail. This method will throw exception
     * if the file cannot be written.
     *
     * You should first verify upload status, with {@link files::uploadStatus()}
     * or PHP native functions.
     *
     * @see files::uploadStatus()
     * @param string    $tmp            Temporary uploaded file path
     * @param string    $dest        Destination file
     * @param boolean    $overwrite    overwrite mode
     * @return string                Destination real path
     */
   
public function uploadFile($tmp, $dest, $overwrite = false)
    {
       
$dest = $this->pwd . '/' . path::clean($dest);

        if (
$this->isFileExclude($dest)) {
            throw new
Exception(__('Uploading this file is not allowed.'));
        }

        if (!
$this->inJail(dirname($dest))) {
            throw new
Exception(__('Destination directory is not in jail.'));
        }

        if (!
$overwrite && file_exists($dest)) {
            throw new
Exception(__('File already exists.'));
        }

        if (!
is_writable(dirname($dest))) {
            throw new
Exception(__('Cannot write in this directory.'));
        }

        if (@
move_uploaded_file($tmp, $dest) === false) {
            throw new
Exception(__('An error occurred while writing the file.'));
        }

       
files::inheritChmod($dest);

        return
path::real($dest);
    }

   
/**
     * Upload file by bits
     *
     * Creates a new file <var>$name</var> with contents of <var>$bits</var> and
     * return the destination file path.
     * <var>$name</var> should be in jail. This method will throw exception
     * if file cannot be written.
     *
     * @param string    $bits        Destination file content
     * @param string    $name        Destination file
     * @return string                Destination real path
     */
   
public function uploadBits($name, $bits)
    {
       
$dest = $this->pwd . '/' . path::clean($name);

        if (
$this->isFileExclude($dest)) {
            throw new
Exception(__('Uploading this file is not allowed.'));
        }

        if (!
$this->inJail(dirname($dest))) {
            throw new
Exception(__('Destination directory is not in jail.'));
        }

        if (!
is_writable(dirname($dest))) {
            throw new
Exception(__('Cannot write in this directory.'));
        }

       
$fp = @fopen($dest, 'wb');
        if (
$fp === false) {
            throw new
Exception(__('An error occurred while writing the file.'));
        }

       
fwrite($fp, $bits);
       
fclose($fp);
       
files::inheritChmod($dest);

        return
path::real($dest);
    }

   
/**
     * New directory
     *
     * Creates a new directory <var>$d</var> relative to working directory.
     *
     * @param string    $d            Directory name
     */
   
public function makeDir($d)
    {
       
files::makeDir($this->pwd . '/' . path::clean($d));
    }

   
/**
     * Move file
     *
     * Moves a file <var>$s</var> to a new destination <var>$d</var>. Both
     * <var>$s</var> and <var>$d</var> are relative to {@link $root}.
     *
     * @param string    $s            Source file
     * @param string    $d            Destination file
     */
   
public function moveFile($s, $d)
    {
       
$s = $this->root . '/' . path::clean($s);
       
$d = $this->root . '/' . path::clean($d);

        if ((
$s = path::real($s)) === false) {
            throw new
Exception(__('Source file does not exist.'));
        }

       
$dest_dir = path::real(dirname($d));

        if (!
$this->inJail($s)) {
            throw new
Exception(__('File is not in jail.'));
        }
        if (!
$this->inJail($dest_dir)) {
            throw new
Exception(__('File is not in jail.'));
        }

        if (!
is_writable($dest_dir)) {
            throw new
Exception(__('Destination directory is not writable.'));
        }

        if (@
rename($s, $d) === false) {
            throw new
Exception(__('Unable to rename file.'));
        }
    }

   
/**
     * Remove item
     *
     * Removes a file or directory <var>$f</var> which is relative to working
     * directory.
     *
     * @param string    $f            Path to remove
     */
   
public function removeItem($f)
    {
       
$file = path::real($this->pwd . '/' . path::clean($f));

        if (
is_file($file)) {
           
$this->removeFile($f);
        } elseif (
is_dir($file)) {
           
$this->removeDir($f);
        }
    }

   
/**
     * Remove item
     *
     * Removes a file <var>$f</var> which is relative to working directory.
     *
     * @param string    $f            File to remove
     */
   
public function removeFile($f)
    {
       
$f = path::real($this->pwd . '/' . path::clean($f));

        if (!
$this->inJail($f)) {
            throw new
Exception(__('File is not in jail.'));
        }

        if (!
files::isDeletable($f)) {
            throw new
Exception(__('File cannot be removed.'));
        }

        if (@
unlink($f) === false) {
            throw new
Exception(__('File cannot be removed.'));
        }
    }

   
/**
     * Remove item
     *
     * Removes a directory <var>$d</var> which is relative to working directory.
     *
     * @param string    $d            Directory to remove
     */
   
public function removeDir($d)
    {
       
$d = path::real($this->pwd . '/' . path::clean($d));

        if (!
$this->inJail($d)) {
            throw new
Exception(__('Directory is not in jail.'));
        }

        if (!
files::isDeletable($d)) {
            throw new
Exception(__('Directory cannot be removed.'));
        }

        if (@
rmdir($d) === false) {
            throw new
Exception(__('Directory cannot be removed.'));
        }
    }

   
/**
     * SortHandler
     *
     * This method is called by {@link getDir()} to sort files. Can be overrided
     * in inherited classes.
     *
     * @param fileItem    $a            fileItem object
     * @param fileItem    $b            fileItem object
     * @return integer
     */
   
protected function sortHandler($a, $b)
    {
        if (
$a->parent && !$b->parent || !$a->parent && $b->parent) {
            return (
$a->parent) ? -1 : 1;
        }

        return
strcasecmp($a->basename, $b->basename);
    }
}

/**
 * @class fileItem
 * @brief File item
 *
 * File item class used by {@link filemanager}. In this class {@link $file} could
 * be either a file or a directory.
 *
 * @package Clearbricks
 * @subpackage Filemanager
 */
class fileItem
{
    public
$file;           ///< string: Complete path to file
   
public $basename;       ///< string: File basename
   
public $dir;            ///< string: File directory name
   
public $file_url;       ///< string: File URL
   
public $dir_url;        ///< string: File directory URL
   
public $extension;      ///< string: File extension
   
public $relname;        ///< string: File path relative to <var>$root</var> given in constructor
   
public $parent = false; ///< boolean: Parent directory (ie. "..")
   
public $type;           ///< string: File MimeType. See {@link files::getMimeType()}.
   
public $type_prefix;    ///< string
   
public $mtime;          ///< integer: File modification timestamp
   
public $size;           ///< integer: File size
   
public $mode;           ///< integer: File permissions mode
   
public $uid;            ///< integer: File owner ID
   
public $gid;            ///< integer: File group ID
   
public $w;              ///< boolean: True if file or directory is writable
   
public $d;              ///< boolean: True if file is a directory
   
public $x;              ///< boolean: True if file file is executable or directory is traversable
   
public $f;              ///< boolean: True if file is a file
   
public $del;            ///< boolean: True if file or directory is deletable

    /**
     * Constructor
     *
     * Creates an instance of fileItem object.
     *
     * @param string    $file        Absolute file or directory path
     * @param string    $root        File root path
     * @param string    $root_url        File root URL
     */
   
public function __construct($file, $root, $root_url = '')
    {
       
$file = path::real($file);
       
$stat = stat($file);
       
$path = path::info($file);

       
$rel = preg_replace('/^' . preg_quote($root, '/') . '\/?/', '', (string) $file);

       
$this->file     = $file;
       
$this->basename = $path['basename'];
       
$this->dir      = $path['dirname'];
       
$this->relname  = $rel;

       
$this->file_url = str_replace('%2F', '/', rawurlencode($rel));
       
$this->file_url = $root_url . $this->file_url;

       
$this->dir_url   = dirname($this->file_url);
       
$this->extension = $path['extension'];

       
$this->mtime = $stat[9];
       
$this->size  = $stat[7];
       
$this->mode  = $stat[2];
       
$this->uid   = $stat[4];
       
$this->gid   = $stat[5];
       
$this->w     = is_writable($file);
       
$this->d     = is_dir($file);
       
$this->f     = is_file($file);
        if (
$this->d) {
           
$this->x = file_exists($file . '/.');
        } else {
           
$this->x = false;
        }
       
$this->del = files::isDeletable($file);

       
$this->type        = $this->d ? null : files::getMimeType($file);
       
$this->type_prefix = preg_replace('/^(.+?)\/.+$/', '$1', (string) $this->type);
    }
}