Location: PHPKode > projects > Aukyla Document Management System > base/URI.php
<?php
/*
     URI.php, handling of local URI's.
     Copyright (C) 2004 Arend van Beelen, Auton Rijnsburg

     This program is free software; you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by the Free
     Software Foundation; either version 2 of the License, or (at your option)
     any later version.

     This program is distributed in the hope that it will be useful, but WITHOUT
     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
     more details.

     You should have received a copy of the GNU General Public License along
     with this program; if not, write to the Free Software Foundation, Inc.,
     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

     For any questions, comments or whatever, you may mail me at: hide@address.com
*/

require_once('Constants.php');
require_once('String.php');

/**
 * Constants for access permissions.
 */
define('PERMISSION_NONE',          0x00000000);
define('PERMISSION_READ',          0x00000001);
define('PERMISSION_APPEND',        0x00000002);
define('PERMISSION_MODIFY',        0x00000004);
define('PERMISSION_DELETE',        0x00000008);
define('PERMISSION_ADMINISTRATE',  0x00000010);
define('PERMISSION_CUSTOM',        0x00010000);

/**
 * @brief Class for managing relocation of local URI's.
 *
 * The Aukyla Framework consists of many different modules and multiple
 * applications can be run on it simultaniously. In many cases it is useful to
 * access documents or other data from other modules and applications, but they
 * should not need to know where and how in the local filesystem other modules
 * store their documents. Therefore, they can pass local URI's to each other
 * using namespaces much in the same way protocols in regular URI's are
 * specified. You can access documents specified through a local URI through the
 * URI class, but all regular PHP file operations will work with them as well.
 * If you use the URI class for operating on said URI's, the URI class will pass
 * the request to the LocalURIManager which will use the correct
 * LocalURINamespace to process the request. Furthermore, the URI class will
 * check for you whether the currently logged in user actually has permissions
 * to perform the requested operation. Now, because the URI class will
 * transparently check all permissions and the LocalURIManager will only forward
 * requests to registered namespaces and not to the local filesystem, you can
 * safely perform all actions from the URI class on user-specified URI's. If you
 * are using standard PHP file operations on URI's however, you will need to
 * check yourself whether the user has the right access permissions to the given
 * URI.
 *
 * <strong>Example:</strong>
 * The application ADMS can register the "ADMS" namespace. If ADMS then has a
 * document called "Bar.txt" in a "Foo" directory, other modules can access this
 * document through the local URI "ADMS://Foo/Bar.txt". This path might in
 * reality resolve to something like
 * "/opt/aukyla/data/Applications/ADMS/docs/Foo/Bar.txt", but it might even not
 * exist at all and be generated when the file is opened. Even if the file is
 * generated real-time however, all PHP file operations will work normally on
 * the URI.
 *
 * @note The LocalURIManager will only use <em>registered</em> namespaces to
 *       forward requests to. But, the LocalURIManager can also automatically
 *       register namespaces when they are needed. Let's consider a namespace
 *       called "Foo", you should then have a namespace class
 *       @p Foo_URINamespace which must inherit LocalURINamespace. This
 *       namespace class should then be put in a file @p Foo_URINamespace.php
 *       in the directory @p plugins/URINamespaces.
 *       Every time the LocalURIManager then finds it needs a handler for the
 *       "Foo" namespace, but it is not yet registered, it will open the said
 *       file and register the said class in it.
 *
 * @warning The file in which an URIHandler is defined is included from this
 *          class, meaning you can't declare global variables in the file
 *          (because of a limitation in PHP all global variables you declare
 *          will become local to this class). To help you out there's an
 *          exception so that you can use the global variable @p Foo_URIManager,
 *          which is referenced for you here, and which you may use globally. Of
 *          course, the name "Foo" should be replaced by the name of your
 *          namespace.
 */
class LocalURIManager
{
	/**
	 * Constructor.
	 *
	 * You do not have to instantiate this class yourself, instead you can
	 * use the static functions provided by this class.
	 */
	public function __construct()
	{
		$this->namespaces = array();
		$this->handles = array();
		$this->nextHandle = 1;
	}

	public function __destruct()
	{
		$this->namespaces = array();
		$this->handles = array();
		$this->nextHandle = 1;
	}

	/**
	 * Registers the given namespace.
	 *
	 * You only need to register a namespace yourself if the class can't be
	 * found under the @p plugins/URINamespaces directory, as explained with
	 * the LocalURIManager.
	 *
	 * @param namespace The namespace to register.
	 * @param className Classname of the namespace to register.
	 * @returns @p true on success, @p false on error.
	 *
	 * @sa LocalURINamespace
	 */
	public static function registerURINamespace($namespace, $className)
	{
		if(stream_wrapper_register($namespace, $className) == false)
		{
			return false;
		}

		$manager = self::instance();

		$manager->namespaces[$namespace] = $className;
		return true;
	}

	/**
	 * @internal
	 *
	 * Calls a @p function in a namespace.
	 *
	 * The namespace to use is determined from the given @p uri and the
	 * @p uri itself will be prepended to the @p args array. Then the
	 * @p function is called with the new @p args list.
	 *
	 * There's no point in calling this function directly. Just use the
	 * functions provided in the URI class.
	 */
	public static function callFunctionByURI($function, $uri, $args = array(), $saveHandle = false)
	{
		$namespace = String::stripSpecialChars(String::substringBefore($uri, ':'));

		if($namespace == '')
		{
			trigger_error("No namespace passed to URI::$function($uri)");
			return false;
		}

		$className = self::loadNamespace($namespace);

		if($className == false)
		{
			trigger_error("Invalid namespace \"$namespace\" passed to URI::$function($uri)");
			return false;
		}

		$namespaceInstance = new $className;

		$args = array_merge(array($uri), $args);
		$result = call_user_func_array(array($namespaceInstance, $function), $args);
		if($result == true && $saveHandle == true)
		{
			$manager = self::instance();

			$handle = $manager->nextHandle++;
			$manager->handles[$handle] = $namespaceInstance;
			return $handle;
		}
		return $result;
	}

