<?php namespace Phpcmf\Control;
/**
 * https://www.besyun.com
 * BESCMS
 * 本文件是框架系统文件，二次开发时不可以修改本文件
 **/

define('IS_INSTALL', 1);
set_time_limit(0);

// 安装程序
class Install extends \Phpcmf\Common {

    public $db;
    private $lock;

    /**
     * 初始化共享控制器
     */
    public function __construct()
    {
        parent::__construct();
        define('SITE_LANGUAGE', is_file(WRITEPATH.'lang.default') ? file_get_contents(WRITEPATH.'lang.default') : 'zh-cn');
        define('SITE_ID', 1);
        define('SITE_URL', '/');
        define('SITE_MURL', '/');
        define('IS_API_HTTP', 0);
        define('SITE_TEMPLATE', 'default');
        define('THEME_PATH', '/static/');
        define('ROOT_THEME_PATH', '/static/');
        define('LANG_PATH', '/api/language/'.SITE_LANGUAGE.'/');
        if (is_file(MYPATH.'Config/Version.php')) {
            $app = require MYPATH.'Config/Version.php';
        } else {
            $app = [
                'id' => 1,
                'name' => 'XunRui',
                'version' => 'dev',
            ];
        }
        define('DR_CMS', $app['id']);
        define('DR_NAME', $app['name']);
        define('DR_VERSION', $app['version']);
        define('SITE_NAME', $app['name']);
        define('CMF_UPDATE_TIME', SYS_TIME);
        \Phpcmf\Service::V()->init('pc');
        \Phpcmf\Service::V()->admin();
    }

