<?php defined('SYSTEM_PATH') or die('No direct access');
/**
* File Functions
*
* Functions that help with handling all types of file system tasks.
*
* @package MicroMVC
* @author David Pennington
* @copyright (c) 2010 MicroMVC Framework
* @license http://micromvc.com/license
********************************** 80 Columns *********************************
*/
/**
* Remove a given file system path from the file/path string.
* If the file/path does not contain the given path - return FALSE.
* @param string $file
* @param string $path
* @return mixed
*/
function remove_path($file, $path = UPLOAD_PATH) {
if(mb_strpos($file, $path) !== FALSE) {
return mb_substr($file, mb_strlen($path));
}
}
/**
* Convert a file system path *UNIX / URL form
* @param string
* @return string
*/
function normalize_path($file = '') {
return str_replace('\\', DS, $file);
}
/**
* unzip a file to a new location
*/
function unzip($file, $new_file) {
if(file_exists($file) AND class_exists('ZipArchive', FALSE))
{
$zip = new ZipArchive;
$zip->open($file);
$zip->extractTo($new_file);
$zip->close();
return TRUE;
}
return FALSE;
}
/**
* Validates that the the given file upload worked, returns an error
* string on failure and FALSE on success.
*
* @param string Field name to check
* @param string Optional name if field name is an array
* @return mixed
*/
function file_upload_error($field = 'userfile', $param = NULL) {
//Get the error from the multiple "file[1,2..]" input or the single "file" input by this name
if( $param && isset($_FILES[$field]['error'][$param]) ) {
$error = $_FILES[$field]['error'][$param];
} elseif ( isset($_FILES[$field]['error']) ) {
$error = $_FILES[$field]['error'];
} else {
$error = UPLOAD_ERR_NO_FILE;
}
//If the error is something OTHER than "OK"
if($error !== UPLOAD_ERR_OK) {
return lang($error);
}
//Get the file from the multiple "file[1,2..]" input or the single "file" input by this name
if( $param && isset($_FILES[$field]['tmp_name'][$param]) ) {
$file = $_FILES[$field]['tmp_name'][$param];
} else if( isset($_FILES[$field]['tmp_name']) ) {
$file = $_FILES[$field]['tmp_name'];
} else {
$file = NULL;
}
//Also check that the file was uploaded via a POST request
if( ! $file OR ! is_uploaded_file($file) ) {
log_message('Possible file upload attack: ' . filename_safe($file));
return lang('upload_invalid_file');
}
return FALSE;
}
/**
* directory
*
* A function to list all files within the specified directory
* and it's sub-directories. This is a recursive function that
* has no limit on the number of levels down you can search.
*
* $data['start_dir'] The directory to start searching from (Required) ("./" = current dir, "../" = up one level)
* $data['good_ext'] The file extensions to allow. (Required) (set to 'array('all') to include everything)
* $data['skip_files'] An array of files to skip. (Required) (empty array if you don't want to skip anything)
* $data['limit'] The limit of dir to search (Required)
* $data['base_dir'] The directory path to remove (Optional)
* $data['type'] Return files or Directories? (Optional) (defaults to BOTH types but can also set to 'dir' or 'file')
* $data['light'] Only return file name and path (Optional) (defaults to false) (true or false)
* $data['array'] Directory results as array (vs string) (Optional) (defaults to false)
*
* @access public
* @param array the search config
* @param int the currenct level
* @return Mixed
*/
if (!function_exists('directory')) {
function directory($data, $level=1) {
//Set optional params to false
foreach(array('type', 'light', 'base_dir', 'array') as $type) {
if(empty($data[$type])) {
$data[$type] = false;
}
}
//If the directory given actually IS a directory
if (is_dir($data['start_dir'])) {
//Then open the directory
$handle = opendir($data['start_dir']);
//Initialize array
$files = array();
//while their are files in the directory...
while (($file = readdir($handle)) !== false) {
//If the file is NOT in the bad file list...
if (!(array_search($file, $data['skip_files']) > -1)) {
//Store the full file path in a var
$path = $data['start_dir']. $file;
//if it is a dir
if (filetype($path) == "dir") {
//add it to our list of dirs
if(!$data['type'] || $data['type'] == 'dir') {
//Add the dir to our list
$files[$path]['file'] = null;
$files[$path]['ext'] = FALSE;
$files[$path]['level'] = $level;
//Remove the base dir from the path?
$files[$path]['dir'] = $data['base_dir'] ? mb_substr($path, mb_strlen($data['base_dir'])) : $path;
//If we are getting detailed data about the directory
if(!$data['light']) {
$files[$path]['stat'] = stat($path);
}
}
//If the dir is NOT deeper than the limit && 'recursive' is set to true
if($data['limit'] > $level){
//Run this function on on the directory to see what is in it (this is where the recursive part starts)
$files2 = directory(array(
'start_dir' => $path. DIRECTORY_SEPARATOR, 'good_ext' => $data['good_ext'],
'skip_files' => $data['skip_files'], 'limit' => $data['limit'],
'type' => $data['type'], 'light' => $data['light'],
'base_dir' => $data['base_dir']), $level + 1
);
//then combine the output with the current $files array
if(is_array($files2) && ! $data['array']) {
$files = array_merge($files, $files2);
} else {
$files[$path]['children'] = $files2;
}
$files2 = null;
}
//Else if it is a file
} else {
//get the extension of the file
//$ext = preg_replace('/(.+)\.([a-z0-9]{2,4})/i', '\\2', $file);
$ext = strrchr($file, '.');
//And if it is in the GOOD file extension list OR if the list is set to allow ALL files
if( (($data['good_ext'][0] == "all") || (array_search($ext, $data['good_ext']) > -1)) && (!$data['type'] || $data['type'] == 'file') ) {
//Add the file to our list
$files[$path]['file'] = $file;
//Remove the base dir from the file path?
$start = $data['base_dir'] ? strlen($data['base_dir']) : 0;
//Get dir path
$files[$path]['dir'] = substr($path, $start, -strlen($file));
//Get the LAST "." followed by 0+ letters/numbers
$files[$path]['ext'] = $ext;
//Get the level of the file
$files[$path]['level'] = $level;
//If we are getting detailed data about the file
if(!$data['light']) {
$files[$path]['stat'] = stat($path);
}
}
}
}
}
//Close the dir handle
closedir($handle);
//If there ARE files to sort
if($files) {
//sort by KEYS
//ksort($files);
uasort($files, 'sort_by_directory');
}
//Return the result
return $files;
}
}
}
/**
* Helper function to sort the array of files by directory,
* then level, and finally file name.
*
* @param $a
* @param $b
* @return unknown_type
*/
function sort_by_directory($a, $b) {
/*
* First sort by directory
*/
//If a is not a file and b is
if(!$a['file'] && $b['file']) {
return -1;
}
//If a is file and b is not
if($a['file'] && !$b['file']) {
return 1;
}
/*
* Then by level
*/
//If a is higher up
if($a['level'] > $b['level']) {
return 1;
//If b is higher up
} elseif ($a['level'] < $b['level']) {
return -1;
}
/*
* Finally by file name
*/
return strcasecmp($a['file'], $b['file']);
/*
* Or file size if you want...
*/
return ($a['stat']['size'] > $b['stat']['size'] ? -1 :
($a['stat']['size'] < $b['stat']['size'] ? 1 : 0));
}
/**
* A function to recursively delete files and folders
* @thanks: dev at grind [[DOT]] lv
*
* @param string $dir The path of the directory you want deleted
* @param boolean $remove Remove Files (false) or Folder and Files (true)
* @return boolean
*/
function destroy_directory($dir='', $remove=true) {
//Try to open the directory handle
if( ! $dh = opendir($dir) ) {
return FALSE;
}
//While there are files and directories in this directory
while (false !== ($obj = readdir($dh))) {
//Skip the object if it is the linux current (.) or parent (..) directory
if($obj=='.' || $obj=='..') continue;
$obj = $dir. $obj;
//If the object is a directory
if(is_dir($obj)) {
//If we could NOT delete this directory
if( ! destroy_directory($obj, $remove) ) {
return FALSE;
}
//Else it must be a file
} else {
if( ! unlink($obj)) {
return FALSE;
}
}
}
//Close the handle
closedir($dh);
if ( $remove && ! rmdir($dir) ){
return FALSE;
}
return TRUE;
}
/**
*
* This function takes a filename as input and filters it to make it safe
* for use. This includes sanitizing the name, encrypting the name (optional),
* checking for the existence of a file with the same name, and prefixing
* all secondary extensions with an underscore.
*
* @param string $file
* @param boolean $overwrite
* @param boolean $encrypt
* @return mixed
*/
function check_filename($file = '', $overwrite = FALSE, $encrypt = FALSE) {
//Break up the path
$pathinfo = pathinfo($file);
//No filename or extension?
if( empty($pathinfo['filename']) OR empty($pathinfo['extension']) ) {
return FALSE;
}
//Break apart
list($dirname, $basename, $extension, $filename) = $pathinfo;
//Should we encrypt the filename?
if ($encrypt) {
//Set the file name to a random value
$filename = md5(uniqid(rand()). $filename);
} else {
//Make the files name "filename" safe (no weird chars allowed like "/")
$filename = filename_safe($filename);
/*
* Separate extensions with "_" to prevent possible script execution
* from Apache's handling of files with multiple extensions.
* http://httpd.apache.org/docs/1.3/mod/mod_mime.html#multipleext
*/
if(strpos($filename, '.') !== FALSE) {
//First, break apart the name into pieces
$parts = explode('.', $filename);
//add a underscore to the name to make it un-usable
foreach($parts as &$part) {
$part .= '_';
}
//Put the filename back togeither
$filename = implode('.', $parts);
}
}
//If we should take existing files into account and protect them
if( ! $overwrite && file_exists($file) ) {
//Then keep adding a number to the file name
//until you find one that doesn't exist!
for ($i = 1; $i < 1000; $i++) {
if ( ! file_exists($dirname. DS. $filename. '_'. $i. '.'. $extension)) {
$filename .= '_'. $i;
break;
}
}
}
return array($dirname, $basename, $extension, $filename);
}
/**
* Break a path apart so that we can use CSS to color different levels
*
* @param string $path
* @return string
*/
function highlight_path($path=null) {
//Remove start and end slashes
$path = trim($path, '\\\/');
//If nothing is left - return empty
if(! $path) { return; }
//Break apart the path
$path = explode(DS, $path);
$output = '';
$x=1;
foreach($path as $value) {
$output .= '<span class="level'. $x. '">'. $value. DS. '</span>';
$x++;
}
return $output;
}
/**
* Check that a directory path is a sub directory (within) the given parent
* path. Please use absolute file system paths. Returns filtered absolute
* path on success.
*
*
* @param string
* @param string
* @return mixed
*/
function is_sub_dir($path = NULL, $parent_folder = SITE_PATH) {
//Get directory path minus last folder
$dir = dirname($path);
$folder = mb_substr($path, mb_strlen($dir));
//Check the the base dir is valid
$dir = realpath($dir);
//Only allow valid filename characters
if( preg_match('/[^a-z0-9\.\-_]/ui', $folder) ) {
return FALSE;
}
//If this is a bad path or a bad end folder name
if( !$dir OR !$folder OR $folder === '.') {
return FALSE;
}
//Rebuild path
$path = $dir. DS. $folder;
//If this path is higher than the parent folder
if( strcasecmp($path, $parent_folder) > 0 ) {
return $path;
}
return FALSE;
}
/**
* Checks that the directory path looks like a sub directory (within) the
* given parent path.
*
* @param string
* @param string
* @return mixed
*/
function looks_like_sub_dir($path = NULL, $parent_folder = SITE_PATH) {
//If no path was given
if( !$path OR ! trim($path, '/\\. ') ) {
return FALSE;
}
$s = (DS === '/' ? '\\' : '/');
// Normalize paths (WINDOWS to *NIX or vice-versa)
$path = str_replace($s, DS, $path);
// Don't allow relative links or multiple forward slashes
if( strpos($path, DS. '..') !== FALSE OR strpos($path, DS.DS) !== FALSE) {
return FALSE;
}
// Remove start and end slashes and dots
$path = trim($path, DS. '. ');
$parent_folder = trim($parent_folder, DS. '. ');
$front = '';
//If windows
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
//Allow "C:" and "AB:" styled dives
$front = substr($path, 0, 4);
$path = substr($path, 4);
//If something else is here
if( preg_match('/[^a-z:'. ( DS == '/' ? DS : '\\\\'). ']/i', $front) ) {
return FALSE;
}
}
//Allowed filesystem path chars
$regex = '[^a-z0-9 \.\-_ '. ( DS == '/' ? DS : '\\\\'). ']';
//If anything funny looking is found - fail (to be safe)
if( preg_match('#'. $regex. '#i', $path) ) {
return FALSE;
}
//Rejoin
$path = $front. $path;
//Must be the same starting path
if( substr($path, 0, strlen($parent_folder)) != $parent_folder) {
return FALSE;
}
//If this path is higher than the parent folder
if( strcmp($path, $parent_folder) > 0 ) {
return TRUE;
}
return FALSE;
}