<?PHP
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 4                                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license,       |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Authors: Stephan Schmidt <schst@php-tools.net>                       |
// |          partly based on an exmple by Wez Furlong                    |
// +----------------------------------------------------------------------+
//
//    $Id: Var.php,v 1.3 2004/10/31 14:52:18 schst Exp $

/**
 * stream is readable
 * This depends on the mode, that is used for opening the stream
 */
define("STREAM_VAR_READABLE", 1);

/**
 * stream is writeable
 * This depends on the mode, that is used for opening the stream
 */
define("STREAM_VAR_WRITEABLE", 2);

/**
 * Stream wrapper to access a variable
 *
 * Stream wrappers allow you to access any datasource using PHP's file manipulation functions
 * like fopen(), fclose(), fseek(), ftell(),.. as well as directory functions like 
 * opendir() readdir() and closedir().
 *
 * This wrapper allows you to access any variable using these functions.
 * You have to specify a scope (GLOBALS, _GET, _POST, etc.) as the host and
 * the variable name as the path. If you want to access a string, that is
 * stored in an array, you can use this array like you would use a directory.
 *
 * Usage:
 * <code>
 *  require_once '../Var.php';
 *  stream_wrapper_register( "var", "Stream_Var" );
 *
 *  $fp = fopen('var://GLOBALS/myArray/index','r');
 *  $data = fread($fp,100);
 *  fclose($fp);
 * </code>
 *
 * This wrapper also has support for dir functions, so it's possible to read any array, like
 * you would read a directory. The following code will list all keys in an array.
 * You could use fopen() to read the values for the keys.
 *
 * <code>
 *  require_once '../Var.php';
 *  stream_wrapper_register( "var", "Stream_Var" );
 *
 *  $dh = opendir('var://_SERVER');
 *  while ($entry = readdir($dh)) {
 *      echo $entry."<br />";
 *  }
 *  closedir($dh);
 * </code>
 *
 * This wrapper allows you to replace files and directories with structures in
 * memory in any application, that relies on filesystem functions. But keep in mind
 * that variables are not persistent during several request, unless you write to
 * var://SESSION.
 * But this can be used to replace temporary files with variables.
 *
 * @category Stream
 * @package  Stream_Var
 * @version  0.2.1
 * @author   Stephan Schmidt <schst@php-tools.net>
 */
class Stream_Var {

   /**
    * pointer to the variable
    * @var mixed   $_pointer
    */
    var $_pointer = null;

   /**
    * flag to indicate whether stream is open
    * @var boolean   $_open
    */
    var $_open = false;

   /**
    * position
    * @var integer $_pos
    */
    var $_pos = 0;

   /**
    * mode of the opened filw
    * @var integer   $_mode
    */
    var $_mode = 0;

   /**
    * method used by fopen
    *
    * @access public
    * @param  string  $path        path to the variable (e.g. var://GLOBALS/myArray/anyIndex)
    * @param  string  $mode        mode to open the stream, like 'r', 'w,',... ({@see fopen()})
    * @param  array   $options     options (not implemented yet)
    * @param  string  $opened_path this will be set to the actual opened path
    * @return boolean $success
    */
    function stream_open($path, $mode, $options, &$opened_path)
    {
        $url = parse_url($path);

        $scope   = $url["host"];
        $varpath = substr($url["path"], 1);
        
        $mode = strtolower($mode);
        switch ($mode{0}) {
            case    "r":
                $status = $this->_setPointer($scope, $varpath, false);
                $this->_mode = $this->_mode | STREAM_VAR_READABLE;
                break;
            case    "w":
            case    "a":
                $status = $this->_setPointer($scope, $varpath, true);
                $this->_mode = $this->_mode | STREAM_VAR_WRITEABLE;
                break;
            case    "x":
                $status = $this->_setPointer($scope, $varpath, false);
                if ($status) {
                    return false;
                }
                $status = $this->_setPointer($scope, $varpath, true);
                $this->_mode = $this->_mode | STREAM_VAR_WRITEABLE;
                break;
        }

        if (!$status) {
            return  false;
        }
        
        if (!is_scalar($this->_pointer)) {
            return false;
        }

        // start at zero
        $this->_pos = 0;
        $this->_open = true;
        $opened_path = $path;

        if ($mode{0} == 'a') {
            $this->stream_seek(0, SEEK_END);
        }

        if (strlen($mode) > 1 && $mode{1} == '+') {
            $this->_mode = $this->_mode | STREAM_VAR_READABLE | STREAM_VAR_WRITEABLE;
        }

        return true;
    }

   /**
    * check for end of stream
    *
    * @access public
    * @return boolean $eof  true if at end of stream
    */
    function stream_eof()
    {
        return ($this->_pos >= strlen($this->_pointer));
    }

   /**
    * return the current position
    *
    * @access public
    * @return integer $position current position in stream
    */
    function stream_tell()
    {
        return $this->_pos;
    }

   /**
    * close the stream
    *
    * @access public
    */
    function stream_close()
    {
        $this->_pos  = 0;
        $this->_open = false;
    }

   /**
    * read from the stream
    *
    * @access public
    * @param  integer $count    amount of bytes to read
    * @return string  $data     data that has been read
    */
    function stream_read($count)
    {
        if (!$this->_open) {
            return false;
        }

        if (!($this->_mode & STREAM_VAR_READABLE)) {
            return false;
        }

        $data = substr($this->_pointer, $this->_pos, $count);
        $this->_pos = $this->_pos + strlen($data);
        return $data;
    }

