<?php
/**
 * YANA library
 *
 * @package yana
 * @licence http://creativecommons.org/licenses/by-nc-sa/2.5/
 */

/**
 * virtual Drive
 *
 * Class to abstract from real filesystems by mapping
 * filenames to aliases (mountpoints).
 * Mountpoints my be mounted and unmounted at runtime.
 * When reading a mountpoint, a corresponding file wrapper
 * will be returned to work on the file.
 *
 * Using virtual drives will clean your source from
 * hard-coded file- and directory names, as well as
 * doing all the job of creating and initializing
 * the file wrappers for you.
 *
 * @access     public
 * @package    yana
 * @subpackage vdrive
 */
class VDrive extends SecureInputStream
{

    /**#@+
     * @ignore
     * @access  private
     */
    /** @var array  */ var $drive   = array();
    /** @var string */ var $baseDir = "";
    /** @var int    */ var $mode    = 0;
    /** @var array  */ var $files   = array();
    /**#@-*/

    /**
     * constructor
     *
     * creates a new virtual drive instance
     *
     * @name   vdrive::__construct()
     * @param  string  $filename
     * @param  array   $OPTIONS
     * @param  string  $baseDir
     * @param  int     $mode
     */
    function VDrive($filename, $OPTIONS, $baseDir, $mode)
    {

        settype($filename, "string");
        settype($baseDir, "string");
        settype($mode, "integer");

        if (is_array($OPTIONS)) {
            $this->attributes = $OPTIONS; 
        }

        $this->filename = $filename;
        $this->resetStats();
        $this->baseDir  = $baseDir;
        $this->mode     = $mode;
    }

    /**
     * add another drive to repository
     *
     * Loads the drive identified by $filename within the directory $baseDir and adds
     * a new mountpoint containing the drive's data to the repository.
     *
     * @access  public
     * @name    vdrive::adddrive()
     * @param   string  $filename
     * @param   int     $mode
     * @return  bool
     */
    function addDrive($filename, $baseDir)
    {
        settype($filename, "string");
        settype($baseDir, "string");

        if (!is_readable($filename)) {
            return false;
        } else {
            $vDriveData = getConfigFile($filename,CASE_MIXED);
            if (isset($vDriveData['SETTINGS']) && is_array($vDriveData['SETTINGS'])) {
                $this->options = $vDriveData['SETTINGS'];
            }
            $this->addMountpoint($vDriveData, $baseDir);
            return true;
        }
    }

    /**
     * mount an unmounted virtual drive
     *
     * Mount the mountpoint identified by $key and copies the contents
     * (if any) to the repository.
     *
     * This function returns bool(true) on success, or bool(false) on error.
     *
     * @access  public
     * @name    vdrive::mount()
     * @param   string  $key  name of the drive to mount
     * @return  bool
     */
    function mount($key)
    {
     settype($key, "string");

        if (isset($this->drive[$key])) {
            $return = $this->drive[$key]->mount();
            if (is_array($return)) {
                $this->options = mergeArrays($this->options, $return);
                return true;
            } else {
                settype($return, "boolean");
                return $return;
            }
        } else {
            return false;
        }
    }

    /**
     * read the virtual drive
     *
     * This (re-)loads the virtual drive and (re-)initializes it's contents.
     *
     * If the file does not exist or is not readable, the functions returns bool(false),
     * else bool(true) is returned.
     *
     * @access  public
     * @name    vdrive::read()
     * @return  bool
     */
    function read()
    {
        if (!$this->exists()) {
            return false;
        } else {
            $this->options = $this->attributes;
            $this->content = $this->source = getConfigFile($this->filename,CASE_MIXED);
            if (isset($this->content['SETTINGS']) && is_array($this->content['SETTINGS'])) {
                    $this->options = mergeArrays($this->options, $this->content['SETTINGS']);
            }
            $this->addMountpoint($this->content, $this->baseDir);
            return true;
        }
    }

    /**
     * get string represenation of a virtual drive
     *
     * This returns a human readable overview of the currently loaded virtual drive and
     * it's contents.
     *
     * You might want to use this for debugging purposes.
     *
     * @uses    print "<pre>".$vDrive->toString()."</pre>";
     *
     * @access  public
     * @name    vdrive::tostring()
     * @return  string
     */
    function toString()
    {
        if ($this->drive) {
            $txt = "";
            foreach ($this->drive as $key=>$element) 
            {
                $drive = $element->getMountpoint();
                $txt .= "Mountpoint '$key':"."\n\t- source = ".$drive->filename."\n\t- type = ".$element->getType()."\n\t- is mounted = ".(($element->mountpoint->isEmpty()) ? 'no' : 'yes')."\n\n";
            }
            return $txt;
        } else {
            return "unmounted virtual Drive\n";
        }
    }