    public function index() {

        $this->lock = WRITEPATH.'install.lock';
        if (!IS_XRDEV && is_file($this->lock)) {
            exit(dr_lang('安装程序已经被锁定，重新安装请删除%s', 'cache/install.lock'));
        }

        $data = [];
        $step = intval($_GET['step']);
        if (!$step && SITE_LANGUAGE != 'zh-cn') {
            $step = 1;
        }
        $error = '';
        $admin = 'admin';

        switch ($step) {

            case 0:
                $gsname = '四川迅睿云软件开发有限公司';
                if (is_file(MYPATH.'Config/License.php')) {
                    $ls = require MYPATH.'Config/License.php';
                    if (isset($ls['name']) && $ls['name']) {
                        $gsname = $ls['name'];
                    }
                }
                \Phpcmf\Service::V()->assign([
                    'gsname' => $gsname,
                ]);
                break;

            case 1:

                $error = 0;
                // 目录权限检查
                $dir = [
                    WRITEPATH,
                    WRITEPATH.'data/',
                    WRITEPATH.'file/',
                    WRITEPATH.'config/',
                    WRITEPATH.'session/',
                    WRITEPATH.'template/',
                    WRITEPATH.'database/',                    CONFIGPATH,
                    WEBPATH.'uploadfile/',
                ];
                $path = [];
                foreach ($dir as $t) {
                    $path[$t] = dr_check_put_path($t);
                    if (!$path[$t]) {
                        $error = 1;
                    }
                }
                $php = [
                    [
                        'name' => dr_lang('PHP扩展%s', 'mbstring'),
                        'code' => function_exists('mb_substr'),
                        'help' => 'http://www.xunruicms.com/doc/742.html',
                    ],
                    [
                        'name' => dr_lang('PHP扩展%s', 'curl'),
                        'code' => function_exists('curl_init'),
                        'help' => 'http://www.xunruicms.com/doc/743.html',
                    ],
                ];
                $db_ext = [
                    [
                        'name' => dr_lang('PHP扩展%s', 'mysqli').'（MySQLi）',
                        'code' => function_exists('mysqli_init'),
                        'help' => 'http://www.xunruicms.com/doc/741.html',
                    ],
                    [
                        'name' => dr_lang('PHP扩展%s', 'sqlite3').'（SQLite3）',
                        'code' => extension_loaded('sqlite3'),
                        'help' => '',
                    ],
                ];
                // 数据库扩展：mysqli / sqlite3 至少启用一个即可
                $has_db_ext = function_exists('mysqli_init') || extension_loaded('sqlite3');
                if (!$has_db_ext) {
                    $error = 1;
                }

                \Phpcmf\Service::V()->assign([
                    'php' => $php,
                    'db_ext' => $db_ext,
                    'has_db_ext' => $has_db_ext,
                    'path' => $path,
                    'error' => $error,
                ]);
                break;

            case 2:
                // 安装信息填写

                $is_demo = is_file(MYPATH.'Config/demo.sql') && !is_file(MYPATH.'Config/Install.sql');

                if (IS_AJAX_POST) {

                    $data = $_POST['data'];
                    if (empty($data['name'])) {
                        $this->_json(0, dr_lang('项目名称不能为空'));
                    } elseif (empty($data['username'])) {
                        $this->_json(0, dr_lang('创始人账号不能为空'));
                    } elseif (empty($data['password'])) {
                        $this->_json(0, dr_lang('创始人密码不能为空'));
                    }  elseif (empty($data['email'])) {
                        $this->_json(0, dr_lang('创始人邮箱不能为空'));
                    }

                    // 数据库驱动：扫描 System/Database 下可用驱动（目录不存在则默认 MySQLi）
                    $drivers = $this->_scan_database_drivers($this->_my_install_sql_requires_mysql());
                    $rawDriver = isset($data['db_driver']) ? trim((string) $data['db_driver']) : '';
                    $selected = null;
                    foreach ($drivers as $d) {
                        if (strcasecmp($d['name'], $rawDriver) === 0) {
                            $selected = $d;
                            break;
                        }
                    }
                    if (!$selected) {
                        // 默认优先 MySQLi
                        foreach ($drivers as $d) {
                            if ($d['enabled']) {
                                $selected = $d;
                                if ($d['name'] === 'MySQLi') {
                                    break;
                                }
                            }
                        }
                    }
                    if (!$selected || !$selected['enabled']) {
                        $this->_json(0, dr_lang('不支持的数据库驱动：%s', $rawDriver !== '' ? $rawDriver : '-'));
                    }
                    $data['db_driver'] = $selected['name'];

                    // 自定义包 Install.sql 含 SQL 时仅允许 MySQLi
                    if ($this->_my_install_sql_requires_mysql() && $data['db_driver'] !== 'MySQLi') {
                        $this->_json(0, dr_lang('当前安装包存在自定义 Install.sql，仅支持 MySQLi 安装'));
                    }

                    if (empty($data['db_prefix'])) {
                        $this->_json(0, dr_lang('数据表前缀不能为空'));
                    }

                    if ($data['db_driver'] === 'SQLite3') {
                        // 路径与随机文件名由 _install_db_bootstrap 生成
                        $data['db_host'] = isset($data['db_host']) ? $data['db_host'] : '';
                        $data['db_user'] = isset($data['db_user']) ? $data['db_user'] : '';
                        $data['db_pass'] = isset($data['db_pass']) ? $data['db_pass'] : '';
                    } else {
                        if (empty($data['db_host'])) {
                            $this->_json(0, dr_lang('数据库地址不能为空'));
                        } elseif (empty($data['db_user'])) {
                            $this->_json(0, dr_lang('数据库账号不能为空'));
                        } elseif (empty($data['db_name'])) {
                            $this->_json(0, dr_lang('数据库名称不能为空'));
                        } elseif (!isset($data['db_pass'])) {
                            $this->_json(0, dr_lang('数据库密码不能为空'));
                        }
                        // 允许空密码（部分本地环境）
                        if (is_numeric(substr($data['db_name'], 0, 1))) {
                            $this->_json(0, dr_lang('数据库名称（%s）不能是数字开头', $data['db_name']));
                        } elseif (strpos($data['db_name'], '.') !== false) {
                            $this->_json(0, dr_lang('数据库名（%s）不能存在.号', $data['db_name']));
                        }
                        $data['db_name'] = dr_safe_filename($data['db_name']);
                    }

                    $rt = $this->_install_db_bootstrap($data);
                    if (!$rt['code']) {
                        $this->_json(0, $rt['msg']);
                    }
                    // SQLite 可能规范化了相对路径（如补全 database/ 前缀）
                    if (!empty($rt['data']) && is_array($rt['data'])) {
                        $data = array_merge($data, $rt['data']);
                    }

                    $data['db_prefix'] = strtolower($data['db_prefix']);

                    // 存储缓存文件中
                    $size = file_put_contents(WRITEPATH.'install.info', dr_array2string($data));
                    if (!$size || $size < 10) {
                        $this->_json(0, dr_lang('临时数据存储失败，cahce目录无法写入'));
                    }

                    // 写入数据库配置（显式 DBDriver，供驱动层 DDL/方言转换使用）
                    $rt = $this->_write_database_config($data);
                    if (!$rt['code']) {
                        $this->_json(0, $rt['msg']);
                    }

                    $this->_json(1, 'index.php?c=install&m=index&is_install_db='.(!$is_demo ? 0 : intval($_POST['is_install_db'])).'&step='.($step+1));
                }
                $force_mysqli = $this->_my_install_sql_requires_mysql();
                $db_drivers = $this->_scan_database_drivers($force_mysqli);
                $default_driver = 'MySQLi';
                $db_dir_missing = !is_dir(FRAMEPATH.'Database/');
                foreach ($db_drivers as $d) {
                    if (!empty($d['enabled'])) {
                        $default_driver = $d['name'];
                        if ($d['name'] === 'MySQLi') {
                            break;
                        }
                    }
                }
                $data = [
                    'do_url' => '/index.php?c=install&m=index&is_install_db='.(!$is_demo ? 0 : intval($_POST['is_install_db'])).'&step=2',
                    'is_demo' => $is_demo,
                    'db_driver' => $default_driver,
                    'db_drivers' => $db_drivers,
                    'db_dir_missing' => $db_dir_missing,
                    'db_host' => defined('INSTALL_DB_HOST') ? INSTALL_DB_HOST : '127.0.0.1',
                    'db_user' => defined('INSTALL_DB_USER') ? INSTALL_DB_USER : '',
                    'db_pass' => defined('INSTALL_DB_PASS') ? INSTALL_DB_PASS : '',
                    'db_name' => defined('INSTALL_DB_NAME') ? INSTALL_DB_NAME : '',
                    'db_prefix' => defined('INSTALL_DB_PREFIX') ? INSTALL_DB_PREFIX : 'dr_',
                    'sqlite_name' => 'database/SQLite3/'.dr_lang('安装时随机生成'),
                    'force_mysqli' => $force_mysqli,
                ];
                \Phpcmf\Service::V()->assign($data);

                break;

            case 3:
                $page = intval($_GET['page']);
                $data = dr_string2array(file_get_contents(WRITEPATH.'install.info'));
                $error = '';
                file_put_contents(WRITEPATH.'install.error', '');
                if (empty($data)) {
                    $error = dr_lang('临时数据获取失败，请返回前一页重新执行');
                } else {
                    $this->db = \Phpcmf\Service::M()->db;
                    // 检查数据库是否存在
                    if (!$this->db->connect(false)) {
                        // 链接失败,尝试创建数据库
                        $error = dr_lang('数据库连接失败，请返回前一页重新执行');
                    } elseif (!isset($_GET['doin']) && $this->db->tableExists($data['db_prefix'].'cron')) {
                        // 判断是否安装过
                        if (isset($data['db_driver']) && $data['db_driver'] === 'SQLite3') {
                            $error = dr_lang('指定的数据库文件（%s）已经安装过表前缀（%s），你可以尝试修改数据库文件名或者数据表前缀', $data['db_name'], $data['db_prefix']);
                        } else {
                            $error = dr_lang('指定的数据库（%s）已经安装过表前缀（%s），你可以尝试修改数据库名或者数据表前缀', $data['db_name'], $data['db_prefix']);
                        }
                    } else {

                        // 导入数据结构
                        if ($page) {
                            // 优先规范化 Schema（Config/Install.php）；自定义包仍可用 MYPATH Install.sql
                            $schema_file = '';
                            if (!is_file(MYPATH.'Config/Install.sql') && is_file(CMSPATH.'Config/Install.php')) {
                                $schema_file = CMSPATH.'Config/Install.php';
                            }
                            if ($schema_file) {
                                if ((int)$page === 1) {
                                    $schema = require $schema_file;
                                    if (!is_array($schema) || empty($schema['tables'])) {
                                        $this->_json(0, dr_lang('安装 Schema 无效：%s', $schema_file));
                                    }
                                    $rt = \Phpcmf\Service::M('table')->install_schema($schema);
                                    if (!$rt['code']) {
                                        $error = '**************************************************************************'
                                            .PHP_EOL.$rt['msg'].PHP_EOL
                                            .'**************************************************************************'.PHP_EOL;
                                        file_put_contents(WRITEPATH.'install.error', $error.PHP_EOL, FILE_APPEND);
                                        $this->_json(0, $rt['msg']);
                                    }
                                    $this->_json(1, dr_lang('表结构安装完成（%s张表）', intval($rt['data']['tables'] ?? 0)), ['page' => 99]);
                                }
                                $this->_json(1, dr_lang('执行完成，即将安装数据信息...'), ['page' => 99]);
                            }

                            if (is_file(MYPATH.'Config/Install.sql')) {
                                $sql = file_get_contents(MYPATH.'Config/Install.sql');
                                if (strlen($sql) < 100) {
                                    $sql = file_get_contents(CMSPATH.'Config/Install.sql');
                                }
                            } else {
                                $sql = file_get_contents(CMSPATH.'Config/Install.sql');
                            }
                            $sql = str_replace('{dbprefix}', $data['db_prefix'], $sql);
                            // 每批条数过少会导致超大 Install.sql 需要数百次 AJAX，易中途超时；可按环境酌情调大
                            $rows = $this->query_rows($sql, 40);
                            $key = $page - 1;
                            if (isset($rows[$key]) && $rows[$key]) {
                                // 安装本次结构
                                $last_sql = '';
                                foreach ($rows[$key] as $statement) {
                                    if (!$statement) {
                                        continue;
                                    }
                                    $statement = trim($statement);
                                    if ($statement === '') {
                                        continue;
                                    }
                                    $last_sql = $statement;
                                    if (!$this->db->simpleQuery($statement)) {
                                        $rt = $this->db->error();
                                        $error = '**************************************************************************'
                                            .PHP_EOL.$statement.PHP_EOL.$rt['message'].PHP_EOL;
                                        $error.= '**************************************************************************'.PHP_EOL;
                                        file_put_contents(WRITEPATH.'install.error', $error.PHP_EOL, FILE_APPEND);
                                        $this->_json(0, 'SQL执行错误：'.$rt['message']);
                                    }
                                }
                                $this->_json(1, dr_lang('正在执行：%s', dr_strcut($last_sql, 70)), ['page' => $page + 1]);

                            } else {
                                $this->_json(1, dr_lang('执行完成，即将安装数据信息...'), ['page' => 99]);
                            }
                        }

                    }
                }

                \Phpcmf\Service::V()->assign([
                    'do_url' => 'index.php?c=install&m=index&step=3&doin=1&is_install_db='.intval($_GET['is_install_db']),
                ]);
                break;

            case 4:

                $data = dr_string2array(file_get_contents(WRITEPATH.'install.info'));
                if (empty($data)) {
                    $error = dr_lang('临时数据获取失败，请返回前一页重新执行');
                } else {
                    $this->db = \Phpcmf\Service::M()->db;
                    // 检查数据库是否存在
                    if (!$this->db->connect(false)) {
                        // 链接失败,尝试创建数据库
                        $error = dr_lang('数据库连接失败，请返回前一页重新执行');
                    } else {
                        // 导入默认安装数据
                        $errorlog = file_get_contents(WRITEPATH.'install.error');
                        if ($errorlog && strlen($errorlog) > 10) {
                            // 出现错误了
                            $error = $errorlog;
                        } else {
                            if (!\Phpcmf\Service::M()->is_table_exists('member')) {
                                if (is_file(MYPATH.'Config/Install.sql')) {
                                    $sql = MYPATH.'Config/Install.sql';
                                } elseif (is_file(CMSPATH.'Config/Install.php')) {
                                    $sql = CMSPATH.'Config/Install.php';
                                } else {
                                    $sql = CMSPATH.'Config/Install.sql';
                                }
                                $error = dr_lang('安装文件：%s', $sql);
                                $error.= '<br>'.dr_lang('关键数据表member结构不完整，检查安装包结构文件是否合理');
                            } else {
                                // 创建账号
                                $pwd = md5(dr_safe_password($data['password']));
                                $salt = substr(md5(rand(0, 999)), 0, 10);
                                $prefix = \Phpcmf\Service::M()->prefix;
                                if (\Phpcmf\Service::M()->table('member')->get(1)) {
                                    \Phpcmf\Service::M()->table('member')->update(1, [
                                        'email' => $data['email'],
                                        'username' => $data['username'],
                                        'password' => md5($pwd.$salt.$pwd),
                                        'salt' => $salt,
                                        'name' => dr_lang('创始人'),
                                        'phone' => '',
                                        'money' => 1000000,
                                        'freeze' => 0,
                                        'spend' => 0,
                                        'score' => 1000000,
                                        'experience' => 1000000,
                                        'regip' => '',
                                        'regtime' => SYS_TIME,
                                        'randcode' => 0,
                                    ]);
                                    $id = 1;
                                } else {
                                    $this->db->table('member')->insert([
                                        'email' => $data['email'],
                                        'username' => $data['username'],
                                        'password' => md5($pwd.$salt.$pwd),
                                        'salt' => $salt,
                                        'name' => dr_lang('创始人'),
                                        'phone' => '',
                                        'money' => 1000000,
                                        'freeze' => 0,
                                        'spend' => 0,
                                        'score' => 1000000,
                                        'experience' => 1000000,
                                        'regip' => '',
                                        'regtime' => SYS_TIME,
                                        'randcode' => 0,
                                    ]);
                                    $id = $this->db->insertID();
                                    $this->db->table('member_data')->insert([
                                        'id' => $id,
                                        'is_lock' => 0,
                                        'is_admin' => 1,
                                        'is_verify' => 1,
                                        'is_mobile' => 1,
                                        'is_complete' => 1,
                                    ]);
                                }

                                // 加入管理员表
                                if (!\Phpcmf\Service::M()->table('admin')->where('uid', $id)->counts()) {
                                    $this->db->table('admin')->insert([
                                        'uid' => $id,
                                        'setting' => '',
                                        'usermenu' => '',
                                    ]);
                                    // 加入角色表
                                    $this->db->table('admin_role_index')->insert([
                                        'uid' => $id,
                                        'roleid' => 1,
                                    ]);
                                }

                                // 创建站点
                                if (\Phpcmf\Service::M()->table('site')->get(1)) {
                                    \Phpcmf\Service::M()->table('site')->update(1, [
                                        'name' => $data['name'],
                                        'domain' => DOMAIN_NAME,
                                    ]);
                                } else {
                                    $this->db->table('site')->replace([
                                        'id' => 1,
                                        'name' => $data['name'],
                                        'domain' => DOMAIN_NAME,
                                        'setting' => '',
                                        'disabled' => 0,
                                        'displayorder' => 0,
                                    ]);
                                }

                                \Phpcmf\Service::M()->site = $this->site = [ 1 => 1 ];

                                $ssl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443') ? 1 : 0;;
                                if (isset($_GET['protocol']) && trim($_GET['protocol'], ':') == 'https') {
                                    $ssl = 1;
                                }

                                // 写配置文件
                                $sys = [
                                    'SYS_DEBUG'                     => '1',
                                    'SYS_ADMIN_CODE'                => '0',
                                    'SYS_ADMIN_LOG'                 => '0',
                                    'SYS_AUTO_FORM'                 => '0',
                                    'SYS_ADMIN_PAGESIZE'            => '10',
                                    'SYS_SMS_IMG_CODE'              => '0',
                                    'SYS_NOT_ADMIN_CACHE'           => '0',
                                    'SYS_URL_ONLY'                  => '0',
                                    'SYS_THEME_ROOT_PATH'           => '1',
                                    'SYS_URL_REL'                   => '1',
                                    'SYS_CRON_AUTH'                 => '0',
                                    'SYS_CSRF'                      => '0',
                                    'SYS_CSRF_TIME'                 => '0',
                                    'SYS_301'                       => '1',
                                    'SYS_NOT_UPDATE'                => '1',
                                    'SYS_KEY'                       => 'PHPCMF'.md5($data['name'].rand(1, 999999)), //安全密匙
                                    'SYS_HTTPS'                     => $ssl,
                                    'SYS_ATTACHMENT_DB'             => '',
                                    'SYS_ATTACHMENT_PATH'           => '',
                                    'SYS_ATTACHMENT_URL'            => '',
                                    'SYS_API_TOKEN'                 => '',
                                ];
                                if (is_file(MYPATH.'Config/License.php')) {
                                    $ls = require MYPATH.'Config/License.php';
                                    if (isset($ls['oem']) && $ls['oem']) {
                                        $sys['SYS_DEBUG'] = 0;
                                    }
                                }
                                \Phpcmf\Service::M('System')->save_config($sys, $sys);

                                // 删除app的install.lock
                                $local = \Phpcmf\Service::Apps();
                                foreach ($local as $dir => $path) {
                                    if (is_file($path.'install.lock')) {
                                        unlink($path.'install.lock');
                                    }
                                }

                                // 执行安装程序废除
                                /*
                                $sql = '';
                                if (is_file(MYPATH.'Config/Install.sql')) {
                                    $sql = file_get_contents(MYPATH.'Config/Install.sql');
                                    $sql = str_replace('{dbprefix}', $data['db_prefix'], $sql);
                                }

                                if (is_file(MYPATH.'Config/Install_site.sql')) {
                                    $s = file_get_contents(MYPATH.'Config/Install_site.sql');
                                    $sql.= PHP_EOL.str_replace('{dbprefix}', $data['db_prefix'].'1_', $s);
                                }
                                $this->query($sql);*/

                                // 运行自定义安装脚本
                                if (is_file(MYPATH.'Config/Install.php')) {
                                    require MYPATH.'Config/Install.php';
                                }

                                // 运行自定义安装脚本②
                                if (is_file(MYPATH.'Config/Install_tpl.php')) {
                                    require MYPATH.'Config/Install_tpl.php';
                                }

                                $errorlog = file_get_contents(WRITEPATH.'install.error');
                                if ($errorlog && strlen($errorlog) > 10) {
                                    // 出现错误了
                                    $error = $errorlog;
                                } else {
                                    // 安装完成
                                    file_put_contents($this->lock, time());
                                    file_put_contents(WRITEPATH.'update.txt', time());
                                    file_put_contents(WRITEPATH.'install.test', time());
                                    unlink(WRITEPATH.'install.info');
                                    unlink(WRITEPATH.'install.error');
                                    // 重命名后台入口
                                    $admin = 'admin'.substr(md5(SYS_TIME.rand(1, 999999)), 0, 12);
                                    $afile = WEBPATH.$admin.'.php';
                                    if (is_file(WEBPATH.'admin.php')) {
                                        copy(WEBPATH.'admin.php', $afile);
                                    }
                                    if (!is_file($afile)) {
                                        file_put_contents($afile, "<?php
define('IS_ADMIN', TRUE);
define('SELF', pathinfo(__FILE__, PATHINFO_BASENAME));
require('index.php');");
                                    }
                                    if (is_file($afile)) {
                                        unlink(WEBPATH.'admin.php');
                                    } else {
                                        $admin = 'admin';
                                    }
                                }
                            }
                        }
                    }
                }

                break;
        }

        \Phpcmf\Service::V()->assign([
            'data' => $data,
            'step' => $step,
            'error' => $error,
            'admin' => $admin,
            'pre_url' => 'index.php?c=install&m=index&step='.($step-1).'&is_install_db='.intval($_GET['is_install_db']),
            'next_url' => 'index.php?c=install&m=index&step='.($step+1).'&is_install_db='.intval($_GET['is_install_db']),
        ]);
        \Phpcmf\Service::V()->display('install/'.$step.'.html');
        exit;
    }

    /**
     * 扫描 FRAMEPATH/Database 目录，列出可识别的数据库驱动
     *
     * @param bool $forceMysqli 自定义 Install.sql 时仅启用 MySQLi
     * @return array [['name','label','ext_ok','installable','enabled','form','tip'], ...]
     */
    private function _scan_database_drivers($forceMysqli = false)
    {
        $base = FRAMEPATH.'Database/';
        $list = [];

        // 无 System/Database 目录时，默认仅 MySQL（MySQLi）模式
        if (!is_dir($base)) {
            $extOk = function_exists('mysqli_init');
            return [[
                'name' => 'MySQLi',
                'label' => 'MySQLi',
                'ext_ok' => $extOk,
                'installable' => true,
                'enabled' => $extOk,
                'form' => 'server',
                'tip' => $extOk ? '' : dr_lang('PHP 未启用对应扩展'),
            ]];
        }

        // PHP 扩展检测
        $extMap = [
            'MySQLi'  => function_exists('mysqli_init'),
            'SQLite3' => extension_loaded('sqlite3'),
            'Postgre' => extension_loaded('pgsql'),
            'SQLSRV'  => extension_loaded('sqlsrv'),
            'OCI8'    => extension_loaded('oci8'),
        ];
        // 表单类型：sqlite 用文件表单，其余用服务器表单
        $formMap = [
            'SQLite3' => 'sqlite',
        ];
        // 安装引导已实现的驱动（其余目录有驱动文件但安装流程未接好）
        $bootstrapOk = ['MySQLi' => true, 'SQLite3' => true];

        $dirs = @scandir($base);
        if (!is_array($dirs) || $dirs === []) {
            $extOk = function_exists('mysqli_init');
            return [[
                'name' => 'MySQLi',
                'label' => 'MySQLi',
                'ext_ok' => $extOk,
                'installable' => true,
                'enabled' => $extOk,
                'form' => 'server',
                'tip' => $extOk ? '' : dr_lang('PHP 未启用对应扩展'),
            ]];
        }

        foreach ($dirs as $name) {
            if ($name === '.' || $name === '..' || !is_dir($base.$name)) {
                continue;
            }
            $conn = $base.$name.'/Connection.php';
            if (!is_file($conn)) {
                continue;
            }
            // 迅睿安装要求驱动实现 formatCreateSql；未实现的不展示在安装界面
            $code = @file_get_contents($conn);
            $hasDdl = is_string($code) && strpos($code, 'function formatCreateSql') !== false;
            if (!$hasDdl) {
                continue;
            }
            $extOk = isset($extMap[$name]) ? (bool) $extMap[$name] : true;
            $installable = !empty($bootstrapOk[$name]);
            $enabled = $installable && $extOk;
            if ($forceMysqli && $name !== 'MySQLi') {
                $enabled = false;
            }

            $tip = '';
            if (empty($bootstrapOk[$name])) {
                $tip = dr_lang('安装引导暂未支持该驱动');
            } elseif (!$extOk) {
                $tip = dr_lang('PHP 未启用对应扩展');
            } elseif ($forceMysqli && $name !== 'MySQLi') {
                $tip = dr_lang('自定义 Install.sql 仅支持 MySQLi');
            }

            $list[] = [
                'name' => $name,
                'label' => $name,
                'ext_ok' => $extOk,
                'installable' => $installable,
                'enabled' => $enabled,
                'form' => isset($formMap[$name]) ? $formMap[$name] : 'server',
                'tip' => $tip,
            ];
        }

        // 目录存在但未扫到任何驱动时，同样回退 MySQLi
        if (!$list) {
            $extOk = function_exists('mysqli_init');
            return [[
                'name' => 'MySQLi',
                'label' => 'MySQLi',
                'ext_ok' => $extOk,
                'installable' => true,
                'enabled' => $extOk,
                'form' => 'server',
                'tip' => $extOk ? '' : dr_lang('PHP 未启用对应扩展'),
            ]];
        }

        // MySQLi / SQLite3 排前面，其余按名称
        usort($list, static function ($a, $b) {
            $order = ['MySQLi' => 0, 'SQLite3' => 1];
            $oa = isset($order[$a['name']]) ? $order[$a['name']] : 50;
            $ob = isset($order[$b['name']]) ? $order[$b['name']] : 50;
            if ($oa === $ob) {
                return strcmp($a['name'], $b['name']);
            }
            return $oa - $ob;
        });

        return $list;
    }

    /**
     * 是否存在自定义 My/Config/Install.sql 且含有效 SQL（此时禁止选 SQLite3）
     */
    private function _my_install_sql_requires_mysql()
    {
        $file = MYPATH.'Config/Install.sql';
        if (!is_file($file)) {
            return false;
        }

        $sql = @file_get_contents($file);
        if ($sql === false || trim($sql) === '') {
            return false;
        }

        // 去掉注释与空白后判断是否还有语句
        $lines = preg_split("/\r\n|\n|\r/", $sql);
        $buf = '';
        if (is_array($lines)) {
            foreach ($lines as $line) {
                $trim = ltrim($line);
                if ($trim === '' || strpos($trim, '--') === 0 || strpos($trim, '#') === 0) {
                    continue;
                }
                $buf .= $trim;
            }
        }

        $buf = trim(preg_replace('/\/\*.*?\*\//s', '', $buf) ?? '');
        if ($buf === '') {
            return false;
        }

        // 至少像一条 SQL（含关键字或分号）
        return (bool) preg_match('/\b(CREATE|INSERT|REPLACE|ALTER|DROP|UPDATE|DELETE|SET)\b/i', $buf);
    }

    /**
     * 生成 SQLite3 库文件路径：cache/database/SQLite3/{随机}.db
     *
     * @return array{rel:string,abs:string,name:string}
     */
    private function _sqlite3_make_db_file()
    {
        try {
            $name = bin2hex(random_bytes(16)).'.db';
        } catch (\Throwable $e) {
            $name = md5(uniqid((string) mt_rand(), true).microtime(true)).'.db';
        }

        $rel = 'database'.DIRECTORY_SEPARATOR.'SQLite3'.DIRECTORY_SEPARATOR.$name;
        // 配置与展示统一用正斜杠相对路径（WRITEPATH 拼接时再转系统分隔符）
        $rel_slash = 'database/SQLite3/'.$name;

        return [
            'name' => $name,
            'rel' => $rel_slash,
            'abs' => WRITEPATH.str_replace('/', DIRECTORY_SEPARATOR, $rel_slash),
        ];
    }

    /**
     * 安装阶段数据库引导连接。
     * 支持 MySQLi / SQLite3；配置写出后再由 CI 驱动执行 Schema/Install.sql。
     *
     * @param array $data
     * @return array
     */
    private function _install_db_bootstrap(array $data)
    {
        $driver = isset($data['db_driver']) ? $data['db_driver'] : 'MySQLi';

        if ($driver === 'SQLite3') {
            if (!extension_loaded('sqlite3') || !class_exists('SQLite3', false)) {
                return dr_return_data(0, dr_lang('PHP环境必须启用SQLite3扩展'));
            }
            // cache/database/SQLite3/{随机名}.db，避免固定路径被猜测
            $info = $this->_sqlite3_make_db_file();
            $data['db_name'] = $info['rel'];
            $file = $info['abs'];
            $dir = dirname($file);
            if (!is_dir($dir) && !@mkdir($dir, 0755, true)) {
                return dr_return_data(0, dr_lang('无法创建数据库目录：%s', $dir));
            }
            if (!is_writable($dir)) {
                return dr_return_data(0, dr_lang('数据库目录不可写：%s', $dir));
            }
            // 禁止目录浏览
            if (!is_file($dir.DIRECTORY_SEPARATOR.'index.html')) {
                @file_put_contents($dir.DIRECTORY_SEPARATOR.'index.html', '');
            }
            try {
                $flags = SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE;
                $pass = isset($data['db_pass']) ? (string) $data['db_pass'] : '';
                $db = ($pass !== '')
                    ? new \SQLite3($file, $flags, $pass)
                    : new \SQLite3($file, $flags);
                $db->close();
            } catch (\Throwable $e) {
                return dr_return_data(0, dr_lang('无法创建或打开SQLite数据库：%s', $e->getMessage()));
            }

            return dr_return_data(1, '', $data);
        }

        if ($driver !== 'MySQLi') {
            return dr_return_data(0, dr_lang('暂不支持的数据库驱动：%s', $driver));
        }

        if (!function_exists('mysqli_init')) {
            return dr_return_data(0, dr_lang('PHP环境必须启用Mysqli扩展'));
        }

        $mysqli = mysqli_init();
        if (!$mysqli) {
            return dr_return_data(0, dr_lang('PHP环境必须启用Mysqli扩展'));
        }

        if (!@mysqli_real_connect($mysqli, $data['db_host'], $data['db_user'], $data['db_pass'])) {
            if (SITE_LANGUAGE == 'zh-cn') {
                return dr_return_data(0, '['.mysqli_connect_errno().'] - 无法连接到数据库服务器（'.$data['db_host'].'），请检查用户名（'.$data['db_user'].'）和密码（'.$data['db_pass'].'）是否正确');
            }
            return dr_return_data(0, mysqli_connect_errno().' '.mysqli_connect_error());
        }

        if (!mysqli_select_db($mysqli, $data['db_name'])) {
            if (!mysqli_query($mysqli, 'CREATE DATABASE `'.$data['db_name'].'` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci')) {
                return dr_return_data(0, dr_lang('指定的数据库（%s）不存在，系统尝试创建失败，请通过其他方式建立数据库', $data['db_name']));
            }
            if (!mysqli_select_db($mysqli, $data['db_name'])) {
                return dr_return_data(0, dr_lang('指定的数据库（%s）不存在，系统尝试创建失败，请通过其他方式建立数据库', $data['db_name']));
            }
        }

        if (!mysqli_set_charset($mysqli, 'utf8mb4')) {
            return dr_return_data(0, dr_lang('当前MySQL不支持utf8mb4编码（%s）建议升级MySQL版本', mysqli_error($mysqli)));
        }

        return dr_return_data(1);
    }

    /**
     * 写出 config/database.php（显式包含 DBDriver）
     *
     * @param array $data
     * @return array
     */
    private function _write_database_config(array $data)
    {
        $driver = isset($data['db_driver']) && $data['db_driver'] ? $data['db_driver'] : 'MySQLi';
        $prefix = dr_safe_filename($data['db_prefix']);

        if ($driver === 'SQLite3') {
            // 相对 WRITEPATH：database/SQLite3/{随机}.db
            $rel = isset($data['db_name']) ? (string) $data['db_name'] : '';
            if ($rel === '' || (strpos($rel, 'SQLite3/') === false && strpos($rel, 'SQLite3'.DIRECTORY_SEPARATOR) === false)) {
                $info = $this->_sqlite3_make_db_file();
                $rel = $info['rel'];
                $data['db_name'] = $rel;
            }
            $rel_fs = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $rel);
            $rel_php = str_replace('\\', '\\\\', $rel_fs);
            $pass = str_replace("'", "\\'", (string) (isset($data['db_pass']) ? $data['db_pass'] : ''));
            $database = '<?php

/**
 * 数据库配置文件（SQLite3）
 * 库文件目录：cache/database/SQLite3/（文件名安装时随机生成）
 */

$db[\'default\']	= [
    \'hostname\'	=> \'\',
    \'username\'	=> \'\',
    \'password\'	=> \''.$pass.'\',
    \'database\'	=> WRITEPATH.\''.$rel_php.'\',
    \'DBPrefix\'	=> \''.$prefix.'\',
    \'DBDriver\'	=> \'SQLite3\',
    \'charset\'	=> \'utf8\',
    \'DBCollat\'	=> \'\',
];';
        } else {
            $database = '<?php

/**
 * 数据库配置文件
 */

$db[\'default\']	= [
    \'hostname\'	=> \''.str_replace("'", "\\'", $data['db_host']).'\',
    \'username\'	=> \''.str_replace("'", "\\'", $data['db_user']).'\',
    \'password\'	=> \''.str_replace("'", "\\'", $data['db_pass']).'\',
    \'database\'	=> \''.str_replace("'", "\\'", $data['db_name']).'\',
    \'DBPrefix\'	=> \''.$prefix.'\',
    \'DBDriver\'	=> \'MySQLi\',
    \'charset\'	=> \'utf8mb4\',
    \'DBCollat\'	=> \'utf8mb4_general_ci\',
];';
        }

        $size = file_put_contents(CONFIGPATH.'database.php', $database);
        if (!$size || $size < 10) {
            return dr_return_data(0, dr_lang('数据库配置文件创建失败，config目录无法写入'));
        }

        return dr_return_data(1);
    }

