<?php

ini_set('display_errors', 'On');
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

use think\facade\Db;

require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../vendor/topthink/framework/src/helper.php';

const DS           = DIRECTORY_SEPARATOR;
const ROOT_PATH    = __DIR__ . DS . '..' . DS;
const INSTALL_PATH = ROOT_PATH . 'config' . DS . 'install' . DS;
const CONFIG_PATH  = ROOT_PATH . 'config' . DS;

$currentHost = ($_SERVER['SERVER_PORT'] == 443 ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . '/';

function isReadWrite($file)
{
    if (DIRECTORY_SEPARATOR == '\\') {
        return true;
    }
    if (DIRECTORY_SEPARATOR == '/' && @ ini_get("safe_mode") === false) {
        return is_writable($file);
    }
    if (!is_file($file) || ($fp = @fopen($file, "r+")) === false) {
        return false;
    }
    fclose($fp);
    return true;
}

$errorInfo = null;
if (is_file(INSTALL_PATH . 'lock' . DS . 'install.lock')) {
    $errorInfo = '已安装系统，如需重新安装请删除文件：/config/install/lock/install.lock';
} elseif (!isReadWrite(ROOT_PATH . 'config' . DS)) {
    $errorInfo = ROOT_PATH . 'config' . DS . '：读写权限不足';
} elseif (!isReadWrite(ROOT_PATH . 'runtime' . DS)) {
    $errorInfo = ROOT_PATH . 'runtime' . DS . '：读写权限不足';
} elseif (!isReadWrite(ROOT_PATH . 'public' . DS)) {
    $errorInfo = ROOT_PATH . 'public' . DS . '：读写权限不足';
} elseif (!checkPhpVersion('7.1.0')) {
    $errorInfo = 'PHP版本不能小于7.1.0';
} elseif (!extension_loaded("PDO")) {
    $errorInfo = '当前未开启PDO，无法进行安装';
}

// POST请求
if (isAjax()) {
    $post = $_POST;

    $cover      = $post['cover'] == 1 ? true : false;
    $database   = $post['database'];
    $hostname   = $post['hostname'];
    $hostport   = $post['hostport'];
    $dbUsername = $post['db_username'];
    $dbPassword = $post['db_password'];
    $prefix     = $post['prefix'];
    $adminUrl   = $post['admin_url'];
    $username   = $post['username'];
    $password   = $post['password'];

    // 参数验证
    $validateError = null;

    // 判断是否有特殊字符
    $check = preg_match('/[0-9a-zA-Z]+$/', $adminUrl, $matches);
    if (!$check) {
        $validateError = '后台地址不能含有特殊字符, 只能包含字母或数字。';
        $data          = [
            'code' => 0,
            'msg'  => $validateError,
        ];
        die(json_encode($data));
    }

    if (strlen($adminUrl) < 2) {
        $validateError = '后台的地址不能小于2位数';
    } elseif (strlen($password) < 5) {
        $validateError = '管理员密码不能小于5位数';
    } elseif (strlen($username) < 4) {
        $validateError = '管理员账号不能小于4位数';
    }
    if (!empty($validateError)) {
        $data = [
            'code' => 0,
            'msg'  => $validateError,
        ];
        die(json_encode($data));
    }
    $envFile = '../.env';
    if (!is_file($envFile)) {
        $data = [
            'code' => 0,
            'msg'  => '.env 配置文件不存在',
        ];
        die(json_encode($data));
    }
    $envConfig = parse_ini_file($envFile, true);
    $charset   = $envConfig['DATABASE']['CHARSET'] ?? 'utf8mb4';

    // DB类初始化
    $config = [
        'type'     => 'mysql',
        'hostname' => $hostname,
        'username' => $dbUsername,
        'password' => $dbPassword,
        'hostport' => $hostport,
        'charset'  => $charset,
        'prefix'   => $prefix,
        'debug'    => true,
    ];
    Db::setConfig([
                      'default'     => 'mysql',
                      'connections' => [
                          'mysql'   => $config,
                          'install' => array_merge($config, ['database' => $database]),
                      ],
                  ]);
    // 检测数据库连接
    if (!checkConnect()) {
        $data = [
            'code' => 0,
            'msg'  => '数据库连接失败',
        ];
        die(json_encode($data));
    }
    // 检测数据库是否存在
    if (!$cover && checkDatabase($database)) {
        $data = [
            'code' => 0,
            'msg'  => '数据库已存在，请选择覆盖安装或者修改数据库名',
        ];
        die(json_encode($data));
    }
    // 创建数据库
    createDatabase($database, $config);
    // 导入sql语句等等
    $install = install($username, $password, array_merge($config, ['database' => $database]), $adminUrl);
    if ($install !== true) {
        $data = [
            'code' => 0,
            'msg'  => '系统安装失败：' . $install,
        ];
        die(json_encode($data));
    }
    $data = [
        'code' => 1,
        'msg'  => '系统安装成功，正在跳转登录页面',
        'url'  => $adminUrl,
    ];
    die(json_encode($data));
}


function isAjax()
{
    if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
        return true;
    } else {
        return false;
    }
}