	/**
	 * Loads the plugin for a namespace.
	 *
	 * @param namespace The namespace to find the plugin for.
	 * @return The class name of the loaded plugin or @p false if no plugin could
	 *         be found.
	 *
	 * @since Aukyla 1.1
	 */
	public static function loadNamespace($namespace)
	{
		$manager = self::instance();

		if(isset($manager->namespaces[$namespace]))
		{
			return $manager->namespaces[$namespace];
		}

		$manager = "{$namespace}_URIManager";
		global $$manager;

		$className = "{$namespace}_URINamespace";
		$include = include_once("URINamespaces/$className.php");
		if($include == false ||
		   self::registerURINamespace($namespace, $className) == false)
		{
			return false;
		}

		return $className;
	}

	/**
	 * @internal
	 *
	 * Calls a @p function in a namespace.
	 *
	 * An instance of the namespace is selected based on the given
	 * @p handle. The @p function is called from this instance with the
	 * @p args list.
	 *
	 * There's no point in calling this function directly. Just use the
	 * functions provided in the URI class.
	 */
	public static function callFunctionByHandle($function, $handle, $args = array(), $closeHandle = false)
	{
		$manager = self::instance();

		$namespaceInstance = $manager->handles[$handle];
		if($namespaceInstance === NULL)
		{
			trigger_error("Invalid handle passed to URI::$function()");
			return false;
		}

		$callback = array($namespaceInstance, $function);
		$result = call_user_func_array($callback, $args);
		if($result == true && $closeHandle == true)
		{
			unset($manager->handles[$handle]);
		}
		return $result;
	}

	private static function instance()
	{
		if(self::$instance == null)
		{
			self::$instance = new LocalURIManager();
		}

		return self::$instance;
	}

	private static $instance = null;
	private $namespaces;
	private $handles;
	private $nextHandle;
}

/**
 * @brief Performs file operations on local URI's.
 *
 * Most functions correspond more or less to regular functions for file
 * operations which already exist in PHP. The most important differences with
 * the regular PHP functions are that these functions will always check whether
 * the currently logged in user has permission to perform the requested action
 * according to the specified namespace and these functions only work with URI's
 * using a registered namespace.
 *
 * If you are using Local URI's use these functions whenever you can. They
 * provide some handy functions and, as said, will check all permissions
 * automatically. If you want to do something with a Local URI that's not
 * possible with these functions however, you should use URI::permissions() to
 * at least check what you are going to do is permitted.
 *
 * @sa LocalURIManager
 */
class URI
{
	/**
	 * Determines whether @p URI is an Aukyla Local URI.
	 *
	 * A URI is found to be an Aukyla Local URI if the supplied protocol portion
	 * of the URI is recognized as a valid namespace for an Aukyla Local URI.
	 *
	 * @param uri The URI to check whether it's found to be an Aukyla Local URI.
	 * @return @p true if the URI is an Aukyla Local URI, @p false otherwise.
	 *
	 * @since Aukyla 1.1
	 */
	public static function isLocalURI($uri)
	{
		$namespace = String::stripSpecialChars(String::substringBefore($uri, ':'));

		if($namespace == '')
		{
			return false;
		}

		if(LocalURIManager::loadNamespace($namespace) === false)
		{
			return false;
		}

		return true;
	}

