<?php
/**
 * DooFile class file.
 * @package doo.helper
 * @author Leng Sheng Hong <darkredz@gmail.com>
 * @link http://www.doophp.net/
 * @copyright Copyright &copy; 2009-2010 Leng Sheng Hong
 * @license http://www.doophp.net/license
 * @since 1.3
 */
 
/**
 * Provides functions for managing file system
 */
class DooFile {

    const LIST_FILE = 'file';
    const LIST_FOLDER = 'folder';

    public $chmod;

    public function  __construct($chmod=null) {
        if($chmod!==null)
            $this->chmod = $chmod;
        else{
            if( class_exists('Doo') )
                $this->chmod = Doo::conf()->get('CHMOD_DEFAULT', 0777);
            else
                $this->chmod = 0777;            
        }
    }

    /**
     * Delete contents in a folder recursively
     * @param string $dir Path of the folder to be deleted
     * @return int Total of deleted files/folders
     */
	public function purgeContent($dir){
        $totalDel = 0;
		$handle = opendir($dir);

		while (false !== ($file = readdir($handle))){
			if ($file != '.' && $file != '..'){
				if (is_dir($dir.$file)){
					$totalDel += $this->purgeContent($dir.$file.'/');
					if( rmdir($dir.$file) )
                        $totalDel++;
				}else{
					if( unlink($dir.$file) )
                        $totalDel++;
				}
			}
		}
		closedir($handle);
        return $totalDel;
	}

    /**
     * Delete a folder (and all files and folders below it)
     * @param string $path Path to folder to be deleted
     * @param bool $deleteSelf true if the folder should be deleted. false if just its contents.
     * @return int|bool Returns the total of deleted files/folder. Returns false if delete failed
     */
	public function delete($path, $deleteSelf=true){

		if (file_exists($path)) {
			//delete all sub folder/files under, then delete the folder itself
			if(is_dir($path)){
				if($path[strlen($path)-1] != '/' && $path[strlen($path)-1] != '\\' ){
					$path .= DIRECTORY_SEPARATOR;
					$path = str_replace('\\', '/', $path);
				}
				if($total = $this->purgeContent($path)){
					if($deleteSelf)
						if($t = rmdir($path))
							return $total + $t;
					return $total;
				}
				else if($deleteSelf){
					return rmdir($path);
				}
				return false;
			}
			else{
				return unlink($path);
			}
		}
    }


	/**
	 * If the folder does not exist creates it (recursively)
	 * @param string $path Path to folder/file to be created
	 * @param mixed $content Content to be written to the file
	 * @param string $writeFileMode Mode to write the file
     * @return bool Returns true if file/folder created
	 */
	public function create($path, $content=null, $writeFileMode='w+') {
        //create file if content not empty
		if (!empty($content)) {
            if(strpos($path, '/')!==false || strpos($path, '\\')!==false){
                $path = str_replace('\\', '/', $path);
                $filename = $path;
                $path = explode('/', $path);
                array_splice($path, sizeof($path)-1);

                $path = implode('/', $path);
                if($path[strlen($path)-1] != '/'){
                    $path .= '/';
                }
            }else{
                $filename = $path;
            }

            if($filename!=$path && !file_exists($path))
                mkdir($path, $this->chmod, true);
            $fp = fopen($filename, $writeFileMode);
            $rs = fwrite($fp, $content);
            fclose($fp);
            
            return ($rs>0);
		}else{
			if (!file_exists($path)) {
				return mkdir($path, $this->chmod, true);
			} else {
				return true;
			}
        }
	}

    /**
     * Move/rename a file/folder
     * @param string $from Original path of the folder/file
     * @param string $to Destination path of the folder/file
     * @return bool Returns true if file/folder created
     */
    public function move($from, $to) {
        if(strpos($to, '/')!==false || strpos($to, '\\')!==false){
            $path = str_replace('\\', '/', $to);
            $path = explode('/', $path);
            array_splice($path, sizeof($path)-1);

            $path = implode('/', $path);
            if($path[strlen($path)-1] != '/'){
                $path .= '/';
            }
            if(!file_exists($path))
                mkdir($path, $this->chmod, true);
        }
        
        return rename($from, $to);
    }