function isPost()
{
    return ($_SERVER['REQUEST_METHOD'] == 'POST' && checkurlHash($GLOBALS['verify'])
            && (empty($_SERVER['HTTP_REFERER']) || preg_replace("~https?:\/\/([^\:\/]+).*~i", "\\1", $_SERVER['HTTP_REFERER']) == preg_replace("~([^\:]+).*~", "\\1", $_SERVER['HTTP_HOST']))) ? 1 : 0;
}

function checkPhpVersion($version)
{
    $php_version = explode('-', phpversion());
    $check       = strnatcasecmp($php_version[0], $version) >= 0 ? true : false;
    return $check;
}

function checkConnect()
{
    try {
        $mysqlVersion = Db::query("select version() as version");
        $_version     = $mysqlVersion[0]['version'] ?? 0;
        if (version_compare($_version, '5.7.0', '<')) {
            $data = [
                'code' => 0,
                'msg'  => 'mysql版本最低要求 5.7.x',
            ];
            die(json_encode($data));
        }
    } catch (\Exception $e) {
        return false;
    }
    return true;
}

function checkDatabase($database)
{
    $check = Db::query("SELECT * FROM information_schema.schemata WHERE schema_name='{$database}'");
    if (empty($check)) {
        return false;
    } else {
        return true;
    }
}

function createDatabase($database, $config)
{
    try {
        Db::execute("CREATE DATABASE IF NOT EXISTS `{$database}` DEFAULT CHARACTER SET {$config['charset']}");
    } catch (\Exception $e) {
        return false;
    }
    return true;
}

function parseSql($sql = '', $to = '', $from = '')
{
    list($pure_sql, $comment) = [[], false];
    $sql = explode("\n", trim(str_replace(["\r\n", "\r"], "\n", $sql)));
    foreach ($sql as $key => $line) {
        if ($line == '') {
            continue;
        }
        if (preg_match("/^(#|--)/", $line)) {
            continue;
        }
        if (preg_match("/^\/\*(.*?)\*\//", $line)) {
            continue;
        }
        if (substr($line, 0, 2) == '/*') {
            $comment = true;
            continue;
        }
        if (substr($line, -2) == '*/') {
            $comment = false;
            continue;
        }
        if ($comment) {
            continue;
        }
        if ($from != '') {
            $line = str_replace('`' . $from, '`' . $to, $line);
        }
        if ($line == 'BEGIN;' || $line == 'COMMIT;') {
            continue;
        }
        array_push($pure_sql, $line);
    }
    //$pure_sql = implode($pure_sql, "\n");
    $pure_sql = implode("\n", $pure_sql);
    $pure_sql = explode(";\n", $pure_sql);
    return $pure_sql;
}

function install($username, $password, $config, $adminUrl)
{
    $sqlPath  = file_get_contents(INSTALL_PATH . 'sql' . DS . 'install.sql');
    $sqlArray = parseSql($sqlPath, $config['prefix'], 'ea_');
    Db::startTrans();
    try {
        foreach ($sqlArray as $vo) {
            Db::connect('install')->execute($vo);
        }
        Db::connect('install')
            ->name('system_admin')
            ->where('id', 1)
            ->delete();
        Db::connect('install')
            ->name('system_admin')
            ->insert([
                         'id'          => 1,
                         'username'    => $username,
                         'head_img'    => '/static/admin/images/head.jpg',
                         'password'    => password($password),
                         'create_time' => time(),
                     ]);

        // 处理安装文件
        !is_dir(INSTALL_PATH) && @mkdir(INSTALL_PATH);
        !is_dir(INSTALL_PATH . 'lock' . DS) && @mkdir(INSTALL_PATH . 'lock' . DS);
        @file_put_contents(INSTALL_PATH . 'lock' . DS . 'install.lock', date('Y-m-d H:i:s'));
        @file_put_contents(CONFIG_PATH . 'app.php', getAppConfig($adminUrl));
        @file_put_contents(CONFIG_PATH . 'DATABASE.php', getDatabaseConfig($config));
        Db::commit();
    } catch (\Exception $e) {
        Db::rollback();
        return $e->getMessage();
    }
    return true;
}

