<?php
// +----------------------------------------------------------------------
// | Wendasns [ 开源问答社区系统 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2020~2021 http://wendasns.com All rights reserved.
// +----------------------------------------------------------------------
namespace wendasns;

use ZipArchive;
use PDO;
use think\Exception;
use think\facade\Db;
use think\facade\Env;

/**
 * 插件服务
 * @package think\addons
 */
class Addon
{
    
    /**
     * 解压插件
     *
     * @param   string $name 插件名称
     * @return  string
     * @throws  Exception
     */
    public function unzip($name)
    {
        $file = plugin_path() . $name . '.zip';
        $dir = plugin_path();
        if (class_exists('ZipArchive')) {
            $zip = new ZipArchive;
            if ($zip->open($file) !== TRUE) {
                throw new Exception('Unable to open the zip file');
            }
            if (!$zip->extractTo($dir)) {
                $zip->close();
                throw new Exception('Unable to extract the file');
            }
            $zip->close();
            return $dir;
        }
        throw new Exception('无法执行解压操作，请确保ZipArchive安装正确');
    }

    /**
     * 检测插件是否完整
     *
     * @param   string $name 插件名称
     * @return  boolean
     * @throws  Exception
     */
    private function check($name)
    {
        $file_xml = plugin_path($name).'Manifest.xml';
        if(!file_exists($file_xml)){
        	throw new Exception('插件不完整，缺少Manifest.xml文件');
        }
        $controller = plugin_path($name).'controller';
        if(!is_dir($controller)){
        	throw new Exception('插件不完整，缺少controller目录');
        }
        $xml_object = simplexml_load_file($file_xml);
        $apps = (array)$xml_object->application;
        $info = [
        	'name' => '',
        	'alias' => '',
        	'description' => '',
        	'author-name' => '',
        	'version' => ''
        ];
        $result = array_intersect_key($apps, $info);
        if(count($result)!=5){
        	throw new Exception('插件Manifest.xml文件格式不正确，或缺少主要信息');
        }
        $my_version = config('wendasns.version');
        $wd_version = isset($apps['wd-version']) ? $apps['wd-version'] : '1.0.0';
        if($my_version < $wd_version){
        	throw new Exception('插件只支持wendasns V'.$wd_version.'及以上版本');
        }
        $result['author-email'] = isset($apps['author-email']) ? $apps['author-email'] : '';
        $logo = isset($apps['logo']) ? $apps['logo'] : '';
        if(filter_var($logo, FILTER_VALIDATE_URL) === false) {
        	$logo = '/addon/'.$name.'/'.$logo;
        }
        $result['logo'] = $logo;
        return $result;
    }

    /**
     * 导入SQL
     *
     * @param   string $name 插件名称
     * @return  boolean
     */
    public function importsql($name)
    {
        $sqlFile = plugin_path($name).'config'.DIRECTORY_SEPARATOR.'data.sql';
        if (is_file($sqlFile)) {
            $dataSql = @file_get_contents($sqlFile);
            
            $config = config('database.connections.mysql');
            $hostname = $config['hostname'];
            $dbname = $config['database'];
            $username = $config['username'];
            $password = $config['password'];
            $prefix = $config['prefix'];
            $dataSql = str_replace('{th}', $prefix, $dataSql);
            
            if(!$this->tableCheck($dataSql, $prefix.'app_'.$name)){
            	return false;
            };
            try {
            	$pdo = new PDO("mysql:dbname={$dbname};host={$hostname}", $username, $password);
            	$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
                $pdo->exec($dataSql);
            } catch (\PDOException $e) {
            	throw new Exception($e->getMessage());
            }
        }
        return true;
    }
    
    //检查data.sql文件里数据表
    protected function tableCheck($data, $prefix)
    {
    	$dm = Db::query('SHOW TABLES');
    	$term = [];
    	$res = true;
    	foreach($dm as $v){
    		foreach($v as $name){
    			$term[] = $name;
    		}
    	}
    	preg_match_all('/CREATE TABLE `(.*?)`/', $data, $create, PREG_PATTERN_ORDER);
    	preg_match_all('/UPDATE `(.*?)`/', $data, $update, PREG_PATTERN_ORDER);
    	preg_match_all('/INSERT INTO `(.*?)`/', $data, $insert, PREG_PATTERN_ORDER);
    	$c = isset($create[1][0]) ? $create[1] : [];
    	$u = isset($update[1][0]) ? $update[1] : [];
    	$i = isset($insert[1][0]) ? $insert[1] : [];
    	$list = array_merge($c, $i, $u);
    	foreach($list as $v){
    		if(in_array($v, $term)){
    			continue;
    		}
    		if(strpos($v, $prefix) === false){
    			$res = false;
    			break;
    		}
    	}
    	return $res;
    }
    