    /**
     * Copy a file/folder to a destination
     * @param string $from Original path of the folder/file
     * @param string $to Destination path of the folder/file
	 * @param array $exclude An array of file and folder names to be excluded from a copy
     * @return bool|int Returns true if file copied. If $from is a folder, returns the number of files/folders copied
     */
    public function copy($from, $to, $exclude=array()) {
        if(is_dir($from)){
            if($to[strlen($to)-1] != '/' && $to[strlen($to)-1] != '\\' ){
                $to .= DIRECTORY_SEPARATOR;
                $to = str_replace('\\', '/', $to);
            }
            if($from[strlen($from)-1] != '/' && $from[strlen($from)-1] != '\\' ){
                $from .= DIRECTORY_SEPARATOR;
                $from = str_replace('\\', '/', $from);
            }
            if(!file_exists($to))
                mkdir($to, $this->chmod, true);

            return $this->copyContent($from, $to, $exclude);
        }else{
            if(strpos($to, '/')!==false || strpos($to, '\\')!==false){
                $path = str_replace('\\', '/', $to);
                $path = explode('/', $path);
                array_splice($path, sizeof($path)-1);

                $path = implode('/', $path);
                if($path[strlen($path)-1] != '/'){
                    $path .= '/';
                }

                if(!file_exists($path))
                    mkdir($path, $this->chmod, true);
            }
            return copy($from, $to);
        }
    }

    /**
     * Copy contents in a folder recursively
     * @param string $dir Path of the folder to be copied
     * @param string $to Destination path
	 * @param array $exclude An array of file and folder names to be excluded from a copy
     * @return int Total of files/folders copied
     */
	public function copyContent($dir, $to, $exclude=array()){
        $totalCopy = 0;
		$handle = opendir($dir);

		while(false !== ($file = readdir($handle))){
			if($file != '.' && $file != '..' && !in_array($file, $exclude)){

                if (is_dir($dir.$file)){
                    if(!file_exists($to.$file))
                        mkdir($to.$file, $this->chmod, true);

					$totalCopy += $this->copyContent($dir.$file.'/', $to.$file.'/', $exclude);
				}else{
					if( copy($dir.$file, $to.$file) )
                        $totalCopy++;
				}
			}
		}
		closedir($handle);
        return $totalCopy;
	}


    /**
     * Get the space used up by a folder recursively.
     * @param string $dir Directory path.
     * @param string $unit Case insensitive units: B, KB, MB, GB or TB
     * @param int $precision 
     * @return float total space used up by the folder (KB)
     */
    public function getSize($dir, $unit='KB', $precision=2){
        if(!is_dir($dir)) return filesize($dir);
        $dir = str_replace('\\', '/', $dir);
        if($dir[strlen($dir)-1] != '/'){
            $dir .= '/';
        }

        $totalSize = 0;
		$handle = opendir($dir);

		while(false !== ($file = readdir($handle))){
			if($file != '.' && $file != '..'){
                if (is_dir($dir.$file)){
					$totalSize += $this->getSize($dir.$file, false);
				}else{
                    $totalSize += filesize($dir.$file);
				}
			}
		}
		closedir($handle);
        return self::formatBytes($totalSize, $unit, $precision);
    }

    /**
     * Convert bytes into KB, MB, GB or TB.
     * @param int $bytes
     * @param string $unit Case insensitive units: B, KB, MB, GB or TB OR false if not to format the size
     * @param int $precision
     * @return float
     */
    public static function formatBytes($bytes, $unit='KB', $precision=2) {
		if ($unit === false) {
			return $bytes;
		}
        $unit = strtoupper($unit);
        $unitPow = array('B'=>0, 'KB'=>1, 'MB'=>2, 'GB'=>3, 'TB'=>4);
        $bytes /= pow(1024, $unitPow[$unit]);
        return round($bytes, $precision);
    }