function password($value)
{
    $value = sha1('blog_') . md5($value) . md5('_encrypt') . sha1($value);
    return sha1($value);
}

function getAppConfig($admin)
{
    $config = <<<EOT
<?php
// +----------------------------------------------------------------------
// | 应用设置
// +----------------------------------------------------------------------

use think\\facade\Env;

return [
    // 应用地址
    'app_host'         => Env::get('APP.HOST', ''),
    // 应用的命名空间
    'app_namespace'    => '',
    // 是否启用路由
    'with_route'       => true,
    // 是否启用事件
    'with_event'       => true,
    // 开启应用快速访问
    'app_express'      => true,
    // 默认应用
    'default_app'      => 'index',
    // 默认时区
    'default_timezone' => 'Asia/Shanghai',
    // 应用映射（自动多应用模式有效）
    'app_map'          => [
        Env::get('EASYADMIN.ADMIN', '{$admin}') => 'admin',
    ],
    // 后台别名
    'admin_alias_name' => Env::get('EASYADMIN.ADMIN', '{$admin}'),
    // 域名绑定（自动多应用模式有效）
    'domain_bind'      => [],
    // 禁止URL访问的应用列表（自动多应用模式有效）
    'deny_app_list'    => ['common'],
    // 异常页面的模板文件
    'exception_tmpl'   => Env::get('APP_DEBUG') == 1 ? app()->getThinkPath() . 'tpl/think_exception.tpl' : app()->getBasePath() . 'common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'think_exception.tpl',
    // 跳转页面的成功模板文件
    'dispatch_success_tmpl'   => app()->getBasePath() . 'common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'dispatch_jump.tpl',
    // 跳转页面的失败模板文件
    'dispatch_error_tmpl'   => app()->getBasePath() . 'common' . DIRECTORY_SEPARATOR . 'tpl' . DIRECTORY_SEPARATOR . 'dispatch_jump.tpl',
    // 错误显示信息,非调试模式有效
    'error_message'    => '页面错误！请稍后再试～',
    // 显示错误信息
    'show_error_msg'   => false,
    // 静态资源上传到OSS前缀
    'oss_static_prefix'   => Env::get('EASYADMIN.OSs_static_prefix', 'static_easyadmin'),
];

EOT;
    return $config;
}

function getDatabaseConfig($data)
{
    $config = <<<EOT
<?php
use think\\facade\Env;

return [
    // 默认使用的数据库连接配置
    'default'         => Env::get('DATABASE.DRIVER', 'mysql'),

    // 自定义时间查询规则
    'time_query_rule' => [],

    // 自动写入时间戳字段
    // true为自动识别类型 false关闭
    // 字符串则明确指定时间字段类型 支持 int timestamp datetime date
    'auto_timestamp'  => true,

    // 时间字段取出后的默认时间格式
    'datetime_format' => 'Y-m-d H:i:s',

    // 数据库连接配置信息
    'connections'     => [
        'mysql' => [
            // 数据库类型
            'type'              => Env::get('DATABASE.TYPE', 'mysql'),
            // 服务器地址
            'hostname'          => Env::get('DATABASE.HOSTNAME', '{$data['hostname']}'),
            // 数据库名
            'database'          => Env::get('DATABASE.DATABASE', '{$data['database']}'),
            // 用户名
            'username'          => Env::get('DATABASE.USERNAME', '{$data['username']}'),
            // 密码
            'password'          => Env::get('DATABASE.PASSWORD', '{$data['password']}'),
            // 端口
            'hostport'          => Env::get('DATABASE.HOSTPORT', '{$data['hostport']}'),
            // 数据库连接参数
            'params'            => [],
            // 数据库编码默认采用utf8mb4
            'charset'           => Env::get('DATABASE.CHARSET', 'utf8mb4'),
            // 数据库表前缀
            'prefix'            => Env::get('DATABASE.PREFIX', '{$data['prefix']}'),

            // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
            'deploy'            => 0,
            // 数据库读写是否分离 主从式有效
            'rw_separate'       => false,
            // 读写分离后 主服务器数量
            'master_num'        => 1,
            // 指定从服务器序号
            'slave_no'          => '',
            // 是否严格检查字段是否存在
            'fields_strict'     => true,
            // 是否需要断线重连
            'break_reconnect'   => false,
            // 监听SQL
            'trigger_sql'       => true,
            // 开启字段缓存
            'fields_cache'      => false,
            // 字段缓存路径
            'schema_cache_path' => app()->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR,
        ],

        // 更多的数据库配置信息
    ],
];

EOT;
    return $config;
}

