<?php

namespace app\service\admin;

use app\model\admin\Menu;
use laytp\library\DirFile;
use laytp\library\Http;
use laytp\traits\Error;
use app\model\Migrations;
use think\facade\Config;
use think\facade\Filesystem;

/**
 * 插件市场服务实现者
 * Class Auth
 * @package app\service\admin
 */
class Plugins
{
    use Error;

    protected $ids=[];

    // 离线安装
    public function offLineInstall()
    {
        if (!class_exists('ZipArchive')) {
            $this->setError('PHP扩展ZipArchive没有正确安装');
            return false;
        }

        $file = request()->file('laytpUploadFile'); // 获取上传的文件
        if (!$file) {
            $this->setError('上传失败，请选择需要上传的文件');
            return false;
        }

        $fileExt = strtolower($file->getOriginalExtension());
        if($fileExt != 'zip'){
            $this->setError('仅允许上传zip文件');
            return false;
        }

        $fileName = $file->getOriginalName();
        $pathinfo = pathinfo($fileName);
        $plugin = $pathinfo['filename'];
        try{
            // 将文件上传到指定目录
            Filesystem::disk('local')->putFileAs('plugins', $file, $fileName );
            $this->insideInstall($plugin);
        }catch (\Exception $e){
            $this->setError($e->getMessage());
            return false;
        }

        return true;
    }

    // 安装
    public function install($plugin, $laytpGwToken)
    {
        if (!class_exists('ZipArchive')) {
            $this->setError('PHP扩展ZipArchive没有正确安装');
            return false;
        }

        $download = $this->download($plugin, $laytpGwToken);

        if(!$download){
            return false;
        }

        $install = $this->insideInstall($plugin);
        if(!$install){
            return false;
        }

        return true;
    }

    // 卸载
    public function unInstall($plugin)
    {
        $pluginDir = $this->getPluginPath($plugin) . DS;
        // 删除数据库文件
        $migrationsFile = DirFile::recurDir($pluginDir . 'database' . DS . 'migrations');
        if($migrationsFile){
            foreach($migrationsFile as $file){
                $baseNameArr = explode('_', $file['baseName']);
                $baseNameArr = explode('.', $baseNameArr[1]);
                $migration = Migrations::where('migration_name', '=', ucfirst($baseNameArr[0]))->find();
                if($migration) $migration->delete();
                @unlink(root_path() . 'database' . DS . 'migrations' . DS . $file['baseName']);
            }
        }
        // 删除菜单
        $info = $this->getPluginInfo($plugin);
        $menuIds = $info['menu_ids'];
        if($menuIds){
            Menu::destroy(function($query) use ($menuIds){
                $query->where('id', 'in', explode(',', $menuIds));
            });
        }
        // 删除public目录下的文件
        $this->removePublicFile($plugin);
        // 删除插件目录
        DirFile::rmDirs($pluginDir);
        // 修改系统插件配置文件config/plugin.php
        $this->unInstallPluginConf($plugin, $info);
        return true;
    }

    // 获取上传zip文件所在目录
    public function getPluginRuntimeDir()
    {
        $dir = runtime_path() . 'storage' . DS . 'plugins';
        DirFile::createDir($dir);
        return $dir;
    }

    // 下载zip文件到本地
    public function download($plugin, $laytpGwToken)
    {
        $ltVersion = request()->param('ltVersion');
        $pluginVersion = request()->param('pluginVersion');
        $res = Http::post(Config::get('plugin.apiUrl') . "/plugins/install", [
            'plugin'=>$plugin,
            'ltVersion'=>$ltVersion,
            'pluginVersion'=>$pluginVersion,
        ], array(
            CURLOPT_HTTPHEADER => array(
                "token: ".$laytpGwToken
            ),
        ));
        $resArr = json_decode($res, true);
        if($resArr['code'] > 0 ){
            $this->setError(['msg'=>$resArr['msg'],'code'=>$resArr['code']]);
            return false;
        }
        $url = $resArr['data']['url'];
        $zipSteam = Http::get($url, [], [
            CURLOPT_CONNECTTIMEOUT => 30,
            CURLOPT_TIMEOUT        => 30,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_HTTPHEADER     => [
                'X-REQUESTED-WITH: XMLHttpRequest'
            ]
        ]);

        $file = $this->getPluginRuntimeDir() . DS . $plugin . '.zip';
        if(file_exists($file)){
            @unlink($file);
        }
        if ($write = fopen($file, 'w')) {
            fwrite($write, $zipSteam);
            fclose($write);
        }
        return true;
    }

