This commit is contained in:
xiaomlove
2021-01-27 16:26:37 +08:00
parent dcdc0dd654
commit 8e792bafc7
11 changed files with 711 additions and 27 deletions

153
nexus/Database/DB.php Normal file
View File

@@ -0,0 +1,153 @@
<?php
namespace Nexus\Database;
class DB
{
private $driver;
private static $instance;
private static $queries = [];
private $isConnected = false;
private function __construct()
{
}
private function __clone()
{
}
public function setDriver(DBInterface $driver)
{
$this->driver = $driver;
return $this;
}
public static function getInstance()
{
if (self::$instance) {
return self::$instance;
}
$instance = new self;
$driver = new DBMysqli();
$instance->setDriver($driver);
return self::$instance = $instance;
}
public function connect($host, $username, $password, $database, $port)
{
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($e->getMessage(), $sql);
}
}
public function error()
{
return $this->driver->error();
}
public function errno()
{
return $this->driver->errno();
}
public function numRows($result)
{
return $this->driver->numRows($result);
}
public function select_db($database)
{
return $this->driver->selectDb($database);
}
public function fetchAssoc($result)
{
return $this->driver->fetchAssoc($result);
}
public function fetchRow($result)
{
return $this->driver->fetchRow($result);
}
public function fetchArray($result, $type = null)
{
return $this->driver->fetchArray($result, $type);
}
public function affectedRows()
{
return $this->driver->affectedRows();
}
public function escapeString(string $string)
{
$this->autoConnect();
return $this->driver->escapeString($string);
}
public function lastInsertId()
{
return $this->driver->lastInsertId();
}
public function freeResult($result)
{
return $this->driver->freeResult($result);
}
public function isConnected()
{
return $this->isConnected;
}
public static function insert($table, $data)
{
if (empty($table) || empty($data) || !is_array($data)) {
throw new DatabaseException("require table and data(array).");
}
$fields = array_map(function ($value) {return "`$value`";}, array_keys($data));
$values = array_map(function ($value) {return sqlesc($value);}, array_values($data));
$sql = sprintf("insert into `%s` (%s) values (%s)", $table, implode(', ', $fields), implode(', ', $values));
sql_query($sql);
return mysql_insert_id();
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Nexus\Database;
interface DBInterface
{
public function connect($host, $username, $password, $database, $port);
public function query(string $sql);
public function error(): string;
public function errno(): int;
public function numRows($result): int;
public function selectDb($database);
public function fetchAssoc($result);
public function fetchRow($result);
public function fetchArray($result, $type);
public function affectedRows(): int;
public function escapeString(string $string): string;
public function lastInsertId(): int;
public function freeResult($result);
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Nexus\Database;
class DBMysqli implements DBInterface
{
private $mysqli;
public function connect($host, $username, $password, $database, $port)
{
$mysqli = new \mysqli($host, $username, $password, $database, $port);
/* check connection */
if (mysqli_connect_errno()) {
throw new DatabaseException(mysqli_connect_error());
}
/* activate reporting */
$driver = new \mysqli_driver();
$driver->report_mode = MYSQLI_REPORT_ALL & ~MYSQLI_REPORT_INDEX;
return $this->mysqli = $mysqli;
}
public function query(string $sql)
{
return $this->mysqli->query($sql);
}
public function error(): string
{
return $this->mysqli->error;
}
public function errno(): int
{
return $this->mysqli->errno;
}
public function numRows($mysqliResult): int
{
return $mysqliResult->num_rows;
}
public function selectDb($database)
{
return $this->mysqli->select_db($database);
}
public function fetchAssoc($mysqliResult)
{
return $mysqliResult->fetch_assoc();
}
public function fetchRow($mysqliResult)
{
return $mysqliResult->fetch_row();
}
public function fetchArray($mysqliResult, $type)
{
if (is_null($type)) {
$type = MYSQLI_BOTH;
}
return $mysqliResult->fetch_array($type);
}
public function affectedRows(): int
{
return $this->mysqli->affected_rows;
}
public function escapeString(string $string): string
{
return $this->mysqli->real_escape_string($string);
}
public function lastInsertId(): int
{
return $this->mysqli->insert_id;
}
public function freeResult($mysqliResult)
{
return $mysqliResult->free_result();
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Nexus\Database;
class DatabaseException extends \Exception
{
public function __construct($message, $query = '')
{
parent::__construct("$message [$query]");
}
}

View File

@@ -0,0 +1,67 @@
<?php
use Nexus\Database\DB;
function mysql_connect($host, $username, $password, $database, $port)
{
return DB::getInstance()->connect($host, $username, $password, $database, $port);
}
function mysql_errno()
{
return DB::getInstance()->errno();
}
function mysql_error()
{
return DB::getInstance()->error();
}
function mysql_query(string $sql)
{
return DB::getInstance()->query($sql);
}
function mysql_select_db($database)
{
return DB::getInstance()->select_db($database);
}
function mysql_num_rows($result)
{
return DB::getInstance()->numRows($result);
}
function mysql_fetch_array($result, $type = null)
{
return DB::getInstance()->fetchArray($result, $type);
}
function mysql_fetch_assoc($result)
{
return DB::getInstance()->fetchAssoc($result);
}
function mysql_fetch_row($result)
{
return DB::getInstance()->fetchRow($result);
}
function mysql_affected_rows()
{
return DB::getInstance()->affectedRows();
}
function mysql_real_escape_string($string)
{
return DB::getInstance()->escapeString($string);
}
function mysql_insert_id()
{
return DB::getInstance()->lastInsertId();
}
function mysql_free_result($result)
{
return DB::getInstance()->freeResult($result);
}

359
nexus/Install/Install.php Normal file
View File

@@ -0,0 +1,359 @@
<?php
namespace Nexus\Install;
use Nexus\Database\DB;
class Install
{
private $currentStep;
private $minimumPhpVersion = '7.2.0';
protected $steps = ['环境检测', '添加 .env 文件', '新建数据表', '导入数据', '创建管理员账号'];
public function __construct()
{
$this->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 = '<div class="table w-full text-left">';
$table .= '<div class="table-row-group">';
$table .= '<div class="table-row">';
foreach ($header as $value) {
$table .= '<div class="table-cell bg-gray-400 text-gray-700 px-4 py-2">' . $value . '</div>';
}
$table .= '</div>';
foreach ($data as $value) {
$table .= '<div class="table-row">';
$table .= '<div class="table-cell bg-gray-200 text-gray-700 px-4 py-2 text-sm">' . $value['label'] . '</div>';
$table .= '<div class="table-cell bg-gray-200 text-gray-700 px-4 py-2 text-sm">' . $value['required'] . '</div>';
$table .= '<div class="table-cell bg-gray-200 text-gray-700 px-4 py-2 text-sm">' . $value['current'] . '</div>';
$table .= '<div class="table-cell bg-' . ($value['result'] == 'YES' ? 'green' : 'gray') . '-200 text-gray-700 px-4 py-2 text-sm">' . $value['result'] . '</div>';
$table .= '</div>';
}
$table .= '</div>';
$table .= '</div>';
return $table;
}
public function renderForm($formControls, $formWidth = '1/2', $labelWidth = '1/3', $valueWidth = '2/3')
{
$form = '<div class="inline-block w-' . $formWidth . '">';
foreach ($formControls as $value) {
$form .= '<div class="flex mt-2">';
$form .= sprintf('<div class="w-%s flex justify-end items-center pr-10"><span>%s</span></div>', $labelWidth, $value['label']);
$form .= sprintf(
'<div class="w-%s flex justify-start items-center pr-10"><input class="border py-2 px-3 text-grey-darkest w-full" type="text" name="%s" value="%s" /></div>',
$valueWidth, $value['name'], $value['value'] ?? ''
);
$form .= '</div>';
}
$form .= '</div>';
return $form;
}
public function renderSteps()
{
$steps = '<div class="flex mt-10 step text-center">';
$currentStep = $this->currentStep();
foreach ($this->steps as $key => $value) {
$steps .= sprintf('<div class="flex-1 %s">', $currentStep > $key + 1 ? 'done' : ($currentStep < $key + 1 ? 'none' : ''));
$steps .= sprintf('<div>第%s步</div>', $key + 1);
$steps .= sprintf('<div>%s</div>', $value);
$steps .= '</div>';
}
$steps .= '</div>';
return $steps;
}
public function listEnvFormControls()
{
$envExampleFile = ROOT_PATH . ".env.example";
$envExampleData = readEnvFile($envExampleFile);
$envFile = ROOT_PATH . '.env';
$envData = [];
if (file_exists($envFile) && is_readable($envFile)) {
//already exists, read it ,and merge
$envData = readEnvFile($envFile);
}
$mergeData = array_merge($envExampleData, $envData);
$formControls = [];
foreach ($mergeData as $key => $value) {
if (isset($_POST[$key])) {
$value = $_POST[$key];
}
$formControls[] = [
'label' => $key,
'name' => $key,
'value' => $value,
];
}
return $formControls;
}
public function createAdministrator($username, $email, $password, $confirmPassword)
{
if (!validusername($username)) {
throw new \InvalidArgumentException("Innvalid username: $username");
}
$email = htmlspecialchars(trim($email));
$email = safe_email($email);
if (!check_email($email)) {
throw new \InvalidArgumentException("Innvalid email: $email");
}
$res = sql_query("SELECT id FROM users WHERE email=" . sqlesc($email));
$arr = mysql_fetch_row($res);
if ($arr) {
throw new \InvalidArgumentException("The email address: $email is already in use");
}
if (mb_strlen($password) < 6 || mb_strlen($password) > 40) {
throw new \InvalidArgumentException("Innvalid password: $password, it should be more than 6 character and less than 40 character");
}
if ($password != $confirmPassword) {
throw new \InvalidArgumentException("confirmPassword: $confirmPassword != password");
}
$setting = get_setting('main');
$secret = mksecret();
$passhash = md5($secret . $password . $secret);
$insert = [
'username' => $username,
'passhash' => $passhash,
'secret' => $secret,
'email' => $email,
'stylesheet' => $setting['defstylesheet'],
'class' => 16,
'status' => 'confirmed',
'added' => date('Y-m-d H:i:s'),
];
$this->doLog("insert user: " . json_encode($insert));
return DB::insert('users', $insert);
}
}