	/**
	 * Returns a unique path which may be created in the given directory.
	 *
	 * If you wish to create a new file or directory in a directory, you are
	 * strongly advised to use this function to determine what name to give
	 * to the new file. The actual name that should be displayed to the user
	 * can then be set using setMetaData(), with the "name" key.
	 *
	 * No file or directory is created by this function.
	 *
	 * @param path       Path to the directory in which to find a unique
	 *                   file or directory name.
	 * @param suggestion The basename you wish to suggest for the file or
	 *                   directory. The resulting filename may include or
	 *                   use this suggestion, but it might also be entirely
	 *                   ignored.
	 * @return A unique path or @p false if an error occurred.
	 *
	 * <strong>Example: </strong>
	 * @code
	 * URI::uniquePath('tmp://Foo', 'Bar')
	 * @endcode
	 * may return "tmp://Foo/Bar0".
	 */
	public static function uniquePath($path, $suggestion)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_READ))
		{
			return false;
		}

		return LocalURIManager::callFunctionByURI('uniquePath', $path, array($suggestion));
	}

	/**
	 * Opens a file.
	 *
	 * If a new file has to be created, it's access permissions are
	 * inherited from the parent directory.
	 *
	 * @param path Path to the file to be opened.
	 * @param mode The fopen() mode to open the file with.
	 * @return File handle or @p false if an error occurred.
	 */
	public static function fopen($path, $mode)
	{
		$permissionMode = str_replace(array('b', 't'), '', $mode);
		if(LocalURIManager::callFunctionByURI('url_stat', $path, array(STREAM_URL_STAT_QUIET)) === false)
		{
			$permissions = LocalURIManager::callFunctionByURI('permissions', dirname($path));
			if($permissionMode{0} == 'r' || !($permissions & PERMISSION_APPEND))
			{
				return false;
			}
		}
		else
		{
			$permissions = LocalURIManager::callFunctionByURI('permissions', $path);
			if($permissionMode{0} == 'x' ||
			   $permissionMode == 'r' && !($permissions & PERMISSION_READ) ||
			   $permissionMode == 'r+' && ($permissions & (PERMISSION_READ | PERMISSION_MODIFY)) != (PERMISSION_READ | PERMISSION_MODIFY) ||
			   $permissionMode == 'w' && !($permissions & PERMISSION_MODIFY) ||
			   $permissionMode == 'w+' && ($permissions & (PERMISSION_READ | PERMISSION_MODIFY)) != (PERMISSION_READ | PERMISSION_MODIFY) ||
			   $permissionMode == 'a' && !($permissions & PERMISSION_APPEND) ||
			   $permissionMode == 'a+' && ($permissions & (PERMISSION_READ | PERMISSION_APPEND)) != (PERMISSION_READ | PERMISSION_MODIFY))
			{
				return false;
			}
		}

		$openedPath = '';
		return LocalURIManager::callFunctionByURI('stream_open', $path, array($mode, 0, $openedPath), true);
	}

	/**
	 * Reads a number of bytes from a file.
	 *
	 * @param handle A valid file handle as returned by URI::fopen().
	 * @param length The maximum number of bytes to be read.
	 * @return The read string or @p false if an error occurred.
	 */
	public static function fread($handle, $length)
	{
		return LocalURIManager::callFunctionByHandle('stream_read', $handle, array($length));
	}

	/**
	 * Writes a string to a file.
	 *
	 * @param handle A valid file handle as returned by URI::fopen().
	 * @param string The string to be written.
	 * @return The number of bytes actually written, might be 0 on error.
	 */
	public static function fwrite($handle, $string)
	{
		return LocalURIManager::callFunctionByHandle('stream_write', $handle, array($string));
	}

	/**
	 * Returns whether the current file position is at the end of a file.
	 *
	 * @param handle A valid file handle as returned by URI::fopen().
	 * @return @p true if the current file position is at the end of the
	 *         file, @p false otherwise.
	 */
	public static function feof($handle)
	{
		return LocalURIManager::callFunctionByHandle('stream_eof', $handle);
	}

	/**
	 * Returns the file position of an open handle.
	 *
	 * @param handle A valid file handle as returned by URI::fopen().
	 * @return The current byte position in the file.
	 */
	public static function ftell($handle)
	{
		return LocalURIManager::callFunctionByHandle('stream_tell', $handle);
	}

	/**
	 * Sets the file position on an open handle.
	 *
	 * @param handle A valid file handle as returned by URI::fopen().
	 * @param offset The new byte position in the file.
	 * @param whence Please refer to the
	 *               <a href="http://www.php.net/manual/en/function.fseek.php">PHP fseek() documentation</a>.
	 * @return The current byte position in the file, or @p false if an
	 *         error occurred.
	 */
	public static function fseek($handle, $offset, $whence = SEEK_SET)
	{
		return LocalURIManager::callFunctionByHandle('stream_seek', $handle, array($offset, $whence));
	}

	/**
	 * Flushes any cached data for a file to disk.
	 *
	 * @param handle A valid file handle as returned by URI::fopen().
	 * @return @p true if the data was successfully flushed (or no data
	 *         needed to be flushed) or @p false if an error occurred.
	 */
	public static function fflush($handle)
	{
		return LocalURIManager::callFunctionByHandle('stream_flush', $handle);
	}

	/**
	 * Returns statistics about an open file.
	 *
	 * @param handle A valid file handle as returned by URI::fopen().
	 * @return An array containing stats information. See the
	 *         <a href="http://nl2.php.net/manual/en/function.stat.php">PHP stat() documentation</a>
	 *         for details.
	 */
	public static function fstat($handle)
	{
		return LocalURIManager::callFunctionByHandle('stream_stat', $handle);
	}

	/**
	 * Closes an open file handle. After calling this function, the given
	 * @p handle is no longer valid.
	 *
	 * @param handle A valid file handle as returned by URI::fopen().
	 * @return @p true on success or @p false if an error occurred.
	 */
	public static function fclose($handle)
	{
		return LocalURIManager::callFunctionByHandle('stream_close', $handle);
	}

	/**
	 * Reads the entire contents of a file to an array of strings.
	 *
	 * Unlike the PHP file() function, the newline characters are stripped
	 * from the individual strings.
	 *
	 * @param path Path to the file to be read.
	 * @return An array of strings containing the entire file contents.
	 *         Every string in the array represents a line in the file, or
	 *         @p false if an error occurred.
	 */
	public static function file($path)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_READ))
		{
			return false;
		}

		$fp     = LocalURIManager::callFunctionByURI   ('stream_open',  $path, array('r'), true);
		$stats  = LocalURIManager::callFunctionByHandle('stream_stat',  $fp,   array(0));
		$string = LocalURIManager::callFunctionByHandle('stream_read',  $fp,   array($stats['size']));
		          LocalURIManager::callFunctionByHandle('stream_close', $fp,   array(), true);
		return explode("\n", $string);
	}

	/**
	 * Reads the entire contents of a file to a string.
	 *
	 * @param path Path to the file to be read.
	 * @return A string containing the entire file contents or @p false if
	 *         an error occurred.
	 */
	public static function fileGetContents($path)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_READ))
		{
			return false;
		}

		$fp       = LocalURIManager::callFunctionByURI   ('stream_open', $path, array('r'), true);
		$stats    = LocalURIManager::callFunctionByHandle('stream_stat', $fp,   array(0));
		if($stats['size'] == 0)
		{
			$contents = '';
		}
		else
		{
			$contents = LocalURIManager::callFunctionByHandle('stream_read', $fp, array($stats['size']));
		}
		LocalURIManager::callFunctionByHandle('stream_close', $fp, array(), true);

		return $contents;
	}

	/**
	 * Copy command for use with Local URI's.
	 *
	 * @param source          Source file to copy.
	 * @param destination     Destination file or directory to copy to.
	 * @param realDestination If @p destination was a directory, this
	 *                        parameter if set to the full path the file was
	 *                        saved under. This usually is something like
	 *                        the destination directory plus the basename of
	 *                        the source file, but it doesn't have to be.
	 * @return @p true if the source file was successfully copied, @p false
	 *         if an error occurred.
	 */
	public static function copy($source, $destination, &$realDestination = '')
	{
		$realDestination = $destination;

		// check read permissions for the source file
		if(!(LocalURIManager::callFunctionByURI('permissions', $source) & PERMISSION_READ))
		{
			return false;
		}
		// if the file does not yet exist, check if we are allowed to create a new file in the directory
		if(($stats = LocalURIManager::callFunctionByURI('url_stat', $destination, array(STREAM_URL_STAT_QUIET))) === false)
		{
			$permissions = LocalURIManager::callFunctionByURI('permissions', dirname($destination));
			if(!($permissions & PERMISSION_APPEND))
			{
				return false;
			}
		}
		// if the destination itself is a directory, check if we are allowed to create a new file in the directory
		else if($stats['mode'] & 0040000)
		{
			$permissions = LocalURIManager::callFunctionByURI('permissions', $destination);
			if(!($permissions & PERMISSION_APPEND))
			{
				return false;
			}
			$destination = LocalURIManager::callFunctionByURI('uniquePath', $destination, array(basename($source)));
			$realDestination = $destination;
		}
		// otherwise, check if the destination file is writable
		else
		{
			$permissions = LocalURIManager::callFunctionByURI('permissions', $destination);
			if(!($permissions & PERMISSION_MODIFY))
			{
				return false;
			}
		}

		$fpIn     = LocalURIManager::callFunctionByURI   ('stream_open',  $source,      array('r'), true);
		$fpOut    = LocalURIManager::callFunctionByURI   ('stream_open',  $destination, array('w'), true);

		$stats    = LocalURIManager::callFunctionByHandle('stream_stat',  $fpIn,        array(0));
		while($stats['size'] > 0)
		{
			$blockSize = ($stats['size'] < 1048576 ? $stats['size'] : 1048576);
			$contents = LocalURIManager::callFunctionByHandle('stream_read',  $fpIn,  array($blockSize));
			            LocalURIManager::callFunctionByHandle('stream_write', $fpOut, array($contents));
			$stats['size'] -= $blockSize;
		}

		LocalURIManager::callFunctionByHandle('stream_close', $fpIn,        array(), true);
		LocalURIManager::callFunctionByHandle('stream_close', $fpOut,       array(), true);

		return true;
	}

	/**
	 * Returns the size of a file.
	 *
	 * @param path Path to the file whose file size should be returned.
	 * @return The size of the file or @p false if an error occurred.
	 */
	public static function fileSize($path)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_READ))
		{
			return false;
		}

		$stats = LocalURIManager::callFunctionByURI('url_stat', $path, array(0));
		return ($stats != false ? $stats['size'] : false);
	}

	/**
	 * Returns the access permissions for the current user to the given
	 * path.
	 *
	 * @param path Path to the file or directory to get the access
	 *             permissions from.
	 * @return The permissions using the predefined PERMISSION_* constants,
	 *         OR'd together, or @p false if an error occurred.
	 *
	 * @note The PERMISSION_* constants are defined as follows:
	 *       <ul>
	 *         <li><code>PERMISSION_NONE         = 0x00000000</code></li>
	 *         <li><code>PERMISSION_READ         = 0x00000001</code></li>
	 *         <li><code>PERMISSION_APPEND       = 0x00000002</code></li>
	 *         <li><code>PERMISSION_MODIFY       = 0x00000004</code></li>
	 *         <li><code>PERMISSION_DELETE       = 0x00000008</code></li>
	 *         <li><code>PERMISSION_ADMINISTRATE = 0x00000010</code></li>
	 *         <li><code>PERMISSION_CUSTOM       = 0x00010000</code></li>
	 *       </ul>
	 *
	 * @sa permissionsArray()
	 */
	public static function permissions($path)
	{
		return LocalURIManager::callFunctionByURI('permissions', $path);
	}

	/**
	 * Returns all access permissions set on the given path in an array.
	 *
	 * @param path Path to the file or directory to get the access
	 *             permissions from.
	 * @returns The current permissions. This is an array containing any out
	 *          of three keys: "user", "group" and "other", where both user
	 *          and group are arrays containing user/group => permissions
	 *          pairs and where other contains the permissions for other
	 *          users. Permissions are specified with the PERMISSION_*
	 *          constants, OR'd together. Returns @p false if an error
	 *          occurred.
	 *
	 * <strong>Example array: </strong>
	 * @code
	 * Array
	 * (
	 *     [user] => Array
	 *               (
	 *                   [junior] => PERMISSION_READ |
	 *                               PERMISSION_APPEND |
	 *                               PERMISSION_MODIFY |
	 *                               PERMISSION_DELETE |
	 *                               PERMISSION_ADMINISTRATE
	 *               )
	 *     [group] => Array
	 *                (
	 *                    [users] => PERMISSION_READ
	 *                )
	 *     [other] => PERMISSION_NONE
	 * )
	 * @endcode
	 *
	 * @sa permissions() setPermissionsArray()
	 */
	public static function permissionsArray($path)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_READ))
		{
			return false;
		}

		return LocalURIManager::callFunctionByURI('permissionsArray', $path, array());
	}

	/**
	 * Changes the permissions of the given file or directory.
	 *
	 * @param path        Path to the file or directory to change the
	 *                    permissions on, including namespace.
	 * @param permissions The new permissions. Permissions is supposed to be
	 *                    an array containing any out of three keys: "user",
	 *                    "group" and "other", where both user and group are
	 *                    arrays containing user/group => permissions pairs
	 *                    and where other contains the permissions value for
	 *                    other users. Permissions values are specified by
	 *                    or'ing Permission* constants.
	 * @param recursive   If this parameter is @p true, then all files and
	 *                    directories under this directory will get assigned the
	 *                    same permissions. This parameter has no effect on files.
	 *                    This parameter was introduced in Aukyla 1.1.
	 * @return @p true on success or @p false if an error occurred.
	 */
	public static function setPermissionsArray($path, $permissions, $recursive = false)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_ADMINISTRATE))
		{
			return false;
		}

		$result = LocalURIManager::callFunctionByURI('setPermissionsArray', $path, array($permissions));

		$stats = LocalURIManager::callFunctionByURI('url_stat', $path, array(STREAM_URL_STAT_QUIET));
		if($recursive == true && ($stats['mode'] & 0040000))
		{
			if(($handle = LocalURIManager::callFunctionByURI('dir_opendir', $path, array(0), true)) === false)
			{
				return $result;
			}

			while(($entry = LocalURIManager::callFunctionByHandle('dir_readdir', $handle)) !== false)
			{
				if(self::setPermissionsArray($entry, $permissions, true) == false)
				{
					$result = false;
				}
			}
			LocalURIManager::callFunctionByHandle('dir_closedir', $handle, array(), true);
		}

		return $result;
	}

	/**
	 * Creates a new directory.
	 *
	 * Access permissions are inherited from the parent directory.
	 *
	 * @param path    Path to the directory to be created.
	 * @param options Use 0 to suppress errors.
	 * @return @p true on success or @p false if an error occurred.
	 */
	public static function mkdir($path, $options = STREAM_REPORT_ERRORS)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', dirname($path)) & PERMISSION_APPEND))
		{
			return false;
		}

		return LocalURIManager::callFunctionByURI('mkdir', $path, array(0700, $options));
	}

	/**
	 * Recursively removes an entire directory.
	 *
	 * @param path      Path to the directory to be removed.
	 * @param recursive If @p true, this will remove the directory, and all
	 *                  contents in it.
	 * @return @p true on success or @p false if an error occurred.
	 */
	public static function rmdir($path, $recursive = false)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_DELETE))
		{
			return false;
		}

		if($recursive)
		{
			return URI::recursiveRmdir($path);
		}

		return LocalURIManager::callFunctionByURI('rmdir', $path);
	}

	/**
	 * @internal
	 */
	private static function recursiveRmdir($path)
	{
		if(($handle = LocalURIManager::callFunctionByURI('dir_opendir', $path, array(0), true)) === false)
		{
			return false;
		}

		$entries = array();
		while(($entry = LocalURIManager::callFunctionByHandle('dir_readdir', $handle)) !== false)
		{
			$entries[] = $entry;
		}
		LocalURIManager::callFunctionByHandle('dir_closedir', $handle, array(), true);

		foreach($entries as $entry)
		{
			$stats = LocalURIManager::callFunctionByURI('url_stat', $entry, array(STREAM_URL_STAT_QUIET));
			if($stats['mode'] & 040000)
			{
				if(URI::recursiveRmdir($entry) == false)
				{
					return false;
				}
			}
			else
			{
				if(LocalURIManager::callFunctionByURI('unlink', $entry) == false)
				{
					return false;
				}
			}
		}

		return LocalURIManager::callFunctionByURI('rmdir', $path);
	}

	/**
	 * Updates the time stamp on a file or creates the file if it
	 * doesn't exist yet.
	 *
	 * If a new file has to be created, it's access permissions are
	 * inherited from the parent directory.
	 *
	 * @param path Path to the file to be touched.
	 * @return @p true on success or @p false if an error occurred.
	 */
	public static function touch($path)
	{
		if(LocalURIManager::callFunctionByURI('url_stat', $path, array(STREAM_URL_STAT_QUIET)) === false)
		{
			if(!(LocalURIManager::callFunctionByURI('permissions', dirname($path)) & PERMISSION_APPEND))
			{
				return false;
			}
		}
		else
		{
			if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_APPEND))
			{
				return false;
			}
		}

		if(($handle = LocalURIManager::callFunctionByURI('stream_open', $path, array('a'), true)) === false)
		{
			return false;
		}
		LocalURIManager::callFunctionByHandle('stream_close', $path, array(), true);

		return true;
	}

	/**
	 * Removes a file.
	 *
	 * @param path Path to the file to be removed.
	 * @return @p true on success or @p false if an error occurred.
	 */
	public static function unlink($path)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_DELETE))
		{
			return false;
		}

		return LocalURIManager::callFunctionByURI('unlink', $path);
	}

	/**
	 * Renames or moves a file or directory.
	 *
	 * @note Moving files between different URI namespaces is not supported.
	 *
	 * @param pathFrom   The file or directory to move.
	 * @param pathTo     The path to move the file or directory to.
	 * @param realPathTo You can give this argument to know what the real location
	 *                   of the entry is after it has been moved.
	 * @return @p true on success or @p false if an error occurred.
	 */
	public static function rename($pathFrom, $pathTo, &$realPathTo = '')
	{
		if(String::substringBefore($pathFrom, '://') != String::substringBefore($pathTo, '://') ||
		   String::startsWith(String::simplifyPath($pathTo), String::simplifyPath($pathFrom)))
		{
			return false;
		}

		if(!(LocalURIManager::callFunctionByURI('permissions', $pathFrom) & PERMISSION_DELETE))
		{
			return false;
		}

		$stats = LocalURIManager::callFunctionByURI('url_stat', $pathTo, array(STREAM_URL_STAT_QUIET));
		if(($stats !== false && $stats['mode'] & 0040000) == false)
		{
			if(!(LocalURIManager::callFunctionByURI('permissions', $pathTo) & PERMISSION_MODIFY))
			{
				return false;
			}
		}
		else
		{
			if(!(LocalURIManager::callFunctionByURI('permissions', $pathTo) & PERMISSION_APPEND))
			{
				return false;
			}

			$realPathTo = self::uniquePath($pathTo, basename($pathFrom));
		}

		if(LocalURIManager::callFunctionByURI('rename', $pathFrom, array($realPathTo)) == true)
		{
			return true;
		}

		$realPathTo = $pathFrom;
		return false;
	}

	/**
	 * Opens a directory and returns a handle for use with subsequent
	 * readdir(), rewinddir() and closedir() calls.
	 *
	 * @param path Path to the directory to be opened.
	 * @return A handle to the open directory, or @p false if an error
	 *         occurred.
	 */
	public static function opendir($path)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_READ))
		{
			return false;
		}

		return LocalURIManager::callFunctionByURI('dir_opendir', $path, array(0), true);
	}

	/**
	 * Returns an entry from the passed directory handle.
	 *
	 * @param handle A valid directory handle as returned by URI::opendir().
	 * @return The name of the next entry in the open directory, or @p false
	 *         if an error occurred.
	 */
	public static function readdir($handle)
	{
		return LocalURIManager::callFunctionByHandle('dir_readdir', $handle);
	}

	/**
	 * Rewinds the directory handle so the next readdir() call will return
	 * the first entry again.
	 *
	 * @param handle A valid directory handle as returned by URI::opendir().
	 * @return @p true on success or @p false if an error occurred.
	 */
	public static function rewinddir($handle)
	{
		return LocalURIManager::callFunctionByHandle('dir_rewinddir', $handle);
	}

	/**
	 * Closes the directory handle. This will free the resources associated
	 * with the handle and the handle is no longer valid.
	 *
	 * @param handle A valid directory handle as returned by URI::opendir().
	 * @return @p true on success or @p false if an error occurred.
	 */
	public static function closedir($handle)
	{
		return LocalURIManager::callFunctionByHandle('dir_closedir', $handle, array(), true);
	}

	/**
	 * Returns all entries in a directory. Basically it opens the directory,
	 * reads all entries, closes it and returns the collected entries in an
	 * array.
	 *
	 * @param path Path to the directory to be opened.
	 * @return An array of strings containing all entries in the directory,
	 *         or @p false if an error occurred.
	 */
	public static function entries($path)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_READ))
		{
			return false;
		}

		if(($handle = LocalURIManager::callFunctionByURI('dir_opendir', $path, array(0), true)) === false)
		{
			return false;
		}

		$entries = array();
		while(($entry = LocalURIManager::callFunctionByHandle('dir_readdir', $handle)) !== false)
		{
			$entries[] = $entry;
		}
		LocalURIManager::callFunctionByHandle('dir_closedir', $handle, array(), true);

		return $entries;
	}

	/**
	 * Returns statistics about a file or directory.
	 *
	 * @param path Path to the file or directory to be examined.
	 * @return An array containing stats information. See the
	 *         <a href="http://nl2.php.net/manual/en/function.stat.php">PHP stat() documentation</a>
	 *         for details.
	 */
	public static function stat($path)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_READ))
		{
			return false;
		}

		return LocalURIManager::callFunctionByURI('url_stat', $path, array(0));
	}

	/**
	 * Returns whether the location pointed to by the given URI exists.
	 *
	 * @param path Path to check for its existence
	 * @return @p true if the URI exists, @p false otherwise.
	 */
	public static function fileExists($path)
	{
		$stats = LocalURIManager::callFunctionByURI('url_stat', $path, array(STREAM_URL_STAT_QUIET));
		return ($stats === false ? false : true);
	}

	/**
	 * Returns whether the given path points to a directory or not.
	 *
	 * @param path Path to check whether it's a directory.
	 * @return @p true if it's a directory, @p false otherwise.
	 */
	public static function isDir($path)
	{
		$stats = LocalURIManager::callFunctionByURI('url_stat', $path, array(STREAM_URL_STAT_QUIET));
		return (($stats !== false && $stats['mode'] & 0040000) ? true : false);
	}

	/**
	 * Returns whether the given path points to a file or not.
	 *
	 * @param path Path to check whether it's a file.
	 * @return @p true if it's a file, @p false otherwise.
	 */
	public static function isFile($path)
	{
		$stats = LocalURIManager::callFunctionByURI('url_stat', $path, array(STREAM_URL_STAT_QUIET));
		return (($stats !== false && $stats['mode'] & 0100000) ? true : false);
	}

	/**
	 * Returns whether the given path points to a symbolic link or not.
	 *
	 * @param path Path to check whether it's a symlink.
	 * @return @p true if it's a symlink, @p false otherwise.
	 */
	public static function isLink($path)
	{
		$stats = LocalURIManager::callFunctionByURI('url_stat', $path, array(STREAM_URL_STAT_QUIET));
		return ($stats !== false && $stats['mode'] == 0120000 ? true : false);
	}

	/**
	 * Returns meta-data about the given path.
	 *
	 * @note Many namespaces use the "name" meta-data key to identify the
	 *       display name of the file or directory. This display name should
	 *       take precedence over the real name when showing the path to the
	 *       user.
	 *
	 * @param path    Path to the file or directory to get the meta-data
	 *                from.
	 * @param key     The meta-data key to request.
	 * @param default The default value returned if the key does not exist.
	 * @return The meta-data associated with the path under the given key,
	 *         or @p default if an error occurred.
	 *
	 * @sa setMetaData()
	 */
	public static function metaData($path, $key, $default = false)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_READ))
		{
			return $default;
		}

		$meta = LocalURIManager::callFunctionByURI('metaData', $path, array($key));
		return ($meta === false ? $default : $meta);
	}

	/**
	 * Sets meta-data on the given path.
	 *
	 * @param path      Path to the file or directory to set the meta-data on.
	 * @param key       The meta-data key to set.
	 * @param value     The value of the meta-data to set.
	 * @param recursive If this parameter is @p true, then all files and
	 *                  directories under this directory will get assigned the
	 *                  same meta key. This parameter has no effect on files.
	 *                  This parameter was introduced in Aukyla 1.1.
	 * @return @p true on success or @p false if an error occurred.
	 *
	 * @sa metaData()
	 */
	public static function setMetaData($path, $key, $value = false, $recursive = false)
	{
		if(!(LocalURIManager::callFunctionByURI('permissions', $path) & PERMISSION_MODIFY))
		{
			return false;
		}

		$result = LocalURIManager::callFunctionByURI('setMetaData', $path, array($key, $value));

		if($recursive == true)
		{
			if(($handle = LocalURIManager::callFunctionByURI('dir_opendir', $path, array(0), true)) === false)
			{
				return $result;
			}

			while(($entry = LocalURIManager::callFunctionByHandle('dir_readdir', $handle)) !== false)
			{
				if($this->setMetaData($entry, $key, $value, true) == false)
				{
					$result = false;
				}
			}
			LocalURIManager::callFunctionByHandle('dir_closedir', $handle, array(), true);
		}

		return $result;
	}
}