    // 内部安装过程
    public function insideInstall($plugin)
    {
        try{
            // 解压zip文件
            $this->unzip($plugin);

            // 复制database文件并执行php think migrate:run命令
            $this->migrate($plugin);

            // 复制静态文件
            if(!function_exists('exec')){
                $this->setError('php函数exec不允许执行');
                return false;
            }
            $this->copyPublicFile($plugin);

            // 生成菜单，同时将新增的菜单id写入info.ini配置文件中，便于卸载时同时删除菜单
            $this->createMenu($plugin);

            // 修改系统插件配置文件config/plugin.php
            $this->installPluginConf($plugin);
            return true;
        }catch (\Exception $e){
            $this->setError(['msg'=>$e->getMessage(). $e->getLine() . $e->getFile(),'code'=>3]);
            return false;
        }
    }

    /**
     * 解压插件zip文件
     * @param $plugin
     * @return bool
     * @throws \Exception
     */
    public function unzip($plugin)
    {
        $file = $this->getPluginRuntimeDir() . DS . $plugin . '.zip';
        $zip = new \ZipArchive;
        if ($zip->open($file) !== TRUE) {
            @unlink($file);
            throw new \Exception('不能打开zip文件');
        }
        if(strtolower(trim($zip->getNameIndex(0),'/')) == strtolower($plugin)){
            $dir = root_path() . 'plugin' . DS;
        }else{
            $dir = root_path() . 'plugin' . DS . $plugin . DS;
        }
        if (!$zip->extractTo($dir)) {
            $zip->close();
            @unlink($file);
            throw new \Exception('不能提取zip文件');
        }
        $zip->close();
        @unlink($file);
        return true;
    }

    // 插件代码文件解压后，执行php run:migrate命令，安装数据库文件
    public function migrate($plugin)
    {
        $pluginDir = $this->getPluginPath($plugin) . DS;
        if(is_dir($pluginDir . 'database' . DS . 'migrations')){
            DirFile::copyDirs($pluginDir . 'database' . DS . 'migrations', root_path() . 'database' . DS . 'migrations');
            // 删除数据库migrations表，已经安装过的版本
            $list = scandir($pluginDir . 'database' . DS . 'migrations');
            $migrationNameArr = [];
            foreach ($list as $value) {
                $pathinfo = pathinfo($value);
                if ($pathinfo['extension'] == 'php') {
                    $tempArr = explode('_', $pathinfo['filename']);
                    $migrationNameArr[] = ucfirst($tempArr[1]);
                }
            }
            Migrations::where('migration_name', 'in', $migrationNameArr)->delete();
            sleep(1);
            exec('php ' . app()->getRootPath() . '\think migrate:run');
        }
        return true;
    }

    // 获取插件路径
    public function getPluginPath($plugin)
    {
        return app()->getRootPath() . DS . 'plugin' . DS . $plugin;
    }

    // 获取插件信息
    public function getPluginInfo($plugin)
    {
        $pluginPath = $this->getPluginPath($plugin);
        $infoFile = $pluginPath . DS . 'info.ini';
        $info = [];
        if (is_file($infoFile)) {
            $info = parse_ini_file($infoFile, true, INI_SCANNER_TYPED) ?: [];
        }
        return $info;
    }

    /**
     * 设置插件配置信息
     * @param $plugin
     * @param $array
     * @return bool
     */
    public function setPluginInfo($plugin, $array)
    {
        $pluginPath = $this->getPluginPath($plugin);
        $file = $pluginPath . DS . 'info.ini';
        if (!isset($array['name'])) {
            $this->setError("插件配置写入失败");
            return false;
        }
        $res = array();
        foreach ($array as $key => $val) {
            if (is_array($val)) {
                $res[] = "[$key]";
                foreach ($val as $sKey => $sVal)
                    $res[] = "$sKey = " . (is_numeric($sVal) ? $sVal : $sVal);
            } else
                $res[] = "$key = " . (is_numeric($val) ? $val : $val);
        }
        if ($handle = fopen($file, 'w')) {
            fwrite($handle, implode("\n", $res) . "\n");
            fclose($handle);
        } else {
            $this->setError("文件没有写入权限");
            return false;
        }
        return true;
    }

