Install.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. <?php
  2. declare (strict_types=1);
  3. namespace app\api\controller;
  4. use Throwable;
  5. use ba\Random;
  6. use ba\Version;
  7. use think\App;
  8. use ba\Terminal;
  9. use ba\Filesystem;
  10. use think\facade\Db;
  11. use think\facade\Config;
  12. use app\common\controller\Api;
  13. use think\db\exception\PDOException;
  14. use app\admin\model\Admin as AdminModel;
  15. use app\admin\model\User as UserModel;
  16. /**
  17. * 安装控制器
  18. */
  19. class Install extends Api
  20. {
  21. protected bool $useSystemSettings = false;
  22. /**
  23. * 环境检查状态
  24. */
  25. static string $ok = 'ok';
  26. static string $fail = 'fail';
  27. static string $warn = 'warn';
  28. /**
  29. * 安装锁文件名称
  30. */
  31. static string $lockFileName = 'install.lock';
  32. /**
  33. * 配置文件
  34. */
  35. static string $dbConfigFileName = 'database.php';
  36. static string $buildConfigFileName = 'buildadmin.php';
  37. /**
  38. * 自动构建的前端文件的 outDir 相对于根目录
  39. */
  40. static string $distDir = 'web' . DIRECTORY_SEPARATOR . 'dist';
  41. /**
  42. * 需要的依赖版本
  43. */
  44. static array $needDependentVersion = [
  45. 'php' => '8.0.2',
  46. 'npm' => '9.8.1',
  47. 'cnpm' => '7.1.0',
  48. 'node' => '18.18.2',
  49. 'yarn' => '1.2.0',
  50. 'pnpm' => '6.32.13',
  51. ];
  52. /**
  53. * 安装完成标记
  54. * 配置完成则建立lock文件
  55. * 执行命令成功执行再写入标记到lock文件
  56. * 实现命令执行失败,重载页面可重新执行
  57. */
  58. static string $InstallationCompletionMark = 'install-end';
  59. /**
  60. * 构造方法
  61. * @param App $app
  62. */
  63. public function __construct(App $app)
  64. {
  65. parent::__construct($app);
  66. }
  67. /**
  68. * 命令执行窗口
  69. * @throws Throwable
  70. */
  71. public function terminal(): void
  72. {
  73. if ($this->isInstallComplete()) {
  74. return;
  75. }
  76. Terminal::instance()->exec(false);
  77. }
  78. public function changePackageManager(): void
  79. {
  80. if ($this->isInstallComplete()) {
  81. return;
  82. }
  83. $newPackageManager = request()->post('manager', Config::get('terminal.npm_package_manager'));
  84. if (Terminal::changeTerminalConfig()) {
  85. $this->success('', [
  86. 'manager' => $newPackageManager
  87. ]);
  88. } else {
  89. $this->error(__('Failed to switch package manager. Please modify the configuration file manually:%s', ['根目录/config/buildadmin.php']));
  90. }
  91. }
  92. /**
  93. * 环境基础检查
  94. */
  95. public function envBaseCheck(): void
  96. {
  97. if ($this->isInstallComplete()) {
  98. $this->error(__('The system has completed installation. If you need to reinstall, please delete the %s file first', ['public/' . self::$lockFileName]), []);
  99. }
  100. if (env('database.type')) {
  101. $this->error(__('The .env file with database configuration was detected. Please clean up and try again!'));
  102. }
  103. // php版本-start
  104. $phpVersion = phpversion();
  105. $phpVersionCompare = Version::compare(self::$needDependentVersion['php'], $phpVersion);
  106. if (!$phpVersionCompare) {
  107. $phpVersionLink = [
  108. [
  109. // 需要PHP版本
  110. 'name' => __('need') . ' >= ' . self::$needDependentVersion['php'],
  111. 'type' => 'text'
  112. ],
  113. [
  114. // 如何解决
  115. 'name' => __('How to solve?'),
  116. 'title' => __('Click to see how to solve it'),
  117. 'type' => 'faq',
  118. 'url' => 'https://wonderful-code.gitee.io/guide/install/preparePHP.html'
  119. ]
  120. ];
  121. }
  122. // php版本-end
  123. // 配置文件-start
  124. $dbConfigFile = config_path() . self::$dbConfigFileName;
  125. $configIsWritable = Filesystem::pathIsWritable(config_path()) && Filesystem::pathIsWritable($dbConfigFile);
  126. if (!$configIsWritable) {
  127. $configIsWritableLink = [
  128. [
  129. // 查看原因
  130. 'name' => __('View reason'),
  131. 'title' => __('Click to view the reason'),
  132. 'type' => 'faq',
  133. 'url' => 'https://wonderful-code.gitee.io/guide/install/dirNoPermission.html'
  134. ]
  135. ];
  136. }
  137. // 配置文件-end
  138. // public-start
  139. $publicIsWritable = Filesystem::pathIsWritable(public_path());
  140. if (!$publicIsWritable) {
  141. $publicIsWritableLink = [
  142. [
  143. 'name' => __('View reason'),
  144. 'title' => __('Click to view the reason'),
  145. 'type' => 'faq',
  146. 'url' => 'https://wonderful-code.gitee.io/guide/install/dirNoPermission.html'
  147. ]
  148. ];
  149. }
  150. // public-end
  151. // PDO-start
  152. $phpPdo = extension_loaded("PDO");
  153. if (!$phpPdo) {
  154. $phpPdoLink = [
  155. [
  156. 'name' => __('PDO extensions need to be installed'),
  157. 'type' => 'text'
  158. ],
  159. [
  160. 'name' => __('How to solve?'),
  161. 'title' => __('Click to see how to solve it'),
  162. 'type' => 'faq',
  163. 'url' => 'https://wonderful-code.gitee.io/guide/install/missingExtension.html'
  164. ]
  165. ];
  166. }
  167. // PDO-end
  168. // GD2和freeType-start
  169. $phpGd2 = extension_loaded('gd') && function_exists('imagettftext');
  170. if (!$phpGd2) {
  171. $phpGd2Link = [
  172. [
  173. 'name' => __('The gd extension and freeType library need to be installed'),
  174. 'type' => 'text'
  175. ],
  176. [
  177. 'name' => __('How to solve?'),
  178. 'title' => __('Click to see how to solve it'),
  179. 'type' => 'faq',
  180. 'url' => 'https://wonderful-code.gitee.io/guide/install/gdFail.html'
  181. ]
  182. ];
  183. }
  184. // GD2和freeType-end
  185. // proc_open
  186. $phpProc = function_exists('proc_open') && function_exists('proc_close') && function_exists('proc_get_status');
  187. if (!$phpProc) {
  188. $phpProcLink = [
  189. [
  190. 'name' => __('View reason'),
  191. 'title' => __('proc_open or proc_close functions in PHP Ini is disabled'),
  192. 'type' => 'faq',
  193. 'url' => 'https://wonderful-code.gitee.io/guide/install/disablement.html'
  194. ],
  195. [
  196. 'name' => __('How to modify'),
  197. 'title' => __('Click to view how to modify'),
  198. 'type' => 'faq',
  199. 'url' => 'https://wonderful-code.gitee.io/guide/install/disablement.html'
  200. ],
  201. [
  202. 'name' => __('Security assurance?'),
  203. 'title' => __('Using the installation service correctly will not cause any potential security problems. Click to view the details'),
  204. 'type' => 'faq',
  205. 'url' => 'https://wonderful-code.gitee.io/guide/install/senior.html'
  206. ],
  207. ];
  208. }
  209. // proc_open-end
  210. $this->success('', [
  211. 'php_version' => [
  212. 'describe' => $phpVersion,
  213. 'state' => $phpVersionCompare ? self::$ok : self::$fail,
  214. 'link' => $phpVersionLink ?? [],
  215. ],
  216. 'config_is_writable' => [
  217. 'describe' => self::writableStateDescribe($configIsWritable),
  218. 'state' => $configIsWritable ? self::$ok : self::$fail,
  219. 'link' => $configIsWritableLink ?? []
  220. ],
  221. 'public_is_writable' => [
  222. 'describe' => self::writableStateDescribe($publicIsWritable),
  223. 'state' => $publicIsWritable ? self::$ok : self::$fail,
  224. 'link' => $publicIsWritableLink ?? []
  225. ],
  226. 'php_pdo' => [
  227. 'describe' => $phpPdo ? __('already installed') : __('Not installed'),
  228. 'state' => $phpPdo ? self::$ok : self::$fail,
  229. 'link' => $phpPdoLink ?? []
  230. ],
  231. 'php_gd2' => [
  232. 'describe' => $phpGd2 ? __('already installed') : __('Not installed'),
  233. 'state' => $phpGd2 ? self::$ok : self::$fail,
  234. 'link' => $phpGd2Link ?? []
  235. ],
  236. 'php_proc' => [
  237. 'describe' => $phpProc ? __('Allow execution') : __('disabled'),
  238. 'state' => $phpProc ? self::$ok : self::$warn,
  239. 'link' => $phpProcLink ?? []
  240. ],
  241. ]);
  242. }
  243. /**
  244. * npm环境检查
  245. */
  246. public function envNpmCheck(): void
  247. {
  248. if ($this->isInstallComplete()) {
  249. $this->error('', [], 2);
  250. }
  251. $packageManager = request()->post('manager', 'none');
  252. // npm
  253. $npmVersion = Version::getVersion('npm');
  254. $npmVersionCompare = Version::compare(self::$needDependentVersion['npm'], $npmVersion);
  255. if (!$npmVersionCompare || !$npmVersion) {
  256. $npmVersionLink = [
  257. [
  258. // 需要版本
  259. 'name' => __('need') . ' >= ' . self::$needDependentVersion['npm'],
  260. 'type' => 'text'
  261. ],
  262. [
  263. // 如何解决
  264. 'name' => __('How to solve?'),
  265. 'title' => __('Click to see how to solve it'),
  266. 'type' => 'faq',
  267. 'url' => 'https://wonderful-code.gitee.io/guide/install/prepareNpm.html'
  268. ]
  269. ];
  270. }
  271. // 包管理器
  272. if (in_array($packageManager, ['npm', 'cnpm', 'pnpm', 'yarn'])) {
  273. $pmVersion = Version::getVersion($packageManager);
  274. $pmVersionCompare = Version::compare(self::$needDependentVersion[$packageManager], $pmVersion);
  275. if (!$pmVersion) {
  276. // 安装
  277. $pmVersionLink[] = [
  278. // 需要版本
  279. 'name' => __('need') . ' >= ' . self::$needDependentVersion[$packageManager],
  280. 'type' => 'text'
  281. ];
  282. if ($npmVersionCompare) {
  283. $pmVersionLink[] = [
  284. // 点击安装
  285. 'name' => __('Click Install %s', [$packageManager]),
  286. 'title' => '',
  287. 'type' => 'install-package-manager'
  288. ];
  289. } else {
  290. $pmVersionLink[] = [
  291. // 请先安装npm
  292. 'name' => __('Please install NPM first'),
  293. 'type' => 'text'
  294. ];
  295. }
  296. } elseif (!$pmVersionCompare) {
  297. // 版本不足
  298. $pmVersionLink[] = [
  299. // 需要版本
  300. 'name' => __('need') . ' >= ' . self::$needDependentVersion[$packageManager],
  301. 'type' => 'text'
  302. ];
  303. $pmVersionLink[] = [
  304. // 请升级
  305. 'name' => __('Please upgrade %s version', [$packageManager]),
  306. 'type' => 'text'
  307. ];
  308. }
  309. } elseif ($packageManager == 'ni') {
  310. $pmVersion = __('nothing');
  311. $pmVersionCompare = true;
  312. } else {
  313. $pmVersion = __('nothing');
  314. $pmVersionCompare = false;
  315. }
  316. // nodejs
  317. $nodejsVersion = Version::getVersion('node');
  318. $nodejsVersionCompare = Version::compare(self::$needDependentVersion['node'], $nodejsVersion);
  319. if (!$nodejsVersionCompare || !$nodejsVersion) {
  320. $nodejsVersionLink = [
  321. [
  322. // 需要版本
  323. 'name' => __('need') . ' >= ' . self::$needDependentVersion['node'],
  324. 'type' => 'text'
  325. ],
  326. [
  327. // 如何解决
  328. 'name' => __('How to solve?'),
  329. 'title' => __('Click to see how to solve it'),
  330. 'type' => 'faq',
  331. 'url' => 'https://wonderful-code.gitee.io/guide/install/prepareNodeJs.html'
  332. ]
  333. ];
  334. }
  335. $this->success('', [
  336. 'npm_version' => [
  337. 'describe' => $npmVersion ?: __('Acquisition failed'),
  338. 'state' => $npmVersionCompare ? self::$ok : self::$warn,
  339. 'link' => $npmVersionLink ?? [],
  340. ],
  341. 'nodejs_version' => [
  342. 'describe' => $nodejsVersion ?: __('Acquisition failed'),
  343. 'state' => $nodejsVersionCompare ? self::$ok : self::$warn,
  344. 'link' => $nodejsVersionLink ?? []
  345. ],
  346. 'npm_package_manager' => [
  347. 'describe' => $pmVersion ?: __('Acquisition failed'),
  348. 'state' => $pmVersionCompare ? self::$ok : self::$warn,
  349. 'link' => $pmVersionLink ?? [],
  350. ]
  351. ]);
  352. }
  353. /**
  354. * 测试数据库连接
  355. */
  356. public function testDatabase(): void
  357. {
  358. $database = [
  359. 'hostname' => $this->request->post('hostname'),
  360. 'username' => $this->request->post('username'),
  361. 'password' => $this->request->post('password'),
  362. 'hostport' => $this->request->post('hostport'),
  363. 'database' => '',
  364. ];
  365. $conn = $this->connectDb($database);
  366. if ($conn['code'] == 0) {
  367. $this->error($conn['msg']);
  368. } else {
  369. $this->success('', [
  370. 'databases' => $conn['databases']
  371. ]);
  372. }
  373. }
  374. /**
  375. * 系统基础配置
  376. * post请求=开始安装
  377. */
  378. public function baseConfig(): void
  379. {
  380. if ($this->isInstallComplete()) {
  381. $this->error(__('The system has completed installation. If you need to reinstall, please delete the %s file first', ['public/' . self::$lockFileName]));
  382. }
  383. $envOk = $this->commandExecutionCheck();
  384. $rootPath = str_replace('\\', '/', root_path());
  385. if ($this->request->isGet()) {
  386. $this->success('', [
  387. 'rootPath' => $rootPath,
  388. 'executionWebCommand' => $envOk
  389. ]);
  390. }
  391. $connectData = $databaseParam = $this->request->only(['hostname', 'username', 'password', 'hostport', 'database', 'prefix']);
  392. // 数据库配置测试
  393. $connectData['database'] = '';
  394. $connect = $this->connectDb($connectData, true);
  395. if ($connect['code'] == 0) {
  396. $this->error($connect['msg']);
  397. }
  398. // 建立数据库
  399. if (!in_array($databaseParam['database'], $connect['databases'])) {
  400. $sql = "CREATE DATABASE IF NOT EXISTS `{$databaseParam['database']}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci";
  401. $connect['pdo']->exec($sql);
  402. }
  403. // 写入数据库配置文件
  404. $dbConfigFile = config_path() . self::$dbConfigFileName;
  405. $dbConfigContent = @file_get_contents($dbConfigFile);
  406. $callback = function ($matches) use ($databaseParam) {
  407. $value = $databaseParam[$matches[1]] ?? '';
  408. return "'$matches[1]'$matches[2]=>$matches[3]env('database.$matches[1]', '$value'),";
  409. };
  410. $dbConfigText = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)env\('database\.(.*)',\s+'(.*)'\),/", $callback, $dbConfigContent);
  411. $result = @file_put_contents($dbConfigFile, $dbConfigText);
  412. if (!$result) {
  413. $this->error(__('File has no write permission:%s', ['config/' . self::$dbConfigFileName]));
  414. }
  415. // 写入.env-example文件
  416. $envFile = root_path() . '.env-example';
  417. $envFileContent = @file_get_contents($envFile);
  418. if ($envFileContent) {
  419. $databasePos = stripos($envFileContent, '[DATABASE]');
  420. if ($databasePos !== false) {
  421. // 清理已有数据库配置
  422. $envFileContent = substr($envFileContent, 0, $databasePos);
  423. }
  424. $envFileContent .= "\n" . '[DATABASE]' . "\n";
  425. $envFileContent .= 'TYPE = mysql' . "\n";
  426. $envFileContent .= 'HOSTNAME = ' . $databaseParam['hostname'] . "\n";
  427. $envFileContent .= 'DATABASE = ' . $databaseParam['database'] . "\n";
  428. $envFileContent .= 'USERNAME = ' . $databaseParam['username'] . "\n";
  429. $envFileContent .= 'PASSWORD = ' . $databaseParam['password'] . "\n";
  430. $envFileContent .= 'HOSTPORT = ' . $databaseParam['hostport'] . "\n";
  431. $envFileContent .= 'PREFIX = ' . $databaseParam['prefix'] . "\n";
  432. $envFileContent .= 'CHARSET = utf8mb4' . "\n";
  433. $envFileContent .= 'DEBUG = true' . "\n";
  434. $result = @file_put_contents($envFile, $envFileContent);
  435. if (!$result) {
  436. $this->error(__('File has no write permission:%s', ['/' . $envFile]));
  437. }
  438. }
  439. // 设置新的Token随机密钥key
  440. $oldTokenKey = Config::get('buildadmin.token.key');
  441. $newTokenKey = Random::build('alnum', 32);
  442. $buildConfigFile = config_path() . self::$buildConfigFileName;
  443. $buildConfigContent = @file_get_contents($buildConfigFile);
  444. $buildConfigContent = preg_replace("/'key'(\s+)=>(\s+)'$oldTokenKey'/", "'key'\$1=>\$2'$newTokenKey'", $buildConfigContent);
  445. $result = @file_put_contents($buildConfigFile, $buildConfigContent);
  446. if (!$result) {
  447. $this->error(__('File has no write permission:%s', ['config/' . self::$buildConfigFileName]));
  448. }
  449. // 建立安装锁文件
  450. $result = @file_put_contents(public_path() . self::$lockFileName, date('Y-m-d H:i:s'));
  451. if (!$result) {
  452. $this->error(__('File has no write permission:%s', ['public/' . self::$lockFileName]));
  453. }
  454. $this->success('', [
  455. 'rootPath' => $rootPath,
  456. 'executionWebCommand' => $envOk
  457. ]);
  458. }
  459. protected function isInstallComplete(): bool
  460. {
  461. if (is_file(public_path() . self::$lockFileName)) {
  462. $contents = @file_get_contents(public_path() . self::$lockFileName);
  463. if ($contents == self::$InstallationCompletionMark) {
  464. return true;
  465. }
  466. }
  467. return false;
  468. }
  469. /**
  470. * 标记命令执行完毕
  471. * @throws Throwable
  472. */
  473. public function commandExecComplete(): void
  474. {
  475. if ($this->isInstallComplete()) {
  476. $this->error(__('The system has completed installation. If you need to reinstall, please delete the %s file first', ['public/' . self::$lockFileName]));
  477. }
  478. $param = $this->request->only(['type', 'adminname', 'adminpassword', 'sitename']);
  479. if ($param['type'] == 'web') {
  480. $result = @file_put_contents(public_path() . self::$lockFileName, self::$InstallationCompletionMark);
  481. if (!$result) {
  482. $this->error(__('File has no write permission:%s', ['public/' . self::$lockFileName]));
  483. }
  484. } else {
  485. // 管理员配置入库
  486. $adminModel = new AdminModel();
  487. $defaultAdmin = $adminModel->where('username', 'admin')->find();
  488. $defaultAdmin->username = $param['adminname'];
  489. $defaultAdmin->nickname = ucfirst($param['adminname']);
  490. $defaultAdmin->save();
  491. if (isset($param['adminpassword']) && $param['adminpassword']) {
  492. $adminModel->resetPassword($defaultAdmin->id, $param['adminpassword']);
  493. }
  494. // 默认用户密码修改
  495. $user = new UserModel();
  496. $user->resetPassword(1, Random::build());
  497. // 修改站点名称
  498. \app\admin\model\Config::where('name', 'site_name')->update([
  499. 'value' => $param['sitename']
  500. ]);
  501. }
  502. $this->success();
  503. }
  504. /**
  505. * 获取命令执行检查的结果
  506. * @return bool 是否拥有执行命令的条件
  507. */
  508. private function commandExecutionCheck(): bool
  509. {
  510. $pm = Config::get('terminal.npm_package_manager');
  511. if ($pm == 'none') {
  512. return false;
  513. }
  514. $check['phpPopen'] = function_exists('proc_open') && function_exists('proc_close');
  515. $check['npmVersionCompare'] = Version::compare(self::$needDependentVersion['npm'], Version::getVersion('npm'));
  516. $check['pmVersionCompare'] = Version::compare(self::$needDependentVersion[$pm], Version::getVersion($pm));
  517. $check['nodejsVersionCompare'] = Version::compare(self::$needDependentVersion['node'], Version::getVersion('node'));
  518. $envOk = true;
  519. foreach ($check as $value) {
  520. if (!$value) {
  521. $envOk = false;
  522. break;
  523. }
  524. }
  525. return $envOk;
  526. }
  527. /**
  528. * 安装指引
  529. */
  530. public function manualInstall(): void
  531. {
  532. $this->success('', [
  533. 'webPath' => str_replace('\\', '/', root_path() . 'web')
  534. ]);
  535. }
  536. public function mvDist(): void
  537. {
  538. if (!is_file(root_path() . self::$distDir . DIRECTORY_SEPARATOR . 'index.html')) {
  539. $this->error(__('No built front-end file found, please rebuild manually!'));
  540. }
  541. if (Terminal::mvDist()) {
  542. $this->success();
  543. } else {
  544. $this->error(__('Failed to move the front-end file, please move it manually!'));
  545. }
  546. }
  547. /**
  548. * 目录是否可写
  549. * @param $writable
  550. * @return string
  551. */
  552. private static function writableStateDescribe($writable): string
  553. {
  554. return $writable ? __('Writable') : __('No write permission');
  555. }
  556. /**
  557. * 数据库连接-获取数据表列表
  558. * @param array $database
  559. * @param bool $returnPdo
  560. * @return array
  561. */
  562. private function connectDb(array $database, bool $returnPdo = false): array
  563. {
  564. try {
  565. $dbConfig = Config::get('database');
  566. $dbConfig['connections']['mysql'] = array_merge($dbConfig['connections']['mysql'], $database);
  567. Config::set(['connections' => $dbConfig['connections']], 'database');
  568. $connect = Db::connect('mysql');
  569. $connect->execute("SELECT 1");
  570. } catch (PDOException $e) {
  571. $errorMsg = $e->getMessage();
  572. return [
  573. 'code' => 0,
  574. 'msg' => __('Database connection failed:%s', [mb_convert_encoding($errorMsg ?: 'unknown', 'UTF-8', 'UTF-8,GBK,GB2312,BIG5')])
  575. ];
  576. }
  577. $databases = [];
  578. // 不需要的数据表
  579. $databasesExclude = ['information_schema', 'mysql', 'performance_schema', 'sys'];
  580. $res = $connect->query("SHOW DATABASES");
  581. foreach ($res as $row) {
  582. if (!in_array($row['Database'], $databasesExclude)) {
  583. $databases[] = $row['Database'];
  584. }
  585. }
  586. return [
  587. 'code' => 1,
  588. 'msg' => '',
  589. 'databases' => $databases,
  590. 'pdo' => $returnPdo ? $connect->getPdo() : '',
  591. ];
  592. }
  593. }