    /**
     * Get a list of files with its path in a directory (recursively)
     * @param string $path
     * @return array 
     */
    public static function getFilePathList($path){
        $path = str_replace('\\', '/', $path);
        if($path[strlen($path)-1] != '/'){
            $path .= '/';
        }
        
        $handle = opendir($path);
        $rs = array();

        while (false !== ($file = readdir($handle))){
            if ($file != '.' && $file != '..' && $file!='.svn'){
                if (is_dir($path.$file)===true){
                        $rs = array_merge($rs, self::getFilePathList($path.$file.'/'));
                }else{
                    $rs[$file] = $path.$file;
                }
            }
        }
        closedir($handle);
        return $rs;
    }    

    /**
     * Get a list of files(indexed array) with its path in a directory (recursively)
     * @param string $path
     * @return array 
     */
    public static function getFilePathIndexList($path){
        $path = str_replace('\\', '/', $path);
        if($path[strlen($path)-1] != '/'){
            $path .= '/';
        }
        
        $handle = opendir($path);
        $rs = array();

        while (false !== ($file = readdir($handle))){
            if ($file != '.' && $file != '..' && $file!='.svn'){
                if (is_dir($path.$file)===true){
                        $rs = array_merge($rs, self::getFilePathIndexList($path.$file.'/'));
                }else{
                    $rs[] = $path.$file;
                }
            }
        }
        closedir($handle);
        return $rs;
    }    
    
    /**
     * Get a list of folders or files or both in a given path.
     *
     * @param string $path Path to get the list of files/folders
     * @param string $listOnly List only files or folders. Use value DooFile::LIST_FILE or DooFile::LIST_FOLDER
     * @param string $unit Unit for the size of files. Case insensitive units: B, KB, MB, GB or TB
     * @param int $precision Number of decimal digits to round the file size to
     * @return array Returns an assoc array with keys: name(file name), path(full path to file/folder), folder(boolean), extension, type, size(KB)
     */
	public function getList($path, $listOnly=null, $unit='B', $precision=2){
        $path = str_replace('\\', '/', $path);
        if($path[strlen($path)-1] != '/'){
            $path .= '/';
        }

		$filetype = array('.', '..');
		$name = array();

		$dir = opendir($path);
		if ($dir === false ) {
			return false;
		}

		while( $file = readdir($dir) ){
			if( !in_array(substr($file, -1, strlen($file)), $filetype) && !in_array(substr($file, -2, strlen($file)), $filetype) ){
				$name[] = $path . $file;
			}
		}
		closedir($dir);

		if(count($name)==0)return false;

        if(!function_exists('mime_content_type')) {
            function mime_content_type($filename) {
                $mime_types = array(
                    'txt' => 'text/plain',
                    'htm' => 'text/html',
                    'html' => 'text/html',
                    'php' => 'text/html',
                    'css' => 'text/css',
                    'js' => 'application/javascript',
                    'json' => 'application/json',
                    'xml' => 'application/xml',
                    'swf' => 'application/x-shockwave-flash',
                    'flv' => 'video/x-flv',
                    'sql' => 'text/x-sql',

                    // images
                    'png' => 'image/png',
                    'jpe' => 'image/jpeg',
                    'jpeg' => 'image/jpeg',
                    'jpg' => 'image/jpeg',
                    'gif' => 'image/gif',
                    'bmp' => 'image/bmp',
                    'ico' => 'image/vnd.microsoft.icon',
                    'tiff' => 'image/tiff',
                    'tif' => 'image/tiff',
                    'svg' => 'image/svg+xml',
                    'svgz' => 'image/svg+xml',

                    // archives
                    'zip' => 'application/zip',
                    'rar' => 'application/x-rar-compressed',
                    'exe' => 'application/x-msdownload',
                    'msi' => 'application/x-msdownload',
                    'cab' => 'application/vnd.ms-cab-compressed',

                    // audio/video
                    'mp3' => 'audio/mpeg',
                    'qt' => 'video/quicktime',
                    'mov' => 'video/quicktime',

                    // adobe
                    'pdf' => 'application/pdf',
                    'psd' => 'image/vnd.adobe.photoshop',
                    'ai' => 'application/postscript',
                    'eps' => 'application/postscript',
                    'ps' => 'application/postscript',

                    // ms office
                    'doc' => 'application/msword',
                    'rtf' => 'application/rtf',
                    'xls' => 'application/vnd.ms-excel',
                    'ppt' => 'application/vnd.ms-powerpoint',

                    // open office
                    'odt' => 'application/vnd.oasis.opendocument.text',
                    'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
                );

                $f = explode('.',$filename);
                $ext = strtolower(array_pop($f));
                if (array_key_exists($ext, $mime_types)) {
                    return $mime_types[$ext];
                }
                elseif (function_exists('finfo_open')) {
                    $finfo = finfo_open(FILEINFO_MIME);
                    $mimetype = finfo_file($finfo, $filename);
                    finfo_close($finfo);
                    return $mimetype;
                }
                else {
                    return 'application/octet-stream';
                }
            }
        }

		$fileInfo=array();
		foreach($name as $key=>$val){
            if($listOnly==DooFile::LIST_FILE){
                if(is_dir($val)) continue;
            }
			if ($listOnly==DooFile::LIST_FOLDER) {
				if(!is_dir($val)) continue;
			}
			$filename = basename($val);
			$ext = $this->getFileExtensionFromPath($val, true);

            if(!is_dir($val)){
                $fileInfo[] = array('name' => $filename,
                                    'path' => $val,
                                    'folder' => is_dir($val),
                                    'extension' => $ext,
                                    'type' => mime_content_type($val),
                                    'size' => $this->formatBytes(filesize($val), $unit, $precision)
                                );
            }else{
                $fileInfo[] = array('name' => $filename,
                                    'path' => $val,
                                    'folder' => is_dir($val));
            }
		}
		return $fileInfo;
	}