   /**
    * write to the stream
    *
    * @access public
    * @param  mixed   $data  data to write
    * @return integer $bytes number of bytes that were written
    */
    function stream_write($data)
    {
        if (!$this->_open) {
            return false;
        }
        
        if (!($this->_mode & STREAM_VAR_WRITEABLE)) {
            return false;
        }
        
        $datalen = strlen($data);
       
        $this->_pointer = substr($this->_pointer, 0, $this->_pos) . $data . substr($this->_pointer, $this->_pos+$datalen);
        $this->_pos = $this->_pos + $datalen;
        return $datalen;
    }

   /**
    * move the position in the stream
    *
    * @access public
    * @param  integer $offset  offset
    * @return integer $whence  point from which the offset should be calculated
    */
    function stream_seek($offset, $whence)
    {
        switch ($whence) {
            //  from start
            case SEEK_SET:
                if ($offset < strlen($this->_pointer) && $offset >= 0) {
                     $this->_pos = $offset;
                     return true;
                } else {
                     return false;
                }
                break;
            // from current position
            case SEEK_CUR:
                if ($offset >= 0) {
                     $this->_pos += $offset;
                     return true;
                } else {
                     return false;
                }
                break;
            // from the end
            case SEEK_END:
                if (strlen($this->_pointer) + $offset >= 0) {
                     $this->_pos = strlen($this->_pointer) + $offset;
                     return true;
                } else {
                     return false;
                }
                break;
            default:
                return false;
        }
    }

   /**
    * write all data to storage
    *
    * @access public
    */
    function stream_flush()
    {
        return true;
    }

   /**
    * return information about the stream
    *
    * @access public
    * @return array $stat   information about the stream (currently only the length is included)
    */
    function stream_stat()
    {
        $stat = array(
                      'size' => strlen($this->_pointer)
                    );
        return $stat;
    }


   /**
    * open 'directory'
    *
    * @access public
    * @param  string    $path    path to the array (i.e. the directory)
    * @param  array     $options not implemented, yet.
    * @return boolean   $success
    */
    function dir_opendir($path, $options)
    {
        $url = parse_url($path);

        $scope   = $url['host'];
        if (isset($url['path'])) {
            $varpath = substr($url['path'], 1);
        } else {
            $varpath = '';
        }
        
        if (!$status = $this->_setPointer($scope, $varpath))
            return  false;

        if (!is_array($this->_pointer)) {
            return false;
        }
        reset($this->_pointer);
        $this->_open = true;
        return true;
    }


   /**
    * close 'directory'
    *
    * @access public
    * @return boolean $success
    */
    function dir_closedir()
    {
        $this->_open = false;
        return true;
    }

   /**
    * rewind 'directory'
    *
    * @access public
    * @return boolean $success
    */
    function dir_rewinddir()
    {
        if (!$this->_open) {
            return false;
        }
        reset($this->_pointer);
        return true;
    }

   /**
    * read one entry from 'directory'
    *
    * @access public
    * @return mixed  $entry entry that has been read, or false if there are no entries left  
    */
    function dir_readdir()
    {
        if (!$this->_open) {
            return false;
        }
        if (current($this->_pointer) == count($this->_pointer)-1) {
            return false;
        }
        list($key) = each($this->_pointer);
        return $key;
    }

   /**
    * set the internal pointer
    *
    * Basically this method only sets the object property _pointer
    * as a reference to a variable
    *
    * @access private
    * @param  string  $scope    scope of the variable: GLOBAL, GET, POST, COOKIE, SESSION, SERVER, ENV
    * @param  string  $path     path to the variable. Array indices are seperated by a slash
    * @param  boolean $create   create the variable, if it does not exist
    */
    function _setPointer($scope, $path, $create = false )
    {
        $varpath = explode("/", $path);

        switch (strtoupper($scope)) {
            // GET variables
            case    "GET":
            case    "_GET":
                $this->_pointer = &$_GET;
                break;

            // POST variables
            case    "POST":
            case    "_POST":
                $this->_pointer = &$_POST;
                break;

            // SERVER variables
            case    "SERVER":
            case    "_SERVER":
                $this->_pointer = &$_SERVER;
                break;

            // SESSION variables
            case    "SESSION":
            case    "_SESSION":
                $this->_pointer = &$_SESSION;
                break;

            // COOKIE variables
            case    "COOKIE":
            case    "_COOKIE":
                $this->_pointer = &$_COOKIE;
                break;

            // ENV variables
            case    "ENV":
            case    "_ENV":
                $this->_pointer = &$_ENV;
                break;

            // global variables
            case    "GLOBALS":
            default:
                $this->_pointer = &$GLOBALS;
                break;
            
        }
        if (empty($varpath)) {
            return true;
        }

        while ($part = array_shift($varpath)) {
            if (!isset($this->_pointer[$part])) {
                if (!$create) {
                    return false;
                }
                if (!empty($varpath)) {
                    return false;
                }
                $this->_pointer[$part] = '';
            }
            $this->_pointer = &$this->_pointer[$part];
        }        
        return true;
    }
}
?>