Files
nexusphp/include/globalfunctions.php
xiaomlove c74c88f5a5 misc up
2025-09-14 00:47:09 +07:00

1631 lines
52 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
function get_global_sp_state()
{
static $global_promotion_state;
$cacheKey = \App\Models\Setting::TORRENT_GLOBAL_STATE_CACHE_KEY;
if (!$global_promotion_state) {
$row = \Nexus\Database\NexusDB::remember($cacheKey, 600, function () use ($cacheKey) {
return \Nexus\Database\NexusDB::getOne('torrents_state', 1);
});
if (is_array($row) && isset($row['deadline']) && $row['deadline'] < date('Y-m-d H:i:s')) {
//expired
$global_promotion_state = \App\Models\Torrent::PROMOTION_NORMAL;
} elseif (is_array($row) && isset($row['begin']) && $row['begin'] > date('Y-m-d H:i:s')) {
//Not begin
$global_promotion_state = \App\Models\Torrent::PROMOTION_NORMAL;
} elseif (is_array($row)) {
$global_promotion_state = $row["global_sp_state"];
} else {
$global_promotion_state = $row;
}
}
return $global_promotion_state;
}
// IP Validation
function validip($ip)
{
if (!ip2long($ip)) //IPv6
return true;
if (!empty($ip) && $ip == long2ip(ip2long($ip)))
{
// reserved IANA IPv4 addresses
// http://www.iana.org/assignments/ipv4-address-space
$reserved_ips = array (
array('192.0.2.0','192.0.2.255'),
array('192.168.0.0','192.168.255.255'),
array('255.255.255.0','255.255.255.255')
);
foreach ($reserved_ips as $r)
{
$min = ip2long($r[0]);
$max = ip2long($r[1]);
if ((ip2long($ip) >= $min) && (ip2long($ip) <= $max)) return false;
}
return true;
}
else return false;
}
function getip($real = true) {
if (isset($_SERVER)) {
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && validip($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (isset($_SERVER['HTTP_CLIENT_IP']) && validip($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} else {
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
}
} else {
if (getenv('HTTP_X_FORWARDED_FOR') && validip(getenv('HTTP_X_FORWARDED_FOR'))) {
$ip = getenv('HTTP_X_FORWARDED_FOR');
} elseif (getenv('HTTP_CLIENT_IP') && validip(getenv('HTTP_CLIENT_IP'))) {
$ip = getenv('HTTP_CLIENT_IP');
} else {
$ip = getenv('REMOTE_ADDR') ?? '';
}
}
$ip = trim(trim($ip), ",");
if ($real && str_contains($ip, ",")) {
return strstr($ip, ",", true);
}
return $ip;
}
function sql_query($query)
{
$begin = microtime(true);
global $query_name;
$result = mysql_query($query);
$end = microtime(true);
$query_name[] = [
'query' => $query,
'time' => sprintf('%.3f ms', ($end - $begin) * 1000),
];
return $result;
}
function sqlesc($value) {
if (is_null($value)) {
return 'null';
}
$value = "'" . mysql_real_escape_string($value) . "'";
return $value;
}
function hash_pad($hash) {
return str_pad($hash, 20);
}
function hash_where($name, $hash) {
// $shhash = preg_replace('/ *$/s', "", $hash);
// return "($name = " . sqlesc($hash) . " OR $name = " . sqlesc($shhash) . ")";
// return sprintf("$name in (%s, %s)", sqlesc($hash), sqlesc($shhash));
return "$name = " . sqlesc($hash);
}
//no need any more...
/*
function strip_magic_quotes($arr)
{
foreach ($arr as $k => $v)
{
if (is_array($v))
{
$arr[$k] = strip_magic_quotes($v);
} else {
$arr[$k] = stripslashes($v);
}
}
return $arr;
}
if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc())
{
if (!empty($_GET)) {
$_GET = strip_magic_quotes($_GET);
}
if (!empty($_POST)) {
$_POST = strip_magic_quotes($_POST);
}
if (!empty($_COOKIE)) {
$_COOKIE = strip_magic_quotes($_COOKIE);
}
}
*/
function get_langfolder_list()
{
//do not access db for speed up, or for flexibility
// return array("en", "chs", "cht", "ko", "ja");
return \App\Models\Language::listAvailable();
}
function printLine($line, $exist = false)
{
echo "[" . date('Y-m-d H:i:s') . "] $line<br />";
if ($exist) {
exit(0);
}
}
function nexus_dd($vars)
{
echo '<pre>';
array_map(function ($var) {
var_dump($var);
}, func_get_args());
echo '</pre>';
exit(0);
}
/**
* write log, use in both pure nexus and inside laravel
*
* @param $log
* @param string $level
*/
function do_log($log, $level = 'info', $echo = false)
{
static $env, $setLogLevel;
if (is_null($setLogLevel)) {
$setLogLevel = nexus_env('LOG_LEVEL', 'debug');
}
if (is_null($env)) {
$env = nexus_env('APP_ENV', 'production');
}
$logLevels = ['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency'];
$setLogLevelKey = array_search($setLogLevel, $logLevels);
$currentLogLevelKey = array_search($level, $logLevels);
if ($currentLogLevelKey === false) {
$level = 'error';
$log = "[ERROR_LOG_LEVEL] $log";
$currentLogLevelKey = array_search($level, $logLevels);
}
if ($currentLogLevelKey < $setLogLevelKey) {
return;
}
$logFile = getLogFile();
if (($fd = fopen($logFile, 'a')) === false) {
$log .= "--------Can not open $logFile";
$fd = fopen(sys_get_temp_dir() . '/nexus.log', 'a');
}
$uid = 0;
if (IN_NEXUS) {
global $CURUSER;
$user = $CURUSER;
$uid = $user['id'] ?? 0;
$passkey = $user['passkey'] ?? $_REQUEST['passkey'] ?? $_REQUEST['authkey'] ?? '';
} else {
try {
$user = \Illuminate\Support\Facades\Auth::user();
$uid = $user->id ?? 0;
$passkey = $user->passkey ?? request('passkey', request('authkey', ''));
} catch (\Throwable $exception) {
$passkey = "!IN_NEXUS:" . $exception->getMessage();
}
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$dt = DateTime::createFromFormat('U.u', sprintf('%.6f', microtime(true)));
$dt->setTimezone(new DateTimeZone(nexus_env('TIMEZONE', 'UTC')));
$content = sprintf(
"[%s] [%s] [%s] [%s] [%s] [%s] %s.%s %s:%s %s%s%s %s%s",
$dt->format("Y-m-d\TH:i:s.vP"),
nexus() ? nexus()->getRequestId() : 'NO_REQUEST_ID',
nexus() ? nexus()->getLogSequence() : 0,
sprintf('%.3f', microtime(true) - (nexus() ? nexus()->getStartTimestamp() : 0)),
$uid,
$passkey,
$env, strtoupper($level),
$backtrace[0]['file'] ?? '',
$backtrace[0]['line'] ?? '',
$backtrace[1]['class'] ?? '',
$backtrace[1]['type'] ?? '',
$backtrace[1]['function'] ?? '',
$log,
PHP_EOL
);
fwrite($fd, $content);
fclose($fd);
if (is_bool($echo) && $echo) {
echo $content . PHP_EOL;
}
if (nexus()) {
nexus()->incrementLogSequence();
}
}
function getLogFile($append = '')
{
static $logFiles = [];
if (isset($logFiles[$append])) {
return $logFiles[$append];
}
$std = ["php://stdout", "php://stderr"];
$logFileFromDotEnv = nexus_env('LOG_FILE');
if ($logFileFromDotEnv && in_array($logFileFromDotEnv, $std)) {
return $logFiles[$append] = $logFileFromDotEnv;
}
$path = getenv('NEXUS_LOG_DIR', true);
if (in_array($path, $std)) {
return $logFiles[$append] = $path;
}
$fromEnv = true;
if ($path === false) {
$fromEnv = false;
$path = sys_get_temp_dir();
}
$logFile = rtrim($path, '/') . '/nexus.log';
if (!$fromEnv && $logFileFromDotEnv) {
$logFile = $logFileFromDotEnv;
}
$lastDotPos = strrpos($logFile, '.');
if ($lastDotPos !== false) {
$prefix = substr($logFile, 0, $lastDotPos);
$suffix = substr($logFile, $lastDotPos);
} else {
$prefix = $logFile;
$suffix = '';
}
$name = $prefix;
if ($append) {
$name .= "-$append";
}
if (isRunningInConsole()) {
$scriptUserInfo = posix_getpwuid(posix_getuid());
$name .= sprintf("-cli-%s", $scriptUserInfo['name']);
}
$name .= "-" . date('Y-m-d');
return $logFiles[$append] = $name . $suffix;
}
function nexus_config($key, $default = null)
{
if (!IN_NEXUS) {
return config($key, $default);
}
static $configs;
if (is_null($configs)) {
//get all configuration from config file
// $files = glob(ROOT_PATH . 'config/*.php');
$files = [
ROOT_PATH . 'config/nexus.php',
ROOT_PATH . 'config/emoji.php',
];
foreach ($files as $file) {
$basename = basename($file);
if ($basename == 'allconfig.php') {
//exclude the NexusPHP default config file
continue;
}
$values = require $file;
$configPrefix = strstr($basename, '.php', true);
$configs[$configPrefix] = $values;
}
}
return arr_get($configs, $key, $default);
}
/**
* get setting for given name and prefix
*
* @date 2021/1/11
* @param null $name
* @param null $default
* @return mixed
*/
function get_setting($name = null, $default = null): mixed
{
static $settings;
if (is_null($settings)) {
$settings = \Nexus\Database\NexusDB::remember("nexus_settings_in_nexus", 600, function () {
//get all settings from database
return \App\Models\Setting::getFromDb();
});
}
if (is_null($name)) {
return $settings;
}
return arr_get($settings, $name, $default);
}
function get_setting_from_db($name = null, $default = null)
{
static $final;
if (is_null($final)) {
$final = \App\Models\Setting::getFromDb();
}
if (is_null($name)) {
return $final;
}
return arr_get($final, $name, $default);
}
function nexus_env($key = null, $default = null)
{
static $env;
if (is_null($env)) {
$envFile = dirname(__DIR__) . '/.env';
$env = readEnvFile($envFile);
}
if (is_null($key)) {
return $env;
}
return $env[$key] ?? $default;
}
function readEnvFile($envFile)
{
if (!file_exists($envFile)) {
if (php_sapi_name() == 'cli') {
return [];
}
throw new \RuntimeException("env file : $envFile is not exists in the root path.");
}
$env = [];
$fp = fopen($envFile, 'r');
if ($fp === false) {
throw new \RuntimeException(".env file: $envFile is not readable.");
}
while (($line = fgets($fp)) !== false) {
$line = trim($line);
if (empty($line)) {
continue;
}
$pos = strpos($line, '=');
if ($pos <= 0) {
continue;
}
if (mb_substr($line, 0, 1, 'utf-8') == '#') {
continue;
}
$lineKey = normalize_env(mb_substr($line, 0, $pos, 'utf-8'));
$lineValue = normalize_env(mb_substr($line, $pos + 1, null, 'utf-8'));
$env[$lineKey] = $lineValue;
}
return $env;
}
function normalize_env($value)
{
$value = trim($value);
$toStrip = ['\'', '"'];
if (in_array(mb_substr($value, 0, 1, 'utf-8'), $toStrip)) {
$value = mb_substr($value, 1, null, 'utf-8');
}
if (in_array(mb_substr($value, -1, null,'utf-8'), $toStrip)) {
$value = mb_substr($value, 0, -1, 'utf-8');
}
switch (strtolower($value)) {
case 'true':
return true;
case 'false':
return false;
case 'null':
return null;
default:
return $value;
}
}
/**
* Get an item from an array using "dot" notation.
*
* reference to Laravel
*
* @date 2021/1/14
* @param $array
* @param $key
* @param null $default
* @return mixed|null
*/
function arr_get($array, $key, $default = null)
{
if (strpos($key, '.') === false) {
return $array[$key] ?? $default;
}
foreach (explode('.', $key) as $segment) {
if (isset($array[$segment])) {
$array = $array[$segment];
} else {
return $default;
}
}
return $array;
}
/**
* From Laravel
*
* Set an array item to a given value using "dot" notation.
*
* If no key is given to the method, the entire array will be replaced.
*
* @param array $array
* @param string|null $key
* @param mixed $value
* @return array
*/
function arr_set(&$array, $key, $value)
{
if (is_null($key)) {
return $array = $value;
}
$keys = explode('.', $key);
foreach ($keys as $i => $key) {
if (count($keys) === 1) {
break;
}
unset($keys[$i]);
// If the key doesn't exist at this depth, we will just create an empty array
// to hold the next value, allowing us to create the arrays to hold final
// values at the correct depth. Then we'll keep digging into the array.
if (! isset($array[$key]) || ! is_array($array[$key])) {
$array[$key] = [];
}
$array = &$array[$key];
}
$array[array_shift($keys)] = $value;
return $array;
}
function isHttps(): bool
{
if (isRunningInConsole()) {
$securityLogin = get_setting("security.securelogin");
if ($securityLogin != "no") {
return true;
}
return false;
}
return nexus()->getRequestSchema() == 'https';
}
function getSchemeAndHttpHost(bool $fromConfig = false)
{
if (isRunningInConsole() || $fromConfig) {
$host = get_setting("basic.BASEURL");
} else {
$host = nexus()->getRequestHost();
}
$isHttps = isHttps();
$protocol = $isHttps ? 'https' : 'http';
return "$protocol://" . $host;
}
function getBaseUrl()
{
$url = getSchemeAndHttpHost();
if (!isRunningInConsole()) {
$requestUri = $_SERVER['REQUEST_URI'];
$pos = strpos($requestUri, '?');
if ($pos !== false) {
$url .= substr($requestUri, 0, $pos);
} else {
$url .= $requestUri;
}
}
return trim($url, '/');
}
function nexus_json_encode($data)
{
return json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
}
function api(...$args)
{
if (func_num_args() < 3) {
//参数少于3个时默认为错误状态。
$ret = -1;
$msg = isset($args[0]) ? $args[0] : 'ERROR';
$data = isset($args[1]) ? $args[1] : [];
} else {
$ret = $args[0];
$msg = $args[1];
$data = $args[2];
}
if ($data instanceof \Illuminate\Http\Resources\Json\JsonResource) {
$data = $data->response()->getData(true);
}
// dd($data);
$time = (float)number_format(microtime(true) - nexus()->getStartTimestamp(), 3);
$count = null;
$resultKey = 'ret';
$msgKey = 'msg';
$format = $_REQUEST['__format'] ?? '';
if (in_array($format, ['layui-table', 'data-table'])) {
$resultKey = 'code';
$count = $data['meta']['total'] ?? 0;
if (isset($data['data'])) {
$data = $data['data'];
}
}
$results = [
$resultKey => (int)$ret,
$msgKey => (string)$msg,
'data' => $data,
'time' => $time,
'rid' => nexus()->getRequestId(),
];
if ($format == 'layui-table') {
$results['count'] = $count;
}
if ($format == 'data-table') {
$results['draw'] = intval($_REQUEST['draw'] ?? 1);
$results['recordsTotal'] = $count;
$results['recordsFiltered'] = $count;
}
return $results;
}
function success(...$args)
{
$ret = 0;
$msg = 'OK';
$data = [];
$argumentCount = func_num_args();
if ($argumentCount == 1) {
$data = $args[0];
} elseif ($argumentCount == 2) {
$msg = $args[0];
$data = $args[1];
}
return api($ret, $msg, $data);
}
function fail(...$args)
{
$ret = -1;
$msg = 'ERROR';
$data = [];
$argumentCount = func_num_args();
if ($argumentCount == 1) {
$data = $args[0];
} elseif ($argumentCount == 2) {
$msg = $args[0];
$data = $args[1];
}
return api($ret, $msg, $data);
}
function last_query($all = false)
{
static $connection, $pdo;
if (is_null($connection)) {
if (IN_NEXUS) {
$connection = \Illuminate\Database\Capsule\Manager::connection(\Nexus\Database\NexusDB::ELOQUENT_CONNECTION_NAME);
} else {
$connection = \Illuminate\Support\Facades\DB::connection(config('database.default'));
}
$pdo = $connection->getPdo();
}
$queries = $connection->getQueryLog();
if (!$all) {
$queries = [last($queries)];
}
$queryFormatted = [];
foreach ($queries as $query) {
$sqlWithPlaceholders = str_replace(['%', '?'], ['%%', '%s'], $query['query']);
$bindings = $query['bindings'];
$realSql = $sqlWithPlaceholders;
if (count($bindings) > 0) {
$realSql = vsprintf($sqlWithPlaceholders, array_map([$pdo, 'quote'], $bindings));
}
$queryFormatted[] = $realSql;
}
if ($all) {
return nexus_json_encode($queryFormatted);
}
return $queryFormatted[0];
}
function format_datetime($datetime, $format = 'Y-m-d H:i')
{
if (empty($datetime)) {
return null;
}
try {
$carbonTime = \Carbon\Carbon::parse($datetime);
return $carbonTime->format($format);
} catch (\Exception) {
do_log("Invalid datetime: $datetime", 'error');
return $datetime;
}
}
function nexus_trans($key, $replace = [], $locale = null)
{
return \Nexus\Nexus::trans($key, $replace, $locale);
}
function isRunningInConsole(): bool
{
return !RUNNING_IN_OCTANE && php_sapi_name() == 'cli';
}
function isRunningOnWindows(): bool
{
return !RUNNING_IN_OCTANE && strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
}
function command_exists($command): bool
{
return !(trim(exec("command -v $command")) == '');
}
function get_tracker_schema_and_host($trackerUrlId, $combine = false): array|string
{
/*
global $https_announce_urls, $announce_urls;
$httpsAnnounceUrls = array_filter($https_announce_urls);
$log = "cookie: " . json_encode($_COOKIE) . ", https_announce_urls: " . json_encode($httpsAnnounceUrls);
if (
(isset($_COOKIE["c_secure_tracker_ssl"]) && $_COOKIE["c_secure_tracker_ssl"] == base64("yeah"))
|| !empty($httpsAnnounceUrls)
|| isHttps()
) {
$log .= ", c_secure_tracker_ssl = base64('yeah'): " . base64("yeah") . ", or not empty https_announce_urls, or isHttps()";
$tracker_ssl = true;
} else {
$tracker_ssl = false;
}
$log .= ", tracker_ssl: $tracker_ssl";
if ($tracker_ssl == true){
$ssl_torrent = "https://";
if ($https_announce_urls[0] != "") {
$log .= ", https_announce_urls not empty, use it";
$base_announce_url = $https_announce_urls[0];
} else {
$log .= ", https_announce_urls empty, use announce_urls[0]";
$base_announce_url = $announce_urls[0];
}
} else {
$ssl_torrent = "http://";
$base_announce_url = $announce_urls[0];
}
do_log($log);
if ($combine) {
return $ssl_torrent . $base_announce_url;
}
*/
$log = "tracker_url_id: $trackerUrlId, combine: $combine";
$url = \App\Models\TrackerUrl::getById($trackerUrlId);
if (empty($url)) {
$ssl_torrent = isHttps() ? 'https://' : 'http://';
$base_announce_url = sprintf("%s/%s", \App\Models\Setting::getBaseUrl(), DEFAULT_TRACKER_URI);
$log .= ", ById no value";
} else {
$ssl_torrent = parse_url($url, PHP_URL_SCHEME) . "://" ;
$base_announce_url = substr($url, strlen($ssl_torrent));
$log .= ", ById has value";
}
do_log("$log, ssl_torrent: $ssl_torrent, base_announce_url: $base_announce_url");
if ($combine) {
return $ssl_torrent . $base_announce_url;
}
return compact('ssl_torrent', 'base_announce_url');
}
function get_hr_ratio($uped, $downed)
{
if ($downed > 0) {
$ratio = $uped / $downed;
$color = get_ratio_color($ratio);
if ($ratio > 10000) $ratio = 'Inf.';
else
$ratio = number_format($ratio, 3);
if ($color)
$ratio = "<font color=\"" . $color . "\">" . $ratio . "</font>";
} elseif ($uped > 0)
$ratio = 'Inf.';
else
$ratio = "---";
return $ratio;
}
function get_row_count($table, $suffix = "")
{
$r = sql_query("SELECT COUNT(*) FROM $table $suffix") or sqlerr(__FILE__, __LINE__);
$a = mysql_fetch_row($r);
return $a[0];
}
function get_user_row($id)
{
global $Cache, $CURUSER;
static $userRows = [];
static $curuserRowUpdated = false;
static $neededColumns = array(
'id', 'noad', 'class', 'enabled', 'privacy', 'avatar', 'signature', 'uploaded', 'downloaded', 'last_access', 'username', 'donor',
'donoruntil', 'leechwarn', 'warned', 'title', 'downloadpos', 'parked', 'clientselect', 'showclienterror',
);
if (isset($userRows[$id])) return $userRows[$id];
$cacheKey = 'user_'.$id.'_content';
$row = \Nexus\Database\NexusDB::remember($cacheKey, 3600, function () use ($id, $neededColumns) {
$user = \App\Models\User::query()->with([
'wearing_medals' => function ($query) {
$query->orderBy('user_medals.priority', 'desc')
->orderBy('user_medals.id', 'desc')
->limit(get_setting('system.maximum_number_of_medals_can_be_worn', 3));
}
])->find($id, $neededColumns);
if (!$user) {
return null;
}
$arr = $user->toArray();
//Rainbow ID
$userRep = new \App\Repositories\UserRepository();
$metas = $userRep->listMetas($id, \App\Models\UserMeta::META_KEY_PERSONALIZED_USERNAME);
if ($metas->isNotEmpty()) {
$arr['__is_rainbow'] = 1;
} else {
$arr['__is_rainbow'] = 0;
}
$arr['__is_donor'] = is_donor($arr);
return apply_filter("user_row", $arr);
});
// if ($CURUSER && $id == $CURUSER['id']) {
// $row = array();
// foreach($neededColumns as $column) {
// $row[$column] = $CURUSER[$column];
// }
// if (!$curuserRowUpdated) {
// $Cache->cache_value('user_'.$CURUSER['id'].'_content', $row, 900);
// $curuserRowUpdated = true;
// }
// } elseif (!$row = $Cache->get_value('user_'.$id.'_content')){
// $res = sql_query("SELECT ".implode(',', $neededColumns)." FROM users WHERE id = ".sqlesc($id)) or sqlerr(__FILE__,__LINE__);
// $row = mysql_fetch_array($res);
// $Cache->cache_value('user_'.$id.'_content', $row, 900);
// }
if (!$row)
return false;
else return $userRows[$id] = $row;
}
function get_user_class()
{
if (IN_NEXUS) {
global $CURUSER;
return $CURUSER["class"] ?? '';
}
return auth()->user()->class;
}
function get_user_id()
{
if (IN_NEXUS) {
global $CURUSER;
return $CURUSER["id"] ?? 0;
}
return auth()->user()->id ?? 0;
}
function get_user_passkey()
{
if (IN_NEXUS) {
global $CURUSER;
return $CURUSER["passkey"] ?? "";
}
return auth()->user()->passkey ?? "";
}
function get_pure_username()
{
if (IN_NEXUS) {
global $CURUSER;
return $CURUSER["username"] ?? "";
}
return auth()->user()->username ?? "";
}
function nexus()
{
return \Nexus\Nexus::instance();
}
function site_info()
{
$setting = \App\Models\Setting::get('basic');
$siteInfo = [
'site_name' => $setting['SITENAME'],
'base_url' => getSchemeAndHttpHost(),
];
return $siteInfo;
}
function isIPV4 ($ip)
{
return filter_var($ip,FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
}
function isIPV6 ($ip)
{
return filter_var($ip,FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
}
function add_filter($name, $function, $priority = 10, $argc = 1)
{
global $hook;
$hook->addFilter($name, $function, $priority, $argc);
}
function apply_filter($name, ...$args)
{
global $hook;
// do_log("[APPLY_FILTER]: $name");
return $hook->applyFilter(...func_get_args());
}
function add_action($name, $function, $priority = 10, $argc = 1)
{
global $hook;
$hook->addAction($name, $function, $priority, $argc);
}
function do_action($name, ...$args)
{
global $hook;
// do_log("[DO_ACTION]: $name");
return $hook->doAction(...func_get_args());
}
function isIPSeedBoxFromASN($ip, $exceptionWhenYes = false): bool
{
$redis = \Nexus\Database\NexusDB::redis();
$key = "nexus_asn";
$notFoundCacheValue = "__NOT_FOUND__";
try {
static $reader;
$database = nexus_env('GEOIP2_ASN_DATABASE');
if (!file_exists($database) || !is_readable($database)) {
do_log("GEOIP2_ASN_DATABASE: $database not exists or not readable", "debug");
return false;
}
if (is_null($reader)) {
$reader = new \GeoIp2\Database\Reader($database);
}
$asnObj = $reader->asn($ip);
$asn = $asnObj->autonomousSystemNumber;
if ($asn <= 0) {
return false;
}
$cacheResult = $redis->hGet($key, $asn);
if ($cacheResult !== false) {
if ($cacheResult === $notFoundCacheValue) {
return false;
} else {
return true;
}
}
$row = \Nexus\Database\NexusDB::getOne("seed_box_records", "asn = $asn", "id");
if (!empty($row)) {
$redis->hSet($key, $asn, $row['id']);
} else {
$redis->hSet($key, $asn, $notFoundCacheValue);
}
} catch (\Throwable $throwable) {
do_log("ip: $ip, " . $throwable->getMessage());
$redis->hSet($key, $asn, $notFoundCacheValue);
}
$result = !empty($row);
if ($result && $exceptionWhenYes) {
throw new \App\Exceptions\SeedBoxYesException($row['id']);
}
return $result;
}
function isIPSeedBox($ip, $uid): bool
{
return \App\Repositories\SeedBoxRepository::isSeedBoxFromUserRecords($uid, $ip)['result'];
/*
$key = "nexus_is_ip_seed_box:ip:$ip:uid:$uid";
$cacheData = \Nexus\Database\NexusDB::cache_get($key);
if (in_array($cacheData, [0, 1, '0', '1'], true) && !$withoutCache) {
do_log("$key, get result from cache: $cacheData(" . gettype($cacheData) . ")");
return (bool)$cacheData;
}
//check from asn
$res = isIPSeedBoxFromASN($ip, $exceptionWhenYes);
if (!empty($res)) {
\Nexus\Database\NexusDB::cache_put($key, 1, 300);
do_log("$key, get result from asn, true");
return true;
}
$ipObject = \PhpIP\IP::create($ip);
$ipNumeric = $ipObject->numeric();
$ipVersion = $ipObject->getVersion();
//check allow list first, not consider specific user
$checkSeedBoxAllowedSql = sprintf(
'select id from seed_box_records where `ip_begin_numeric` <= "%s" and `ip_end_numeric` >= "%s" and `version` = %s and `status` = %s and `is_allowed` = 1 and asn = 0 limit 1',
$ipNumeric, $ipNumeric, $ipVersion, \App\Models\SeedBoxRecord::STATUS_ALLOWED
);
$res = \Nexus\Database\NexusDB::select($checkSeedBoxAllowedSql);
if (!empty($res)) {
\Nexus\Database\NexusDB::cache_put($key, 0, 300);
do_log("$key, get result from database, is_allowed = 1, false");
return false;
}
$checkSeedBoxAdminSql = sprintf(
'select id from seed_box_records where `ip_begin_numeric` <= "%s" and `ip_end_numeric` >= "%s" and `type` = %s and `version` = %s and `status` = %s and `is_allowed` = 0 and asn = 0 limit 1',
$ipNumeric, $ipNumeric, \App\Models\SeedBoxRecord::TYPE_ADMIN, $ipVersion, \App\Models\SeedBoxRecord::STATUS_ALLOWED
);
$res = \Nexus\Database\NexusDB::select($checkSeedBoxAdminSql);
if (!empty($res)) {
\Nexus\Database\NexusDB::cache_put($key, 1, 300);
do_log("$key, get result from admin, true");
if ($exceptionWhenYes) {
throw new \App\Exceptions\SeedBoxYesException($res[0]['id']);
}
return true;
}
if ($uid !== null) {
$checkSeedBoxUserSql = sprintf(
'select id from seed_box_records where `ip_begin_numeric` <= "%s" and `ip_end_numeric` >= "%s" and `uid` = %s and `type` = %s and `version` = %s and `status` = %s and `is_allowed` = 0 and asn = 0 limit 1',
$ipNumeric, $ipNumeric, $uid, \App\Models\SeedBoxRecord::TYPE_USER, $ipVersion, \App\Models\SeedBoxRecord::STATUS_ALLOWED
);
$res = \Nexus\Database\NexusDB::select($checkSeedBoxUserSql);
if (!empty($res)) {
\Nexus\Database\NexusDB::cache_put($key, 1, 300);
do_log("$key, get result from user, true");
if ($exceptionWhenYes) {
throw new \App\Exceptions\SeedBoxYesException($res[0]['id']);
}
return true;
}
}
\Nexus\Database\NexusDB::cache_put($key, 0, 300);
do_log("$key, no result, false");
return false;
*/
}
function getDataTraffic(array $torrent, array $queries, array $user, $peer, $snatch, $promotionInfo)
{
if (!isset($user['__is_donor'])) {
throw new \InvalidArgumentException("user no '__is_donor' field");
}
$log = sprintf(
"torrent: %s, owner: %s, user: %s, peerUploaded: %s, peerDownloaded: %s, queriesUploaded: %s, queriesDownloaded: %s",
$torrent['id'], $torrent['owner'], $user['id'], $peer['uploaded'] ?? '', $peer['downloaded'] ?? '', $queries['uploaded'], $queries['downloaded']
);
if (!empty($peer)) {
$realUploaded = max(bcsub($queries['uploaded'], $peer['uploaded']), 0);
$realDownloaded = max(bcsub($queries['downloaded'], $peer['downloaded']), 0);
$log .= ", [PEER_EXISTS], realUploaded: $realUploaded, realDownloaded: $realDownloaded, [SP_STATE]";
$spStateGlobal = get_global_sp_state();
$spStateNormal = \App\Models\Torrent::PROMOTION_NORMAL;
if (!empty($promotionInfo) && isset($promotionInfo['__ignore_global_sp_state'])) {
$log .= ', use promotionInfo';
$spStateReal = $promotionInfo['sp_state'];
} elseif ($spStateGlobal != $spStateNormal) {
$log .= ", use global";
$spStateReal = $spStateGlobal;
} else {
$log .= ", use torrent individual";
$spStateReal = $torrent['sp_state'];
}
if (!isset(\App\Models\Torrent::$promotionTypes[$spStateReal])) {
$log .= ", spStateReal = $spStateReal, invalid, reset to: $spStateNormal";
$spStateReal = $spStateNormal;
}
$uploaderRatio = get_setting('torrent.uploaderdouble');
$log .= ", uploaderRatio: $uploaderRatio";
if ($torrent['owner'] == $user['id'] && $uploaderRatio != 1) {
//uploader, use the bigger one
$upRatio = max($uploaderRatio, \App\Models\Torrent::$promotionTypes[$spStateReal]['up_multiplier']);
$log .= ", [IS_UPLOADER] && uploaderRatio != 1, upRatio: $upRatio";
} else {
$upRatio = \App\Models\Torrent::$promotionTypes[$spStateReal]['up_multiplier'];
$log .= ", [IS_NOT_UPLOADER] || uploaderRatio == 1, upRatio: $upRatio";
}
/**
* VIP do not calculate downloaded
* @since 1.7.13
*/
if ($user['class'] == \App\Models\User::CLASS_VIP) {
$downRatio = 0;
$log .= ", [IS_VIP], downRatio: $downRatio";
} else {
$downRatio = \App\Models\Torrent::$promotionTypes[$spStateReal]['down_multiplier'];
$log .= ", [IS_NOT_VIP], downRatio: $downRatio";
}
} else {
$realUploaded = $queries['uploaded'];
$realDownloaded = $queries['downloaded'];
/**
* If peer not exits, user increment = 0;
*/
$upRatio = 0;
$downRatio = 0;
$log .= ", [PEER_NOT_EXISTS], realUploaded: $realUploaded, realDownloaded: $realDownloaded, upRatio: $upRatio, downRatio: $downRatio";
}
$uploadedIncrementForUser = $realUploaded * $upRatio;
$downloadedIncrementForUser = $realDownloaded * $downRatio;
$log .= ", uploadedIncrementForUser: $uploadedIncrementForUser, downloadedIncrementForUser: $downloadedIncrementForUser";
/**
* check seed box rule
*/
$isSeedBoxRuleEnabled = get_setting('seed_box.enabled') == 'yes';
$log .= ", isSeedBoxRuleEnabled: $isSeedBoxRuleEnabled, user class: {$user['class']}, __is_donor: {$user['__is_donor']}";
if ($isSeedBoxRuleEnabled && $torrent['owner'] != $user['id'] && !($user['class'] >= \App\Models\User::CLASS_VIP || $user['__is_donor'])) {
$isIPSeedBox = isIPSeedBox($queries['ip'], $user['id']);
$log .= ", isIPSeedBox: $isIPSeedBox";
if ($isIPSeedBox) {
$isSeedBoxNoPromotion = get_setting('seed_box.no_promotion') == 'yes';
$log .= ", isSeedBoxNoPromotion: $isSeedBoxNoPromotion";
if ($isSeedBoxNoPromotion) {
$uploadedIncrementForUser = $realUploaded;
$downloadedIncrementForUser = $realDownloaded;
$log .= ", isIPSeedBox && isSeedBoxNoPromotion, increment for user = real";
}
$maxUploadedTimes = get_setting('seed_box.max_uploaded');
$maxUploadedDurationSeconds = get_setting('seed_box.max_uploaded_duration', 0) * 3600;
$torrentTTL = time() - strtotime($torrent['added']);
$timeRangeValid = ($maxUploadedDurationSeconds == 0) || ($torrentTTL < $maxUploadedDurationSeconds);
$log .= ", maxUploadedTimes: $maxUploadedTimes, maxUploadedDurationSeconds: $maxUploadedDurationSeconds, timeRangeValid: $timeRangeValid";
if ($maxUploadedTimes > 0 && $timeRangeValid) {
$log .= ", [LIMIT_UPLOADED]";
if (!empty($snatch) && isset($torrent['size']) && $snatch['uploaded'] >= $torrent['size'] * $maxUploadedTimes) {
$log .= ", snatchUploaded({$snatch['uploaded']}) >= torrentSize({$torrent['size']}) * times($maxUploadedTimes), uploadedIncrementForUser = 0";
$uploadedIncrementForUser = 0;
} else {
$log .= ", snatchUploaded({$snatch['uploaded']}) < torrentSize({$torrent['size']}) * times($maxUploadedTimes), uploadedIncrementForUser do not change to 0";
}
} else {
$log .= ", [NOT_LIMIT_UPLOADED]";
}
}
}
$result = [
'uploaded_increment' => $realUploaded,
'uploaded_increment_for_user' => $uploadedIncrementForUser,
'downloaded_increment' => $realDownloaded,
'downloaded_increment_for_user' => $downloadedIncrementForUser,
];
do_log("$log, result: " . json_encode($result), 'info');
return $result;
}
function clear_user_cache($uid, $passkey = '')
{
do_log("clear_user_cache, uid: $uid, passkey: $passkey");
\Nexus\Database\NexusDB::cache_del("user_{$uid}_content");
\Nexus\Database\NexusDB::cache_del("user_{$uid}_roles");
\Nexus\Database\NexusDB::cache_del("announce_user_passkey_$uid");//announce.php
\Nexus\Database\NexusDB::cache_del(\App\Models\Setting::DIRECT_PERMISSION_CACHE_KEY_PREFIX . $uid);
\Nexus\Database\NexusDB::cache_del("user_role_ids:$uid");
\Nexus\Database\NexusDB::cache_del("direct_permissions:$uid");
if ($passkey) {
\Nexus\Database\NexusDB::cache_del('user_passkey_'.$passkey.'_content');//announce.php
}
$userInfo = \App\Models\User::query()->find($uid, \App\Models\User::$commonFields);
if ($userInfo) {
fire_event("user_updated", $userInfo);
}
}
function clear_setting_cache()
{
do_log("clear_setting_cache");
\Nexus\Database\NexusDB::cache_del('nexus_settings_in_laravel');
\Nexus\Database\NexusDB::cache_del('nexus_settings_in_nexus');
\Nexus\Database\NexusDB::cache_del('setting_protected_forum');
$channel = nexus_env("CHANNEL_NAME_SETTING");
if (!empty($channel)) {
\Nexus\Database\NexusDB::redis()->publish($channel, "update");
}
}
/**
* @see functions.php::get_category_row(), genrelist()
*/
function clear_category_cache()
{
do_log("clear_category_cache");
\Nexus\Database\NexusDB::cache_del('category_content');
$searchBoxList = \App\Models\SearchBox::query()->get(['id']);
foreach ($searchBoxList as $item) {
\Nexus\Database\NexusDB::cache_del("category_list_mode_{$item->id}");
}
}
/**
* @see functions.php::searchbox_item_list()
*/
function clear_taxonomy_cache($table)
{
do_log("clear_taxonomy_cache: $table");
$list = \App\Models\SearchBox::query()->get(['id']);
foreach ($list as $item) {
\Nexus\Database\NexusDB::cache_del("{$table}_list_mode_{$item->id}");
}
\Nexus\Database\NexusDB::cache_del("{$table}_list_mode_0");
}
function clear_staff_message_cache()
{
do_log("clear_staff_message_cache");
\App\Repositories\MessageRepository::updateStaffMessageCountCache(false);
}
/**
* @see functions.php::get_searchbox_value()
*/
function clear_search_box_cache()
{
do_log("clear_search_box_cache");
\Nexus\Database\NexusDB::cache_del("search_box_content");
}
/**
* @see functions.php::get_category_icon_row()
*/
function clear_icon_cache()
{
do_log("clear_icon_cache");
\Nexus\Database\NexusDB::cache_del("category_icon_content");
}
function clear_inbox_count_cache($uid)
{
do_log("clear_inbox_count_cache");
foreach (\Illuminate\Support\Arr::wrap($uid) as $id) {
\Nexus\Database\NexusDB::cache_del('user_'.$id.'_inbox_count');
\Nexus\Database\NexusDB::cache_del('user_'.$id.'_unread_message_count');
}
}
function clear_agent_allow_deny_cache()
{
do_log("clear_agent_allow_deny_cache");
$allowCacheKey = nexus_env("CACHE_KEY_AGENT_ALLOW", "all_agent_allows");
$denyCacheKey = nexus_env("CACHE_KEY_AGENT_DENY", "all_agent_denies");
foreach (["", ":php", ":go"] as $suffix) {
\Nexus\Database\NexusDB::cache_del($allowCacheKey . $suffix);
\Nexus\Database\NexusDB::cache_del($denyCacheKey . $suffix);
}
}
/**
* @see announce.php
* @param $infoHash
* @return void
*/
function clear_torrent_cache($infoHash)
{
do_log("clear_torrent_cache");
\Nexus\Database\NexusDB::cache_del('torrent_hash_'.$infoHash.'_content');
\Nexus\Database\NexusDB::cache_del("torrent_not_exists:$infoHash");
}
function user_can($permission, $fail = false, $uid = 0): bool
{
$log = "permission: $permission, fail: $fail, user: $uid";
static $userCanCached = [];
static $sequence = 0;
if ($uid == 0) {
$uid = get_user_id();
$log .= ", set current uid: $uid";
}
if ($uid <= 0) {
if ($fail) {
goto FAIL;
}
do_log("$log, unauthenticated, false");
return false;
}
if (!$fail && isset($userCanCached[$permission][$uid])) {
return $userCanCached[$permission][$uid];
}
$userInfo = get_user_row($uid);
$class = $userInfo['class'];
$log .= ", userClass: $class";
if ($class == \App\Models\User::CLASS_STAFF_LEADER) {
do_log("$log, CLASS_STAFF_LEADER, true");
$userCanCached[$permission][$uid] = true;
return true;
}
$userAllPermissions = \App\Repositories\ToolRepository::listUserAllPermissions($uid);
$result = isset($userAllPermissions[$permission]);
if ($sequence == 0) {
$sequence++;
$log .= ", userAllPermissions: " . json_encode($userAllPermissions);
}
$log .= ", result: $result";
if (!$fail || $result) {
do_log($log);
$userCanCached[$permission][$uid] = $result;
return $result;
}
FAIL:
do_log("$log, [FAIL]");
if (IN_NEXUS && !IN_TRACKER) {
global $lang_functions;
$requireClass = get_setting("authority.$permission");
if (isset(\App\Models\User::$classes[$requireClass])) {
stderr($lang_functions['std_sorry'],$lang_functions['std_permission_denied_only'].get_user_class_name($requireClass,false,true,true).sprintf($lang_functions['std_or_above_can_view'], \App\Models\Setting::getSiteName()),false);
} else {
stderr($lang_functions['std_error'], $lang_functions['std_permission_denied']);
}
}
throw new \App\Exceptions\InsufficientPermissionException();
}
function assert_has_permission(bool $permissionCheckResult): void
{
if (!$permissionCheckResult) {
throw new \App\Exceptions\InsufficientPermissionException();
}
}
function is_donor(array $userInfo): bool
{
return $userInfo['donor'] == 'yes' && ($userInfo['donoruntil'] === null || $userInfo['donoruntil'] == '0000-00-00 00:00:00' || $userInfo['donoruntil'] >= date('Y-m-d H:i:s'));
}
/**
* @deprecated
* @param $authkey
* @return false|int|mixed|string|null
* @throws \App\Exceptions\NexusException
* @see download.php
*/
function get_passkey_by_authkey($authkey)
{
return \Nexus\Database\NexusDB::remember("authkey2passkey:$authkey", 3600*24, function () use ($authkey) {
$arr = explode('|', $authkey);
if (count($arr) != 3) {
throw new \InvalidArgumentException("Invalid authkey: $authkey, format error");
}
$uid = $arr[1];
$torrentRep = new \App\Repositories\TorrentRepository();
$decrypted = $torrentRep->checkTrackerReportAuthKey($authkey);
if (empty($decrypted)) {
throw new \InvalidArgumentException("Invalid authkey: $authkey");
}
$userInfo = \Nexus\Database\NexusDB::remember("announce_user_passkey_$uid", 3600, function () use ($uid) {
return \App\Models\User::query()->where('id', $uid)->first(['id', 'passkey']);
});
return $userInfo->passkey;
});
}
function executeCommand($command, $format = 'string', $artisan = false, $exception = true): string|array
{
$append = " 2>&1";
if (!str_ends_with($command, $append)) {
$command .= $append;
}
if ($artisan) {
$phpPath = nexus_env('PHP_PATH') ?: 'php';
$webRoot = rtrim(ROOT_PATH, '/');
$command = "$phpPath $webRoot/artisan $command";
}
do_log("command: $command");
$result = exec($command, $output, $result_code);
$outputString = implode("\n", $output);
$log = sprintf('result_code: %s, result: %s, output: %s', $result_code, $result, $outputString);
if ($result_code != 0) {
do_log($log, "error");
if ($exception) {
throw new \RuntimeException($outputString);
}
} else {
do_log($log);
}
return $format == 'string' ? $outputString : $output;
}
function has_role_work_seeding($uid)
{
$result = apply_filter('user_has_role_work_seeding', false, $uid);
do_log("uid: $uid, result: $result");
return $result;
}
function is_danger_url($url): bool
{
$dangerScriptsPattern = "/(logout|login|ajax|announce|scrape|adduser|modtask|take.*)\.php/i";
$match = preg_match($dangerScriptsPattern, $url);
if ($match > 0) {
return true;
}
return false;
}
//here must retrieve the real time info, no cache!!!
function get_snatch_info($torrentId, $userId)
{
return mysql_fetch_assoc(sql_query(sprintf('select * from snatched where torrentid = %s and userid = %s order by id desc limit 1', $torrentId, $userId)));
}
/**
* 完整的 Laravel 事件, 在 php 端有监听者的需要触发. 同样会执行 publish_model_event()
*/
function fire_event(string $name, \Illuminate\Database\Eloquent\Model $model, ?\Illuminate\Database\Eloquent\Model $oldModel = null): void
{
if (!isset(\App\Enums\ModelEventEnum::$eventMaps[$name])) {
throw new \InvalidArgumentException("Event $name is not a valid event enumeration");
}
if (IN_NEXUS) {
$prefix = "fire_event:";
$idKey = $prefix . \Illuminate\Support\Str::random();
$idKeyOld = "";
\Nexus\Database\NexusDB::cache_put($idKey, serialize($model->toArray()), 3600*24*30);
if ($oldModel) {
$idKeyOld = $prefix . \Illuminate\Support\Str::random();
\Nexus\Database\NexusDB::cache_put($idKeyOld, serialize($oldModel->toArray()), 3600*24*30);
}
// executeCommand("event:fire --name=$name --idKey=$idKey --idKeyOld=$idKeyOld", "string", true, false);
\Nexus\Nexus::dispatchQueueJob(new \App\Jobs\FireEvent($name, $idKey, $idKeyOld));
do_log("success fire_event in nexus, name: $name, idKey: $idKey, idKeyOld: $idKeyOld");
} else {
$eventClass = \App\Enums\ModelEventEnum::$eventMaps[$name]['event'];
if (str_ends_with($name, '_deleted')) {
//if deleted from database, can not pass model instance, use array
$params = [$model->toArray()];
if ($oldModel) {
$params[] = $oldModel->toArray();
}
} else {
$params = [$model];
if ($oldModel) {
$params[] = $oldModel;
}
}
call_user_func_array([$eventClass, "dispatch"], $params);
publish_model_event($name, $model->id, $model->toJson());
do_log("success fire_event in laravel, name: $name, id: $model->id, oldId: " . ($oldModel ? $oldModel->id : ""));
}
}
/**
* 仅仅是往 redis 发布事件, php 端无监听者仅在其他平台有需要的触发这个即可, 较轻量
*/
function publish_model_event(string $event, int $id, string $json = ""): void
{
$channel = nexus_env("CHANNEL_NAME_MODEL_EVENT");
if (!empty($channel)) {
\Nexus\Database\NexusDB::redis()->publish($channel, json_encode(["event" => $event, "id" => $id, "json" => $json]));
} else {
do_log("event: $event, id: $id, channel: $channel, channel is empty!", "error");
}
}
function convertNamespaceToSnake(string $str): string
{
return str_replace(["\\", "::"], ["_", "."], $str);
}
function get_user_locale(int $uid): string
{
$sql = "select language.site_lang_folder from users inner join language on users.lang = language.id where users.id = $uid limit 1";
$result = \Nexus\Database\NexusDB::select($sql);
if (empty($result) || empty($result[0]['site_lang_folder'])) {
return "en";
}
return \App\Http\Middleware\Locale::$languageMaps[$result[0]['site_lang_folder']] ?? $result[0]['site_lang_folder'];
}
function send_admin_success_notification(string $msg = ""): void {
\Filament\Notifications\Notification::make()->success()->title($msg ?: "Success!")->send();
}
function send_admin_fail_notification(string $msg = ""): void {
\Filament\Notifications\Notification::make()->danger()->title($msg ?: "Fail!")->send();
}
function ability(\App\Enums\Permission\RoutePermissionEnum $permission): string {
return sprintf("ability:%s", $permission->value);
}
function get_challenge_key(string $challenge): string {
return "challenge:".$challenge;
}
function get_user_from_cookie(array $cookie, $isArray = true): array|\App\Models\User|null {
$log = "cookie: " . json_encode($cookie);
$result = get_user_id_and_signature_from_cookie($cookie);
if (empty($result)) {
return null;
}
$id = $result['user_id'];
$tokenJson = $result['token_json'];
$signature = $result['signature'];
$log .= ", uid = $id";
if ($isArray) {
$res = sql_query("SELECT * FROM users WHERE users.id = ".sqlesc($id)." AND users.enabled='yes' AND users.status = 'confirmed' LIMIT 1");
$row = mysql_fetch_array($res);
if (!$row) {
do_log("$log, user not exists");
return null;
}
$authKey = $row["auth_key"];
unset($row['auth_key'], $row['passhash']);
} else {
$row = \App\Models\User::query()->find($id);
if (!$row) {
do_log("$log, user not exists");
return null;
}
$row->checkIsNormal();
$authKey = $row->auth_key;
}
$expectedSignature = hash_hmac('sha256', $tokenJson, $authKey);
if (!hash_equals($expectedSignature, $signature)) {
do_log("$log, !hash_equals, expectedSignature: $expectedSignature, actualSignature: $signature");
return null;
}
return $row;
}
function get_user_id_and_signature_from_cookie(array $cookie): array|null
{
$log = "cookie: " . json_encode($cookie);
if (empty($cookie["c_secure_pass"])) {
do_log("$log, param not enough");
return null;
}
$base64Decoded = base64_decode($cookie["c_secure_pass"]);
if (empty($base64Decoded)) {
do_log("$log, invalid c_secure_pass");
return null;
}
$log .= ", base64 decoded: " . $base64Decoded;
$tokenJsonAndSignature = explode(".", $base64Decoded);
if (count($tokenJsonAndSignature) != 2) {
do_log("$log, invalid c_secure_pass base64_decoded");
return null;
}
$tokenJson = $tokenJsonAndSignature[0];
$signature = $tokenJsonAndSignature[1];
if (empty($tokenJson) || empty($signature)) {
do_log("$log, no tokenJson or signature");
return null;
}
$tokenData = json_decode($tokenJson, true);
if (!isset($tokenData['user_id'])) {
do_log("$log, no user_id");
return null;
}
if (!isset($tokenData['expires']) || $tokenData['expires'] < time()) {
do_log("$log, signature expired");
return null;
}
return [
"user_id" => $tokenData['user_id'],
'token_json' => $tokenJson,
'signature' => $signature,
];
}
function render_password_hash_js(string $formId, string $passwordOriginalClass, string $passwordHashedName, bool $passwordRequired, string $passwordConfirmClass = "password_confirmation", string $usernameName = "username"): void {
$tipTooShort = nexus_trans('signup.password_too_short');
$tipTooLong = nexus_trans('signup.password_too_long');
$tipEqualUsername = nexus_trans('signup.password_equals_username');
$tipNotMatch = nexus_trans('signup.passwords_unmatched');
$passwordValidateJS = "";
if ($passwordRequired) {
$passwordValidateJS = <<<JS
if (password.length < 6) {
layer.alert("$tipTooShort")
return
}
if (password.length > 40) {
layer.alert("$tipTooLong")
return
}
JS;
}
$formVar = "jqForm" . md5($formId);
$js = <<<JS
var $formVar = jQuery("#{$formId}");
$formVar.on("click", "input[type=button]", function() {
let jqUsername = $formVar.find("[name={$usernameName}]")
let jqPassword = $formVar.find(".{$passwordOriginalClass}")
let jqPasswordConfirm = $formVar.find(".{$passwordConfirmClass}")
let password = jqPassword.val()
$passwordValidateJS
if (jqUsername.length > 0 && jqUsername.val() === password) {
layer.alert("$tipEqualUsername")
return
}
if (jqPasswordConfirm.length > 0 && password !== jqPasswordConfirm.val()) {
layer.alert("$tipNotMatch")
return
}
if (password !== "") {
const passwordHashed = sha256(password)
$formVar.find("input[name={$passwordHashedName}]").val(passwordHashed)
$formVar.submit()
} else {
$formVar.submit()
}
})
JS;
\Nexus\Nexus::js("js/crypto-js.js", 'footer', true);
\Nexus\Nexus::js($js, 'footer', false);
}
function render_password_challenge_js(string $formId, string $usernameName, string $passwordOriginalClass): void {
$formVar = "jqForm" . md5($formId);
$js = <<<JS
var $formVar = jQuery("#{$formId}");
$formVar.on("click", "input[type=button]", function() {
let useChallengeResponseAuthentication = $formVar.find("input[name=response]").length > 0
if (!useChallengeResponseAuthentication) {
return $formVar.submit()
}
let jqUsername = $formVar.find("[name={$usernameName}]")
let jqPassword = $formVar.find(".{$passwordOriginalClass}")
let username = jqUsername.val()
let password = jqPassword.val()
login(username, password, $formVar)
})
async function login(username, password, jqForm) {
try {
jQuery('body').loading({stoppable: false});
const challengeResponse = await fetch('/api/challenge', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username: username })
});
jQuery('body').loading('stop');
const challengeData = await challengeResponse.json();
if (challengeData.ret !== 0) {
layer.alert(challengeData.msg)
return
}
const clientHashedPassword = sha256(password);
const serverSideHash = sha256(challengeData.data.secret + clientHashedPassword);
const clientResponse = hmacSha256(challengeData.data.challenge, serverSideHash);
jqForm.find("input[name=response]").val(clientResponse)
jqForm.submit()
} catch (error) {
console.error(error);
layer.alert(error.toString())
}
}
JS;
\Nexus\Nexus::js("vendor/jquery-loading/jquery.loading.min.js", 'footer', true);
\Nexus\Nexus::js("js/crypto-js.js", 'footer', true);
\Nexus\Nexus::js($js, 'footer', false);
}