    /**
     * get a mountpoint
     *
     * Returns the mountpoint specified by $key or bool(false)
     * if no such mountpoint exists, or the mountpoint was unable to
     * return any contents.
     *
     * @access  public
     * @name    vdrive::get()
     * @param   string  $key  name of the drive to mount
     * @return  bool(false)|Mountpoint
     */
    function get($key)
    {
        settype($key, "string");

        if (isset($this->drive[$key])) {
            $mountpoint = @$this->drive[$key]->getMountpoint();
            if (!empty($mountpoint)) {
                return $mountpoint;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * add a mountpoint
     *
     * This function is for internal use inside the class only.
     *
     * @access  private
     * @name    vdrive::addmountpoint()
     * @param   array   $drive
     * @param   string  $baseDir
     *
     * @ignore
     */
    function addMountpoint($drive, $baseDir)
    {
        settype($drive, "array");
        settype($baseDir, "string");
        uasort($drive, array("vDrive", "sortByPriority"));
        if (is_int(strpos($baseDir, YANA_UNI_DELIMITER))) {
            $baseDir = Layout::fillTemplate($baseDir, $this->options);
        }
        
        foreach ($drive as $key => $element)
        {
            settype($element['SRC'], "array");
            if ($this->mode == 1 && isset($element["DEFAULT"])) { 
                $element['SRC'] = array($element['DEFAULT']); 
            }
            if (isset($element['TYPE']) && $element['TYPE']=="dir") { 
                $this->addMountpoint( ( (isset($element['CONTENT'])) ? $element['CONTENT'] : NULL ), $baseDir.$element['SRC'][0]);
            }
            for ($i=0; $i<count($element['SRC']); $i++)
            {
                if (is_int(strpos($element['SRC'][$i], YANA_UNI_DELIMITER))) {
                    $element['SRC'][$i] = $baseDir. Layout::fillTemplate($element['SRC'][$i], $this->options);
                } else {
                    $element['SRC'][$i] = $baseDir.$element['SRC'][$i];
                }
            }
            $element['AUTOMOUNT'] = !empty($element['AUTOMOUNT']) && ($element['AUTOMOUNT']=="yes" || $element['AUTOMOUNT']==1 || $element['AUTOMOUNT']=="true");
            if (isset($element['TYPE']) && $element['TYPE']=="dir") {
                $this->drive[$key] = new vDrive_dir($element['SRC'],$element['TYPE'], $element['AUTOMOUNT'], ( (isset($element['FILTER'])) ? $element['FILTER'] : NULL ));
            }
            if (isset($element['TYPE']) && $element['TYPE']!="dir") {
                $this->drive[$key] = new vDrive_file($element['SRC'],$element['TYPE'],$element['AUTOMOUNT']);
            }
            if (isset($this->drive[$key])) {
                    $mountpoint = $this->drive[$key]->getMountpoint();
                if ($mountpoint) {
                     $this->files[$key] = $mountpoint;
                }
                if (isset($mountpoint->value) && $mountpoint->value) {
                     $this->options = mergeArrays($this->options, $mountpoint->value);
                }
            }
        }
    }

    /**
     * check a virtual drive for errors
     *
     * This is to check a virtual drive for syntax errors and missing files.
     *
     * Returns the result as a string in human readable form.
     *
     * @access  public
     * @name    vdrive::check()
     * @return  string
     *
     * @ignore
     */
    function check()
    {
        if ($this->isEmpty()) {
             return "Cannot perform check! Drive is not mounted.\n";
        } else {
            $drive[0] = $this->content;
            $i = 0;
            $j = 1;
            $txt = "";

            while ($i<$j)
            {
                foreach ($drive[$i] as $key => $element)
                {
                    if (!empty($element['TYPE']) && $element['TYPE']=="dir" && isset($element['CONTENT'])) {
                        $drive[count($drive)] = $element['CONTENT'];
                    }

                    if (!empty($this->drive[$key])) {
                        $mountpoint = $this->drive[$key]->getMountpoint();
                    }
                    $filename = (isset($mountpoint) && method_exists($mountpoint, 'getFilename')) ? $mountpoint->getFilename() : "";

                    if (!empty($element['SRC'])) {
                        switch ($element['REQUIRED']) {
                            case "exists":
                                if (!file_exists($filename)) {
                                    $txt .= "<p>ERROR: File '$filename' is a required ressource, but it does not exist.</p>\n";
                                }
                                break;
                            case "readable":
                                if (!is_readable($filename)) {
                                    $txt .= "<p>ERROR: File '$filename' is not readable.<br>Please make sure the file exists AND this program has permission to open it.</p>\n";
                                } else {
                                    $txt .= vDrive::checkFilePermission($filename, 444);
                                }
                            break;
                            case "writeable":
                                if (!is_writeable($filename)) {
                                    $txt .= "<p>ERROR: File '$filename' is not writeable.<br>Please make sure the file exists AND this program has permission to write at the file and the containing directory.</p>\n";
                                } else {
                                    $txt .= vDrive::checkFilePermission($filename, 666);
                                }
                                break;
                            case "create":
                                if (file_exists($filename) && !is_writeable($filename)) {
                                    $txt .= "<p>ERROR: File '$filename' exists but is not writeable.<br>Please make sure the file exists AND this program has permission to write at the file and the containing directory.</p>\n";
                                } elseif (file_exists($filename)) {
                                    $txt .= vDrive::checkFilePermission($filename, 666);
                                }
                                break;
                            default:
                            break;
                        }
                    }
                } /* end for */
                clearstatcache();
                $i++;
                $j = count($drive);
            } /* end while */
             return $txt;
        } /* end if */
    }

    /**
     * check a virtual drive for errors
     *
     * This is only to be used by vdrive::check() for validation.
     *
     * @access  private
     * @static
     * @name    vdrive::checkfilepermission()
     * @return  string
     *
     * @ignore
     */
    function checkFilePermission($filename, $permission2)
    {
        $txt = "";
        $permission1 = decoct(fileperms($filename));

        settype($permission1, "string");
        settype($permission2, "string");
        $permission1 = preg_replace("/.*(\d\d\d)$/s", "$1", $permission1);
        $permission2 = preg_replace("/.*(\d\d\d)$/s", "$1", $permission2);

        if (strlen($permission1)>2) {
            $permission[0] = (int) preg_replace("/.*(\d)\d\d$/s", "$1", $permission1);
            $permission[1] = (int) preg_replace("/.*\d(\d)\d$/s", "$1", $permission1);
            $permission[2] = (int) preg_replace("/.*\d\d(\d)$/s", "$1", $permission1);
        }
        if (strlen($permission2)>2) {
            $permission[3] = (int) preg_replace("/.*(\d)\d\d$/s", "$1", $permission2);
            $permission[4] = (int) preg_replace("/.*\d(\d)\d$/s", "$1", $permission2);
            $permission[5] = (int) preg_replace("/.*\d\d(\d)$/s", "$1", $permission2);
        }
        if (function_exists("posix_getuid") && fileowner($filename) && fileowner($filename) == posix_getuid()
            && $permission[0] >= $permission[3])
        {
            /* intenionally left blank */
        } else if ($permission[0] < $permission[3] || $permission[1] < $permission[4] || $permission[2] < $permission[5]) {
            $txt .= "<p>ERROR: This script requires a higher permission level for File '$filename'. <br> Permissions are actually set to '$permission1', required level is '$permission2'. Use CHMOD command to change permission level. See script documentation for details.</p>\n";
        }

        clearstatcache();

        return $txt;
    }
  

    /**
     * sorting function
     *
     * Internal function for sorting two arrays with sort().
     *
     * @access  private
     * @static
     * @name    vdrive::sortbypriority()
     * @param   array $a
     * @param   array $b
     * @return  int
     *
     * @ignore
     */
    function sortByPriority($a,$b)
    {
        if (!isset($a['PRIORITY'])) {
            $a['PRIORITY'] = 0;
        }
        if (!isset($b['PRIORITY'])) {
            $b['PRIORITY'] = 0;
        }
        if ($a['PRIORITY'] == $b['PRIORITY']) {
            return 0;
        } else {
            return ($a['PRIORITY'] < $b['PRIORITY']) ? 1 : -1;
        }
    }

}
?>