    /**
     * Save the uploaded file(s) in HTTP File Upload variables
     *
     * @param string $uploadPath Path to save the uploaded file(s)
     * @param string $filename The file input field name in $_FILES HTTP File Upload variables
     * @param string $rename Rename the uploaded file (without extension)
     * @return string|array The file name of the uploaded file.
     */
    public function upload($uploadPath, $filename, $rename=''){
        $file = !empty($_FILES[$filename]) ? $_FILES[$filename] : null;
        if($file==Null)return;

		if (!file_exists($uploadPath)) {
			$this->create($uploadPath);
		}

        if(is_array($file['name'])===False){
			$ext = $this->getFileExtensionFromPath($file['name']);

            if($rename=='')
                $newName = time().'-'.mt_rand(1000,9999) . '.' . $ext;
            else
                $newName = $rename . '.' . $ext;

			$filePath = $uploadPath . $newName;

            if (move_uploaded_file($file['tmp_name'], $filePath)){
                return $newName;
            }
        }
        else{
			if (!file_exists($uploadPath)) {
				$this->create($uploadPath);
			}
            $uploadedPath = array();
            foreach($file['error'] as $k=>$error){
                if(empty($file['name'][$k])) continue;
                if ($error == UPLOAD_ERR_OK) {
				   $ext = $this->getFileExtensionFromPath($file['name'][$k]);

                   if($rename=='')
                       $newName = time().'-'.mt_rand(1000,9999) . '_' . $k . '.' . $ext;
                   else
                       $newName = $rename . '_' . $k . '.' . $ext;

				   $filePath = $uploadPath . $newName;

                   if (move_uploaded_file($file['tmp_name'][$k], $filePath)){
                       $uploadedPath[] = $newName;
                   }
                }else{
                   return false;
                }
            }
            return $uploadedPath;
        }
    }