?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>安装EasyAdmin后台程序</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="static/plugs/layui-v2.5.6/css/layui.css?v=<?php
    echo time() ?>" media="all">
    <link rel="stylesheet" href="static/common/css/insatll.css?v=<?php
    echo time() ?>" media="all">
</head>
<body>
<h1><img src="static/common/images/logo-1.png"></h1>
<h2>安装 EasyAdmin8 后台系统</h2>
<div class="content">
    <form class="layui-form layui-form-pane" action="">
        <?php
        if ($errorInfo): ?>
            <div class="error">
                <?php
                echo $errorInfo; ?>
            </div>
        <?php
        endif; ?>
        <div class="bg">
            <div class="layui-form-item">
                <label class="layui-form-label">数据库地址</label>
                <div class="layui-input-block">
                    <input class="layui-input" name="hostname" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据库地址" placeholder="请输入数据库地址" value="127.0.0.1">
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label">数据库端口</label>
                <div class="layui-input-block">
                    <input class="layui-input" name="hostport" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据库端口" placeholder="请输入数据库端口" value="3306">
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label">数据库名称</label>
                <div class="layui-input-block">
                    <input class="layui-input" name="database" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据库名称" placeholder="请输入数据库名称" value="easyadmin8">
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label">数据表前缀</label>
                <div class="layui-input-block">
                    <input class="layui-input" name="prefix" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据表前缀" placeholder="请输入数据表前缀" value="ea8_">
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label">数据库账号</label>
                <div class="layui-input-block">
                    <input class="layui-input" name="db_username" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据库账号" placeholder="请输入数据库账号" value="root">
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label">数据库密码</label>
                <div class="layui-input-block">
                    <input type="password" class="layui-input" name="db_password" autocomplete="off" lay-verify="required" lay-reqtext="请输入数据库密码" placeholder="请输入数据库密码">
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label">覆盖数据库</label>
                <div class="layui-input-block" style="text-align: left">
                    <input type="radio" name="cover" value="1" title="覆盖">
                    <input type="radio" name="cover" value="0" title="不覆盖" checked>
                </div>
            </div>
        </div>
        <div class="bg">
            <div class="layui-form-item">
                <label class="layui-form-label">后台的地址</label>
                <div class="layui-input-block">
                    <input class="layui-input" id="admin_url" name="admin_url" autocomplete="off" lay-verify="required" lay-reqtext="请输入后台的地址" placeholder="为了后台安全，不建议将后台路径设置为admin" value="admin">
                    <span class="tips">后台登录地址： <?php
                        echo $currentHost; ?><span id="admin_name">admin</span></span>
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label">管理员账号</label>
                <div class="layui-input-block">
                    <input class="layui-input" name="username" autocomplete="off" lay-verify="required" lay-reqtext="请输入管理员账号" placeholder="请输入管理员账号" value="admin">
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label">管理员密码</label>
                <div class="layui-input-block">
                    <input type="password" class="layui-input" name="password" autocomplete="off" lay-verify="required" lay-reqtext="请输入管理员密码" placeholder="请输入管理员密码">
                </div>
            </div>
        </div>

        <div class="layui-form-item">
            <button class="layui-btn layui-btn-normal <?php
            echo $errorInfo ? 'layui-btn-disabled' : '' ?>" lay-submit="" lay-filter="install">确定安装
            </button>
        </div>
    </form>
</div>
<script src="static/plugs/layui-v2.5.6/layui.js?v=<?php
echo time() ?>" charset="utf-8"></script>
<script>
    layui.use(['form', 'layer'], function () {
        var $ = layui.jquery,
            form = layui.form,
            layer = layui.layer;

        $("#admin_url").bind("input propertychange", function () {
            var val = $(this).val();
            $("#admin_name").text(val);
        });

        form.on('submit(install)', function (data) {
            if ($(this).hasClass('layui-btn-disabled')) {
                return false;
            }
            var _data = data.field;
            var loading = layer.msg('正在安装...', {
                icon: 16,
                shade: 0.2,
                time: false
            });
            $.ajax({
                url: window.location.href,
                type: 'post',
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "json",
                data: _data,
                timeout: 60000,
                success: function (data) {
                    layer.close(loading);
                    if (data.code === 1) {
                        layer.msg(data.msg, {icon: 1}, function () {
                            window.location.href = location.protocol + '//' + location.host + '/' + data.url;
                        });
                    } else {
                        layer.msg(data.msg, {icon: 2});
                    }
                },
                error: function (xhr, textstatus, thrown) {
                    layer.close(loading);
                    layer.msg('Status:' + xhr.status + '，' + xhr.statusText + '，请稍后再试！', {icon: 2});
                    return false;
                }
            });
            return false;
        });
    });
</script>
</body>
</html>