    /**
     * 安装插件
     *
     * @param   string $name 插件名称
     * @param   boolean $force 是否覆盖
     * @param   array $extend 扩展参数
     * @return  boolean
     * @throws  Exception
     * @throws  AddonException
     */
    public function install($name)
    {

        // 解压插件
        $addonDir = $this->unzip($name);
        
        //检查插件完整性
        $apps = $this->check($name);
        
        $data = [
        	'name' => $apps['name'],
        	'alias' => $apps['alias'],
        	'intro' => $apps['description'],
        	'author' => $apps['author-name'],
        	'version' => $apps['version'],
        	'email' => $apps['author-email'],
        	'logo' => $apps['logo'],
        ];
        
        $Id = Db::name('application')->insertGetId($data);
        
        // 移除临时文件
        $tmpFile = plugin_path().$name.'.zip';
        @unlink($tmpFile);
        
        //导入sql文件
        Addon::importsql($name);
        
        //检查是否有res/css/js等文件
        $addonResDir = plugin_path($name).'res';
        if (is_dir($addonResDir)) {
        	$target_dir = public_path('addon').$name;
            mkdir($target_dir, 0755, true);
            $this->copyDir($addonResDir, $target_dir);
        }
        
        //创建install_.lock
        $myfile = fopen($addonDir.DIRECTORY_SEPARATOR.$name.DIRECTORY_SEPARATOR.'install_.lock', 'w');
        fwrite($myfile, time());
        fclose($myfile);
        return true;
    }

    /**
     * 卸载插件
     *
     * @param   string $name
     * @param   boolean $force 是否强制卸载
     * @return  boolean
     * @throws  Exception
     */
    public function uninstall(string $name)
    {
    	$prefix = config('database.connections.mysql.prefix').'app_'.$name;
    	$dm = Db::query('SHOW TABLES');
    	foreach($dm as $v){
    		foreach($v as $table_name){
    			if(strpos($table_name, $prefix) !== false){
    				Db::query("DROP TABLE `{$table_name}`");
    			}
    		}
    	}
    	Db::name('application')->where('alias', $name)->delete();
        $res_path = public_path('addon').$name;
        $addon_path = plugin_path($name);
        $this->deleteDir($res_path);
        $this->deleteDir($addon_path);
        return true;
    }

    //删除插件目录和静态目录
    protected function deleteDir($dirName)
    {
    	if(!is_dir($dirName)){
    		return false;
    	}

    	$handle = @opendir($dirName);//打开目录
    	while(($file = @readdir($handle)) !== false){
    		if($file != '.' && $file != '..'){
    			$dir = $dirName . DIRECTORY_SEPARATOR . $file;
    			is_dir($dir) ? $this->deleteDir($dir) : @unlink($dir);
    		}
    	}
    	
    	closedir($handle);
    	return rmdir($dirName);
    }

    //复制静态文件
    protected function copyDir($oldDir, $aimDir)
    {
    	if(!is_dir($oldDir)){
    		return false;
    	}
    	
    	$handle = @opendir($oldDir);//打开目录
    	while(($file = @readdir($handle)) !== false){
    		if($file != '.' && $file != '..'){
    			$new_oldDir = $oldDir . DIRECTORY_SEPARATOR . $file;
    			$new_aimDir = $aimDir . DIRECTORY_SEPARATOR . $file;
    			if(is_dir($new_oldDir)){
    				if(!is_dir($new_aimDir)){
    					$row = mkdir($new_aimDir, 0755, true);
    					if($row) $this->copyDir($new_oldDir, $new_aimDir);
    				}
    			}else{
    				copy($new_oldDir, $new_aimDir);
    			}
    		}
    	}
    	
    	closedir($handle);
    	return true;
    }
    
    //获取所有插件后台左侧菜单
    public function menu()
    {
    	$dm = Db::name('application')->where('menu', 1)->select();
    	$res = [];
    	foreach($dm as $v){
    		$path = plugin_path($v['alias']).'config'.DIRECTORY_SEPARATOR.'adminmenu.php';
    		if(!is_file($path)){
    			continue;
    		}
    		$row = include $path;
    		if(!is_array($row)){
    			continue;
    		}
    		$res = array_merge($res, $row);
    	}
    	return $res;
    }
}
