<?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;
}
}