<?php
/**
 * Link         :   http://www.phpcorner.net
 * User         :   qingbing<780042175@qq.com>
 * Date         :   2019-01-07
 * Version      :   1.0
 */

namespace Helper;

/**
 * 文件目录管理
 * Class FileManager
 * @package Helper
 */
class FileManager
{
    /**
     * 确定指定文件的"mime"类型
     * @param string $file the file name.
     * @param string $magicFile name of a magic database file, usually something like /path/to/magic.mime.
     * this will be passed as the second parameter to {@link http://php.net/manual/en/function.finfo-open.php finfo_open}.
     * Magic file format described in {@link http://linux.die.net/man/5/magic man 5 magic}
     * @param bool $checkExtension whether to check the file extension in case the MIME type cannot be determined based on finfo and mime_content_type.
     * @return mixed|null|string
     */
    static public function getMimeType($file, $magicFile = null, $checkExtension = true)
    {
        if (function_exists('finfo_open')) {
            $options = defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME;
            $info = null === $magicFile ? finfo_open($options) : finfo_open($options, $magicFile);
            if ($info && false !== ($result = finfo_file($info, $file))) {
                return $result;
            }
        }
        if (function_exists('mime_content_type') && false !== ($result = mime_content_type($file))) {
            return $result;
        }
        return $checkExtension ? self::getMimeTypeByExtension($file) : null;
    }

    /**
     * 根据指定文件的扩展名确定文件的 "mime" 类型
     * @param string $file the file name.
     * @param string $magicFile the path of the file that contains all available MIME type information. default 'system.units.mimeTypes'
     * @return string the MIME type. Null if returnd if the MIME type cannot be determined.
     */
    static public function getMimeTypeByExtension($file, $magicFile = null)
    {
        static $extensions = null;
        static $customExtensions = [];
        if (null === $magicFile && null === $extensions) {
            $extensions = require(__DIR__ . '/mimeTypes.php');
        } else if (null !== $magicFile && !isset($customExtensions[$magicFile])) {
            $customExtensions[$magicFile] = require($magicFile);
        }
        if ('' !== ($ext = pathinfo($file, PATHINFO_EXTENSION))) {
            $ext = strtolower($ext);
            if (null === $magicFile && isset($extensions[$ext])) {
                return $extensions[$ext];
            } else if (null !== $magicFile && isset($customExtensions[$magicFile][$ext])) {
                return $customExtensions[$magicFile][$ext];
            }
        }
        return null;
    }

    /**
     * 返回文件的扩展名
     * @param string $path
     * @return string
     */
    public static function getExtension($path)
    {
        return pathinfo($path, PATHINFO_EXTENSION);
    }

    /**
     * 创建文件夹
     * @param string $dst
     * @param integer $mode
     * @param bool $recursive
     * @return bool
     */
    public static function mkdir($dst, $mode = null, $recursive = false)
    {
        if ($mode === null) {
            $mode = 0777;
        }
        $prevDir = dirname($dst);
        if ($recursive && !is_dir($dst) && !is_dir($prevDir)) {
            self::mkdir(dirname($dst), $mode, true);
        }
        $res = mkdir($dst, $mode);
        @chmod($dst, $mode);
        return $res;
    }

    /**
     * 删除文件夹
     * @param string $dir
     * @param bool $recursive
     * @return bool
     */
    static public function rmdir($dir, $recursive = false)
    {
        $dir = realpath($dir);
        if (!is_dir($dir)) return true;
        if (true !== $recursive) return @rmdir($dir);
        $dp = @opendir($dir);
        while ($file = @readdir($dp)) {
            if ('.' === $file || '..' === $file) continue;
            $cur_file = $dir . '/' . $file;
            if (is_dir($cur_file)) {
                self::rmdir($cur_file, true);
            } elseif (is_file($cur_file)) {
                @unlink($cur_file);
            }
        }
        @closedir($dp);
        return @rmdir($dir);
    }

    /**
     * @param string $src 拷贝源
     * @param string $dst 拷贝目标
     * @param array $options 拷贝选项
     *      array $fileTypes 需要拷贝的文件后缀列表，如果设置，将只拷贝这些后缀的文件
     *      array $exclude 忽略拷贝的文件或目录
     *          ".svn" : 将忽略所有名为".svn" 的目录和文件
     *          "/a/b" : 将忽略名为"$src/a/b" 的目录或文件
     *      int newDirMode 拷贝到新目录的目录权限，默认 "0777"
     *      int newFileMode 拷贝新文件的文件权限，默认 为当前系统的用户权限
     * @throws Exception
     */
    public static function copyDir($src, $dst, $options = [])
    {
        $fileTypes = [];
        $exclude = [];
        extract($options);
        if (!is_dir($dst)) {
            self::mkdir($dst, isset($options['newDirMode']) ? $options['newDirMode'] : null, true);
        }

        self::copyDirRecursive($src, $dst, '', $fileTypes, $exclude, $options);
    }