    // 生成插件菜单
    public function createMenu($plugin)
    {
        $menuFile = root_path() . 'plugin' . DS . $plugin . DS . 'menu.php';
        if(!is_file($menuFile)){
            return true;
        }
        $menus = include_once $menuFile;
        $info = $this->getPluginInfo($plugin);
        if(isset($info['parent_menu']) && $info['parent_menu'] === 'first'){
            $firstMenuId = Menu::where(['pid' => 0, 'is_show' => 1])->order(['sort'=>'desc', 'id'=>'asc'])->value('id');
            $ids = $this->createMenuIds($menus, $firstMenuId);
        }else{
            $ids = $this->createMenuIds($menus, 0);
        }
        $info['menu_ids'] = implode(',', $ids);
        if(!$this->setPluginInfo($plugin, $info)) return false;
        return true;
    }

    public function createMenuIds($menus, $pid=0)
    {
        foreach($menus as $menu){
            $id = Menu::insertGetId([
                'name' => $menu['name'],
                'des' => isset($menu['des']) ? $menu['des'] : '',
                'href' => isset($menu['href']) ? $menu['href'] : '',
                'rule' => isset($menu['rule']) ? $menu['rule'] : '',
                'is_menu' => $menu['is_menu'],
                'pid' => $pid,
                'icon' => isset($menu['icon']) ? $menu['icon'] : ''
            ]);
            $this->ids[] = $id;

            if(isset($menu['children'])){
                self::createMenuIds($menu['children'],$id);
            }
        }
        return $this->ids;
    }

    // 复制静态文件，包括html css js
    public function copyPublicFile($plugin)
    {
        $pluginDir = $this->getPluginPath($plugin) . DS;
        if(is_dir($pluginDir . 'public')){
            DirFile::copyDirs($pluginDir . 'public', root_path() . 'public');
        }
        return true;
    }

    // 删除静态文件，包括html css js
    public function removePublicFile($plugin)
    {
        $pluginDir = $this->getPluginPath($plugin) . DS;
        $pluginHtmlDir = $pluginDir . 'public' . DS . 'admin' . DS . 'plugin' . DS . $plugin;
        $publicHtmlDir = root_path() . 'public' . DS . 'admin' . DS . 'plugin' . DS . $plugin;
        if(is_dir($pluginHtmlDir) && $publicHtmlDir){
            DirFile::rmDirs($publicHtmlDir);
        }
        $pluginStaticDir = $pluginDir . 'public' . DS . 'static' . DS . 'plugin' . DS . $plugin;
        $publicStaticDir = root_path() . 'public' . DS . 'static' . DS . 'plugin' . DS . $plugin;
        if(is_dir($pluginStaticDir) && $publicStaticDir){
            DirFile::rmDirs($publicStaticDir);
        }
        return true;
    }

    // 重新生成config/plugin.php文件
    public function installPluginConf($plugin)
    {
        $pluginConf = Config::get('plugin');
        $pluginConf['installed'][] = $plugin;
        $pluginConf['installed'] = array_unique($pluginConf['installed']);
        sort($pluginConf['installed']);

        $info = $this->getPluginInfo($plugin);
        if(isset($info['is_editor']) && $info['is_editor']){
            $pluginConf['installedEditor'][] = $plugin;
            $pluginConf['installedEditor'] = array_unique($pluginConf['installedEditor']);
            sort($pluginConf['installedEditor']);
        }

        $fileName = root_path() .  DS . 'config' . DS . 'plugin.php';
        file_put_contents($fileName,"<?php\nreturn " . var_export($pluginConf,true) . ';');
        return true;
    }

    // 重新生成config/plugin.php文件
    public function unInstallPluginConf($plugin, $info)
    {
        $pluginConf = Config::get('plugin');
        foreach($pluginConf['installed'] as $k=>$installed){
            if($installed === $plugin) unset($pluginConf['installed'][$k]);
        }
        $pluginConf['installed'] = array_unique($pluginConf['installed']);
        sort($pluginConf['installed']);

        if(isset($info['is_editor']) && $info['is_editor']){
            foreach($pluginConf['installedEditor'] as $k=>$installedEditor){
                if($installedEditor === $plugin) unset($pluginConf['installedEditor'][$k]);
            }
            $pluginConf['installedEditor'] = array_unique($pluginConf['installedEditor']);
            sort($pluginConf['installedEditor']);
        }

        $fileName = root_path() .  DS . 'config' . DS . 'plugin.php';
        file_put_contents($fileName,"<?php\nreturn " . var_export($pluginConf,true) . ';');
        return true;
    }
}