<?php
// +----------------------------------------------------------------------
// | ShopXO 国内领先企业级B2C免费开源电商系统
// +----------------------------------------------------------------------
// | Copyright (c) 2011~2099 http://shopxo.net All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( https://opensource.org/licenses/mit-license.php )
// +----------------------------------------------------------------------
// | Author: Devil
// +----------------------------------------------------------------------
namespace app\install\controller;

use think\Db;

/**
 * 安装程序
 * @author   Devil
 * @blog    http://gong.gg/
 * @version 1.0.0
 * @date    2018-11-30
 * @desc    description
 */
class Index extends Common
{
    // 编码类型
    private $charset_type_list;

    // 安装日志上报
    private $behavior_obj;

    /**
     * 构造方法
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-11-30
     * @desc    description
     */
    public function __construct()
    {
        parent::__construct();

        // 编码类型
        $this->charset_type_list = [
            'utf8mb4'   => [
                'charset'   => 'utf8mb4',
                'collate'   => 'utf8mb4_general_ci',
                'version'   => 5.6,
            ],
            'utf8'      => [
                'charset'   => 'utf8',
                'collate'   => 'utf8_general_ci',
                'version'   => 5.0,
            ],
        ];

        // 安装日志上报类库
        $this->behavior_obj = new \base\Behavior();
    }

    /**
     * 是否已安装
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     */
    private function IsInstall()
    {
        // 是否已安装
        if(file_exists(ROOT.'config/database.php'))
        {
            exit('你已经安装过该系统，重新安装需要先删除 ./config/database.php 文件');
        }
    }

    /**
     * 协议
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     */
    public function Index()
    {
        $this->IsInstall();
        $this->behavior_obj->ReportInstallLog(['msg'=>'协议阅读']);
        return $this->fetch();
    }

    /**
     * 检查
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     */
    public function Check()
    {
        $this->IsInstall();
        $this->behavior_obj->ReportInstallLog(['msg'=>'环境检测']);
        return $this->fetch();
    }

    /**
     * 创建数据库
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     */
    public function Create()
    {
        $this->IsInstall();
        $this->behavior_obj->ReportInstallLog(['msg'=>'数据信息填写']);

        $this->assign('charset_type_list' , $this->charset_type_list);
        return $this->fetch();
    }

    /**
     * 完成
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     */
    public function Successful()
    {
        // 检测是否是新安装
        if(is_dir(ROOT.'config') && !file_exists(ROOT.'config/database.php'))
        {
            if(empty($_GET['s']) || stripos($_GET['s'], 'install') === false)
            {
                $url = __MY_URL__.'index.php?s=/install/index/index';
                exit(header('location:'.$url));
            }
        }
        return $this->fetch();
    }

    /**
     * 安装
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     */
    public function Add()
    {
        // 是否ajax
        if(!IS_AJAX)
        {
            die('非法访问');
        }

        // 参数
        $params = input('post.');
        $ret = $this->ParamsCheck($params);
        if($ret['code'] != 0)
        {
            $this->behavior_obj->ReportInstallLog(['msg'=>'参数校验['.$ret['msg'].']']);
            return $ret;
        }

        // 配置文件校验
        if(file_exists(ROOT.'config/database.php'))
        {
            $this->behavior_obj->ReportInstallLog(['msg'=>'你已经安装过该系统，重新安装需要先删除[./config/database.php 文件]']);
            return DataReturn('你已经安装过该系统，重新安装需要先删除[./config/database.php 文件]', -1);
        }

        // 开始安装
        $db = $this->DbObj($params);
        if(!is_object($db))
        {
            $this->behavior_obj->ReportInstallLog(['msg'=>'数据库连接失败']);
            return DataReturn('数据库连接失败', -1);
        }

        // mysql版本
        $ret = $this->IsVersion($db, $params['DB_CHARSET']);
        if($ret['code'] != 0)
        {
            return $ret;
        }

        // 检查数据表是否存在
        if(!$this->IsDbExist($db, $params['DB_NAME']))
        {
            if($this->DbNameCreate($db, $params['DB_NAME'], $params['DB_CHARSET']))
            {
                $db = $this->DbObj($params, $params['DB_NAME']);
            } else {
                $this->behavior_obj->ReportInstallLog(['msg'=>'数据库创建失败']);
                return DataReturn('数据库创建失败', -1);
            }
        } else {
            $db = $this->DbObj($params, $params['DB_NAME']);
        }
        if(!is_object($db))
        {
            $this->behavior_obj->ReportInstallLog(['msg'=>'数据库连接失败']);
            return DataReturn('数据库连接失败', -1);
        }

        // 创建数据表
        $ret = $this->CreateTable($db, $params);
        if($ret['code'] != 0)
        {
            return $ret;
        }

        // 生成数据库配置文件
        return $this->CreateDbConfig($params);
    }