    /**
     * @param string $src 拷贝源
     * @param string $dst 拷贝目标
     * @param string $base 拷贝当前目录
     * @param array $fileTypes 需要拷贝的文件后缀列表，如果设置，将只拷贝这些后缀的文件
     * @param array $exclude 忽略拷贝的文件或目录
     *      ".svn" : 将忽略所有名为".svn" 的目录和文件
     *      "/a/b" : 将忽略名为"$src/a/b" 的目录或文件
     * @param $options
     * @throws Exception
     */
    protected static function copyDirRecursive($src, $dst, $base, $fileTypes, $exclude, $options)
    {
        if (!is_dir($dst)) {
            self::mkdir($dst, isset($options['newDirMode']) ? $options['newDirMode'] : null, false);
        }

        $folder = opendir($src);
        if ($folder === false) {
            throw new Exception(str_cover('不能打开目录"{path}"', [
                '{path}' => $src,
            ]));
        }
        while (($file = readdir($folder)) !== false) {
            if ('.' === $file || '..' === $file) {
                continue;
            }
            $path = $src . DIRECTORY_SEPARATOR . $file;
            $isFile = is_file($path);
            if (self::validatePath($base, $file, $isFile, $fileTypes, $exclude)) {
                if ($isFile) {
                    copy($path, $dst . DIRECTORY_SEPARATOR . $file);
                    if (isset($options['newFileMode'])) {
                        @chmod($dst . DIRECTORY_SEPARATOR . $file, $options['newFileMode']);
                    }
                } else {
                    self::copyDirRecursive($path, $dst . DIRECTORY_SEPARATOR . $file, $base . '/' . $file, $fileTypes, $exclude, $options);
                }
            }
        }
        closedir($folder);
    }

    /**
     * 拷贝文件
     * @param string $src 拷贝源
     * @param string $dst 拷贝目标
     * @param null|int $newFileMode 文件权限
     */
    public static function copyFile($src, $dst, $newFileMode = null)
    {
        copy($src, $dst);
        if (null !== $newFileMode) {
            @chmod($dst, $newFileMode);
        }
    }

    /**
     * @param string $dir 查找源目录
     * @param array $options 选项
     *      array $fileTypes 需要查找的文件后缀列表，如果设置，将只查找这些后缀的文件
     *      array $exclude 忽略查找的文件或目录
     *          ".svn" : 将忽略所有名为".svn" 的目录和文件
     *          "/a/b" : 将忽略名为"$src/a/b" 的目录或文件
     * @return array
     * @throws Exception
     */
    public static function findFiles($dir, $options = array())
    {
        $fileTypes = array();
        $exclude = array();
        $level = -1;
        $absolutePaths = true;
        extract($options);
        $list = self::findFilesRecursive($dir, '', $fileTypes, $exclude, $level, $absolutePaths);
        sort($list);
        return $list;
    }

    /**
     * @param string $dir 当前目录
     * @param string $base 和源目录的相对目录
     * @param array $fileTypes 需要的文件后缀列表
     * @param array $exclude 忽略的文件或目录
     * @param int $level 递归的深度，默认-1，表示无限
     *      -1 ：表示目录下的所有文件
     *      0 ：表示仅显示源目录下的文件
     *      n ：表示显示源目录及深度为n的目录下的文件
     * @param boolean $absolutePaths 是否显示绝对地址
     * @return array
     * @throws Exception
     */
    protected static function findFilesRecursive($dir, $base, $fileTypes, $exclude, $level, $absolutePaths)
    {
        $list = array();
        $handle = opendir($dir . $base);
        if ($handle === false) {
            throw new Exception(str_cover('不能打开目录"{path}"', [
                '{path}' => $dir . $base,
            ]));
        }
        while (($file = readdir($handle)) !== false) {
            if ('.' === $file || '..' === $file) {
                continue;
            }
            $path = substr($base . DIRECTORY_SEPARATOR . $file, 1);
            $fullPath = $dir . DIRECTORY_SEPARATOR . $path;
            $isFile = is_file($fullPath);
            if (self::validatePath($base, $file, $isFile, $fileTypes, $exclude)) {
                if ($isFile) {
                    $list[] = $absolutePaths ? $fullPath : $path;
                } elseif ($level) {
                    $list = array_merge($list, self::findFilesRecursive($dir, $base . '/' . $file, $fileTypes, $exclude, $level - 1, $absolutePaths));
                }
            }
        }
        closedir($handle);
        return $list;
    }

    /**
     * 检验是否为合格有效的拷贝目录
     * @param string $base 和源目录的相对目录
     * @param string $file 当前文件名
     * @param boolean $isFile 是否文件
     * @param array $fileTypes 需要的文件后缀列表
     * @param array $exclude 忽略的文件或目录
     * @return bool
     */
    protected static function validatePath($base, $file, $isFile, $fileTypes, $exclude)
    {
        foreach ($exclude as $e) {
            if ($file === $e || 0 === strpos($base . '/' . $file, $e)) {
                // 被忽略copy的文件或目录
                return false;
            }
        }
        if (!$isFile || empty($fileTypes)) {
            return true;
        }
        if ('' !== ($type = self::getExtension($file))) {
            return in_array($type, $fileTypes);
        }
        return false;
    }
}