You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
923 lines
27 KiB
923 lines
27 KiB
7 years ago
|
<?php
|
||
|
/**
|
||
|
* Part of the Fuel framework.
|
||
|
*
|
||
|
* @package Fuel
|
||
|
* @version 1.8
|
||
|
* @author Fuel Development Team
|
||
|
* @license MIT License
|
||
|
* @copyright 2010 - 2016 Fuel Development Team
|
||
|
* @link http://fuelphp.com
|
||
|
*/
|
||
|
|
||
|
namespace Fuel\Core;
|
||
|
|
||
|
class FileAccessException extends \FuelException {}
|
||
|
class OutsideAreaException extends \OutOfBoundsException {}
|
||
|
class InvalidPathException extends \FileAccessException {}
|
||
|
|
||
|
// ------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* File Class
|
||
|
*
|
||
|
* @package Fuel
|
||
|
* @subpackage Core
|
||
|
* @category Core
|
||
|
*/
|
||
|
class File
|
||
|
{
|
||
|
/**
|
||
|
* @var array loaded area's
|
||
|
*/
|
||
|
protected static $areas = array();
|
||
|
|
||
|
public static function _init()
|
||
|
{
|
||
|
\Config::load('file', true);
|
||
|
|
||
|
// make sure the configured chmod values are octal
|
||
|
$chmod = \Config::get('file.chmod.folders', 0777);
|
||
|
is_string($chmod) and \Config::set('file.chmod.folders', octdec($chmod));
|
||
|
$chmod = \Config::get('file.chmod.files', 0666);
|
||
|
is_string($chmod) and \Config::set('file.chmod.files', octdec($chmod));
|
||
|
|
||
|
static::$areas[null] = \File_Area::forge(\Config::get('file.base_config', array()));
|
||
|
|
||
|
foreach (\Config::get('file.areas', array()) as $name => $config)
|
||
|
{
|
||
|
static::$areas[$name] = \File_Area::forge($config);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function forge(array $config = array())
|
||
|
{
|
||
|
return \File_Area::forge($config);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Instance
|
||
|
*
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return File_Area
|
||
|
*/
|
||
|
public static function instance($area = null)
|
||
|
{
|
||
|
if ($area instanceof File_Area)
|
||
|
{
|
||
|
return $area;
|
||
|
}
|
||
|
|
||
|
$instance = array_key_exists($area, static::$areas) ? static::$areas[$area] : false;
|
||
|
|
||
|
if ($instance === false)
|
||
|
{
|
||
|
throw new \InvalidArgumentException('There is no file instance named "'.$area.'".');
|
||
|
}
|
||
|
|
||
|
return $instance;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* File & directory objects factory
|
||
|
*
|
||
|
* @param string $path path to the file or directory
|
||
|
* @param array $config configuration items
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return File_Handler_File
|
||
|
*/
|
||
|
public static function get($path, array $config = array(), $area = null)
|
||
|
{
|
||
|
return static::instance($area)->get_handler($path, $config);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the url.
|
||
|
*
|
||
|
* @param string $path
|
||
|
* @param array $config
|
||
|
* @param null $area
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function get_url($path, array $config = array(), $area = null)
|
||
|
{
|
||
|
return static::get($path, $config, $area)->get_url();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check for file existence
|
||
|
*
|
||
|
* @param string $path path to file to check
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function exists($path, $area = null)
|
||
|
{
|
||
|
$path = rtrim(static::instance($area)->get_path($path), '\\/');
|
||
|
|
||
|
// resolve symlinks
|
||
|
while ($path and is_link($path))
|
||
|
{
|
||
|
$path = readlink($path);
|
||
|
}
|
||
|
|
||
|
return is_file($path);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create a file
|
||
|
*
|
||
|
* @param string $basepath directory where to create file
|
||
|
* @param string $name filename
|
||
|
* @param null $contents contents of file
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return bool
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function create($basepath, $name, $contents = null, $area = null)
|
||
|
{
|
||
|
$basepath = rtrim(static::instance($area)->get_path($basepath), '\\/').DS;
|
||
|
$new_file = static::instance($area)->get_path($basepath.$name);
|
||
|
|
||
|
if ( ! is_dir($basepath) or ! is_writable($basepath))
|
||
|
{
|
||
|
throw new \InvalidPathException('Invalid basepath: "'.$basepath.'", cannot create file at this location.');
|
||
|
}
|
||
|
elseif (is_file($new_file))
|
||
|
{
|
||
|
throw new \FileAccessException('File: "'.$new_file.'" already exists, cannot be created.');
|
||
|
}
|
||
|
|
||
|
$file = static::open_file(@fopen($new_file, 'c'), true, $area);
|
||
|
fwrite($file, $contents);
|
||
|
static::close_file($file, $area);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create an empty directory
|
||
|
*
|
||
|
* @param string directory where to create new dir
|
||
|
* @param string dirname
|
||
|
* @param int (octal) file permissions
|
||
|
* @param string|File_Area|null file area name, object or null for non-specific
|
||
|
* @return bool
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function create_dir($basepath, $name, $chmod = null, $area = null)
|
||
|
{
|
||
|
$basepath = rtrim(static::instance($area)->get_path($basepath), '\\/').DS;
|
||
|
$new_dir = static::instance($area)->get_path($basepath.trim($name, '\\/'));
|
||
|
is_null($chmod) and $chmod = \Config::get('file.chmod.folders', 0777);
|
||
|
|
||
|
if ( ! is_dir($basepath) or ! is_writable($basepath))
|
||
|
{
|
||
|
throw new \InvalidPathException('Invalid basepath: "'.$basepath.'", cannot create directory at this location.');
|
||
|
}
|
||
|
elseif (is_dir($new_dir))
|
||
|
{
|
||
|
throw new \FileAccessException('Directory: "'.$new_dir.'" exists already, cannot be created.');
|
||
|
}
|
||
|
|
||
|
// unify the path separators, and get the part we need to add to the basepath
|
||
|
$new_dir = str_replace(array('\\', '/'), DS, $new_dir);
|
||
|
|
||
|
// recursively create the directory. we can't use mkdir permissions or recursive
|
||
|
// due to the fact that mkdir is restricted by the current users umask
|
||
|
$path = '';
|
||
|
foreach (explode(DS, $new_dir) as $dir)
|
||
|
{
|
||
|
// some security checking
|
||
|
if ($dir == '.' or $dir == '..')
|
||
|
{
|
||
|
throw new \FileAccessException('Directory to be created contains illegal segments.');
|
||
|
}
|
||
|
|
||
|
$path .= DS.$dir;
|
||
|
if ( ! is_dir($path))
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if ( ! mkdir($path))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
chmod($path, $chmod);
|
||
|
}
|
||
|
catch (\PHPErrorException $e)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read file
|
||
|
*
|
||
|
* @param string $path file to read
|
||
|
* @param bool $as_string whether to use readfile() or file_get_contents()
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return IO|string file contents
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function read($path, $as_string = false, $area = null)
|
||
|
{
|
||
|
$path = static::instance($area)->get_path($path);
|
||
|
|
||
|
if ( ! is_file($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Cannot read file: "'.$path.'", file does not exists.');
|
||
|
}
|
||
|
|
||
|
$file = static::open_file(@fopen($path, 'r'), LOCK_SH, $area);
|
||
|
$return = $as_string ? file_get_contents($path) : readfile($path);
|
||
|
static::close_file($file, $area);
|
||
|
|
||
|
return $return;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read directory
|
||
|
*
|
||
|
* @param string $path directory to read
|
||
|
* @param int $depth depth to recurse directory, 1 is only current and 0 or smaller is unlimited
|
||
|
* @param Array|null $filter array of partial regexps or non-array for default
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return array
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function read_dir($path, $depth = 0, $filter = null, $area = null)
|
||
|
{
|
||
|
$path = rtrim(static::instance($area)->get_path($path), '\\/').DS;
|
||
|
|
||
|
if ( ! is_dir($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Invalid path: "'.$path.'", directory cannot be read.');
|
||
|
}
|
||
|
|
||
|
if ( ! $fp = @opendir($path))
|
||
|
{
|
||
|
throw new \FileAccessException('Could not open directory: "'.$path.'" for reading.');
|
||
|
}
|
||
|
|
||
|
// Use default when not set
|
||
|
if ( ! is_array($filter))
|
||
|
{
|
||
|
$filter = array('!^\.');
|
||
|
if ($extensions = static::instance($area)->extensions())
|
||
|
{
|
||
|
foreach($extensions as $ext)
|
||
|
{
|
||
|
$filter[] = '\.'.$ext.'$';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$files = array();
|
||
|
$dirs = array();
|
||
|
$new_depth = $depth - 1;
|
||
|
|
||
|
while (false !== ($file = readdir($fp)))
|
||
|
{
|
||
|
// Remove '.', '..'
|
||
|
if (in_array($file, array('.', '..')))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
// use filters when given
|
||
|
elseif ( ! empty($filter))
|
||
|
{
|
||
|
$continue = false; // whether or not to continue
|
||
|
$matched = false; // whether any positive pattern matched
|
||
|
$positive = false; // whether positive filters are present
|
||
|
foreach($filter as $f => $type)
|
||
|
{
|
||
|
if (is_numeric($f))
|
||
|
{
|
||
|
// generic rule
|
||
|
$f = $type;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// type specific rule
|
||
|
$is_file = is_file($path.$file);
|
||
|
if (($type === 'file' and ! $is_file) or ($type !== 'file' and $is_file))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$not = substr($f, 0, 1) === '!'; // whether it's a negative condition
|
||
|
$f = $not ? substr($f, 1) : $f;
|
||
|
// on negative condition a match leads to a continue
|
||
|
if (($match = preg_match('/'.$f.'/uiD', $file) > 0) and $not)
|
||
|
{
|
||
|
$continue = true;
|
||
|
}
|
||
|
|
||
|
$positive = $positive ?: ! $not; // whether a positive condition was encountered
|
||
|
$matched = $matched ?: ($match and ! $not); // whether one of the filters has matched
|
||
|
}
|
||
|
|
||
|
// continue when negative matched or when positive filters and nothing matched
|
||
|
if ($continue or $positive and ! $matched)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (@is_dir($path.$file))
|
||
|
{
|
||
|
// Use recursion when depth not depleted or not limited...
|
||
|
if ($depth < 1 or $new_depth > 0)
|
||
|
{
|
||
|
$dirs[$file.DS] = static::read_dir($path.$file.DS, $new_depth, $filter, $area);
|
||
|
}
|
||
|
// ... or set dir to false when not read
|
||
|
else
|
||
|
{
|
||
|
$dirs[$file.DS] = false;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$files[] = $file;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
closedir($fp);
|
||
|
|
||
|
// sort dirs & files naturally and return array with dirs on top and files
|
||
|
uksort($dirs, 'strnatcasecmp');
|
||
|
natcasesort($files);
|
||
|
return array_merge($dirs, $files);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update a file
|
||
|
*
|
||
|
* @param string $basepath directory where to write the file
|
||
|
* @param string $name filename
|
||
|
* @param string $contents contents of file
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return bool
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function update($basepath, $name, $contents = null, $area = null)
|
||
|
{
|
||
|
$basepath = rtrim(static::instance($area)->get_path($basepath), '\\/').DS;
|
||
|
$new_file = static::instance($area)->get_path($basepath.$name);
|
||
|
|
||
|
if ( ! $file = static::open_file(@fopen($new_file, 'w'), true, $area))
|
||
|
{
|
||
|
if ( ! is_dir($basepath) or ! is_writable($basepath))
|
||
|
{
|
||
|
throw new \InvalidPathException('Invalid basepath: "'.$basepath.'", cannot update a file at this location.');
|
||
|
}
|
||
|
|
||
|
throw new \FileAccessException('No write access to: "'.$basepath.'", cannot update a file.');
|
||
|
}
|
||
|
|
||
|
fwrite($file, $contents);
|
||
|
static::close_file($file, $area);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Append to a file
|
||
|
*
|
||
|
* @param string $basepath directory where to write the file
|
||
|
* @param string $name filename
|
||
|
* @param string $contents contents of file
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return bool
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function append($basepath, $name, $contents = null, $area = null)
|
||
|
{
|
||
|
$basepath = rtrim(static::instance($area)->get_path($basepath), '\\/').DS;
|
||
|
$new_file = static::instance($area)->get_path($basepath.$name);
|
||
|
|
||
|
if ( ! is_file($new_file))
|
||
|
{
|
||
|
throw new \FileAccessException('File: "'.$new_file.'" does not exist, cannot be appended.');
|
||
|
}
|
||
|
|
||
|
if ( ! $file = static::open_file(@fopen($new_file, 'a'), true, $area))
|
||
|
{
|
||
|
if ( ! is_dir($basepath) or ! is_writable($basepath))
|
||
|
{
|
||
|
throw new \InvalidPathException('Invalid basepath: "'.$basepath.'", cannot append to a file at this location.');
|
||
|
}
|
||
|
|
||
|
throw new \FileAccessException('No write access, cannot append to the file: "'.$file.'".');
|
||
|
}
|
||
|
|
||
|
fwrite($file, $contents);
|
||
|
static::close_file($file, $area);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the octal permissions for a file or directory
|
||
|
*
|
||
|
* @param string $path path to the file or directory
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return string octal file permissions
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function get_permissions($path, $area = null)
|
||
|
{
|
||
|
$path = static::instance($area)->get_path($path);
|
||
|
|
||
|
if ( ! file_exists($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Path: "'.$path.'" is not a directory or a file, cannot get permissions.');
|
||
|
}
|
||
|
|
||
|
return substr(sprintf('%o', fileperms($path)), -4);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a file's or directory's created or modified timestamp.
|
||
|
*
|
||
|
* @param string $path path to the file or directory
|
||
|
* @param string $type modified or created
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return int Unix Timestamp
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function get_time($path, $type = 'modified', $area = null)
|
||
|
{
|
||
|
$path = static::instance($area)->get_path($path);
|
||
|
|
||
|
if ( ! file_exists($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Path: "'.$path.'" is not a directory or a file, cannot get creation timestamp.');
|
||
|
}
|
||
|
|
||
|
if ($type === 'modified')
|
||
|
{
|
||
|
return filemtime($path);
|
||
|
}
|
||
|
elseif ($type === 'created')
|
||
|
{
|
||
|
return filectime($path);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
throw new \UnexpectedValueException('File::time $type must be "modified" or "created".');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a file's size.
|
||
|
*
|
||
|
* @param string $path path to the file or directory
|
||
|
* @param mixed $area file area name, object or null for base area
|
||
|
* @return int the file's size in bytes
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function get_size($path, $area = null)
|
||
|
{
|
||
|
$path = static::instance($area)->get_path($path);
|
||
|
|
||
|
if ( ! file_exists($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Path: "'.$path.'" is not a directory or a file, cannot get size.');
|
||
|
}
|
||
|
|
||
|
return filesize($path);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rename directory or file
|
||
|
*
|
||
|
* @param string $path path to file or directory to rename
|
||
|
* @param string $new_path new path (full path, can also cause move)
|
||
|
* @param string|File_Area|null $source_area source path file area name, object or null for non-specific
|
||
|
* @param string|File_Area|null $target_area target path file area name, object or null for non-specific. Defaults to source_area if not set.
|
||
|
* @return bool
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function rename($path, $new_path, $source_area = null, $target_area = null)
|
||
|
{
|
||
|
$path = static::instance($source_area)->get_path($path);
|
||
|
$new_path = static::instance($target_area ?: $source_area)->get_path($new_path);
|
||
|
|
||
|
return rename($path, $new_path);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Alias for rename(), not needed but consistent with other methods
|
||
|
*
|
||
|
* @param string $path path to directory to rename
|
||
|
* @param string $new_path new path (full path, can also cause move)
|
||
|
* @param string|File_Area|null $source_area source path file area name, object or null for non-specific
|
||
|
* @param string|File_Area|null $target_area target path file area name, object or null for non-specific. Defaults to source_area if not set.
|
||
|
* @return bool
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function rename_dir($path, $new_path, $source_area = null, $target_area = null)
|
||
|
{
|
||
|
return static::rename($path, $new_path, $source_area, $target_area);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Copy file
|
||
|
*
|
||
|
* @param string path path to file to copy
|
||
|
* @param string new_path new base directory (full path)
|
||
|
* @param string|File_Area|null source_area source path file area name, object or null for non-specific
|
||
|
* @param string|File_Area|null target_area target path file area name, object or null for non-specific. Defaults to source_area if not set.
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function copy($path, $new_path, $source_area = null, $target_area = null)
|
||
|
{
|
||
|
$path = static::instance($source_area)->get_path($path);
|
||
|
$new_path = static::instance($target_area ?: $source_area)->get_path($new_path);
|
||
|
|
||
|
if ( ! is_file($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Cannot copy file: given path: "'.$path.'" is not a file.');
|
||
|
}
|
||
|
elseif (file_exists($new_path))
|
||
|
{
|
||
|
throw new \FileAccessException('Cannot copy file: new path: "'.$new_path.'" already exists.');
|
||
|
}
|
||
|
return copy($path, $new_path);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Copy directory
|
||
|
*
|
||
|
* @param string $path path to directory which contents will be copied
|
||
|
* @param string $new_path new base directory (full path)
|
||
|
* @param string|File_Area|null $source_area source path file area name, object or null for non-specific
|
||
|
* @param string|File_Area|null $target_area target path file area name, object or null for non-specific. Defaults to source_area if not set.
|
||
|
* @throws \FileAccessException when something went wrong
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function copy_dir($path, $new_path, $source_area = null, $target_area = null)
|
||
|
{
|
||
|
$target_area = $target_area ?: $source_area;
|
||
|
|
||
|
$path = rtrim(static::instance($source_area)->get_path($path), '\\/').DS;
|
||
|
$new_path = rtrim(static::instance($target_area)->get_path($new_path), '\\/').DS;
|
||
|
|
||
|
if ( ! is_dir($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Cannot copy directory: given path: "'.$path.'" is not a directory: '.$path);
|
||
|
}
|
||
|
elseif ( ! file_exists($new_path))
|
||
|
{
|
||
|
$newpath_dirname = pathinfo($new_path, PATHINFO_DIRNAME);
|
||
|
static::create_dir($newpath_dirname, pathinfo($new_path, PATHINFO_BASENAME), fileperms($newpath_dirname) ?: 0777, $target_area);
|
||
|
}
|
||
|
|
||
|
$files = static::read_dir($path, -1, array(), $source_area);
|
||
|
foreach ($files as $dir => $file)
|
||
|
{
|
||
|
if (is_array($file))
|
||
|
{
|
||
|
$check = static::create_dir($new_path.DS, substr($dir, 0, -1), fileperms($path.$dir) ?: 0777, $target_area);
|
||
|
$check and static::copy_dir($path.$dir.DS, $new_path.$dir, $source_area, $target_area);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$check = static::copy($path.$file, $new_path.$file, $source_area, $target_area);
|
||
|
}
|
||
|
|
||
|
// abort if something went wrong
|
||
|
if ( ! $check)
|
||
|
{
|
||
|
throw new \FileAccessException('Directory copy aborted prematurely, part of the operation failed during copying: '.(is_array($file) ? $dir : $file));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create a new symlink
|
||
|
*
|
||
|
* @param string $path target of symlink
|
||
|
* @param string $link_path destination of symlink
|
||
|
* @param bool $is_file true for file, false for directory
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return bool
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function symlink($path, $link_path, $is_file = true, $area = null)
|
||
|
{
|
||
|
$path = rtrim(static::instance($area)->get_path($path), '\\/');
|
||
|
$link_path = rtrim(static::instance($area)->get_path($link_path), '\\/');
|
||
|
|
||
|
if ($is_file and ! is_file($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Cannot symlink: given file: "'.$path.'" does not exist.');
|
||
|
}
|
||
|
elseif ( ! $is_file and ! is_dir($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Cannot symlink: given directory: "'.$path.'" does not exist.');
|
||
|
}
|
||
|
elseif (file_exists($link_path))
|
||
|
{
|
||
|
throw new \FileAccessException('Cannot symlink: link: "'.$link_path.'" already exists.');
|
||
|
}
|
||
|
|
||
|
return symlink($path, $link_path);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete file
|
||
|
*
|
||
|
* @param string $path path to file to delete
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return bool
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function delete($path, $area = null)
|
||
|
{
|
||
|
$path = rtrim(static::instance($area)->get_path($path), '\\/');
|
||
|
|
||
|
if ( ! is_file($path) and ! is_link($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Cannot delete file: given path "'.$path.'" is not a file.');
|
||
|
}
|
||
|
|
||
|
return unlink($path);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete directory
|
||
|
*
|
||
|
* @param string $path path to directory to delete
|
||
|
* @param bool $recursive whether to also delete contents of subdirectories
|
||
|
* @param bool $delete_top whether to delete the parent dir itself when empty
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return bool
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function delete_dir($path, $recursive = true, $delete_top = true, $area = null)
|
||
|
{
|
||
|
$path = rtrim(static::instance($area)->get_path($path), '\\/').DS;
|
||
|
if ( ! is_dir($path))
|
||
|
{
|
||
|
throw new \InvalidPathException('Cannot delete directory: given path: "'.$path.'" is not a directory.');
|
||
|
}
|
||
|
|
||
|
$files = static::read_dir($path, -1, array(), $area);
|
||
|
|
||
|
$not_empty = false;
|
||
|
$check = true;
|
||
|
foreach ($files as $dir => $file)
|
||
|
{
|
||
|
if (is_array($file))
|
||
|
{
|
||
|
if ($recursive)
|
||
|
{
|
||
|
$check = static::delete_dir($path.$dir, true, true, $area);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$not_empty = true;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$check = static::delete($path.$file, $area);
|
||
|
}
|
||
|
|
||
|
// abort if something went wrong
|
||
|
if ( ! $check)
|
||
|
{
|
||
|
throw new \FileAccessException('Directory deletion aborted prematurely, part of the operation failed.');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ! $not_empty and $delete_top)
|
||
|
{
|
||
|
return rmdir($path);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Open and lock file
|
||
|
*
|
||
|
* @param resource|string $path file resource or path
|
||
|
* @param constant|bool $lock either valid lock constant or true=LOCK_EX / false=LOCK_UN
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return bool|resource
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function open_file($path, $lock = true, $area = null)
|
||
|
{
|
||
|
if (is_string($path))
|
||
|
{
|
||
|
$path = static::instance($area)->get_path($path);
|
||
|
$resource = fopen($path, 'r+');
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$resource = $path;
|
||
|
}
|
||
|
|
||
|
// Make sure the parameter is a valid resource
|
||
|
if ( ! is_resource($resource))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If locks aren't used, don't lock
|
||
|
if ( ! static::instance($area)->use_locks())
|
||
|
{
|
||
|
return $resource;
|
||
|
}
|
||
|
|
||
|
// Accept valid lock constant or set to LOCK_EX
|
||
|
$lock = in_array($lock, array(LOCK_SH, LOCK_EX, LOCK_NB)) ? $lock : LOCK_EX;
|
||
|
|
||
|
// Try to get a lock, timeout after 5 seconds
|
||
|
$lock_mtime = microtime(true);
|
||
|
while ( ! flock($resource, $lock))
|
||
|
{
|
||
|
if (microtime(true) - $lock_mtime > 5)
|
||
|
{
|
||
|
throw new \FileAccessException('Could not secure file lock, timed out after 5 seconds.');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $resource;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Close file resource & unlock
|
||
|
*
|
||
|
* @param resource $resource open file resource
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
*/
|
||
|
public static function close_file($resource, $area = null)
|
||
|
{
|
||
|
// If locks aren't used, don't unlock
|
||
|
if ( static::instance($area)->use_locks())
|
||
|
{
|
||
|
flock($resource, LOCK_UN);
|
||
|
}
|
||
|
|
||
|
fclose($resource);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get detailed information about a file
|
||
|
*
|
||
|
* @param string $path file path
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @return array
|
||
|
* @throws \FileAccessException
|
||
|
* @throws \InvalidPathException
|
||
|
* @throws \OutsideAreaException
|
||
|
*/
|
||
|
public static function file_info($path, $area = null)
|
||
|
{
|
||
|
$info = array(
|
||
|
'original' => $path,
|
||
|
'realpath' => '',
|
||
|
'dirname' => '',
|
||
|
'basename' => '',
|
||
|
'filename' => '',
|
||
|
'extension' => '',
|
||
|
'mimetype' => '',
|
||
|
'charset' => '',
|
||
|
'size' => 0,
|
||
|
'permissions' => '',
|
||
|
'time_created' => '',
|
||
|
'time_modified' => '',
|
||
|
);
|
||
|
|
||
|
if ( ! $info['realpath'] = static::instance($area)->get_path($path) or ! is_file($info['realpath']))
|
||
|
{
|
||
|
throw new \InvalidPathException('Filename given is not a valid file.');
|
||
|
}
|
||
|
|
||
|
$info = array_merge($info, pathinfo($info['realpath']));
|
||
|
|
||
|
if ( ! $fileinfo = new \finfo(FILEINFO_MIME, \Config::get('file.magic_file', null)))
|
||
|
{
|
||
|
throw new \InvalidArgumentException('Can not retrieve information about this file.');
|
||
|
}
|
||
|
|
||
|
$fileinfo = explode(';', $fileinfo->file($info['realpath']));
|
||
|
|
||
|
$info['mimetype'] = isset($fileinfo[0]) ? $fileinfo[0] : 'application/octet-stream';
|
||
|
|
||
|
if (isset($fileinfo[1]))
|
||
|
{
|
||
|
$fileinfo = explode('=', $fileinfo[1]);
|
||
|
$info['charset'] = isset($fileinfo[1]) ? $fileinfo[1] : '';
|
||
|
}
|
||
|
|
||
|
$info['size'] = static::get_size($info['realpath'], $area);
|
||
|
$info['permissions'] = static::get_permissions($info['realpath'], $area);
|
||
|
$info['time_created'] = static::get_time($info['realpath'], 'created', $area);
|
||
|
$info['time_modified'] = static::get_time($info['realpath'], 'modified', $area);
|
||
|
|
||
|
return $info;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Download a file
|
||
|
*
|
||
|
* @param string $path file path
|
||
|
* @param string|null $name custom name for the file to be downloaded
|
||
|
* @param string|null $mime custom mime type or null for file mime type
|
||
|
* @param string|File_Area|null $area file area name, object or null for base area
|
||
|
* @param bool $delete delete the file after download when true
|
||
|
* @param string $disposition disposition, must be 'attachment' or 'inline'
|
||
|
*/
|
||
|
public static function download($path, $name = null, $mime = null, $area = null, $delete = false, $disposition = 'attachment')
|
||
|
{
|
||
|
$info = static::file_info($path, $area);
|
||
|
$class = get_called_class();
|
||
|
empty($mime) or $info['mimetype'] = $mime;
|
||
|
empty($name) or $info['basename'] = $name;
|
||
|
in_array($disposition, array('inline', 'attachment')) or $disposition = 'attachment';
|
||
|
|
||
|
\Event::register('fuel-shutdown', function () use($info, $area, $class, $delete, $disposition) {
|
||
|
|
||
|
if ( ! $file = call_user_func(array($class, 'open_file'), @fopen($info['realpath'], 'rb'), LOCK_SH, $area))
|
||
|
{
|
||
|
throw new \FileAccessException('Filename given could not be opened for download.');
|
||
|
}
|
||
|
|
||
|
while (ob_get_level() > 0)
|
||
|
{
|
||
|
ob_end_clean();
|
||
|
}
|
||
|
|
||
|
ini_get('zlib.output_compression') and ini_set('zlib.output_compression', 0);
|
||
|
! ini_get('safe_mode') and set_time_limit(0);
|
||
|
|
||
|
header('Content-Type: '.$info['mimetype']);
|
||
|
header('Content-Disposition: '.$disposition.'; filename="'.$info['basename'].'"');
|
||
|
$disposition === 'attachment' and header('Content-Description: File Transfer');
|
||
|
header('Content-Length: '.$info['size']);
|
||
|
header('Content-Transfer-Encoding: binary');
|
||
|
$disposition === 'attachment' and header('Expires: 0');
|
||
|
$disposition === 'attachment' and header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
|
||
|
|
||
|
while( ! feof($file))
|
||
|
{
|
||
|
echo fread($file, 2048);
|
||
|
}
|
||
|
|
||
|
call_user_func(array($class, 'close_file'), $file, $area);
|
||
|
|
||
|
if ($delete)
|
||
|
{
|
||
|
call_user_func(array($class, 'delete'), $info['realpath'], $area);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
exit;
|
||
|
}
|
||
|
|
||
|
}
|