    /**
     * 将安装 SQL 拆成单条语句。
     * Install.sql 以 MySQL 方言为源，经 dr_format_create_sql → 驱动 formatCreateSql 转换。
     * 先统一为 \n 再占位，避免 Windows 换行在 Linux 下经 PHP_EOL 替换后出现双占位符，导致无法按 ; 拆分、多条语句粘成一条执行失败。
     * 拆分后还原真实换行交给数据库解析，避免逐行剔除 #/-- 时误伤多行字符串内的合法内容。
     */
    private function split_install_sql_statements($sql)
    {
        if (!$sql) {
            return [];
        }

        $sql = dr_format_create_sql($sql);
        $sql = str_replace(["\r\n", "\r"], "\n", $sql);
        $ph = 'SQL_FINECMS_EOL';
        $sql = str_replace("\n", $ph, $sql);
        $sql_data = explode(';'.$ph, trim($sql));
        $rt = [];
        foreach ($sql_data as $chunk) {
            $chunk = trim($chunk);
            if ($chunk === '') {
                continue;
            }
            $rt[] = str_replace($ph, "\n", $chunk);
        }

        return $rt;
    }

    // 数据分组
    private function query_rows($sql, $num = 0)
    {
        if (!$sql) {
            return [];
        }

        $rt = $this->split_install_sql_statements($sql);

        return $num ? array_chunk($rt, $num) : $rt;
    }

    // 数据执行
    private function query($sql)
    {
        if (!$sql) {
            return '';
        }

        foreach ($this->split_install_sql_statements($sql) as $ret) {
            if (!$this->db->simpleQuery($ret)) {
                $rt = $this->db->error();
                $error = '**************************************************************************'
                    .PHP_EOL.$ret.PHP_EOL.$rt['message'].PHP_EOL;
                $error.= '**************************************************************************'.PHP_EOL;
                file_put_contents(WRITEPATH.'install.error', $error.PHP_EOL, FILE_APPEND);
            }
        }
    }

}