/**
 * @brief A namespace to be used with the LocalURIManager.
 *
 * You should subclass this class to implement your own URI namespaces. You may
 * either use LocalURIManager::registerURINamespace() to register your namespace
 * with the URI manager after which it may be used, or you can put a symlink to
 * the file in which the namespace is declared in @p plugins/URINamespaces and
 * the namespace will be registered automatically when needed. Note that your
 * class should be named something like @p FooURINamespace and the symlink
 * should have the name of the class, with @p (.php) extension.
 *
 * @note Besides using this class for URI namespaces, you may also implement
 *       your own class as long as you strictly follow the rules for
 *       <a href="http://nl2.php.net/manual/en/function.stream-wrapper-register.php">PHP URL wrappers</a>,
 *       but be aware PHP URL wrappers are less flexible and lack some features,
 *       like access permissions and meta-data.
 *
 * @note With the exception of stream_open() and stream_close() all functions
 *       have a default implementation. If you do not reimplement some or all of
 *       these functions, your namespace will simply not support the features
 *       provided by those functions and no errors will be raised.
 */
abstract class LocalURINamespace
{
	/**
	 * @copydoc LocalURINamespace::stream_close()
	 *
	 * @note The PHP documentation tells you to use the @p include_path if
	 *       the @p STREAM_USE_PATH flag is set. However, unless you are
	 *       implementing a @p file namespace (which you shouldn't), you can
	 *       ignore this.
	 */
	abstract public function stream_open($path, $mode, $options = 0, &$opened_path = '');