    /**
     * Get the uploaded files' type
     *
     * @param string $filename The file field name in $_FILES HTTP File Upload variables
     * @return string|array The image format type of the uploaded image.
     */
    public function getUploadFormat($filename){
        if(!empty($_FILES[$filename])){
            $type = $_FILES[$filename]['type'];
            if(is_array($type)===False){
                if(!empty($type)){
                    return $type;
                }
            }
            else{
                $typelist = array();
                foreach($type as $t){
                    $typelist[] = $t;
                }
                return $typelist;
            }
        }
    }

    /**
     * Checks if file mime type of the uploaded file(s) is in the allowed list
     *
     * @param string $filename The file input field name in $_FILES HTTP File Upload variables
     * @param array $allowType Allowed file type.
     * @return bool Returns true if file mime type is in the allowed list.
     */
    public function checkFileType($filename, $allowType){
        $type = $this->getUploadFormat($filename);
        if(is_array($type)===False)
            return in_array($type, $allowType);
        else{
            foreach($type as $t){
                if($t===Null || $t==='') continue;
                if(!in_array($t, $allowType)){
                    return false;
                }
            }
            return true;
        }
    }

    /**
     * Checks if file extension of the uploaded file(s) is in the allowed list.
     *
     * @param string $filename The file input field name in $_FILES HTTP File Upload variables
     * @param array $allowExt Allowed file extensions.
     * @return bool Returns true if file extension is in the allowed list.
     */
    public function checkFileExtension($filename, $allowExt){
        if(!empty($_FILES[$filename])){
            $name = $_FILES[$filename]['name'];
            if(is_array($name)===false){
                $ext = strtolower($this->getFileExtensionFromPath($name));
                return in_array($ext, $allowExt);
            }
            else{
                foreach($name as $nm){
					$ext = strtolower($this->getFileExtensionFromPath($nm));
                    if(!in_array($ext, $allowExt)){
                        return false;
                    }
                }
                return true;
            }
        }
    }

    /**
     * Checks if file size does not exceed the max file size allowed.
     *
     * @param string $filename The file input field name in $_FILES HTTP File Upload variables
     * @param int $maxSize Allowed max file size in kilo bytes.
     * @return bool Returns true if file size does not exceed the max file size allowed.
     */
    public function checkFileSize($filename, $maxSize){
        if(!empty($_FILES[$filename])){
            $size = $_FILES[$filename]['size'];
            if(is_array($size)===False){
                if(($size/1024)>$maxSize){
                    return false;
                }
            }
            else{
                foreach($size as $s){
                    if(($s/1024)>$maxSize){
                        return false;
                    }
                }
            }
            return true;
        }
    }

	/**
	 * Reads the contents of a given file
	 * @param string $fullFilePath Full path to file whose contents should be read
	 * @return string|bool Returns file contents or false if file not found
	 */
	public function readFileContents($fullFilePath, $flags = 0, resource $context = null, $offset = -1, $maxlen = null) {
		if (file_exists($fullFilePath)) {
			if ($maxlen !== null)
				return file_get_contents($fullFilePath, $flags, $context, $offset, $maxlen);
			else
				return file_get_contents($fullFilePath, $flags, $context, $offset);
		} else {
			return false;
		}
	}

	/**
	 * Extracts the file extension (characters following last '.' in string) from a file path.
	 * @param string $filePath Full path or filename including extension to be extracted
	 * @param bool $toLowerCase Should the extension be converted to lower case ?
	 * @return string|Returns the file extension (characters following last . in string)
	 */
	public function getFileExtensionFromPath($path, $toLowerCase = false) {
		$ext = substr($path, strrpos($path, '.') + 1);
		return ($toLowerCase == true) ? strtolower($ext) : $ext;
	}

}
