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");
}
function printLine($line, $exist = false)
{
echo "[" . date('Y-m-d H:i:s') . "] $line
";
if ($exist) {
exit(0);
}
}
function nexus_dd($vars)
{
echo '
';
array_map(function ($var) {
var_dump($var);
}, func_get_args());
echo '';
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);
$content = sprintf(
"[%s] [%s] [%s] [%s] [%s] [%s] %s.%s %s:%s %s%s%s %s%s",
date('Y-m-d H:i:s'),
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];
}
$config = nexus_config('nexus');
$path = getenv('NEXUS_LOG_DIR', true);
$fromEnv = true;
if ($path === false) {
$fromEnv = false;
$path = sys_get_temp_dir();
}
$logFile = rtrim($path, '/') . '/nexus.log';
if (!$fromEnv && !empty($config['log_file'])) {
$logFile = $config['log_file'];
}
$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";
}
$logFile = sprintf('%s-%s%s', $name, date('Y-m-d'), $suffix);
return $logFiles[$append] = $logFile;
}
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();
$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\ResourceCollection || $data instanceof \Illuminate\Http\Resources\Json\JsonResource) {
$data = $data->response()->getData(true);
if (isset($data['data']) && count($data) == 1) {
//单纯的集合,无分页等其数据
$data = $data['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 '';
}
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($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;
}
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 = "" . $ratio . "";
} 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'])->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): bool
{
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;
}
$row = \Nexus\Database\NexusDB::getOne("seed_box_records", "asn = $asn", "id");
return !empty($row);
} catch (\Throwable $throwable) {
do_log("ip: $ip, error: " . $throwable->getMessage(), "error");
return false;
}
}
function isIPSeedBox($ip, $uid, $withoutCache = false): bool
{
$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);
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");
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");
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
}
}
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);
}
}
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).$lang_functions['std_or_above_can_view'],false);
} else {
stderr($lang_functions['std_error'], $lang_functions['std_permission_denied']);
}
}
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'));
}
/**
* @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;
}
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
{
$prefix = "fire_event:";
$idKey = $prefix . \Illuminate\Support\Str::random();
$idKeyOld = "";
\Nexus\Database\NexusDB::cache_put($idKey, serialize($model), 3600*24*30);
if ($oldModel) {
$idKeyOld = $prefix . \Illuminate\Support\Str::random();
\Nexus\Database\NexusDB::cache_put($idKeyOld, serialize($oldModel), 3600*24*30);
}
executeCommand("event:fire --name=$name --idKey=$idKey --idKeyOld=$idKeyOld", "string", true, false);
}
/**
* 仅仅是往 redis 发布事件, php 端无监听者仅在其他平台有需要的触发这个即可, 较轻量
*/
function publish_model_event(string $event, int $id): void
{
$channel = nexus_env("CHANNEL_NAME_MODEL_EVENT");
if (!empty($channel)) {
\Nexus\Database\NexusDB::redis()->publish($channel, json_encode(["event" => $event, "id" => $id]));
} else {
do_log("event: $event, id: $id, channel: $channel, channel is empty!", "error");
}
}
function convertNamespaceToSnake(string $str): string
{
return str_replace(["\\", "::"], ["_", "."], $str);
}