	/**
	 * Please refer to the
	 * <a href="http://nl2.php.net/manual/en/function.stream-wrapper-register.php">PHP URL wrapper documentation</a>.
	 */
	abstract public function stream_close();

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 *
	 * @warning When implementing this function, make sure the file you are
	 *          operating on has actually been opened for reading and return
	 *          @p false if it hasn't.
	 */
	public function stream_read($count)
	{
		return false;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 *
	 * @warning When implementing this function, make sure the file you are
	 *          operating on has actually been opened for writing and return
	 *          @p false if it hasn't.
	 */
	public function stream_write($data)
	{
		return 0;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function stream_eof()
	{
		return true;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function stream_tell()
	{
		return 0;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function stream_seek($offset, $whence)
	{
		return false;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function stream_flush()
	{
		return true;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function stream_stat()
	{
		return array('dev'     =>  0,
		             'ino'     =>  0,
		             'mode'    =>  0,
		             'nlink'   =>  0,
		             'uid'     =>  0,
		             'gid'     =>  0,
		             'rdev'    => -1,
		             'size'    =>  0,
		             'atime'   =>  0,
		             'mtime'   =>  0,
		             'ctime'   =>  0,
		             'blksize' => -1,
		             'blocks'  =>  0);
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function unlink($path)
	{
		return false;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function rename($path_from, $path_to)
	{
		return false;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function mkdir($path, $mode, $options)
	{
		return false;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function rmdir($path, $options)
	{
		return false;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function dir_opendir($path, $options)
	{
		return false;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function url_stat($path, $flags)
	{
		return array('dev'     =>  0,
		             'ino'     =>  0,
		             'mode'    =>  0,
		             'nlink'   =>  0,
		             'uid'     =>  0,
		             'gid'     =>  0,
		             'rdev'    => -1,
		             'size'    =>  0,
		             'atime'   =>  0,
		             'mtime'   =>  0,
		             'ctime'   =>  0,
		             'blksize' => -1,
		             'blocks'  =>  0);
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function dir_readdir()
	{
		return false;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function dir_rewinddir()
	{
		return false;
	}

	/**
	 * @copydoc LocalURINamespace::stream_close()
	 */
	public function dir_closedir()
	{
		return false;
	}

	/**
	 * Returns a unique path under the given directory.
	 *
	 * @param path       Path to the directory in which to find a unique
	 *                   file or directory name.
	 * @param suggestion A suggestion for the pathname. The resulting
	 *                   pathname may include or use this suggestion, but it
	 *                   may also be entirely ignored.
	 * @return A unique path or @p false if an error occurred.
	 */
	public function uniquePath($path, $suggestion)
	{
		return false;
	}

	/**
	 * Returns the access permissions for the current user to the given
	 * path. See permissionsArray() for a detailed explanation about how
	 * permissions should be interpreted.
	 *
	 * @warning The default implementation does not support access
	 *          permissions and thus grants the user all permissions.
	 *
	 * @param path Path to the file or directory to get the access
	 *             permissions from.
	 * @return The permissions using the predefined PERMISSION_* constants,
	 *         OR'd together, or @p false if the path doesn't exist.
	 *
	 * @sa permissionsArray()
	 */
	public function permissions($path)
	{
		return ($this->url_stat($path, STREAM_URL_STAT_QUIET) === false ? false :
		        (PERMISSION_READ |
		         PERMISSION_APPEND |
		         PERMISSION_MODIFY |
		         PERMISSION_DELETE |
		         PERMISSION_ADMINISTRATE));
	}

	/**
	 * Returns all access permissions set on the given path in an array.
	 *
	 * @warning The default implementation does not support access
	 *          permissions and thus grants everyone all permissions.
	 *
	 * @param path Path to the file or directory to get the access
	 *             permissions from.
	 * @returns The current permissions. This is an array containing any out
	 *          of three keys: "ip", "user", "group" and "other", where ip,
	 *          user and group are arrays containing ip/user/group => permissions
	 *          pairs and where other contains the permissions for other
	 *          users. Permissions are specified with the PERMISSION_*
	 *          constants, OR'd together. Returns @p false on error.
	 *
	 * <strong>Example array: </strong>
	 * @code
	 * Array
	 * (
	 *     [ip] => Array
	 *             (
	 *                 ["192.168.0.*"] => PERMISSION_READ
	 *             )
	 *     [user] => Array
	 *               (
	 *                   [junior] => PERMISSION_READ |
	 *                               PERMISSION_APPEND |
	 *                               PERMISSION_MODIFY |
	 *                               PERMISSION_DELETE |
	 *                               PERMISSION_ADMINISTRATE
	 *               )
	 *     [group] => Array
	 *                (
	 *                    [users] => PERMISSION_READ
	 *                )
	 *     [other] => PERMISSION_NONE
	 * )
	 * @endcode
	 *
	 * In this example, anyone who connects from an internal IP address, in this
	 * case an address which starts with "192.168.0.", will at least be granted
	 * read permissions. Anyone outside of this IP range will get no permissions
	 * by default. Then the user is checked, so if the current user is "junior"
	 * all permissions will be granted, regardless from where he connects. If the
	 * user is not "junior", the group is checked". If the current user is a
	 * member of the group "users", read permissions are granted. This means
	 * anyone in this group will also be able to read documents when he's not
	 * connecting from the internal network. If the current user is neither
	 * "junior", nor a member of the group "users", no more permissions are
	 * granted, leaving internal users with only read permissions and external
	 * users without any permissions.
	 *
	 * @note Support for IP ranges was introduced in Aukyla 1.1.
	 *
	 * @sa permissions() setPermissionsArray()
	 */
	public function permissionsArray($path)
	{
		return ($this->url_stat($path, STREAM_URL_STAT_QUIET) === false ? false :
		        array('other' => (PERMISSION_READ |
		                          PERMISSION_APPEND |
		                          PERMISSION_MODIFY |
		                          PERMISSION_DELETE |
		                          PERMISSION_ADMINISTRATE)));
	}

	/**
	 * Sets the access permissions on the given path.
	 *
	 * @param path        Path to the file or directory to set the
	 *                    permissions on.
	 * @param permissions The new permissions. See permissionsArray() for a
	 *                    description of the permissions array.
	 * @return @p true on success, @p false if an error occurred.
	 *
	 * @sa permissionsArray()
	 */
	public function setPermissionsArray($path, $permissions)
	{
		return false;
	}

	/**
	 * Returns meta-data about the given path.
	 *
	 * @note Many namespaces use the "name" meta-data key to identify the
	 *       display name of the file or directory. This display name should
	 *       take precedence over the real name when showing the path to the
	 *       user.
	 *
	 * @param path Path to the file or directory to get the meta-data from.
	 * @param key  The meta-data key to request.
	 * @return The meta-data associated with the path under the given key,
	 *         or @p false if the key does not exist.
	 *
	 * @sa setMetaData()
	 */
	public function metaData($path, $key)
	{
		return false;
	}

	/**
	 * Sets meta-data on the given path.
	 *
	 * @param path  Path to the file or directory to set the meta-data on.
	 * @param key   The meta-data key to set.
	 * @param value The value of the meta-data to set.
	 * @return @p true on success or @p false on failure.
	 *
	 * @sa metaData()
	 */
	public function setMetaData($path, $key, $value)
	{
		return false;
	}
}
Return current item: Aukyla Document Management System