    /**
     * 生成配置文件
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     * @param   [array]          $params [输入参数]
     */
    private function CreateDbConfig($params = [])
    {
        // 配置文件信息处理
        $db_str=<<<php
<?php
// +----------------------------------------------------------------------
// | ShopXO 国内领先企业级B2C免费开源电商系统
// +----------------------------------------------------------------------
// | Copyright (c) 2011~2099 http://shopxo.net All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( https://opensource.org/licenses/mit-license.php )
// +----------------------------------------------------------------------
// | Author: Devil
// +----------------------------------------------------------------------

// +----------------------------------------------------------------------
// | 数据库配置
// +----------------------------------------------------------------------
return [
    // 数据库类型
    'type'            => '{$params['DB_TYPE']}',
    // 服务器地址
    'hostname'        => '{$params['DB_HOST']}',
    // 数据库名
    'database'        => '{$params['DB_NAME']}',
    // 用户名
    'username'        => '{$params['DB_USER']}',
    // 密码
    'password'        => '{$params['DB_PWD']}',
    // 端口
    'hostport'        => '{$params['DB_PORT']}',
    // 连接dsn
    'dsn'             => '',
    // 数据库连接参数
    'params'          => [
        \PDO::ATTR_CASE => \PDO::CASE_LOWER,
        \PDO::ATTR_EMULATE_PREPARES => true,
    ],
    // 数据库编码默认采用utf8mb4
    'charset'         => '{$params['DB_CHARSET']}',
    // 数据库表前缀
    'prefix'          => '{$params['DB_PREFIX']}',
    // 数据库调试模式
    'debug'           => true,
    // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
    'deploy'          => 0,
    // 数据库读写是否分离 主从式有效
    'rw_separate'     => false,
    // 读写分离后 主服务器数量
    'master_num'      => 1,
    // 指定从服务器序号
    'slave_no'        => '',
    // 自动读取主库数据
    'read_master'     => false,
    // 是否严格检查字段是否存在
    'fields_strict'   => true,
    // 数据集返回类型
    'resultset_type'  => 'array',
    // 自动写入时间戳字段
    'auto_timestamp'  => false,
    // 时间字段取出后的默认时间格式
    'datetime_format' => 'Y-m-d H:i:s',
    // 是否需要进行SQL性能分析
    'sql_explain'     => false,
    // Builder类
    'builder'         => '',
    // Query类
    'query'           => '\\think\\db\\Query',
    // 是否需要断线重连
    'break_reconnect' => false,
    // 断线标识字符串
    'break_match_str' => [],
];
?>
php;
        if(@file_put_contents(ROOT.'config/database.php', $db_str) === false)
        {
            $this->behavior_obj->ReportInstallLog(['msg'=>'配置文件创建失败']);
            return DataReturn('配置文件创建失败', -1);
        }

        $this->behavior_obj->ReportInstallLog(['msg'=>'安装成功']);
        return DataReturn('安装成功', 0);
    }

    /**
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     * @param   [object]          $db      [db对象]
     * @param   [array]           $params  [输入参数]
     */
    private function CreateTable($db, $params)
    {
        if(!file_exists(ROOT.'config/shopxo.sql'))
        {
            $this->behavior_obj->ReportInstallLog(['msg'=>'数据库sql文件不存在']);
            return DataReturn('数据库sql文件不存在', -1);
        }

        // sql文件
        $sql = file_get_contents(ROOT.'config/shopxo.sql');

        // 替换表前缀
        if($params['DB_PREFIX'] != 'sxo_')
        {
            $sql = str_replace("`sxo_", " `{$params['DB_PREFIX']}", $sql);
        }

        // 编码替换,utf8mb4则做替换操作
        $charset = $this->charset_type_list[$params['DB_CHARSET']];
        if($charset['charset'] != 'utf8mb4')
        {
            $sql = str_replace('SET NAMES utf8mb4;', "SET NAMES {$charset['charset']};", $sql);
            $sql = str_replace('CHARSET=utf8mb4', "CHARSET={$charset['charset']}", $sql);
            $sql = str_replace('utf8mb4_general_ci', "{$charset['collate']}", $sql);
        }

        // 转为数组
        $sql_all = preg_split("/;[\r\n]+/", $sql);

        $success = 0;
        $failure = 0;
        foreach($sql_all as $v)
        {
            if (!empty($v))
            {
                if($db->execute($v) !== false)
                {
                    $success++;
                } else {
                    $failure++;
                }               
            }
        }

        $result = [
            'success'   => $success,
            'failure'   => $failure,
        ];
        $this->behavior_obj->ReportInstallLog(['msg'=>'sql运行 成功['.$success.']条, 失败['.$failure.']条']);
        if($failure > 0)
        {
            return DataReturn('sql运行失败['.$failure.']条', -1);
        }

        return DataReturn('success', 0, $result);
    }

