diff --git a/_doc/install/setting.default.php b/_doc/install/settings.default.php similarity index 100% rename from _doc/install/setting.default.php rename to _doc/install/settings.default.php diff --git a/composer.json b/composer.json index b0f43514..c056d296 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,10 @@ }, "require": { "php": "^7.2|^8", + "ext-gd": "*", + "ext-mbstring": "*", + "ext-mysqli": "*", + "ext-json": "*", "swiftmailer/swiftmailer": "^6.2", "guzzlehttp/guzzle": "~6.0", "imdbphp/imdbphp": "^6.4" diff --git a/include/core.php b/include/core.php index 420e3a76..c2d15b36 100644 --- a/include/core.php +++ b/include/core.php @@ -2,8 +2,12 @@ if(!defined('IN_TRACKER')) { die('Hacking attempt!'); } +if (!file_exists($rootpath . '.env')) { + header('Location: ' . getBaseUrl() . '/install/install.php'); + exit(0); +} error_reporting(E_ALL); -ini_set('display_errors', 1); +ini_set('display_errors', 0); if (!empty($_SERVER['HTTP_X_REQUEST_ID'])) { define('REQUEST_ID', $_SERVER['HTTP_X_REQUEST_ID']); } else { @@ -12,13 +16,8 @@ if (!empty($_SERVER['HTTP_X_REQUEST_ID'])) { define('ROOT_PATH', $rootpath); define('VERSION_NUMBER', '1.6.0'); define('IS_ANNOUNCE', (basename($_SERVER['SCRIPT_FILENAME']) == 'announce.php')); - -require $rootpath . 'include/database/interface_db.php'; -require $rootpath . 'include/database/class_db_mysqli.php'; -require $rootpath . 'include/database/class_db.php'; -require $rootpath . 'include/database/helpers.php'; -require $rootpath . 'include/database/class_exception.php'; - +require $rootpath . 'vendor/autoload.php'; +require $rootpath . 'nexus/Database/helpers.php'; require $rootpath . 'classes/class_advertisement.php'; require $rootpath . 'classes/class_cache_redis.php'; require $rootpath . 'include/config.php'; @@ -52,5 +51,3 @@ define ("UC_SYSOP",15); define ("UC_STAFFLEADER",16); ignore_user_abort(1); @set_time_limit(60); - -require dirname(__DIR__) . '/vendor/autoload.php'; diff --git a/include/functions.php b/include/functions.php index d6a9a0a8..259aabd3 100644 --- a/include/functions.php +++ b/include/functions.php @@ -1752,17 +1752,7 @@ function getExportedValue($input,$t = null) { function dbconn($autoclean = false, $doLogin = true) { - if (DB::getInstance()->isConnected()) { - return; - } - $config = config('database.mysql'); - if (!mysql_connect($config['host'], $config['username'], $config['password'], $config['database'], $config['port'])) - { - die("[" . mysql_errno() . "] dbconn: mysql_connect: " . mysql_error()); - } - mysql_query("SET NAMES UTF8"); - mysql_query("SET collation_connection = 'utf8_general_ci'"); - mysql_query("SET sql_mode=''"); + \Nexus\Database\DB::getInstance()->autoConnect(); if ($doLogin) { userlogin(); diff --git a/include/database/class_db.php b/nexus/Database/DB.php similarity index 78% rename from include/database/class_db.php rename to nexus/Database/DB.php index 69df470c..b606b84d 100644 --- a/include/database/class_db.php +++ b/nexus/Database/DB.php @@ -1,5 +1,7 @@ isConnected) { + if (!$this->isConnected()) { $this->driver->connect($host, $username, $password, $database, $port); $this->isConnected = true; } return true; } + public function autoConnect() + { + if ($this->isConnected()) { + return; + } + $config = config('database.mysql'); + if (!mysql_connect($config['host'], $config['username'], $config['password'], $config['database'], $config['port'])) { + throw new DatabaseException(sprintf("mysql connect error: [%s] %s", mysql_errno(), mysql_error())); + } + mysql_query("SET NAMES UTF8"); + mysql_query("SET collation_connection = 'utf8_general_ci'"); + mysql_query("SET sql_mode=''"); + $this->isConnected = true; + } + public function query(string $sql) { try { + $this->autoConnect(); return $this->driver->query($sql); } catch (\Exception $e) { do_log(sprintf("%s [%s] %s", $e->getMessage(), $sql, $e->getTraceAsString())); - throw new \DatabaseException($sql, $e->getMessage()); + throw new DatabaseException($e->getMessage(), $sql); } } @@ -101,6 +119,7 @@ class DB public function escapeString(string $string) { + $this->autoConnect(); return $this->driver->escapeString($string); } diff --git a/include/database/interface_db.php b/nexus/Database/DBInterface.php similarity index 95% rename from include/database/interface_db.php rename to nexus/Database/DBInterface.php index 1fed7776..5f41457a 100644 --- a/include/database/interface_db.php +++ b/nexus/Database/DBInterface.php @@ -1,4 +1,5 @@ report_mode = MYSQLI_REPORT_ALL & ~MYSQLI_REPORT_INDEX; return $this->mysqli = $mysqli; diff --git a/include/database/class_exception.php b/nexus/Database/DatabaseException.php similarity index 58% rename from include/database/class_exception.php rename to nexus/Database/DatabaseException.php index b66eb8e6..2ed21cec 100644 --- a/include/database/class_exception.php +++ b/nexus/Database/DatabaseException.php @@ -1,9 +1,10 @@ currentStep = min(intval($_REQUEST['step'] ?? 1) ?: 1, count($this->steps) + 1); + } + + public function currentStep() + { + return $this->currentStep; + } + + public function getLogFile() + { + return sprintf('%s/nexus_install_%s.log', sys_get_temp_dir(), date('Ymd')); + } + + public function getInsallDirectory() + { + return ROOT_PATH . 'public/install'; + } + + public function doLog($log) + { + $log = sprintf('[%s] [%s] %s%s', date('Y-m-d H:i:s'), $this->currentStep, $log, PHP_EOL); + file_put_contents($this->getLogFile(), $log, FILE_APPEND); + } + + public function listAllTableCreate($sqlFile = '') + { + if (empty($sqlFile)) { + $sqlFile = ROOT_PATH . '_db/dbstructure_v1.6.sql'; + } + $pattern = '/CREATE TABLE `(.*)`.*;/isU'; + $string = file_get_contents($sqlFile); + if ($string === false) { + throw new \RuntimeException("sql file: $sqlFile can not read, make sure it exits and can be read."); + } + $count = preg_match_all($pattern, $string, $matches, PREG_SET_ORDER); + if ($count == 0) { + return []; + } + return array_column($matches, 0, 1); + } + + public function listExistsTable() + { + dbconn(false, false); + $sql = 'show tables'; + $res = sql_query($sql); + $data = []; + while ($row = mysql_fetch_row($res)) { + $data[] = $row[0]; + } + return $data; + } + + public function listShouldAlterTableTableRows() + { + $tables = $this->listExistsTable(); + $data = []; + foreach ($tables as $table) { + $sql = "desc $table"; + $res = sql_query($sql); + while ($row = mysql_fetch_assoc($res)) { + if ($row['Type'] == 'datetime' && $row['Default'] == '0000-00-00 00:00:00') { + $data[$table][] = $row['Field']; + $data[] = [ + 'label' => "$table." . $row['Field'], + 'required' => 'default null', + 'current' => '0000-00-00 00:00:00', + 'result' => 'NO', + ]; + } + } + } + return $data; + } + + public function listRequirementTableRows() + { + $gdInfo = gd_info(); + $tableRows = [ + [ + 'label' => 'PHP version', + 'required' => '>= ' . $this->minimumPhpVersion, + 'current' => PHP_VERSION, + 'result' => $this->yesOrNo(version_compare(PHP_VERSION, $this->minimumPhpVersion, '>=')), + ], + [ + 'label' => 'PHP extension redis', + 'required' => 'optional', + 'current' => extension_loaded('redis'), + 'result' => $this->yesOrNo(extension_loaded('redis')), + ], + [ + 'label' => 'PHP extension mysqli', + 'required' => 'enabled', + 'current' => extension_loaded('mysqli'), + 'result' => $this->yesOrNo(extension_loaded('mysqli')), + ], + [ + 'label' => 'PHP extension mbstring', + 'required' => 'enabled', + 'current' => extension_loaded('mbstring'), + 'result' => $this->yesOrNo(extension_loaded('mbstring')), + ], + [ + 'label' => 'PHP extension gd', + 'required' => 'enabled', + 'current' => extension_loaded('gd'), + 'result' => $this->yesOrNo(extension_loaded('gd')), + ], + [ + 'label' => 'PHP extension gd JPEG Support', + 'required' => 'true', + 'current' => $gdInfo['JPEG Support'], + 'result' => $this->yesOrNo($gdInfo['JPEG Support']), + ], + [ + 'label' => 'PHP extension gd PNG Support', + 'required' => 'true', + 'current' => $gdInfo['PNG Support'], + 'result' => $this->yesOrNo($gdInfo['PNG Support']), + ], + [ + 'label' => 'PHP extension gd GIF Read Support', + 'required' => 'true', + 'current' => $gdInfo['GIF Read Support'], + 'result' => $this->yesOrNo($gdInfo['GIF Read Support']), + ], + ]; + $fails = array_filter($tableRows, function ($value) {return $value['required'] == 'true' && $value['result'] == 'NO';}); + $pass = empty($fails); + + return [ + 'table_rows' => $tableRows, + 'pass' => $pass, + ]; + } + + public function listSettingTableRows() + { + $defaultSettingsFile = ROOT_PATH . '_doc/install/settings.default.php'; + $originalConfigFile = ROOT_PATH . 'config/allconfig.php'; + if (!file_exists($defaultSettingsFile)) { + throw new \RuntimeException("default setting file: $defaultSettingsFile not exists."); + } + if (!file_exists($originalConfigFile)) { + throw new \RuntimeException("original setting file: $originalConfigFile not exists."); + } + $tableRows = [ + [ + 'label' => basename($defaultSettingsFile), + 'required' => 'exists && readable', + 'current' => $defaultSettingsFile, + 'result' => $this->yesOrNo(file_exists($defaultSettingsFile) && is_readable($defaultSettingsFile)), + ], + [ + 'label' => basename($originalConfigFile), + 'required' => 'exists && readable', + 'current' => $originalConfigFile, + 'result' => $this->yesOrNo(file_exists($originalConfigFile) && is_readable($originalConfigFile)), + ], + ]; + $requireDirs = [ + 'main' => ['bitbucket', 'torrent_dir'], + 'attachment' => ['savedirectory', ], + ]; + $symbolicLinks = []; + require $originalConfigFile; + $settings = require $defaultSettingsFile; + foreach ($settings as $prefix => &$group) { + $prefixUpperCase = strtoupper($prefix); + $oldGroupValues = $$prefixUpperCase ?? null; + foreach ($group as $key => &$value) { + //merge original config to default setting + if (isset($oldGroupValues) && isset($oldGroupValues[$key])) { + $value = $oldGroupValues[$key];; + } + if (isset($requireDirs[$prefix]) && in_array($key, $requireDirs[$prefix])) { + $dir = getFullDirectory($value); + $tableRows[] = [ + 'label' => "{$prefix}.{$key}", + 'required' => 'exists && readable', + 'current' => $dir, + 'result' => $this->yesOrNo(is_dir($dir) && is_readable($dir)), + ]; + $symbolicLinks[] = $dir; + } + } + } + $fails = array_filter($tableRows, function ($value) {return $value['required'] == 'true' && $value['result'] == 'NO';}); + $pass = empty($fails); + return [ + 'table_rows' => $tableRows, + 'symbolic_links' => $symbolicLinks, + 'settings' => $settings, + 'pass' => $pass, + ]; + } + + public function nextStep() + { + $this->gotoStep($this->currentStep + 1); + } + + public function gotoStep($step) + { + header('Location: ' . getBaseUrl() . "?step=$step"); + exit(0); + } + + public function maxStep() + { + return count($this->steps); + } + + public function yesOrNo($condition) { + if ($condition) { + return 'YES'; + } + return 'NO'; + } + + public function renderTable($header, $data) + { + $table = '