    /**
     * 数据库版本校验
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     * @param   [object]          $db           [db对象]
     * @param   [string]          $db_charset   [数据库编码]
     */
    private function IsVersion($db, $db_charset)
    {
        $data = $db->query("select version() AS version");
        if(empty($data[0]['version']))
        {
            $this->behavior_obj->ReportInstallLog(['msg'=>'查询数据库版本失败']);
            return DataReturn('查询数据库版本失败', -1);
        } else {
            $mysql_version = str_replace('-log', '', $data[0]['version']);
            if($mysql_version < $this->charset_type_list[$db_charset]['version'])
            {
                $msg = '数据库版本过低、需要>='.$this->charset_type_list[$db_charset]['version'].'、当前'.$mysql_version;
                $this->behavior_obj->ReportInstallLog(['msg'=>$msg, 'mysql_version'=>$mysql_version]);
                return DataReturn($msg, -1);
            }
        }
        return DataReturn('success', 0);
    }

    /**
     * 数据库创建
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     * @param   [object]          $db           [db对象]
     * @param   [string]          $db_name      [数据库名称]
     * @param   [string]          $db_charset   [数据库编码]
     */
    private function DbNameCreate($db, $db_name, $db_charset)
    {
        $sql = "CREATE DATABASE {$db_name} DEFAULT CHARSET {$this->charset_type_list[$db_charset]['charset']} COLLATE {$this->charset_type_list[$db_charset]['collate']}";
        if($db->execute($sql) !== false)
        {
            return $this->IsDbExist($db, $db_name);
        }
        return false;
    }

    /**
     * 检查数据库是否存在
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     * @param   [object]          $db      [db对象]
     * @param   [string]          $db_name [数据库名称]
     */
    private function IsDbExist($db, $db_name)
    {
        $temp = $db->query("show databases like '{$db_name}'");
        return !empty($temp);
    }

    /**
     * 获取数据库操作对象
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     * @param   array           $params  [输入参数]
     * @param   string          $db_name [数据库名称]
     */
    private function DbObj($params = [], $db_name = '')
    {
        return Db::connect([
            // 数据库类型
            'type'        => $params['DB_TYPE'],
            // 数据库连接DSN配置
            'dsn'         => '',
            // 服务器地址
            'hostname'    => $params['DB_HOST'],
            // 数据库名
            'database'    => $db_name,
            // 数据库用户名
            'username'    => $params['DB_USER'],
            // 数据库密码
            'password'    => $params['DB_PWD'],
            // 数据库连接端口
            'hostport'    => $params['DB_PORT'],
            // 数据库连接参数
            'params'      => [
                \PDO::ATTR_CASE => \PDO::CASE_LOWER,
                \PDO::ATTR_EMULATE_PREPARES => true,
            ],
            // 数据库编码默认采用utf8mb4
            'charset'     => $params['DB_CHARSET'],
            // 数据库表前缀
            'prefix'      => $params['DB_PREFIX'],
        ]);
    }

    /**
     * 参数校验
     * @author   Devil
     * @blog    http://gong.gg/
     * @version 1.0.0
     * @date    2018-12-28
     * @desc    description
     * @param   [array]           $params [输入参数]
     */
    private function ParamsCheck($params = [])
    {
        // 请求类型
        $p = [
            [
                'checked_type'      => 'empty',
                'key_name'          => 'DB_TYPE',
                'error_msg'         => '请选择数据库类型',
            ],
            [
                'checked_type'      => 'in',
                'key_name'          => 'DB_CHARSET',
                'checked_data'      => array_column($this->charset_type_list, 'charset'),
                'error_msg'         => '请选择数据编码',
            ],
            [
                'checked_type'      => 'empty',
                'key_name'          => 'DB_HOST',
                'error_msg'         => '请填写数据库服务器地址',
            ],
            [
                'checked_type'      => 'empty',
                'key_name'          => 'DB_PORT',
                'error_msg'         => '请填写数据库端口',
            ],
            [
                'checked_type'      => 'empty',
                'key_name'          => 'DB_NAME',
                'error_msg'         => '请填写数据库名',
            ],
            [
                'checked_type'      => 'empty',
                'key_name'          => 'DB_USER',
                'error_msg'         => '请填写数据库用户名',
            ],
            [
                'checked_type'      => 'empty',
                'key_name'          => 'DB_PWD',
                'error_msg'         => '请填写数据库密码',
            ],
            [
                'checked_type'      => 'empty',
                'key_name'          => 'DB_PREFIX',
                'error_msg'         => '请填写数据表前缀',
            ],
        ];
        $ret = ParamsChecked($params, $p);
        if($ret !== true)
        {
            return DataReturn($ret, -1);
        }
        return DataReturn('success', 0);
    }
}
?>