mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-03 06:00:55 +08:00
API: torrents upload/list
This commit is contained in:
@@ -3,16 +3,65 @@
|
||||
namespace App\Auth;
|
||||
|
||||
use App\Enums\Permission\PermissionEnum;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class Permission
|
||||
{
|
||||
public static function canUploadToSpecialSection(): bool
|
||||
{
|
||||
return user_can(PermissionEnum::UPLOAD_TO_SPECIAL_SECTION->value);
|
||||
return self::canUploadToNormalSection() && user_can(PermissionEnum::UPLOAD_TO_SPECIAL_SECTION->value);
|
||||
}
|
||||
|
||||
public static function canUploadToNormalSection(): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
return $user->uploadpos == "yes" && user_can(PermissionEnum::UPLOAD->value);
|
||||
}
|
||||
|
||||
public static function canViewSpecialSection(): bool
|
||||
{
|
||||
return user_can(PermissionEnum::TORRENT_VIEW_SPECIAL->value);
|
||||
}
|
||||
|
||||
public static function canBeAnonymous(): bool
|
||||
{
|
||||
return user_can(PermissionEnum::BE_ANONYMOUS->value);
|
||||
}
|
||||
|
||||
public static function canSetTorrentHitAndRun(): bool
|
||||
{
|
||||
return user_can(PermissionEnum::TORRENT_SET_HR->value);
|
||||
}
|
||||
|
||||
public static function canSetTorrentPrice(): bool
|
||||
{
|
||||
return user_can(PermissionEnum::TORRENT_SET_PRICE->value);
|
||||
}
|
||||
|
||||
public static function canSetTorrentPosState(): bool
|
||||
{
|
||||
return user_can(PermissionEnum::TORRENT_SET_STICKY->value);
|
||||
}
|
||||
|
||||
public static function canTorrentApprovalAllowAutomatic(): bool
|
||||
{
|
||||
return user_can(PermissionEnum::TORRENT_APPROVAL_ALLOW_AUTOMATIC->value);
|
||||
}
|
||||
|
||||
public static function canManageTorrent(): bool
|
||||
{
|
||||
return user_can(PermissionEnum::TORRENT_MANAGE->value);
|
||||
}
|
||||
|
||||
public static function canPickTorrent(): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
return $user->picker == "yes" && self::canManageTorrent() || $user->class >= User::CLASS_SYSOP;
|
||||
}
|
||||
|
||||
public static function canSetTorrentSpecialTag(): bool
|
||||
{
|
||||
return user_can(PermissionEnum::TORRENT_SET_SPECIAL_TAG->value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Events\NewsCreated;
|
||||
use App\Events\TorrentCreated;
|
||||
use App\Events\TorrentDeleted;
|
||||
use App\Events\TorrentUpdated;
|
||||
use App\Events\UserCreated;
|
||||
use App\Events\UserDestroyed;
|
||||
use App\Events\UserDisabled;
|
||||
use App\Events\UserEnabled;
|
||||
use App\Events\UserUpdated;
|
||||
use App\Models\News;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use App\Enums\ModelEventEnum;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Nexus\Database\NexusDB;
|
||||
@@ -35,20 +24,6 @@ class FireEvent extends Command
|
||||
*/
|
||||
protected $description = 'Fire an event, options: --name, --idKey --idKeyOld';
|
||||
|
||||
protected array $eventMaps = [
|
||||
"torrent_created" => ['event' => TorrentCreated::class, 'model' => Torrent::class],
|
||||
"torrent_updated" => ['event' => TorrentUpdated::class, 'model' => Torrent::class],
|
||||
"torrent_deleted" => ['event' => TorrentDeleted::class, 'model' => Torrent::class],
|
||||
|
||||
"user_created" => ['event' => UserCreated::class, 'model' => User::class],
|
||||
"user_destroyed" => ['event' => UserDestroyed::class, 'model' => User::class],
|
||||
"user_disabled" => ['event' => UserDisabled::class, 'model' => User::class],
|
||||
"user_enabled" => ['event' => UserEnabled::class, 'model' => User::class],
|
||||
"user_updated" => ['event' => UserUpdated::class, 'model' => User::class],
|
||||
|
||||
"news_created" => ['event' => NewsCreated::class, 'model' => News::class],
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
@@ -60,8 +35,8 @@ class FireEvent extends Command
|
||||
$idKey = $this->option('idKey');
|
||||
$idKeyOld = $this->option('idKeyOld');
|
||||
$log = "FireEvent, name: $name, idKey: $idKey, idKeyOld: $idKeyOld";
|
||||
if (isset($this->eventMaps[$name])) {
|
||||
$eventName = $this->eventMaps[$name]['event'];
|
||||
if (isset(ModelEventEnum::$eventMaps[$name])) {
|
||||
$eventName = ModelEventEnum::$eventMaps[$name]['event'];
|
||||
$model = unserialize(NexusDB::cache_get($idKey));
|
||||
if ($model instanceof Model) {
|
||||
$params = [$model];
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\PersonalAccessToken;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use App\Repositories\UploadRepository;
|
||||
use Illuminate\Console\Command;
|
||||
use NexusPlugin\Menu\Filament\MenuItemResource\Pages\ManageMenuItems;
|
||||
use NexusPlugin\Menu\MenuRepository;
|
||||
@@ -15,6 +18,7 @@ use NexusPlugin\StickyPromotion\Models\StickyPromotionParticipator;
|
||||
use NexusPlugin\Tracker\TrackerRepository;
|
||||
use NexusPlugin\Work\Models\RoleWork;
|
||||
use NexusPlugin\Work\WorkRepository;
|
||||
use Stichoza\GoogleTranslate\GoogleTranslate;
|
||||
|
||||
class Test extends Command
|
||||
{
|
||||
@@ -49,8 +53,12 @@ class Test extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$result = getLogFile();
|
||||
dd($result);
|
||||
$a = ['acb' => 2];
|
||||
|
||||
if ($a = isset($a['ab'])) {
|
||||
$this->info("isset ab = true");
|
||||
}
|
||||
dd($a);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
214
app/Console/Commands/TranslateLang.php
Normal file
214
app/Console/Commands/TranslateLang.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Stichoza\GoogleTranslate\GoogleTranslate;
|
||||
|
||||
class TranslateLang extends Command
|
||||
{
|
||||
protected $signature = 'lang:translate {source} {target}
|
||||
{filename? : Optional file to translate (php file or json)}
|
||||
{--dry-run : Only print translations without writing files}
|
||||
{--ignore= : Comma-separated keys to ignore (e.g. key1,key2)}
|
||||
{--json : Also translate JSON language files}';
|
||||
|
||||
protected $description = 'Translate Laravel language files (PHP or JSON) using Google Translate';
|
||||
|
||||
protected $tr;
|
||||
protected $ignoreKeys = [];
|
||||
protected $cache = [];
|
||||
protected $cachePath;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$source = $this->argument('source');
|
||||
$target = $this->argument('target');
|
||||
$filename = $this->argument('filename');
|
||||
$this->ignoreKeys = array_filter(explode(',', $this->option('ignore')));
|
||||
$this->cachePath = storage_path("framework/lang-translate-cache.{$source}.{$target}.json");
|
||||
|
||||
$this->loadCache();
|
||||
|
||||
$this->tr = new GoogleTranslate();
|
||||
$this->tr->setSource($source);
|
||||
$this->tr->setTarget($target);
|
||||
|
||||
$langPath = resource_path('lang');
|
||||
|
||||
//谷歌使用的是 -, 本地使用 _
|
||||
$source = str_replace("-", "_", $source);
|
||||
$target = str_replace("-", "_", $target);
|
||||
|
||||
$dir = "{$langPath}/{$source}";
|
||||
if ($filename) {
|
||||
// 👇 指定具体文件翻译
|
||||
$this->translateSpecificFile($filename, $source, $target);
|
||||
} else {
|
||||
// 👇 未指定时,用户确认是否翻译所有文件
|
||||
$answer = $this->ask("你没有指定文件名,是否翻译目录 $dir 下所有语言文件?请输入 yes 确认");
|
||||
if (strtolower($answer) === 'yes') {
|
||||
foreach (File::files("{$langPath}/{$source}") as $file) {
|
||||
if ($file->getExtension() === 'php') {
|
||||
$this->translatePhpFile($file->getPathname(), $source, $target);
|
||||
}
|
||||
}
|
||||
if ($this->option('json')) {
|
||||
$jsonFile = "{$langPath}/{$source}.json";
|
||||
if (file_exists($jsonFile)) {
|
||||
$this->translateJsonFile($jsonFile, $source, $target);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 👇 用户的输入被当作 filename 处理
|
||||
$this->translateSpecificFile($answer, $source, $target);
|
||||
}
|
||||
}
|
||||
|
||||
$this->saveCache();
|
||||
|
||||
$this->info("🎉 $source => $target Done !");
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function translatePhpFile($sourceFile, $sourceLang, $targetLang)
|
||||
{
|
||||
$relativePath = basename($sourceFile);
|
||||
$targetFile = resource_path("lang/{$targetLang}/{$relativePath}");
|
||||
|
||||
$data = require $sourceFile;
|
||||
$translated = $this->translateArray($data);
|
||||
|
||||
$export = var_export($translated, true);
|
||||
if ($this->option('dry-run')) {
|
||||
$this->line("🔍 Would write to: $targetFile\n$export\n");
|
||||
} else {
|
||||
if (!file_exists(dirname($targetFile))) {
|
||||
mkdir(dirname($targetFile), 0755, true);
|
||||
}
|
||||
file_put_contents($targetFile, "<?php\n\nreturn $export;\n");
|
||||
$this->info("✅ Wrote translated file: $targetFile");
|
||||
}
|
||||
}
|
||||
|
||||
protected function translateJsonFile($jsonFile, $sourceLang, $targetLang)
|
||||
{
|
||||
$targetFile = resource_path("lang/{$targetLang}.json");
|
||||
$content = json_decode(file_get_contents($jsonFile), true);
|
||||
$translated = [];
|
||||
|
||||
foreach ($content as $key => $value) {
|
||||
if (in_array($key, $this->ignoreKeys)) {
|
||||
$translated[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$translated[$key] = $this->translateText($value);
|
||||
}
|
||||
|
||||
$pretty = $this->json_encode_pretty($translated);
|
||||
|
||||
if ($this->option('dry-run')) {
|
||||
$this->line("🔍 Would write to: $targetFile\n$pretty\n");
|
||||
} else {
|
||||
file_put_contents($targetFile, $pretty);
|
||||
$this->info("✅ Wrote translated JSON: $targetFile");
|
||||
}
|
||||
}
|
||||
|
||||
protected function translateArray(array $data)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($data as $key => $value) {
|
||||
if (in_array($key, $this->ignoreKeys)) {
|
||||
$result[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$result[$key] = $this->translateArray($value);
|
||||
} else {
|
||||
$result[$key] = $this->translateText($value);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function translateText(string $text): string
|
||||
{
|
||||
if (isset($this->cache[$text])) {
|
||||
$this->line("⚡️ Cached: $text => {$this->cache[$text]}");
|
||||
return $this->cache[$text];
|
||||
}
|
||||
|
||||
try {
|
||||
$translated = $this->tr->translate($text);
|
||||
$this->cache[$text] = $translated;
|
||||
$this->line("🌍 $text => $translated");
|
||||
return $translated;
|
||||
} catch (\Exception $e) {
|
||||
$this->warn("❌ Failed to translate: $text");
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
protected function translateSpecificFile($filename, $source, $target)
|
||||
{
|
||||
$langPath = resource_path("lang");
|
||||
|
||||
if (str_ends_with($filename, '.json')) {
|
||||
$jsonPath = "{$langPath}/{$filename}";
|
||||
if (!file_exists($jsonPath)) {
|
||||
$jsonPath = "{$langPath}/{$source}.json";
|
||||
}
|
||||
if (file_exists($jsonPath)) {
|
||||
$this->translateJsonFile($jsonPath, $source, $target);
|
||||
} else {
|
||||
$this->error("❌ JSON 文件未找到:$filename");
|
||||
}
|
||||
} else {
|
||||
$phpPath = "{$langPath}/{$source}/$filename";
|
||||
if (!str_ends_with($filename, '.php')) {
|
||||
$phpPath .= '.php';
|
||||
}
|
||||
if (file_exists($phpPath)) {
|
||||
$this->translatePhpFile($phpPath, $source, $target);
|
||||
} else {
|
||||
$this->error("❌ PHP 语言文件未找到:$filename");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function loadCache()
|
||||
{
|
||||
if (file_exists($this->cachePath)) {
|
||||
$this->cache = json_decode(file_get_contents($this->cachePath), true);
|
||||
}
|
||||
}
|
||||
|
||||
protected function saveCache()
|
||||
{
|
||||
if (!$this->option('dry-run')) {
|
||||
file_put_contents($this->cachePath, json_encode($this->cache, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
|
||||
protected function json_encode_pretty($data, int $indentSize = 4): string
|
||||
{
|
||||
// 默认格式化(PHP 默认是 2 空格)
|
||||
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// 自定义缩进
|
||||
$indentChar = str_repeat(' ', $indentSize);
|
||||
|
||||
// 将默认的 2 空格缩进替换为自定义缩进
|
||||
$formatted = preg_replace_callback('/^( +)/m', function ($matches) use ($indentChar) {
|
||||
$level = strlen($matches[1]) / 2;
|
||||
return str_repeat($indentChar, (int)$level);
|
||||
}, $json);
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
}
|
||||
44
app/Enums/ModelEventEnum.php
Normal file
44
app/Enums/ModelEventEnum.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Events\NewsCreated;
|
||||
use App\Events\TorrentCreated;
|
||||
use App\Events\TorrentDeleted;
|
||||
use App\Events\TorrentUpdated;
|
||||
use App\Events\UserCreated;
|
||||
use App\Events\UserDeleted;
|
||||
use App\Events\UserDisabled;
|
||||
use App\Events\UserEnabled;
|
||||
use App\Events\UserUpdated;
|
||||
use App\Models\News;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
|
||||
final class ModelEventEnum {
|
||||
const TORRENT_CREATED = 'torrent_created';
|
||||
const TORRENT_UPDATED = 'torrent_updated';
|
||||
const TORRENT_DELETED = 'torrent_deleted';
|
||||
|
||||
const USER_CREATED = 'user_created';
|
||||
const USER_UPDATED = 'user_updated';
|
||||
const USER_DELETED = 'user_deleted';
|
||||
const USER_ENABLED = 'user_enabled';
|
||||
const USER_DISABLED = 'user_disabled';
|
||||
|
||||
const NEWS_CREATED = 'news_created';
|
||||
|
||||
public static array $eventMaps = [
|
||||
self::TORRENT_CREATED => ['event' => TorrentCreated::class, 'model' => Torrent::class],
|
||||
self::TORRENT_UPDATED => ['event' => TorrentUpdated::class, 'model' => Torrent::class],
|
||||
self::TORRENT_DELETED => ['event' => TorrentDeleted::class, 'model' => Torrent::class],
|
||||
|
||||
self::USER_CREATED => ['event' => UserCreated::class, 'model' => User::class],
|
||||
self::USER_UPDATED => ['event' => UserUpdated::class, 'model' => User::class],
|
||||
self::USER_DELETED => ['event' => UserDeleted::class, 'model' => User::class],
|
||||
self::USER_ENABLED => ['event' => UserEnabled::class, 'model' => User::class],
|
||||
self::USER_DISABLED => ['event' => UserDisabled::class, 'model' => User::class],
|
||||
|
||||
self::NEWS_CREATED => ['event' => NewsCreated::class, 'model' => News::class],
|
||||
];
|
||||
}
|
||||
@@ -7,5 +7,15 @@ enum PermissionEnum: string {
|
||||
case BE_ANONYMOUS = 'beanonymous';
|
||||
|
||||
case TORRENT_LIST = 'torrent:list';
|
||||
case TORRENT_VIEW = 'torrent:view';
|
||||
case TORRENT_VIEW_SPECIAL = 'view_special_torrent';
|
||||
case TORRENT_SET_HR = 'torrent_hr';
|
||||
case TORRENT_SET_PRICE = 'torrent-set-price';
|
||||
case TORRENT_SET_STICKY = 'torrentsticky';
|
||||
case TORRENT_MANAGE = 'torrentmanage';
|
||||
case TORRENT_APPROVAL_ALLOW_AUTOMATIC = 'torrent-approval-allow-automatic';
|
||||
case TORRENT_SET_SPECIAL_TAG = 'torrent-set-special-tag';
|
||||
case UPLOAD = 'upload';
|
||||
|
||||
case USER_VIEW = "user:view";
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UserDestroyed
|
||||
class UserDeleted
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
@@ -101,6 +101,9 @@ class Handler extends ExceptionHandler
|
||||
if (config('app.debug')) {
|
||||
$data['trace'] = $trace;
|
||||
}
|
||||
if ($e instanceof \Error || $e instanceof \ErrorException) {
|
||||
do_log(sprintf(get_class($e) . ": %s, trace: %s", $msg, $e->getTraceAsString()), "error");
|
||||
}
|
||||
return new JsonResponse(
|
||||
fail($msg, $data),
|
||||
$httpStatusCode,
|
||||
|
||||
@@ -15,6 +15,7 @@ use Filament\Tables\Table;
|
||||
use Filament\Tables;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class SectionResource extends Resource
|
||||
{
|
||||
@@ -59,8 +60,13 @@ class SectionResource extends Resource
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->label(__('label.search_box.name'))
|
||||
->rules('alpha_dash')
|
||||
->required()
|
||||
->rules(function ($record) {
|
||||
return [
|
||||
'required',
|
||||
'alpha_dash:ascii',
|
||||
Rule::unique('searchbox', 'name')->ignore($record?->id)
|
||||
];
|
||||
})
|
||||
,
|
||||
Forms\Components\TextInput::make('catsperrow')
|
||||
->label(__('label.search_box.catsperrow'))
|
||||
|
||||
@@ -4,25 +4,34 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\InsufficientPermissionException;
|
||||
use App\Models\Setting;
|
||||
use App\Utils\ApiQueryBuilder;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Http\Resources\MissingValue;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
|
||||
/**
|
||||
* @OA\Info(
|
||||
* title="NexusPHP API",
|
||||
* version="1.0"
|
||||
* )
|
||||
*/
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
|
||||
public function success($data, $msg = null)
|
||||
protected ?array $extraFields = null;
|
||||
protected ?array $extraSettingNames = null;
|
||||
|
||||
/**
|
||||
* 返回成功信息,这里是旧方法,大部分情况下 $data 已经是 JsonResource
|
||||
* 但很多地方有使用,历史原因保留不动,尽量使用 successJsonResource
|
||||
*
|
||||
* @param $data
|
||||
* @param $msg
|
||||
* @return array
|
||||
*/
|
||||
public function success($data, $msg = null): array
|
||||
{
|
||||
if (is_null($msg)) {
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
@@ -32,6 +41,34 @@ class Controller extends BaseController
|
||||
return success($msg, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回成功信息,对于不是 JsonResource 的数据,进行包装。返回的数据在 data.data 中
|
||||
*
|
||||
* @param $data
|
||||
* @param $msg
|
||||
* @return array
|
||||
*/
|
||||
public function successJsonResource($data, $msg = null): array
|
||||
{
|
||||
if (is_null($msg)) {
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
$caller = $backtrace[1];
|
||||
$msg = $this->getReturnMsg($caller);
|
||||
}
|
||||
if ($data instanceof JsonResource) {
|
||||
return $this->success($data, $msg);
|
||||
}
|
||||
$resource = new JsonResource($data);
|
||||
return $this->success($resource, $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回失败信息,目前对于失败信息不需要包装
|
||||
*
|
||||
* @param $data
|
||||
* @param $msg
|
||||
* @return array
|
||||
*/
|
||||
public function fail($data, $msg = null)
|
||||
{
|
||||
if (is_null($msg)) {
|
||||
@@ -78,6 +115,31 @@ class Controller extends BaseController
|
||||
return [$perPage, ['*'], 'page', $page];
|
||||
}
|
||||
|
||||
protected function hasExtraField($field): bool
|
||||
{
|
||||
if ($this->extraFields === null) {
|
||||
$extraFieldsStr = request()->input("extra_fields", '');
|
||||
$this->extraFields = explode(',', $extraFieldsStr);
|
||||
}
|
||||
do_log(sprintf("field: %s, extraFields: %s", $field, json_encode($this->extraFields)));
|
||||
return in_array($field, $this->extraFields);
|
||||
}
|
||||
|
||||
protected function appendExtraSettings(array &$additional, array $names): void
|
||||
{
|
||||
if ($this->extraSettingNames === null) {
|
||||
$extraSettingStr = request()->input("extra_settings", '');
|
||||
$this->extraSettingNames = explode(',', $extraSettingStr);
|
||||
}
|
||||
$results = [];
|
||||
foreach ($names as $name) {
|
||||
if (in_array($name, $this->extraSettingNames)) {
|
||||
$results[$name] = get_setting($name);
|
||||
}
|
||||
}
|
||||
if (!empty($results)) {
|
||||
$additional['extra_settings'] = $results;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,14 +27,12 @@ class TokenController extends Controller
|
||||
$user = Auth::user();
|
||||
$count = $user->tokens()->count();
|
||||
if ($count >= 5) {
|
||||
throw new NexusException("Token limit exceeded");
|
||||
throw new NexusException(nexus_trans("token.maximum_allow_number_reached"));
|
||||
}
|
||||
$newAccessToken = $user->createToken($request->name, $request->permissions);
|
||||
PersonalAccessTokenPlain::query()->create([
|
||||
'access_token_id' => $newAccessToken->accessToken->getKey(),
|
||||
'plain_text_token' => $newAccessToken->plainTextToken,
|
||||
]);
|
||||
return $this->success(true);
|
||||
$tokenText = $newAccessToken->plainTextToken;
|
||||
$msg = nexus_trans("token.create_success_tip", ['token' => $tokenText]);
|
||||
return $this->successJsonResource(['token' => $tokenText], $msg);
|
||||
} catch (\Exception $exception) {
|
||||
return $this->fail(false, $exception->getMessage());
|
||||
}
|
||||
@@ -47,11 +45,7 @@ class TokenController extends Controller
|
||||
'id' => 'required|integer',
|
||||
]);
|
||||
$user = Auth::user();
|
||||
$token = $user->tokens()->where("id", $request->id)->first();
|
||||
if ($token) {
|
||||
PersonalAccessTokenPlain::query()->where("access_token_id", $token->id)->delete();
|
||||
$token->delete();
|
||||
}
|
||||
$user->tokens()->where("id", $request->id)->delete();
|
||||
return $this->success(true);
|
||||
} catch (\Exception $exception) {
|
||||
return $this->fail(false, $exception->getMessage());
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\PluginStore;
|
||||
use App\Repositories\ToolRepository;
|
||||
use App\Repositories\UploadRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
@@ -31,9 +32,9 @@ class ToolController extends Controller
|
||||
|
||||
public function test(Request $request)
|
||||
{
|
||||
$rep = new UploadRepository();
|
||||
$result = $rep->listSections();
|
||||
return $result;
|
||||
$result = ['id' => 1];
|
||||
$resource = new JsonResource($result);
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ use App\Models\TorrentDenyReason;
|
||||
use App\Models\TorrentOperationLog;
|
||||
use App\Models\User;
|
||||
use App\Repositories\TorrentRepository;
|
||||
use App\Repositories\UploadRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class TorrentController extends Controller
|
||||
@@ -23,17 +26,10 @@ class TorrentController extends Controller
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
public function index(Request $request, string $section = null)
|
||||
{
|
||||
$params = $request->all();
|
||||
$params['visible'] = Torrent::VISIBLE_YES;
|
||||
$params['category_mode'] = Setting::get('main.browsecat');
|
||||
$result = $this->repository->getList($params, Auth::user());
|
||||
$result = $this->repository->getList($request, Auth::user(), $section);
|
||||
$resource = TorrentResource::collection($result);
|
||||
// $resource->additional([
|
||||
// 'page_title' => nexus_trans('torrent.index.page_title'),
|
||||
// ]);
|
||||
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
@@ -41,18 +37,21 @@ class TorrentController extends Controller
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return array
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
$uploadRep = new UploadRepository();
|
||||
$newTorrent = $uploadRep->upload($request);
|
||||
$resource = new JsonResource(["id" => $newTorrent->id]);
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return array
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
@@ -60,17 +59,15 @@ class TorrentController extends Controller
|
||||
* @var User
|
||||
*/
|
||||
$user = Auth::user();
|
||||
$result = $this->repository->getDetail($id, $user);
|
||||
$isBookmarked = $user->bookmarks()->where('torrentid', $id)->exists();
|
||||
|
||||
$resource = new TorrentResource($result);
|
||||
$resource->additional([
|
||||
// 'page_title' => nexus_trans('torrent.show.page_title'),
|
||||
// 'field_labels' => Torrent::getFieldLabels(),
|
||||
'is_bookmarked' => (int)$isBookmarked,
|
||||
'bonus_reward_values' => Torrent::BONUS_REWARD_VALUES,
|
||||
]);
|
||||
|
||||
$torrent = $this->repository->getDetail($id, $user);
|
||||
$resource = new TorrentResource($torrent);
|
||||
$additional = [];
|
||||
if ($this->hasExtraField('bonus_reward_values')) {
|
||||
$additional['bonus_reward_values'] = Torrent::BONUS_REWARD_VALUES;
|
||||
}
|
||||
$extraSettingsNames = ['torrent.claim_torrent_user_counts_up_limit'];
|
||||
$this->appendExtraSettings($additional, $extraSettingsNames);
|
||||
$resource->additional($additional);
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\SearchBoxResource;
|
||||
use App\Http\Resources\TorrentResource;
|
||||
use App\Repositories\SearchBoxRepository;
|
||||
use App\Repositories\UploadRepository;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -26,10 +27,4 @@ class UploadController extends Controller
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
public function upload(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
return $this->success("OK");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Repositories\UserRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use League\OAuth2\Server\Grant\AuthCodeGrant;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
@@ -64,10 +65,15 @@ class UserController extends Controller
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
public function show($id)
|
||||
public function show($id = null)
|
||||
{
|
||||
$result = $this->repository->getDetail($id);
|
||||
return $this->success($result);
|
||||
$currentUser = Auth::user();
|
||||
if ($id === null) {
|
||||
$id = $currentUser->id;
|
||||
}
|
||||
$result = $this->repository->getDetail($id, $currentUser);
|
||||
$resource = new UserResource($result);
|
||||
return $this->success($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
25
app/Http/Resources/BaseResource.php
Normal file
25
app/Http/Resources/BaseResource.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
abstract class BaseResource extends JsonResource
|
||||
{
|
||||
protected abstract function getResourceName(): string;
|
||||
|
||||
protected function whenIncludeField($field): bool
|
||||
{
|
||||
return $this->whenInclude($field, "include_fields");
|
||||
}
|
||||
|
||||
private function whenInclude($field, $prefix): bool
|
||||
{
|
||||
$fields = request()->input("$prefix." . $this->getResourceName());
|
||||
if (!$fields) {
|
||||
return false;
|
||||
}
|
||||
$fieldsArr = explode(',', $fields);
|
||||
return in_array($field, $fieldsArr);
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,15 @@ class MedalResource extends JsonResource
|
||||
'image_large' => $this->image_large,
|
||||
'image_small' => $this->image_small,
|
||||
'price' => $this->price,
|
||||
'price_human' => number_format($this->price),
|
||||
'duration' => $this->duration,
|
||||
'description' => $this->description,
|
||||
'expire_at' => $this->whenPivotLoaded('user_medals', function () {return $this->pivot->expire_at;}),
|
||||
'user_medal_id' => $this->whenPivotLoaded('user_medals', function () {return $this->pivot->id;}),
|
||||
'wearing_status' => $this->whenPivotLoaded('user_medals', function () {return $this->pivot->status;}),
|
||||
'wearing_status_text' => $this->whenPivotLoaded('user_medals', function () {
|
||||
return nexus_trans("medal.wearing_status_text." . $this->pivot->status);
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,23 +19,20 @@ class SearchBoxResource extends JsonResource
|
||||
$searchBox = $this->resource;
|
||||
$out = [
|
||||
'id' => $this->id,
|
||||
'name' => $this->displaySectionName,
|
||||
'name' => $this->name,
|
||||
'display_name' => $this->displaySectionName,
|
||||
'categories' => CategoryResource::collection($this->whenLoaded('categories')),
|
||||
'tags' => TagResource::collection($this->whenLoaded('tags')),
|
||||
];
|
||||
if ($searchBox->showsubcat) {
|
||||
$subCategories = [];
|
||||
$lang = get_langfolder_cookie();
|
||||
$fields = array_keys(SearchBox::$taxonomies);
|
||||
if (!empty($searchBox->extra['taxonomy_labels'])) {
|
||||
$fields = array_column($searchBox->extra['taxonomy_labels'], 'torrent_field');
|
||||
}
|
||||
foreach ($fields as $field) {
|
||||
$relationName = "taxonomy_$field";
|
||||
if ($searchBox->relationLoaded($relationName)) {
|
||||
$subCategories[] = [
|
||||
'field' => $field,
|
||||
'label' => $item['display_text'][$lang] ?? (nexus_trans("searchbox.sub_category_{$field}_label") ?: ucfirst($field)),
|
||||
'label' => $searchBox->getTaxonomyLabel($field),
|
||||
'data' => MediaResource::collection($searchBox->{$relationName}),
|
||||
];
|
||||
}
|
||||
|
||||
24
app/Http/Resources/TorrentExtraResource.php
Normal file
24
app/Http/Resources/TorrentExtraResource.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TorrentExtraResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'descr' => $this->descr,
|
||||
'media_info' => $this->media_info,
|
||||
'media_info_summary' => $this->media_info_summary,
|
||||
'nfo' => $this->nfo,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,21 @@
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Attachment;
|
||||
use App\Models\SearchBox;
|
||||
use App\Models\Torrent;
|
||||
use App\Repositories\TorrentRepository;
|
||||
use Carbon\CarbonInterface;
|
||||
use Elasticsearch\Endpoints\Search;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Nexus\Nexus;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TorrentResource extends JsonResource
|
||||
class TorrentResource extends BaseResource
|
||||
{
|
||||
protected $imageTypes = ['image', 'attachment'];
|
||||
const NAME = 'torrent';
|
||||
|
||||
protected static TorrentRepository $torrentRep;
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
@@ -24,72 +31,82 @@ class TorrentResource extends JsonResource
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'filename' => $this->filename,
|
||||
'hash' => preg_replace_callback('/./s', [$this, "hex_esc"], $this->info_hash),
|
||||
'cover' => $this->cover,
|
||||
'small_descr' => $this->small_descr,
|
||||
'comments' => $this->comments,
|
||||
'category' => $this->category,
|
||||
'category_info' => new CategoryResource($this->whenLoaded('basic_category')),
|
||||
'size' => $this->size,
|
||||
'size_human' => mksize($this->size),
|
||||
'added' => $this->added->toDateTimeString(),
|
||||
'added_human' => $this->added->format('Y-m-d H:i'),
|
||||
'ttl' => $this->added->diffForHumans(['syntax' => CarbonInterface::DIFF_ABSOLUTE]),
|
||||
'leechers' => $this->leechers,
|
||||
'seeders' => $this->seeders,
|
||||
'times_completed' => $this->times_completed,
|
||||
'numfiles' => $this->numfiles,
|
||||
'added' => format_datetime($this->added),
|
||||
'added_human' => gettime($this->added),
|
||||
'numfiles' => $this->numfiles ?: 0,
|
||||
'leechers' => $this->leechers ?: 0,
|
||||
'seeders' => $this->seeders ?: 0,
|
||||
'times_completed' => $this->times_completed ?: 0,
|
||||
'views' => $this->views ?: 0,
|
||||
'hits' => $this->hits ?: 0,
|
||||
'comments' => $this->comments ?: 0,
|
||||
'pos_state' => $this->pos_state,
|
||||
'pos_state_until' => format_datetime($this->pos_state_until),
|
||||
'pos_state_until_human' => gettime($this->pos_state_until),
|
||||
'sp_state' => $this->sp_state,
|
||||
'sp_state_real' => $this->sp_state_real,
|
||||
'promotion_info' => $this->promotionInfo,
|
||||
'hr' => $this->hr,
|
||||
'hr' => $this->hr ?: 0,
|
||||
'pick_type' => $this->picktype,
|
||||
'pick_time' => $this->picktime,
|
||||
'pick_info' => $this->pickInfo,
|
||||
'download_url' => $this->download_url,
|
||||
'user' => new UserResource($this->whenLoaded('user')),
|
||||
'anonymous' => $this->anonymous,
|
||||
'basic_category' => new CategoryResource($this->whenLoaded('basic_category')),
|
||||
'last_action' => format_datetime($this->last_action),
|
||||
'last_action_human' => gettime($this->last_action),
|
||||
'thank_users_count' => $this->whenCounted('thank_users'),
|
||||
'reward_logs_count' => $this->whenCounted('reward_logs'),
|
||||
'claims_count' => $this->whenCounted('claims'),
|
||||
'has_bookmarked' => $this->whenHas('has_bookmarked'),
|
||||
'has_claimed' => $this->whenHas('has_claimed'),
|
||||
'has_thanked' => $this->whenHas('has_thanked'),
|
||||
'has_rewarded' => $this->whenHas('has_rewarded'),
|
||||
'description' => $this->whenHas('description'),
|
||||
'images' => $this->whenHas('images'),
|
||||
'download_url' => $this->whenHas('download_url'),
|
||||
'user' => new UserResource($this->whenLoaded('user')),
|
||||
'extra' => new TorrentExtraResource($this->whenLoaded('extra')),
|
||||
'tags' => TagResource::collection($this->whenLoaded('tags')),
|
||||
'thanks' => ThankResource::collection($this->whenLoaded('thanks')),
|
||||
'reward_logs' => RewardResource::collection($this->whenLoaded('reward_logs')),
|
||||
];
|
||||
if ($this->cover) {
|
||||
$cover = $this->cover;
|
||||
} else {
|
||||
$descriptionArr = format_description($this->descr);
|
||||
$cover = get_image_from_description($descriptionArr, true);
|
||||
}
|
||||
$out['cover'] = resize_image($cover, 100, 100);
|
||||
if ($request->routeIs('torrents.show')) {
|
||||
if (!isset($descriptionArr)) {
|
||||
$descriptionArr = format_description($this->descr);
|
||||
$subCategories = [];
|
||||
foreach (SearchBox::$taxonomies as $field => $info) {
|
||||
$relation = "basic_$field";
|
||||
if ($this->resource->{$field} > 0 && $this->resource->relationLoaded($relation)) {
|
||||
$subCategories[$field] = [
|
||||
'label' => $this->resource->getSubCategoryLabel($field),
|
||||
'value' => $this->resource->{$relation}->name ?? '',
|
||||
];
|
||||
}
|
||||
$baseInfo = [
|
||||
['label' => nexus_trans('torrent.show.size'), 'value' => mksize($this->size)],
|
||||
];
|
||||
foreach (Torrent::getBasicInfo() as $relation => $text) {
|
||||
if ($info = $this->whenLoaded($relation)) {
|
||||
$baseInfo[] = ['label' => $text, 'value' => $info->name];
|
||||
}
|
||||
}
|
||||
$out['base_info'] = $baseInfo;
|
||||
|
||||
$out['description'] = $descriptionArr;
|
||||
|
||||
$out['images'] = get_image_from_description($descriptionArr);
|
||||
|
||||
$out['thank_users_count'] = $this->thank_users_count;
|
||||
$out['peers_count'] = $this->peers_count;
|
||||
$out['reward_logs_count'] = $this->reward_logs_count;
|
||||
}
|
||||
if (nexus()->isPlatformAdmin()) {
|
||||
$out['details_url'] = sprintf('%s/details.php?id=%s', getSchemeAndHttpHost(), $this->id);
|
||||
}
|
||||
|
||||
// $out['upload_peers_count'] = $this->upload_peers_count;
|
||||
// $out['download_peers_count'] = $this->download_peers_count;
|
||||
// $out['finish_peers_count'] = $this->finish_peers_count;
|
||||
|
||||
$out['sub_categories'] = empty($subCategories) ? null : $subCategories;
|
||||
return $out;
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected function getResourceName(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
private function getTorrentRep(): TorrentRepository
|
||||
{
|
||||
if (!isset(self::$torrentRep)) {
|
||||
self::$torrentRep = new TorrentRepository();
|
||||
}
|
||||
return self::$torrentRep;
|
||||
}
|
||||
|
||||
protected function hex_esc($matches) {
|
||||
return sprintf("%02x", ord($matches[0]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class UserResource extends JsonResource
|
||||
{
|
||||
const NAME = 'user';
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
@@ -21,7 +22,9 @@ class UserResource extends JsonResource
|
||||
'status' => $this->status,
|
||||
'enabled' => $this->enabled,
|
||||
'added' => format_datetime($this->added),
|
||||
'added_human' => gettime($this->added),
|
||||
'last_access' => format_datetime($this->last_access),
|
||||
'last_access_human' => gettime($this->last_access),
|
||||
'class' => $this->class,
|
||||
'class_text' => $this->class_text,
|
||||
'avatar' => $this->avatar,
|
||||
@@ -31,8 +34,10 @@ class UserResource extends JsonResource
|
||||
'uploaded_text' => mksize($this->uploaded),
|
||||
'downloaded' => $this->downloaded,
|
||||
'downloaded_text' => mksize($this->downloaded),
|
||||
'bonus' => number_format($this->seedbonus, 1),
|
||||
'seed_points' => number_format($this->seed_points, 1),
|
||||
'bonus' => floatval($this->seedbonus),
|
||||
'bonus_human' => number_format($this->seedbonus, 1),
|
||||
'seed_points' => floatval($this->seed_points),
|
||||
'seed_points_human' => number_format($this->seed_points, 1),
|
||||
'seedtime' => $this->seedtime,
|
||||
'seedtime_text' => mkprettytime($this->seedtime),
|
||||
'leechtime' => $this->leechtime,
|
||||
@@ -40,27 +45,27 @@ class UserResource extends JsonResource
|
||||
'inviter' => new UserResource($this->whenLoaded('inviter')),
|
||||
'valid_medals' => MedalResource::collection($this->whenLoaded('valid_medals')),
|
||||
];
|
||||
if ($request->routeIs('user.me')) {
|
||||
$out['downloaded_human'] = mksize($this->downloaded);
|
||||
$out['uploaded_human'] = mksize($this->uploaded);
|
||||
$out['seed_time'] = mkprettytime($this->seedtime);
|
||||
$out['leech_time'] = mkprettytime($this->leechtime);
|
||||
$out['share_ratio'] = get_share_ratio($this->uploaded, $this->downloaded);
|
||||
$out['comments_count'] = $this->comments_count;
|
||||
$out['posts_count'] = $this->posts_count;
|
||||
$out['torrents_count'] = $this->torrents_count;
|
||||
$out['seeding_torrents_count'] = $this->seeding_torrents_count;
|
||||
$out['leeching_torrents_count'] = $this->leeching_torrents_count;
|
||||
$out['completed_torrents_count'] = $this->completed_torrents_count;
|
||||
$out['incomplete_torrents_count'] = $this->incomplete_torrents_count;
|
||||
}
|
||||
if ($request->routeIs("oauth.user_info")) {
|
||||
$out['name'] = $this->username;
|
||||
}
|
||||
|
||||
if (nexus()->isPlatformAdmin() && $request->routeIs('users.show')) {
|
||||
$out['two_step_secret'] = $this->two_step_secret;
|
||||
}
|
||||
// if ($request->routeIs('user.me')) {
|
||||
// $out['downloaded_human'] = mksize($this->downloaded);
|
||||
// $out['uploaded_human'] = mksize($this->uploaded);
|
||||
// $out['seed_time'] = mkprettytime($this->seedtime);
|
||||
// $out['leech_time'] = mkprettytime($this->leechtime);
|
||||
// $out['share_ratio'] = get_share_ratio($this->uploaded, $this->downloaded);
|
||||
// $out['comments_count'] = $this->comments_count;
|
||||
// $out['posts_count'] = $this->posts_count;
|
||||
// $out['torrents_count'] = $this->torrents_count;
|
||||
// $out['seeding_torrents_count'] = $this->seeding_torrents_count;
|
||||
// $out['leeching_torrents_count'] = $this->leeching_torrents_count;
|
||||
// $out['completed_torrents_count'] = $this->completed_torrents_count;
|
||||
// $out['incomplete_torrents_count'] = $this->incomplete_torrents_count;
|
||||
// }
|
||||
// if ($request->routeIs("oauth.user_info")) {
|
||||
// $out['name'] = $this->username;
|
||||
// }
|
||||
//
|
||||
// if (nexus()->isPlatformAdmin() && $request->routeIs('users.show')) {
|
||||
// $out['two_step_secret'] = $this->two_step_secret;
|
||||
// }
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Listeners;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class DeductUserBonusWhenTorrentDeleted
|
||||
class DeductUserBonusWhenTorrentDeleted implements ShouldQueue
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
|
||||
30
app/Listeners/SendEmailNotificationWhenTorrentCreated.php
Normal file
30
app/Listeners/SendEmailNotificationWhenTorrentCreated.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\TorrentCreated;
|
||||
use App\Repositories\UploadRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class SendEmailNotificationWhenTorrentCreated implements ShouldQueue
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(TorrentCreated $event): void
|
||||
{
|
||||
$torrent = $event->model;
|
||||
$uploadRepo = new UploadRepository();
|
||||
$result = $uploadRepo->sendEmailNotification($torrent);
|
||||
do_log("torrent: $torrent->id, sendEmailNotification result: " . var_export($result, true));
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use App\Repositories\ToolRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class SyncTorrentToEs implements ShouldQueue
|
||||
class SyncTorrentToElasticsearch implements ShouldQueue
|
||||
{
|
||||
|
||||
public $tries = 3;
|
||||
@@ -32,6 +32,10 @@ class SyncTorrentToEs implements ShouldQueue
|
||||
public function handle($event)
|
||||
{
|
||||
$id = $event->model?->id ?? 0;
|
||||
if ($id == 0) {
|
||||
do_log("event: " . get_class($event) . " no model id", 'error');
|
||||
return;
|
||||
}
|
||||
$searchRep = new SearchRepository();
|
||||
$result = $searchRep->updateTorrent($id);
|
||||
do_log(sprintf("updateTorrent: %s result: %s", $id, var_export($result, true)));
|
||||
34
app/Listeners/SyncTorrentToMeilisearch.php
Normal file
34
app/Listeners/SyncTorrentToMeilisearch.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\TorrentCreated;
|
||||
use App\Repositories\MeiliSearchRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class SyncTorrentToMeilisearch implements ShouldQueue
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle($event): void
|
||||
{
|
||||
$id = $event->model->id ?? 0;
|
||||
if ($id == 0) {
|
||||
do_log("event: " . get_class($event) . " no model id", 'error');
|
||||
return;
|
||||
}
|
||||
$meiliSearch = new MeiliSearchRepository();
|
||||
$result = $meiliSearch->doImportFromDatabase($id);
|
||||
do_log(sprintf("doImportFromDatabase: %s result: %s", $id, var_export($result, true)));
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ class BonusLogs extends NexusModel
|
||||
const BUSINESS_TYPE_TORRENT_BE_DOWNLOADED = 1001;
|
||||
const BUSINESS_TYPE_RECEIVE_REWARD = 1002;
|
||||
const BUSINESS_TYPE_RECEIVE_GIFT = 1003;
|
||||
const BUSINESS_TYPE_UPLOAD_TORRENT = 1004;
|
||||
|
||||
public static array $businessTypes = [
|
||||
self::BUSINESS_TYPE_CANCEL_HIT_AND_RUN => ['text' => 'Cancel H&R'],
|
||||
@@ -73,6 +74,7 @@ class BonusLogs extends NexusModel
|
||||
self::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => ['text' => 'Torrent be downloaded'],
|
||||
self::BUSINESS_TYPE_RECEIVE_REWARD => ['text' => 'Receive reward'],
|
||||
self::BUSINESS_TYPE_RECEIVE_GIFT => ['text' => 'Receive gift'],
|
||||
self::BUSINESS_TYPE_UPLOAD_TORRENT => ['text' => 'Upload torrent'],
|
||||
];
|
||||
|
||||
public function getBusinessTypeTextAttribute()
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
class PersonalAccessTokenPlain extends NexusModel
|
||||
{
|
||||
protected $fillable = ['access_token_id', 'plain_text_token'];
|
||||
|
||||
public $timestamps = true;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Auth\Permission;
|
||||
use App\Http\Middleware\Locale;
|
||||
use App\Repositories\TagRepository;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
@@ -106,7 +107,9 @@ class SearchBox extends NexusModel
|
||||
$lang = get_langfolder_cookie();
|
||||
foreach ($this->extra[self::EXTRA_TAXONOMY_LABELS] ?? [] as $item) {
|
||||
if ($item['torrent_field'] == $torrentField) {
|
||||
return $item['display_text'][$lang] ?? 'Unknown';
|
||||
if (!empty($item['display_text'][$lang])) {
|
||||
return $item['display_text'][$lang];
|
||||
}
|
||||
}
|
||||
}
|
||||
return nexus_trans("searchbox.sub_category_{$torrentField}_label") ?: ucfirst($torrentField);
|
||||
@@ -214,7 +217,7 @@ class SearchBox extends NexusModel
|
||||
|
||||
public static function isSpecialEnabled(): bool
|
||||
{
|
||||
return Setting::get('main.spsct') == 'yes';
|
||||
return Setting::getIsSpecialSectionEnabled();
|
||||
}
|
||||
|
||||
public static function getBrowseMode()
|
||||
@@ -303,7 +306,12 @@ class SearchBox extends NexusModel
|
||||
|
||||
public function loadTags(): void
|
||||
{
|
||||
$this->setRelation("tags", TagRepository::listAll($this->getKey()));
|
||||
$allTags = TagRepository::listAll($this->getKey());
|
||||
if (!Permission::canSetTorrentSpecialTag()) {
|
||||
$specialTagIdList = Tag::listSpecial();
|
||||
$allTags = $allTags->filter(fn ($item) => !in_array($item->id, $specialTagIdList));
|
||||
}
|
||||
$this->setRelation("tags", $allTags);
|
||||
}
|
||||
|
||||
public static function getDefaultSearchMode()
|
||||
@@ -349,5 +357,4 @@ class SearchBox extends NexusModel
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ class Setting extends NexusModel
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function getDefaultLang()
|
||||
public static function getDefaultLang(): string
|
||||
{
|
||||
return self::get("main.defaultlang");
|
||||
}
|
||||
@@ -110,4 +110,112 @@ class Setting extends NexusModel
|
||||
return self::get("security.use_challenge_response_authentication") == "yes";
|
||||
}
|
||||
|
||||
public static function getUploadTorrentMaxSize(): int
|
||||
{
|
||||
return intval(self::get("main.max_torrent_size"));
|
||||
}
|
||||
|
||||
public static function getUploadTorrentMaxPrice(): int
|
||||
{
|
||||
return intval(self::get("torrent.max_price"));
|
||||
}
|
||||
|
||||
public static function getIsPaidTorrentEnabled(): bool
|
||||
{
|
||||
return self::get("torrent.paid_torrent_enabled") == "yes";
|
||||
}
|
||||
|
||||
public static function getUploadDenyApprovalDenyCount(): int
|
||||
{
|
||||
return intval(self::get("main.upload_deny_approval_deny_count"));
|
||||
}
|
||||
|
||||
public static function getOfferSkipApprovedCount(): int
|
||||
{
|
||||
return intval(self::get("main.offer_skip_approved_count"));
|
||||
}
|
||||
|
||||
public static function getLargeTorrentSize(): int
|
||||
{
|
||||
return intval(self::get("torrent.largesize"));
|
||||
}
|
||||
|
||||
public static function getLargeTorrentSpState(): int
|
||||
{
|
||||
return intval(self::get("torrent.largepro"));
|
||||
}
|
||||
|
||||
public static function getUploadTorrentHalfDownProbability(): int
|
||||
{
|
||||
return intval(self::get("torrent.randomhalfleech"));
|
||||
}
|
||||
|
||||
public static function getUploadTorrentFreeProbability(): int
|
||||
{
|
||||
return intval(self::get("torrent.randomfree"));
|
||||
}
|
||||
|
||||
public static function getUploadTorrentTwoTimesUpProbability(): int
|
||||
{
|
||||
return intval(self::get("torrent.randomtwoup"));
|
||||
}
|
||||
|
||||
public static function getUploadTorrentFreeTwoTimesUpProbability(): int
|
||||
{
|
||||
return intval(self::get("torrent.randomtwoupfree"));
|
||||
}
|
||||
|
||||
public static function getUploadTorrentHalfDownTwoTimesUpProbability(): int
|
||||
{
|
||||
return intval(self::get("torrent.randomtwouphalfdown"));
|
||||
}
|
||||
|
||||
public static function getUploadTorrentOneThirdDownProbability(): int
|
||||
{
|
||||
return intval(self::get("torrent.randomthirtypercentdown"));
|
||||
}
|
||||
|
||||
public static function getUploadTorrentRewardBonus(): int
|
||||
{
|
||||
return intval(self::get("bonus.uploadtorrent"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function getIsUploadOpenAtWeekend(): bool
|
||||
{
|
||||
return self::get("main.sptime") == "yes";
|
||||
}
|
||||
|
||||
public static function getIsSpecialSectionEnabled(): bool
|
||||
{
|
||||
return self::get('main.spsct') == 'yes';
|
||||
}
|
||||
|
||||
public static function getIsAllowUserReceiveEmailNotification(): bool
|
||||
{
|
||||
return self::get('smtp.emailnotify') == 'yes';
|
||||
}
|
||||
|
||||
public static function getBaseUrl(): string
|
||||
{
|
||||
$result = self::get('basic.BASEURL', $_SERVER['HTTP_HOST'] ?? '');
|
||||
return rtrim($result, '/');
|
||||
}
|
||||
|
||||
public static function getSiteName(): string
|
||||
{
|
||||
return self::get("basic.SITENAME");
|
||||
}
|
||||
|
||||
public static function getTorrentSaveDir(): string
|
||||
{
|
||||
return self::get("main.torrent_dir");
|
||||
}
|
||||
|
||||
public static function getSmtpType(): string
|
||||
{
|
||||
return self::get("smtp.smtptype");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
12
app/Models/SiteLog.php
Normal file
12
app/Models/SiteLog.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
class SiteLog extends NexusModel
|
||||
{
|
||||
protected $table = 'sitelog';
|
||||
|
||||
protected $fillable = ['added', 'txt', 'security_level', 'uid'];
|
||||
|
||||
}
|
||||
@@ -13,8 +13,9 @@ class Torrent extends NexusModel
|
||||
'category', 'source', 'medium', 'codec', 'standard', 'processing', 'team', 'audiocodec',
|
||||
'size', 'added', 'type', 'numfiles', 'owner', 'nfo', 'sp_state', 'promotion_time_type',
|
||||
'promotion_until', 'anonymous', 'url', 'pos_state', 'cache_stamp', 'picktype', 'picktime',
|
||||
'last_reseed', 'leechers', 'seeders', 'cover', 'last_action',
|
||||
'last_reseed', 'leechers', 'seeders', 'cover', 'last_action', 'info_hash', 'pieces_hash',
|
||||
'times_completed', 'approval_status', 'banned', 'visible', 'pos_state_until', 'price',
|
||||
'hr',
|
||||
];
|
||||
|
||||
const VISIBLE_YES = 'yes';
|
||||
@@ -27,6 +28,7 @@ class Torrent extends NexusModel
|
||||
'added' => 'datetime',
|
||||
'promotion_until' => 'datetime',
|
||||
'pos_state_until' => 'datetime',
|
||||
'last_action' => 'datetime',
|
||||
];
|
||||
|
||||
public static $commentFields = [
|
||||
@@ -334,15 +336,6 @@ class Torrent extends NexusModel
|
||||
return implode('', $html);
|
||||
}
|
||||
|
||||
public static function getBasicInfo(): array
|
||||
{
|
||||
$result = [];
|
||||
foreach (self::$basicRelations as $relation) {
|
||||
$result[$relation] = nexus_trans("torrent.show.$relation");
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function listPosStates($onlyKeyValue = false, $valueField = 'text'): array
|
||||
{
|
||||
$result = self::$posStates;
|
||||
@@ -382,6 +375,11 @@ class Torrent extends NexusModel
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSubCategoryLabel($field): string
|
||||
{
|
||||
return $this->basic_category->search_box->getTaxonomyLabel($field);
|
||||
}
|
||||
|
||||
public function bookmarks(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Bookmark::class, 'torrentid');
|
||||
@@ -452,7 +450,7 @@ class Torrent extends NexusModel
|
||||
return $this->belongsTo(Source::class, 'source');
|
||||
}
|
||||
|
||||
public function basic_media()
|
||||
public function basic_medium()
|
||||
{
|
||||
return $this->belongsTo(Media::class, 'medium');
|
||||
}
|
||||
@@ -477,11 +475,21 @@ class Torrent extends NexusModel
|
||||
return $this->belongsTo(Team::class, 'team');
|
||||
}
|
||||
|
||||
public function basic_audio_codec()
|
||||
public function basic_audiocodec()
|
||||
{
|
||||
return $this->belongsTo(AudioCodec::class, 'audiocodec');
|
||||
}
|
||||
|
||||
public function claim_users(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'claims', 'torrent_id');
|
||||
}
|
||||
|
||||
public function claims()
|
||||
{
|
||||
return $this->hasMany(Claim::class, 'torrent_id');
|
||||
}
|
||||
|
||||
public function scopeVisible($query, $visible = self::VISIBLE_YES)
|
||||
{
|
||||
$query->where('visible', $visible);
|
||||
|
||||
@@ -3,16 +3,25 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Nexus\Database\NexusDB;
|
||||
use Nexus\Torrent\TechnicalInformation;
|
||||
|
||||
class TorrentExtra extends NexusModel
|
||||
{
|
||||
public $timestamps = true;
|
||||
|
||||
protected $fillable = ['torrent_id', 'descr', 'ori_descr', 'media_info'];
|
||||
protected $fillable = ['torrent_id', 'descr', 'ori_descr', 'media_info', 'nfo'];
|
||||
|
||||
public function torrent()
|
||||
{
|
||||
return $this->belongsTo(Torrent::class, 'torrent_id');
|
||||
}
|
||||
|
||||
protected $appends = ['media_info_summary'];
|
||||
|
||||
public function getMediaInfoSummaryAttribute(): array
|
||||
{
|
||||
$technicalInfo = new TechnicalInformation($this->media_info ?? '');
|
||||
return $technicalInfo->getSummaryInfo();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -540,6 +540,11 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
return $this->hasMany(UserModifyLog::class, "user_id");
|
||||
}
|
||||
|
||||
public function claims(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Claim::class, 'uid');
|
||||
}
|
||||
|
||||
public function getAvatarAttribute($value)
|
||||
{
|
||||
if ($value) {
|
||||
|
||||
@@ -6,13 +6,15 @@ use App\Events\SeedBoxRecordUpdated;
|
||||
use App\Events\TorrentCreated;
|
||||
use App\Events\TorrentDeleted;
|
||||
use App\Events\TorrentUpdated;
|
||||
use App\Events\UserDestroyed;
|
||||
use App\Events\UserDeleted;
|
||||
use App\Events\UserDisabled;
|
||||
use App\Listeners\DeductUserBonusWhenTorrentDeleted;
|
||||
use App\Listeners\FetchTorrentImdb;
|
||||
use App\Listeners\RemoveOauthTokens;
|
||||
use App\Listeners\RemoveSeedBoxRecordCache;
|
||||
use App\Listeners\SyncTorrentToEs;
|
||||
use App\Listeners\SendEmailNotificationWhenTorrentCreated;
|
||||
use App\Listeners\SyncTorrentToElasticsearch;
|
||||
use App\Listeners\SyncTorrentToMeilisearch;
|
||||
use App\Listeners\TestTorrentUpdated;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
@@ -34,11 +36,14 @@ class EventServiceProvider extends ServiceProvider
|
||||
RemoveSeedBoxRecordCache::class,
|
||||
],
|
||||
TorrentUpdated::class => [
|
||||
SyncTorrentToEs::class,
|
||||
TestTorrentUpdated::class,
|
||||
SyncTorrentToElasticsearch::class,
|
||||
SyncTorrentToMeilisearch::class,
|
||||
],
|
||||
TorrentCreated::class => [
|
||||
FetchTorrentImdb::class,
|
||||
SyncTorrentToElasticsearch::class,
|
||||
SyncTorrentToMeilisearch::class,
|
||||
SendEmailNotificationWhenTorrentCreated::class,
|
||||
],
|
||||
TorrentDeleted::class => [
|
||||
DeductUserBonusWhenTorrentDeleted::class,
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\Setting;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use Illuminate\Encryption\Encrypter;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BaseRepository
|
||||
@@ -20,6 +21,11 @@ class BaseRepository
|
||||
return [$field, $type];
|
||||
}
|
||||
|
||||
protected function getPerPageFromRequest(Request $request)
|
||||
{
|
||||
return $request->get('per_page');
|
||||
}
|
||||
|
||||
protected function handleAnonymous($username, $user, User $authenticator, Torrent $torrent = null)
|
||||
{
|
||||
if (!$user) {
|
||||
|
||||
@@ -7,7 +7,9 @@ class TokenRepository extends BaseRepository
|
||||
{
|
||||
private static array $userTokenPermissions = [
|
||||
PermissionEnum::TORRENT_LIST,
|
||||
PermissionEnum::TORRENT_VIEW,
|
||||
PermissionEnum::UPLOAD,
|
||||
PermissionEnum::USER_VIEW,
|
||||
];
|
||||
|
||||
public function listUserTokenPermissions(): array
|
||||
|
||||
@@ -346,7 +346,8 @@ class ToolRepository extends BaseRepository
|
||||
->from(new Address(Setting::get('main.SITEEMAIL'), Setting::get('basic.SITENAME')))
|
||||
->to($to)
|
||||
->subject($subject)
|
||||
->html($body)
|
||||
->text($body)
|
||||
->html(nl2br($body))
|
||||
;
|
||||
|
||||
// Send the message
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Auth\Permission;
|
||||
use App\Exceptions\InsufficientPermissionException;
|
||||
use App\Exceptions\NexusException;
|
||||
use App\Http\Resources\TorrentResource;
|
||||
use App\Models\AudioCodec;
|
||||
use App\Models\Category;
|
||||
use App\Models\Claim;
|
||||
@@ -26,9 +28,12 @@ use App\Models\TorrentOperationLog;
|
||||
use App\Models\TorrentSecret;
|
||||
use App\Models\TorrentTag;
|
||||
use App\Models\User;
|
||||
use App\Utils\ApiQueryBuilder;
|
||||
use Carbon\Carbon;
|
||||
use Hashids\Hashids;
|
||||
use Elasticsearch\Endpoints\Search;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -55,81 +60,134 @@ class TorrentRepository extends BaseRepository
|
||||
|
||||
/**
|
||||
* fetch torrent list
|
||||
*
|
||||
* @param array $params
|
||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public function getList(array $params, User $user)
|
||||
public function getList(Request $request, Authenticatable $user, string $sectionName = null)
|
||||
{
|
||||
$query = Torrent::query();
|
||||
if (!empty($params['category'])) {
|
||||
$query->where('category', $params['category']);
|
||||
if (empty($sectionName)) {
|
||||
$sectionId = SearchBox::getBrowseMode();
|
||||
$searchBox = SearchBox::query()->find($sectionId);
|
||||
} else {
|
||||
$searchBox = SearchBox::query()->where('name', $sectionName)->first();
|
||||
}
|
||||
if (!empty($params['source'])) {
|
||||
$query->where('source', $params['source']);
|
||||
if (empty($searchBox)) {
|
||||
throw new NexusException(nexus_trans("upload.invalid_section"));
|
||||
}
|
||||
if (!empty($params['medium'])) {
|
||||
$query->where('medium', $params['medium']);
|
||||
if ($searchBox->isSectionSpecial() && !Permission::canViewSpecialSection()) {
|
||||
throw new InsufficientPermissionException();
|
||||
}
|
||||
if (!empty($params['codec'])) {
|
||||
$query->where('codec', $params['codec']);
|
||||
$categoryIdList = $searchBox->categories()->pluck('id')->toArray();
|
||||
//query this info default
|
||||
$query = Torrent::query()->with([
|
||||
'basic_category', 'basic_category.search_box',
|
||||
'basic_audiocodec', 'basic_codec', 'basic_medium',
|
||||
'basic_source', 'basic_processing', 'basic_standard', 'basic_team',
|
||||
])
|
||||
->whereIn('category', $categoryIdList)
|
||||
->orderBy("pos_state", "DESC");
|
||||
$allowIncludes = ['user', 'extra', 'tags'];
|
||||
$allowIncludeCounts = ['thank_users', 'reward_logs', 'claims'];
|
||||
$allowIncludeFields = [
|
||||
'has_bookmarked', 'has_claimed', 'has_thanked', 'has_rewarded',
|
||||
'description', 'download_url'
|
||||
];
|
||||
$allowFilters = [
|
||||
'title', 'category', 'source', 'medium', 'codec', 'audiocodec', 'standard', 'processing', 'team',
|
||||
'owner', 'visible', 'added', 'size', 'sp_state', 'leechers', 'seeders', 'times_completed'
|
||||
];
|
||||
$allowSorts = ['id', 'comments', 'size', 'seeders', 'leechers', 'times_completed'];
|
||||
$apiQueryBuilder = ApiQueryBuilder::for(TorrentResource::NAME, $query, $request)
|
||||
->allowIncludes($allowIncludes)
|
||||
->allowIncludeCounts($allowIncludeCounts)
|
||||
->allowIncludeFields($allowIncludeFields)
|
||||
->allowFilters($allowFilters)
|
||||
->allowSorts($allowSorts)
|
||||
->registerCustomFilter('title', function (Builder $query, Request $request) {
|
||||
$title = $request->input(ApiQueryBuilder::PARAM_NAME_FILTER.".title");
|
||||
if ($title) {
|
||||
$query->where(function (Builder $query) use ($title) {
|
||||
$query->where('name', 'like', '%' . $title . '%')
|
||||
->orWhere('small_descr', 'like', '%' . $title . '%');
|
||||
});
|
||||
}
|
||||
})
|
||||
;
|
||||
$query = $apiQueryBuilder->build();
|
||||
if (!$apiQueryBuilder->hasSort()) {
|
||||
$query->orderBy("id", "DESC");
|
||||
}
|
||||
if (!empty($params['audio_codec'])) {
|
||||
$query->where('audiocodec', $params['audio_codec']);
|
||||
}
|
||||
if (!empty($params['standard'])) {
|
||||
$query->where('standard', $params['standard']);
|
||||
}
|
||||
if (!empty($params['processing'])) {
|
||||
$query->where('processing', $params['processing']);
|
||||
}
|
||||
if (!empty($params['team'])) {
|
||||
$query->where('team', $params['team']);
|
||||
}
|
||||
if (!empty($params['owner'])) {
|
||||
$query->where('owner', $params['owner']);
|
||||
}
|
||||
if (!empty($params['visible'])) {
|
||||
$query->where('visible', $params['visible']);
|
||||
}
|
||||
|
||||
if (!empty($params['query'])) {
|
||||
$query->where(function (Builder $query) use ($params) {
|
||||
$query->where('name', 'like', "%{$params['query']}%")
|
||||
->orWhere('small_descr', 'like', "%{$params['query']}%");
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($params['category_mode'])) {
|
||||
$query->whereHas('basic_category', function (Builder $query) use ($params) {
|
||||
$query->where('mode', $params['category_mode']);
|
||||
});
|
||||
}
|
||||
|
||||
$query = $this->handleGetListSort($query, $params);
|
||||
|
||||
$with = ['user', 'tags'];
|
||||
$torrents = $query->with($with)->paginate();
|
||||
foreach ($torrents as &$item) {
|
||||
$item->download_url = $this->getDownloadUrl($item->id, $user);
|
||||
}
|
||||
return $torrents;
|
||||
$torrents = $query->paginate($this->getPerPageFromRequest($request));
|
||||
return $this->appendIncludeFields($apiQueryBuilder, $user, $torrents);
|
||||
}
|
||||
|
||||
public function getDetail($id, User $user)
|
||||
public function getDetail($id, Authenticatable $user)
|
||||
{
|
||||
$with = [
|
||||
'user', 'basic_audio_codec', 'basic_category', 'basic_codec', 'basic_media', 'basic_source', 'basic_standard', 'basic_team',
|
||||
'thanks' => function ($query) use ($user) {
|
||||
$query->where('userid', $user->id);
|
||||
},
|
||||
'reward_logs' => function ($query) use ($user) {
|
||||
$query->where('userid', $user->id);
|
||||
},
|
||||
//query this info default
|
||||
$query = Torrent::query()->with([
|
||||
'basic_category', 'basic_category.search_box',
|
||||
'basic_audiocodec', 'basic_codec', 'basic_medium',
|
||||
'basic_source', 'basic_processing', 'basic_standard', 'basic_team',
|
||||
]);
|
||||
$allowIncludes = ['user', 'extra', 'tags'];
|
||||
$allowIncludeCounts = ['thank_users', 'reward_logs', 'claims'];
|
||||
$allowIncludeFields = [
|
||||
'has_bookmarked', 'has_claimed', 'has_thanked', 'has_rewarded',
|
||||
'description', 'download_url'
|
||||
];
|
||||
$result = Torrent::query()->with($with)->withCount(['peers', 'thank_users', 'reward_logs'])->visible()->findOrFail($id);
|
||||
$result->download_url = $this->getDownloadUrl($id, $user);
|
||||
return $result;
|
||||
$apiQueryBuilder = ApiQueryBuilder::for(TorrentResource::NAME, $query)
|
||||
->allowIncludes($allowIncludes)
|
||||
->allowIncludeCounts($allowIncludeCounts)
|
||||
->allowIncludeFields($allowIncludeFields)
|
||||
;
|
||||
$torrent = $apiQueryBuilder->build()->findOrFail($id);
|
||||
$torrentList = $this->appendIncludeFields($apiQueryBuilder, $user, [$torrent]);
|
||||
return $torrentList[0];
|
||||
}
|
||||
|
||||
private function appendIncludeFields(ApiQueryBuilder $apiQueryBuilder, Authenticatable $user, $torrentList)
|
||||
{
|
||||
$torrentIdArr = $bookmarkData = $claimData = $thankData = $rewardData =[];
|
||||
foreach ($torrentList as $torrent) {
|
||||
$torrentIdArr[] = $torrent->id;
|
||||
}
|
||||
unset($torrent);
|
||||
if ($hasFieldHasBookmarked = $apiQueryBuilder->hasIncludeField('has_bookmarked')) {
|
||||
$bookmarkData = $user->bookmarks()->whereIn('torrentid', $torrentIdArr)->get()->keyBy('torrentid');
|
||||
}
|
||||
if ($hasFieldHasClaimed = $apiQueryBuilder->hasIncludeField('has_claimed')) {
|
||||
$claimData = $user->claims()->whereIn('torrent_id', $torrentIdArr)->get()->keyBy('torrent_id');
|
||||
}
|
||||
if ($hasFieldHasThanked = $apiQueryBuilder->hasIncludeField('has_thanked')) {
|
||||
$thankData = $user->thank_torrent_logs()->whereIn('torrentid', $torrentIdArr)->get()->keyBy('torrentid');
|
||||
}
|
||||
if ($hasFieldHasRewarded = $apiQueryBuilder->hasIncludeField('has_rewarded')) {
|
||||
$rewardData = $user->reward_torrent_logs()->whereIn('torrentid', $torrentIdArr)->get()->keyBy('torrentid');
|
||||
}
|
||||
|
||||
foreach ($torrentList as $torrent) {
|
||||
$id = $torrent->id;
|
||||
if ($hasFieldHasBookmarked) {
|
||||
$torrent->has_bookmarked = $bookmarkData->has($id);
|
||||
}
|
||||
if ($hasFieldHasClaimed) {
|
||||
$torrent->has_claimed = $claimData->has($id);
|
||||
}
|
||||
if ($hasFieldHasThanked) {
|
||||
$torrent->has_thanked = $thankData->has($id);
|
||||
}
|
||||
if ($hasFieldHasRewarded) {
|
||||
$torrent->has_rewarded = $rewardData->has($id);
|
||||
}
|
||||
|
||||
if ($apiQueryBuilder->hasIncludeField('description') && $apiQueryBuilder->hasInclude('extra')) {
|
||||
$descriptionArr = format_description($torrent->extra->descr ?? '');
|
||||
$torrent->description = $descriptionArr;
|
||||
$torrent->images = get_image_from_description($descriptionArr);
|
||||
}
|
||||
if ($apiQueryBuilder->hasIncludeField("download_url")) {
|
||||
$torrent->download_url = $this->getDownloadUrl($id, $user);
|
||||
}
|
||||
}
|
||||
return $torrentList;
|
||||
}
|
||||
|
||||
public function getDownloadUrl($id, array|User $user): string
|
||||
|
||||
@@ -1,52 +1,195 @@
|
||||
<?php
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Auth\Permission;
|
||||
use App\Enums\ModelEventEnum;
|
||||
use App\Exceptions\NexusException;
|
||||
use App\Http\Resources\SearchBoxResource;
|
||||
use App\Models\BonusLogs;
|
||||
use App\Models\Category;
|
||||
use App\Models\File;
|
||||
use App\Models\SearchBox;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\TorrentExtra;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Rhilip\Bencode\Bencode;
|
||||
use Rhilip\Bencode\ParseException;
|
||||
|
||||
class UploadRepository extends BaseRepository
|
||||
{
|
||||
/**
|
||||
* @throws NexusException
|
||||
*/
|
||||
public function upload(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
if ($user->uploadpos != 'yes') {
|
||||
throw new NexusException("user upload permission is disabled");
|
||||
if (empty($request->name)) {
|
||||
throw new NexusException(nexus_trans("upload.require_name"));
|
||||
}
|
||||
$rules = [
|
||||
'descr' => 'required',
|
||||
'type' => 'required',
|
||||
'name' => 'required',
|
||||
];
|
||||
$request->validate($rules);
|
||||
$category = Category::query()->findOrFail($request->type);
|
||||
$mode = $category->mode;
|
||||
$searchBox = SearchBox::query()->findOrFail($mode);
|
||||
$searchBox->loadSubCategories();
|
||||
$searchBox->loadTags();
|
||||
if (empty($request->descr)) {
|
||||
throw new NexusException(nexus_trans("upload.blank_description"));
|
||||
}
|
||||
if (empty($request->type)) {
|
||||
throw new NexusException(nexus_trans("upload.category_unselected"));
|
||||
}
|
||||
$category = Category::query()->find($request->type);
|
||||
if (!$category) {
|
||||
throw new NexusException(nexus_trans("upload.invalid_category"));
|
||||
}
|
||||
$torrentFile = $this->getTorrentFile($request);
|
||||
$filepath = $torrentFile->getRealPath();
|
||||
try {
|
||||
$dict = Bencode::load($filepath);
|
||||
} catch (ParseException $e) {
|
||||
do_log("Bencode load error:" . $e->getMessage(), 'error');
|
||||
throw new NexusException("upload.not_bencoded_file");
|
||||
}
|
||||
$info = $this->checkTorrentDict($dict, 'info');
|
||||
if (isset($dict['piece layers']) || isset($info['files tree']) || (isset($info['meta version']) && $info['meta version'] == 2)) {
|
||||
throw new NexusException("Torrent files created with Bittorrent Protocol v2, or hybrid torrents are not supported.");
|
||||
}
|
||||
$this->checkTorrentDict($info, 'piece length', 'integer'); // Only Check without use
|
||||
$dname = $this->checkTorrentDict($info, 'name', 'string');
|
||||
$pieces = $this->checkTorrentDict($info, 'pieces', 'string');
|
||||
if (strlen($pieces) % 20 != 0) {
|
||||
throw new NexusException(nexus_trans("upload.invalid_pieces"));
|
||||
}
|
||||
$dict['info']['private'] = 1;
|
||||
$dict['info']['source'] = sprintf("[%s] %s", Setting::getBaseUrl(), Setting::getSiteName());
|
||||
unset ($dict['announce-list']); // remove multi-tracker capability
|
||||
unset ($dict['nodes']); // remove cached peers (Bitcomet & Azareus)
|
||||
|
||||
$infoHash = pack("H*", sha1(Bencode::encode($dict['info'])));
|
||||
$exists = Torrent::query()->where('info_hash', $infoHash)->first(['id']);
|
||||
if ($exists) {
|
||||
throw new NexusException(nexus_trans('upload.torrent_existed', ['id' => $exists->id]));
|
||||
}
|
||||
$subCategoriesAngTags = $this->getSubCategoriesAndTags($request, $category);
|
||||
$fileListInfo = $this->getFileListInfo($info, $dname);
|
||||
$posStateInfo = $this->getPosStateInfo($request);
|
||||
$pickInfo = $this->getPickInfo($request);
|
||||
$anonymous = "no";
|
||||
$uploaderUsername = $user->username;
|
||||
if ($request->uplver == 'yes' && Permission::canBeAnonymous()) {
|
||||
if ($request->uplver == 'yes') {
|
||||
if (!Permission::canBeAnonymous()) {
|
||||
throw new NexusException(nexus_trans('upload.no_permission_to_be_anonymous'));
|
||||
}
|
||||
$anonymous = "yes";
|
||||
$uploaderUsername = "Anonymous";
|
||||
}
|
||||
|
||||
|
||||
|
||||
$torrentSavePath = $this->getTorrentSavePath();
|
||||
$nowStr = Carbon::now()->toDateTimeString();
|
||||
$torrentInsert = [
|
||||
'filename' => $torrentFile->getClientOriginalName(),
|
||||
'owner' => $user->id,
|
||||
'visible' => 'yes',
|
||||
'anonymous' => $anonymous,
|
||||
'name' => $request->name,
|
||||
'size' => $fileListInfo['totalLength'],
|
||||
'numfiles' => count($fileListInfo['fileList']),
|
||||
'type' => $fileListInfo['type'],
|
||||
'url' => parse_imdb_id($request->url ?? ''),
|
||||
'small_descr' => $request->small_descr ?? '',
|
||||
'category' => $category->id,
|
||||
'source' => $subCategoriesAngTags['subCategories']['source'],
|
||||
'medium' => $subCategoriesAngTags['subCategories']['medium'],
|
||||
'codec' => $subCategoriesAngTags['subCategories']['codec'],
|
||||
'audiocodec' => $subCategoriesAngTags['subCategories']['audiocodec'],
|
||||
'standard' => $subCategoriesAngTags['subCategories']['standard'],
|
||||
'processing' => $subCategoriesAngTags['subCategories']['processing'],
|
||||
'team' => $subCategoriesAngTags['subCategories']['team'],
|
||||
'save_as' => $dname,
|
||||
'sp_state' => $this->getSpState($fileListInfo['totalLength']),
|
||||
'added' => $nowStr,
|
||||
'last_action' => $nowStr,
|
||||
'info_hash' => $infoHash,
|
||||
'cover' => $this->getCover($request),
|
||||
'pieces_hash' => sha1($info['pieces']),
|
||||
'cache_stamp' => time(),
|
||||
'hr' => $this->getHitAndRun($request),
|
||||
'pos_state' => $posStateInfo['posState'],
|
||||
'pos_state_until' => $posStateInfo['posStateUntil'],
|
||||
'picktype' => $pickInfo['pickType'],
|
||||
'picktime' => $pickInfo['pickTime'],
|
||||
'approval_status' => $this->getApprovalStatus($request),
|
||||
'price' => $this->getPrice($request),
|
||||
];
|
||||
$extraInsert = [
|
||||
'descr' => $request->descr ?? '',
|
||||
'media_info' => $request->technical_info ?? '',
|
||||
'nfo' => $this->getNfoContent($request),
|
||||
'created_at' => $nowStr,
|
||||
];
|
||||
$newTorrent = DB::transaction(function () use ($torrentInsert, $extraInsert, $fileListInfo, $subCategoriesAngTags, $dict, $torrentSavePath) {
|
||||
$newTorrent = Torrent::query()->create($torrentInsert);
|
||||
$id = $newTorrent->id;
|
||||
$torrentFilePath = "$torrentSavePath/$id.torrent";
|
||||
$saveResult = Bencode::dump($torrentFilePath, $dict);
|
||||
if ($saveResult === false) {
|
||||
do_log("save torrent failed: $torrentFilePath", 'error');
|
||||
throw new NexusException(nexus_trans('upload.save_torrent_file_failed'));
|
||||
}
|
||||
$extraInsert['torrent_id'] = $id;
|
||||
TorrentExtra::query()->insert($extraInsert);
|
||||
$fileInsert = [];
|
||||
foreach ($fileListInfo['fileList'] as $fileItem) {
|
||||
$fileInsert[] = [
|
||||
'torrent' => $id,
|
||||
'filename' => $fileItem[0],
|
||||
'size' => $fileItem[1],
|
||||
];
|
||||
}
|
||||
File::query()->insert($fileInsert);
|
||||
if (!empty($subCategoriesAngTags['tags'])) {
|
||||
insert_torrent_tags($id, $subCategoriesAngTags['tags']);
|
||||
}
|
||||
$this->sendReward($id);
|
||||
return $newTorrent;
|
||||
});
|
||||
$id = $newTorrent->id;
|
||||
$torrentRep = new TorrentRepository();
|
||||
$torrentRep->addPiecesHashCache($id, $torrentInsert['pieces_hash']);
|
||||
write_log("Torrent $id ($newTorrent->name) was uploaded by $uploaderUsername");
|
||||
fire_event(ModelEventEnum::TORRENT_CREATED, $newTorrent);
|
||||
return $newTorrent;
|
||||
}
|
||||
|
||||
private function getTorrentFile(Request $request): UploadedFile
|
||||
{
|
||||
$file = $request->file('file');
|
||||
if (empty($file)) {
|
||||
throw new NexusException("torrent file not found");
|
||||
throw new NexusException(nexus_trans('upload.missing_torrent_file'));
|
||||
}
|
||||
if (!$file->isValid()) {
|
||||
throw new NexusException("upload torrent file error");
|
||||
}
|
||||
$size = $file->getSize();
|
||||
$maxAllowSize = Setting::getUploadTorrentMaxSize();
|
||||
if ($size > $maxAllowSize) {
|
||||
$msg = sprintf("%s%s%s",
|
||||
nexus_trans("upload.torrent_file_too_big"),
|
||||
number_format($maxAllowSize),
|
||||
nexus_trans("upload.remake_torrent_note")
|
||||
);
|
||||
throw new NexusException($msg);
|
||||
}
|
||||
if ($size == 0) {
|
||||
throw new NexusException("upload.empty_file");
|
||||
}
|
||||
$filename = $file->getClientOriginalName();
|
||||
if (!validfilename($filename)) {
|
||||
throw new NexusException("upload.invalid_filename");
|
||||
}
|
||||
if (!preg_match('/^(.+)\.torrent$/si', $filename, $matches)) {
|
||||
throw new NexusException("upload.filename_not_torrent");
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
@@ -61,16 +204,387 @@ class UploadRepository extends BaseRepository
|
||||
return '';
|
||||
}
|
||||
if (!$file->isValid()) {
|
||||
throw new NexusException("upload nfo file error");
|
||||
throw new NexusException(nexus_trans("upload.nfo_upload_failed"));
|
||||
}
|
||||
$size = $file->getSize();
|
||||
if ($size == 0) {
|
||||
throw new NexusException("upload nfo file size is zero");
|
||||
throw new NexusException(nexus_trans("upload.zero_byte_nfo"));
|
||||
}
|
||||
if ($size > 65535) {
|
||||
throw new NexusException("upload nfo file size is too large");
|
||||
throw new NexusException(nexus_trans("upload.nfo_too_big"));
|
||||
}
|
||||
return str_replace("\x0d\x0d\x0a", "\x0d\x0a", $file->getContent());
|
||||
}
|
||||
|
||||
private function getApprovalStatus(Request $request): int
|
||||
{
|
||||
if (Permission::canTorrentApprovalAllowAutomatic()) {
|
||||
return Torrent::APPROVAL_STATUS_ALLOW;
|
||||
}
|
||||
return Torrent::APPROVAL_STATUS_NONE;
|
||||
}
|
||||
|
||||
private function getPrice(Request $request): int
|
||||
{
|
||||
$price = $request->price ?: 0;
|
||||
if (!is_numeric($price)) {
|
||||
throw new NexusException(nexus_trans('upload.invalid_price', ['price' => $price]));
|
||||
}
|
||||
if ($price > 0) {
|
||||
if (!Permission::canSetTorrentPrice()) {
|
||||
throw new NexusException(nexus_trans("upload.no_permission_to_set_torrent_price"));
|
||||
}
|
||||
$paidTorrentEnabled = Setting::getIsPaidTorrentEnabled();
|
||||
if (!$paidTorrentEnabled) {
|
||||
throw new NexusException(nexus_trans("upload.paid_torrent_not_enabled"));
|
||||
}
|
||||
$maxPrice = Setting::getUploadTorrentMaxPrice();
|
||||
if ($maxPrice > 0 && $price > $maxPrice) {
|
||||
throw new NexusException(nexus_trans('upload.price_too_much'));
|
||||
}
|
||||
}
|
||||
return intval($price);
|
||||
}
|
||||
|
||||
private function getHitAndRun(Request $request): int
|
||||
{
|
||||
$hr = $request->hr ?? 0;
|
||||
if ($hr > 0 && !Permission::canSetTorrentHitAndRun()) {
|
||||
throw new NexusException(nexus_trans("upload.no_permission_to_set_torrent_hr"));
|
||||
}
|
||||
if (!in_array($hr, [0, 1])) {
|
||||
throw new NexusException(nexus_trans('upload.invalid_hr'));
|
||||
}
|
||||
return intval($hr);
|
||||
}
|
||||
|
||||
private function getPosStateInfo(Request $request): array
|
||||
{
|
||||
$posState = $request->pos_state ?: Torrent::POS_STATE_STICKY_NONE;
|
||||
$posStateUntil = $request->pos_state_until ?: null;
|
||||
if ($posState !== Torrent::POS_STATE_STICKY_NONE) {
|
||||
if (!Permission::canSetTorrentPosState()) {
|
||||
throw new NexusException("upload.no_permission_to_set_torrent_pos_state");
|
||||
}
|
||||
if (!isset(Torrent::$posStates[$posState])) {
|
||||
throw new NexusException(nexus_trans('upload.invalid_pos_state', ['pos_state' => $posState]));
|
||||
}
|
||||
}
|
||||
if ($posState == Torrent::POS_STATE_STICKY_NONE) {
|
||||
$posStateUntil = null;
|
||||
}
|
||||
if ($posStateUntil && Carbon::parse($posStateUntil)->lt(Carbon::now())) {
|
||||
throw new NexusException(nexus_trans('upload.invalid_pos_state_until'));
|
||||
}
|
||||
return compact('posState', 'posStateUntil');
|
||||
}
|
||||
|
||||
private function getPickInfo(Request $request): array
|
||||
{
|
||||
$pickType = $request->pick_type ?: Torrent::PICK_NORMAL;
|
||||
$pickTime = null;
|
||||
if ($pickType != Torrent::PICK_NORMAL) {
|
||||
if (!isset(Torrent::$pickTypes[$pickType])) {
|
||||
throw new NexusException(nexus_trans('upload.invalid_pick_type', ['pick_type' => $pickType]));
|
||||
}
|
||||
if (!Permission::canPickTorrent()) {
|
||||
throw new NexusException("upload.no_permission_to_pick_torrent");
|
||||
}
|
||||
$pickTime = Carbon::now();
|
||||
}
|
||||
return compact('pickType', 'pickTime');
|
||||
}
|
||||
|
||||
private function checkTorrentDict($dict, $key, $type = null)
|
||||
{
|
||||
if (!is_array($dict)) {
|
||||
throw new NexusException(nexus_trans("upload.not_a_dictionary"));
|
||||
}
|
||||
if (!isset($dict[$key])) {
|
||||
throw new NexusException(nexus_trans("upload.dictionary_is_missing_key"));
|
||||
}
|
||||
$value = $dict[$key];
|
||||
if (!is_null($type)) {
|
||||
$isFunction = 'is_' . $type;
|
||||
if (function_exists($isFunction) && !$isFunction($value)) {
|
||||
throw new NexusException(nexus_trans("upload.invalid_entry_in_dictionary"));
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NexusException
|
||||
*/
|
||||
private function getFileListInfo(array $info, string $dname): array
|
||||
{
|
||||
$filelist = array();
|
||||
$totallen = 0;
|
||||
if (isset($info['length'])) {
|
||||
$totallen = $info['length'];
|
||||
$filelist[] = array($dname, $totallen);
|
||||
$type = "single";
|
||||
} else {
|
||||
$flist = $this->checkTorrentDict($info, 'files', 'array');
|
||||
|
||||
if (!count($flist)) {
|
||||
throw new NexusException(nexus_trans("upload.empty_file"));
|
||||
}
|
||||
foreach ($flist as $fn) {
|
||||
$ll = $this->checkTorrentDict($fn, 'length', 'integer');
|
||||
$path_key = isset($fn['path.utf-8']) ? 'path.utf-8' : 'path';
|
||||
$ff = $this->checkTorrentDict($fn, $path_key, 'list');
|
||||
|
||||
$totallen += $ll;
|
||||
$ffa = array();
|
||||
foreach ($ff as $ffe) {
|
||||
if (!is_string($ffe)) {
|
||||
throw new NexusException(nexus_trans("upload.filename_errors"));
|
||||
}
|
||||
$ffa[] = $ffe;
|
||||
}
|
||||
|
||||
if (!count($ffa)) {
|
||||
throw new NexusException(nexus_trans("upload.filename_errors"));
|
||||
}
|
||||
$ffe = implode("/", $ffa);
|
||||
$filelist[] = array($ffe, $ll);
|
||||
}
|
||||
$type = "multi";
|
||||
}
|
||||
return [
|
||||
'type' => $type,
|
||||
'totalLength' => $totallen,
|
||||
'fileList' => $filelist,
|
||||
];
|
||||
}
|
||||
|
||||
private function canUploadToSection(SearchBox $section): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
$uploadDenyApprovalDenyCount = Setting::getUploadDenyApprovalDenyCount();
|
||||
$approvalDenyCount = Torrent::query()->where('owner', $user->id)
|
||||
->where('approval_status', Torrent::APPROVAL_STATUS_DENY)
|
||||
->count()
|
||||
;
|
||||
if ($uploadDenyApprovalDenyCount > 0 && $approvalDenyCount >= $uploadDenyApprovalDenyCount) {
|
||||
throw new NexusException(nexus_trans("upload.approval_deny_reach_upper_limit"));
|
||||
}
|
||||
if ($section->isSectionBrowse()) {
|
||||
$offerSkipApprovedCount = Setting::getOfferSkipApprovedCount();
|
||||
if ($user->offer_allowed_count >= $offerSkipApprovedCount) {
|
||||
return true;
|
||||
}
|
||||
if (get_if_restricted_is_open()) {
|
||||
return true;
|
||||
}
|
||||
if (!Permission::canUploadToNormalSection()) {
|
||||
throw new NexusException(nexus_trans('upload.unauthorized_upload_freely'));
|
||||
}
|
||||
return true;
|
||||
} elseif ($section->isSectionSpecial()) {
|
||||
if (!Setting::getIsSpecialSectionEnabled()) {
|
||||
throw new NexusException(nexus_trans('upload.special_section_not_enabled'));
|
||||
}
|
||||
if (!Permission::canUploadToSpecialSection()) {
|
||||
throw new NexusException(nexus_trans('upload.unauthorized_upload_freely'));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
throw new NexusException(nexus_trans('upload.invalid_section'));
|
||||
}
|
||||
|
||||
private function getSpState($torrentSize): int
|
||||
{
|
||||
$largeTorrentSize = Setting::getLargeTorrentSize();
|
||||
if ($largeTorrentSize > 0 && $torrentSize > $largeTorrentSize * 1073741824) {
|
||||
$largeTorrentSpState = Setting::getLargeTorrentSpState();
|
||||
if (isset(Torrent::$promotionTypes[$largeTorrentSpState])) {
|
||||
do_log("large torrent, sp state from config: $largeTorrentSpState");
|
||||
return $largeTorrentSpState;
|
||||
}
|
||||
do_log("invalid large torrent sp state: $largeTorrentSpState", 'error');
|
||||
return Torrent::PROMOTION_NORMAL;
|
||||
} else {
|
||||
$probabilities = [
|
||||
Torrent::PROMOTION_FREE => Setting::getUploadTorrentFreeProbability(),
|
||||
Torrent::PROMOTION_TWO_TIMES_UP => Setting::getUploadTorrentTwoTimesUpProbability(),
|
||||
Torrent::PROMOTION_FREE_TWO_TIMES_UP => Setting::getUploadTorrentFreeTwoTimesUpProbability(),
|
||||
Torrent::PROMOTION_HALF_DOWN => Setting::getUploadTorrentHalfDownProbability(),
|
||||
Torrent::PROMOTION_HALF_DOWN_TWO_TIMES_UP => Setting::getUploadTorrentHalfDownTwoTimesUpProbability(),
|
||||
Torrent::PROMOTION_ONE_THIRD_DOWN => Setting::getUploadTorrentOneThirdDownProbability(),
|
||||
];
|
||||
$sum = array_sum($probabilities);
|
||||
if ($sum == 0) {
|
||||
do_log("no random sp state", 'warning');
|
||||
return Torrent::PROMOTION_NORMAL;
|
||||
}
|
||||
$random = mt_rand(1, $sum);
|
||||
$currentProbability = 0;
|
||||
foreach ($probabilities as $k => $v) {
|
||||
$currentProbability += $v;
|
||||
if ($random <= $currentProbability) {
|
||||
do_log(sprintf("random sp state, probabilities: %s, get result: %s by probability: %s", json_encode($probabilities), $k, $v));
|
||||
return $k;
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NexusException
|
||||
*/
|
||||
private function getSubCategoriesAndTags(Request $request, Category $category): array
|
||||
{
|
||||
$searchBoxRep = new SearchBoxRepository();
|
||||
$sections = $searchBoxRep->listSections()->keyBy('id');
|
||||
if (!$sections->has($category->mode)) {
|
||||
throw new NexusException(nexus_trans('upload.invalid_section'));
|
||||
}
|
||||
/**
|
||||
* @var $section SearchBox
|
||||
*/
|
||||
$section = $sections->get($category->mode);
|
||||
$this->canUploadToSection($section);
|
||||
|
||||
$sectionResource = new SearchBoxResource($section);
|
||||
$sectionData = $sectionResource->response()->getData(true);
|
||||
$sectionInfo = $sectionData['data'];
|
||||
$categories = array_column($sectionInfo['categories'], 'id');
|
||||
if (!in_array($category->id, $categories)) {
|
||||
throw new NexusException(nexus_trans('upload.invalid_category'));
|
||||
}
|
||||
$subCategoryInfo = array_column($sectionInfo['sub_categories'], null, 'field');
|
||||
$subCategories = [];
|
||||
foreach (SearchBox::$taxonomies as $name => $info) {
|
||||
$value = $request->get($name, 0);
|
||||
if ($value > 0) {
|
||||
if (!isset($subCategoryInfo[$name])) {
|
||||
throw new NexusException(nexus_trans('upload.not_supported_sub_category_field', ['field' => $name]));
|
||||
}
|
||||
$subCategoryValues = array_column($subCategoryInfo[$name]['data'], 'name', 'id');
|
||||
if (!isset($subCategoryValues[$value])) {
|
||||
throw new NexusException(nexus_trans(
|
||||
'upload.invalid_sub_category_value',
|
||||
['field' => $name, 'label' => $subCategoryInfo[$name]['label'], 'value' => $value]
|
||||
));
|
||||
}
|
||||
}
|
||||
$subCategories[$name] = $value;
|
||||
}
|
||||
$tags = $request->tags ?: [];
|
||||
if (!is_array($tags)) {
|
||||
$tags = explode(',', $tags);
|
||||
}
|
||||
$allTags = array_column($sectionInfo['tags'], 'name', 'id');
|
||||
foreach ($tags as $tag) {
|
||||
if (!isset($allTags[$tag])) {
|
||||
throw new NexusException(nexus_trans('upload.invalid_tag', ['tag' => $tag]));
|
||||
}
|
||||
}
|
||||
return compact('subCategories', 'tags');
|
||||
}
|
||||
|
||||
private function getCover(Request $request):string
|
||||
{
|
||||
$descr = $request->descr ?? '';
|
||||
if (empty($descr)) {
|
||||
return '';
|
||||
}
|
||||
$descriptionArr = format_description($descr);
|
||||
return get_image_from_description($descriptionArr, true, false);
|
||||
}
|
||||
|
||||
private function getTorrentSavePath(): string
|
||||
{
|
||||
$torrentSavePath = getFullDirectory(Setting::getTorrentSaveDir());
|
||||
if (!is_dir($torrentSavePath)) {
|
||||
do_log(sprintf("torrentSavePath: %s not exists", $torrentSavePath), 'error');
|
||||
throw new NexusException(nexus_trans('upload.torrent_save_dir_not_exists'));
|
||||
}
|
||||
if (!is_writable($torrentSavePath)) {
|
||||
do_log(sprintf("torrentSavePath: %s not writable", $torrentSavePath), 'error');
|
||||
throw new NexusException(nexus_trans('upload.torrent_save_dir_not_writable'));
|
||||
}
|
||||
return $torrentSavePath;
|
||||
}
|
||||
|
||||
private function sendReward($torrentId): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
$old = $user->seedbonus;
|
||||
$delta = Setting::getUploadTorrentRewardBonus();
|
||||
if ($delta > 0) {
|
||||
$new = $old + $delta;
|
||||
$user->increment('seedbonus', $delta);
|
||||
BonusLogs::add($user->id, $old, $delta, $new, "Upload torrent: $torrentId", BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT);
|
||||
do_log("upload torrent: $torrentId, success send reward: $delta");
|
||||
} else {
|
||||
do_log("upload torrent: $torrentId, no reward");
|
||||
}
|
||||
}
|
||||
|
||||
public function sendEmailNotification(Torrent $torrent, $userId = 0): int
|
||||
{
|
||||
$logMsg = sprintf("torrent: %s, category: %s", $torrent->id, $torrent->category);
|
||||
if (!Setting::getIsAllowUserReceiveEmailNotification() || Setting::getSmtpType() == 'none') {
|
||||
do_log("$logMsg, not allow user receive email notification or smtp type is none");
|
||||
return 0;
|
||||
}
|
||||
$page = 1;
|
||||
$size = 1000;
|
||||
$query = User::query()
|
||||
->where("notifs", "like", "%[cat$torrent->category]%")
|
||||
->where("notifs", "like","%[email]%")
|
||||
->normal()
|
||||
;
|
||||
if ($userId > 0) {
|
||||
$query->where("id", $userId);
|
||||
}
|
||||
$total = (clone $query)->count();
|
||||
if ($total == 0) {
|
||||
do_log(sprintf("%s, no user receive email notification", $logMsg));
|
||||
return 0;
|
||||
}
|
||||
$toolRep = new ToolRepository();
|
||||
$categoryName = $torrent->basic_category->name;
|
||||
$torrentUploader = $torrent->user;
|
||||
$successCount = 0;
|
||||
while (true) {
|
||||
$logPage = "$logMsg, page: $page";
|
||||
$users = (clone $query)->with(['language'])->forPage($page, $size)->get(['id', 'email', 'lang']);
|
||||
if ($users->isEmpty()) {
|
||||
do_log(sprintf("%s, no more user", $logPage));
|
||||
break;
|
||||
}
|
||||
foreach ($users as $user) {
|
||||
$locale = $user->locale;
|
||||
$logUser = "$logPage, user $user->id, locale: $locale";
|
||||
$subject = nexus_trans("upload.email_notification_subject", [
|
||||
'site_name' => Setting::getSiteName()
|
||||
], $locale);
|
||||
$body = nexus_trans("upload.email_notification_body", [
|
||||
'site_name' => Setting::getSiteName(),
|
||||
'name' => $torrent->name,
|
||||
'size' => mksize($torrent->size),
|
||||
'category' => $categoryName,
|
||||
'upload_by' => $this->handleAnonymous($torrentUploader->username, $torrentUploader, $user, $torrent),
|
||||
'description' => Str::limit(strip_tags(format_comment($torrent->extra->descr)), 500),
|
||||
'torrent_url' => sprintf("%s/details.php?id=%s&hit=1", getBaseUrl(), $torrent->id),
|
||||
], $locale);
|
||||
$sendResult = $toolRep->sendMail($user->email, $subject, $body);
|
||||
do_log(sprintf("%s, send result: %s", $logUser, $sendResult));
|
||||
if ($sendResult) {
|
||||
$successCount++;
|
||||
}
|
||||
}
|
||||
$page++;
|
||||
}
|
||||
do_log("$logMsg, receive email notification user total: $total, successCount: $successCount, done!");
|
||||
return $successCount;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Repositories;
|
||||
use App\Exceptions\InsufficientPermissionException;
|
||||
use App\Exceptions\NexusException;
|
||||
use App\Http\Resources\ExamUserResource;
|
||||
use App\Http\Resources\TorrentResource;
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Models\ExamUser;
|
||||
use App\Models\Invite;
|
||||
@@ -11,12 +12,15 @@ use App\Models\LoginLog;
|
||||
use App\Models\Message;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Snatch;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use App\Models\UserBanLog;
|
||||
use App\Models\UserMeta;
|
||||
use App\Models\UserModifyLog;
|
||||
use App\Models\UsernameChangeLog;
|
||||
use App\Utils\ApiQueryBuilder;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -53,28 +57,30 @@ class UserRepository extends BaseRepository
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function getDetail($id)
|
||||
public function getDetail($id, Authenticatable $currentUser)
|
||||
{
|
||||
$with = [
|
||||
'inviter' => function ($query) {return $query->select(User::$commonFields);},
|
||||
'valid_medals'
|
||||
];
|
||||
$user = User::query()->with($with)->findOrFail($id);
|
||||
$userResource = new UserResource($user);
|
||||
$baseInfo = $userResource->response()->getData(true)['data'];
|
||||
//query this info default
|
||||
$query = User::query()->with([]);
|
||||
$allowIncludes = ['inviter', 'valid_medals'];
|
||||
$allowIncludeCounts = [];
|
||||
$allowIncludeFields = [];
|
||||
$apiQueryBuilder = ApiQueryBuilder::for(UserResource::NAME, $query)
|
||||
->allowIncludes($allowIncludes)
|
||||
->allowIncludeCounts($allowIncludeCounts)
|
||||
->allowIncludeFields($allowIncludeFields)
|
||||
;
|
||||
$user = $apiQueryBuilder->build()->findOrFail($id);
|
||||
return $this->appendIncludeFields($apiQueryBuilder, $currentUser, $user);
|
||||
}
|
||||
|
||||
$examRep = new ExamRepository();
|
||||
$examProgress = $examRep->getUserExamProgress($id, ExamUser::STATUS_NORMAL);
|
||||
if ($examProgress) {
|
||||
$examResource = new ExamUserResource($examProgress);
|
||||
$examInfo = $examResource->response()->getData(true)['data'];
|
||||
} else {
|
||||
$examInfo = null;
|
||||
}
|
||||
return [
|
||||
'base_info' => $baseInfo,
|
||||
'exam_info' => $examInfo,
|
||||
];
|
||||
private function appendIncludeFields(ApiQueryBuilder $apiQueryBuilder, Authenticatable $currentUser, User $user): User
|
||||
{
|
||||
// $id = $torrent->id;
|
||||
// if ($apiQueryBuilder->hasIncludeField('has_bookmarked')) {
|
||||
// $torrent->has_bookmarked = (int)$user->bookmarks()->where('torrentid', $id)->exists();;
|
||||
// }
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
235
app/Utils/ApiQueryBuilder.php
Normal file
235
app/Utils/ApiQueryBuilder.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use App\Http\Resources\TorrentResource;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* GET /api/v1/torrents
|
||||
* ?includes=author,comments.user
|
||||
* &include_counts=thanks_user,reward_logs
|
||||
* &extra_fields=id,title
|
||||
* &include_fields[torrent]=thanks_user,reward_logs
|
||||
* &filter[status]=published
|
||||
* &filter[size][gt]=1024
|
||||
* filter_any[source][gt]=1
|
||||
* filter_any[codec]=1
|
||||
* &sorts=-created_at
|
||||
* &page=1
|
||||
* &per_page=10
|
||||
*/
|
||||
|
||||
class ApiQueryBuilder
|
||||
{
|
||||
protected array $allowedIncludes = [],
|
||||
$requestIncludes = [],
|
||||
$allowedFilters = [],
|
||||
$allowedSorts = [],
|
||||
$requestSorts = [],
|
||||
$allowedIncludeCounts = [],
|
||||
$allowedIncludeFields = [],
|
||||
$requestIncludeFields = []
|
||||
;
|
||||
|
||||
protected array $customFilterCallbacks = [];
|
||||
|
||||
const PARAM_NAME_INCLUDES = "includes";
|
||||
const PARAM_NAME_INCLUDE_COUNTS = "include_counts";
|
||||
const PARAM_NAME_INCLUDE_FIELDS = "include_fields";
|
||||
const PARAM_NAME_FILTER = "filter";
|
||||
const PARAM_NAME_FILTER_ANY = "filter_any";
|
||||
const PARAM_NAME_SORTS = "sorts";
|
||||
|
||||
|
||||
public function __construct(protected Request $request, protected Builder $query, protected string $resourceName) {}
|
||||
|
||||
public static function for(string $resourceName, Builder $query, Request $request = null): self
|
||||
{
|
||||
return new self($request ?? request(), $query, $resourceName);
|
||||
}
|
||||
|
||||
public function allowIncludes(array $includes): self
|
||||
{
|
||||
$this->allowedIncludes = $includes;
|
||||
$requestIncludesStr = $this->request->input(self::PARAM_NAME_INCLUDES);
|
||||
$this->requestIncludes = explode(',', $requestIncludesStr);
|
||||
return $this;
|
||||
}
|
||||
public function allowFilters(array $filters): self { $this->allowedFilters = $filters; return $this; }
|
||||
public function allowSorts(array $sorts): self
|
||||
{
|
||||
$this->allowedSorts = $sorts;
|
||||
$requestSortsStr = $this->request->input(self::PARAM_NAME_SORTS, "");
|
||||
foreach (explode(',', $requestSortsStr) as $sort) {
|
||||
$direction = str_starts_with($sort, '-') ? 'desc' : 'asc';
|
||||
$field = ltrim($sort, '-');
|
||||
$this->requestSorts[$field] = $direction;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function allowIncludeCounts(array $counts): self { $this->allowedIncludeCounts = $counts; return $this; }
|
||||
|
||||
public function allowIncludeFields(array $fields): self
|
||||
{
|
||||
$this->allowedIncludeFields = $fields;
|
||||
$requestIncludeFieldsStr = $this->request->input(sprintf("%s.%s", self::PARAM_NAME_INCLUDE_FIELDS, $this->resourceName), "");
|
||||
$this->requestIncludeFields = explode(',', $requestIncludeFieldsStr);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build(): Builder
|
||||
{
|
||||
$this->applyIncludes();
|
||||
$this->applyFilters();
|
||||
$this->applyOrFilters();
|
||||
$this->applyCustomFilters();
|
||||
$this->applySorts();
|
||||
$this->applyIncludeCounts();
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
protected function applyIncludes(): void
|
||||
{
|
||||
$includes = explode(',', $this->request->query(self::PARAM_NAME_INCLUDES, ''));
|
||||
$valid = array_intersect($this->allowedIncludes, $includes);
|
||||
$this->query->with($valid);
|
||||
}
|
||||
|
||||
protected function applyIncludeCounts(): void
|
||||
{
|
||||
$includeCounts = explode(',', $this->request->query(self::PARAM_NAME_INCLUDE_COUNTS, ''));
|
||||
$valid = array_intersect($this->allowedIncludeCounts, $includeCounts);
|
||||
do_log(sprintf(
|
||||
"includeCounts: %s, allow: %s, valid: %s",
|
||||
json_encode($includeCounts), json_encode($this->allowedIncludeCounts), json_encode($valid)
|
||||
));
|
||||
$this->query->withCount($valid);
|
||||
}
|
||||
|
||||
protected function applyFilters(): void
|
||||
{
|
||||
$filters = $this->request->input(self::PARAM_NAME_FILTER, []);
|
||||
|
||||
// dd($filters);
|
||||
|
||||
foreach ($filters as $field => $value) {
|
||||
if (!in_array($field, $this->allowedFilters)) continue;
|
||||
if (isset($this->customFilterCallbacks[$field])) continue;
|
||||
|
||||
// 如果是复杂条件
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $operator => $val) {
|
||||
$this->applyFilterOperator($field, $operator, $val);
|
||||
}
|
||||
} else {
|
||||
// 简单形式,默认等于
|
||||
$this->query->where($field, '=', $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyFilterOperator(string $field, string $operator, mixed $value): void
|
||||
{
|
||||
match ($operator) {
|
||||
'eq' => $this->query->where($field, '=', $value),
|
||||
'gt' => $this->query->where($field, '>', $value),
|
||||
'lt' => $this->query->where($field, '<', $value),
|
||||
'gte' => $this->query->where($field, '>=', $value),
|
||||
'lte' => $this->query->where($field, '<=', $value),
|
||||
'like' => $this->query->where($field, 'like', $value),
|
||||
'in' => $this->query->whereIn($field, is_array($value) ? $value : explode(',', $value)),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
protected function applyOrFilters(): void
|
||||
{
|
||||
$filters = $this->request->input(self::PARAM_NAME_FILTER_ANY, []);
|
||||
|
||||
if (!empty($filters)) {
|
||||
$this->query->where(function ($q) use ($filters) {
|
||||
foreach ($filters as $field => $value) {
|
||||
if (!in_array($field, $this->allowedFilters)) continue;
|
||||
if (isset($this->customFilterCallbacks[$field])) continue;
|
||||
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $operator => $val) {
|
||||
$this->applyFilterAnyOperator($q, $field, $operator, $val);
|
||||
}
|
||||
} else {
|
||||
$q->orWhere($field, '=', $value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyFilterAnyOperator(Builder $query, string $field, string $operator, mixed $value): void
|
||||
{
|
||||
match ($operator) {
|
||||
'eq' => $query->orWhere($field, '=', $value),
|
||||
'gt' => $query->orWhere($field, '>', $value),
|
||||
'lt' => $query->orWhere($field, '<', $value),
|
||||
'gte' => $query->orWhere($field, '>=', $value),
|
||||
'lte' => $query->orWhere($field, '<=', $value),
|
||||
'like' => $query->orWhere($field, 'like', $value),
|
||||
'in' => $query->orWhereIn($field, is_array($value) ? $value : explode(',', $value)),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
public function registerCustomFilter(string $field, callable $callback): self
|
||||
{
|
||||
$this->customFilterCallbacks[$field] = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function applyCustomFilters(): void
|
||||
{
|
||||
foreach ($this->customFilterCallbacks as $field => $callback) {
|
||||
call_user_func($callback, $this->query, $this->request);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function applySorts(): void
|
||||
{
|
||||
$sorts = explode(',', $this->request->query(self::PARAM_NAME_SORTS, ''));
|
||||
foreach ($sorts as $sort) {
|
||||
$direction = str_starts_with($sort, '-') ? 'desc' : 'asc';
|
||||
$field = ltrim($sort, '-');
|
||||
if (in_array($field, $this->allowedSorts)) {
|
||||
$this->query->orderBy($field, $direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function hasIncludeField(string $field = null): bool
|
||||
{
|
||||
return $this->hasBoth($this->allowedIncludeFields, $this->requestIncludeFields, $field);
|
||||
}
|
||||
|
||||
public function hasInclude(string $name = null): bool
|
||||
{
|
||||
return $this->hasBoth($this->allowedIncludes, $this->requestIncludes, $name);
|
||||
}
|
||||
|
||||
public function hasSort(string $field = ""):bool
|
||||
{
|
||||
return $this->hasBoth($this->allowedSorts, array_keys($this->requestSorts), $field);
|
||||
}
|
||||
|
||||
private function hasBoth(array $dataA, array $dataB, mixed $oneKey = null): bool
|
||||
{
|
||||
$result = array_intersect($dataA, $dataB);
|
||||
if (empty($oneKey)) {
|
||||
return !empty($result);
|
||||
}
|
||||
return in_array($oneKey, $result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -29,11 +29,11 @@
|
||||
"ext-mbstring": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-pcntl": "*",
|
||||
"ext-posix": "*",
|
||||
"ext-redis": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-zend-opcache": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-posix": "*",
|
||||
"calebporzio/sushi": "^2.5",
|
||||
"elasticsearch/elasticsearch": "^7.16",
|
||||
"filament/filament": "^3.2",
|
||||
@@ -51,7 +51,8 @@
|
||||
"orangehill/iseed": "^3.0",
|
||||
"phpgangsta/googleauthenticator": "dev-master",
|
||||
"rhilip/bencode": "^2.0",
|
||||
"rlanvin/php-ip": "^3.0"
|
||||
"rlanvin/php-ip": "^3.0",
|
||||
"stichoza/google-translate-php": "^5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
|
||||
86
composer.lock
generated
86
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4391f5fb2633f1fa33c4099a1b176b78",
|
||||
"content-hash": "e1de7765d56f7550f8cae764df3f7753",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
@@ -6965,6 +6965,87 @@
|
||||
],
|
||||
"time": "2024-12-30T13:13:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "stichoza/google-translate-php",
|
||||
"version": "v5.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Stichoza/google-translate-php.git",
|
||||
"reference": "9429773d991c98f68a25bec40d20f590ea3312a0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Stichoza/google-translate-php/zipball/9429773d991c98f68a25bec40d20f590ea3312a0",
|
||||
"reference": "9429773d991c98f68a25bec40d20f590ea3312a0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"guzzlehttp/guzzle": "^7.0",
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5.10"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Stichoza\\GoogleTranslate\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Levan Velijanashvili",
|
||||
"email": "me@stichoza.com"
|
||||
}
|
||||
],
|
||||
"description": "Free Google Translate API PHP Package",
|
||||
"homepage": "https://github.com/Stichoza/google-translate-php",
|
||||
"keywords": [
|
||||
"google",
|
||||
"php",
|
||||
"translate",
|
||||
"translating",
|
||||
"translator"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Stichoza/google-translate-php/issues",
|
||||
"source": "https://github.com/Stichoza/google-translate-php/tree/v5.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://btc.com/bc1qc25j4x7yahghm8nnn6lypnw59nptylsw32nkfl",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.paypal.me/stichoza",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/stichoza",
|
||||
"type": "ko_fi"
|
||||
},
|
||||
{
|
||||
"url": "https://liberapay.com/stichoza",
|
||||
"type": "liberapay"
|
||||
},
|
||||
{
|
||||
"url": "https://opencollective.com/stichoza",
|
||||
"type": "open_collective"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/stichoza",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2024-08-05T19:11:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/clock",
|
||||
"version": "v7.2.0",
|
||||
@@ -13044,11 +13125,12 @@
|
||||
"ext-mbstring": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-pcntl": "*",
|
||||
"ext-posix": "*",
|
||||
"ext-redis": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-zend-opcache": "*",
|
||||
"ext-zip": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
||||
@@ -44,14 +44,15 @@ return [
|
||||
'single' => [
|
||||
'driver' => 'single',
|
||||
'tap' => [\App\Logging\NexusFormatter::class],
|
||||
'path' => env('LOG_FILE', '/tmp/nexus.log'),
|
||||
'path' => getLogFile(),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
'daily' => [
|
||||
'driver' => 'daily',
|
||||
'path' => env('LOG_FILE', '/tmp/nexus.log'),
|
||||
// 'path' => env('LOG_FILE', '/tmp/nexus.log'),
|
||||
'path' => getLogFile(),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'tap' => [\App\Logging\NexusFormatter::class],
|
||||
'days' => 14,
|
||||
@@ -104,7 +105,8 @@ return [
|
||||
],
|
||||
|
||||
'emergency' => [
|
||||
'path' => env('LOG_FILE', '/tmp/nexus.log'),
|
||||
// 'path' => env('LOG_FILE', '/tmp/nexus.log'),
|
||||
'path' => getLogFile(),
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -11,11 +11,8 @@ return new class extends Migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_token_plains', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->bigInteger('access_token_id')->unsigned();
|
||||
$table->string("plain_text_token");
|
||||
$table->timestamps();
|
||||
Schema::table('sitelog', function (Blueprint $table) {
|
||||
$table->integer('uid')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +21,8 @@ return new class extends Migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_token_plains');
|
||||
Schema::table('sitelog', function (Blueprint $table) {
|
||||
$table->dropColumn('uid');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
defined('VERSION_NUMBER') || define('VERSION_NUMBER', '1.9.0');
|
||||
defined('RELEASE_DATE') || define('RELEASE_DATE', '2025-04-05');
|
||||
defined('RELEASE_DATE') || define('RELEASE_DATE', '2025-04-17');
|
||||
defined('IN_TRACKER') || define('IN_TRACKER', false);
|
||||
defined('PROJECTNAME') || define("PROJECTNAME","NexusPHP");
|
||||
defined('NEXUSPHPURL') || define("NEXUSPHPURL","https://nexusphp.org");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Models\SearchBox;
|
||||
use Carbon\CarbonInterface;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -722,10 +723,12 @@ function get_slr_color($ratio)
|
||||
|
||||
function write_log($text, $security = "normal")
|
||||
{
|
||||
$text = sqlesc($text);
|
||||
$added = sqlesc(date("Y-m-d H:i:s"));
|
||||
$security = sqlesc($security);
|
||||
sql_query("INSERT INTO sitelog (added, txt, security_level) VALUES($added, $text, $security)") or sqlerr(__FILE__, __LINE__);
|
||||
\App\Models\SiteLog::query()->insert([
|
||||
'added' => now(),
|
||||
'txt' => $text,
|
||||
'security_level' => $security,
|
||||
'uid' => get_user_id(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -2219,9 +2222,8 @@ function validlang($langid) {
|
||||
|
||||
function get_if_restricted_is_open()
|
||||
{
|
||||
global $sptime;
|
||||
// it's sunday
|
||||
if($sptime == 'yes' && (date("w",time()) == '0' || (date("w",time()) == 6) && (date("G",time()) >=12 && date("G",time()) <=23)))
|
||||
if(\App\Models\Setting::getIsUploadOpenAtWeekend() && (date("w",time()) == '0' || (date("w",time()) == 6) && (date("G",time()) >=12 && date("G",time()) <=23)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2992,7 +2994,6 @@ function set_langfolder_cookie($folder, $expires = 0x7fffffff)
|
||||
|
||||
function get_protocol_prefix()
|
||||
{
|
||||
global $securelogin;
|
||||
if (isHttps()) {
|
||||
return "https://";
|
||||
}
|
||||
@@ -4277,7 +4278,18 @@ function permissiondenied($allowMinimumClass = null){
|
||||
}
|
||||
|
||||
function gettime($time, $withago = true, $twoline = false, $forceago = false, $oneunit = false, $isfuturetime = false){
|
||||
global $lang_functions, $CURUSER;
|
||||
if (!IN_NEXUS) {
|
||||
if (empty($time)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return \Carbon\Carbon::parse($time)->diffForHumans();
|
||||
} catch (\Exception $e) {
|
||||
do_log($e->getMessage() . $e->getTraceAsString(), 'error');
|
||||
return $time;
|
||||
}
|
||||
}
|
||||
global $lang_functions, $CURUSER;
|
||||
if (isset($CURUSER) && $CURUSER['timetype'] != 'timealive' && !$forceago){
|
||||
$newtime = $time;
|
||||
if ($twoline){
|
||||
@@ -5910,7 +5922,8 @@ function insert_torrent_tags($torrentId, $tagIdArr, $sync = false)
|
||||
$canSetSpecialTag = user_can('torrent-set-special-tag');
|
||||
$dateTimeStringNow = date('Y-m-d H:i:s');
|
||||
if ($sync) {
|
||||
sql_query("delete from torrent_tags where torrent_id = $torrentId");
|
||||
\App\Models\TorrentTag::query()->where("torrent_id", $torrentId)->delete();
|
||||
// sql_query("delete from torrent_tags where torrent_id = $torrentId");
|
||||
}
|
||||
if (empty($tagIdArr)) {
|
||||
return;
|
||||
@@ -5926,7 +5939,8 @@ function insert_torrent_tags($torrentId, $tagIdArr, $sync = false)
|
||||
}
|
||||
$insertTagsSql .= implode(', ', $values);
|
||||
do_log("[INSERT_TAGS], torrent: $torrentId with tags: " . nexus_json_encode($tagIdArr));
|
||||
sql_query($insertTagsSql);
|
||||
\Nexus\Database\NexusDB::statement($insertTagsSql);
|
||||
// sql_query($insertTagsSql);
|
||||
}
|
||||
|
||||
function get_smile($num)
|
||||
|
||||
@@ -242,6 +242,9 @@ function getLogFile($append = '')
|
||||
return $logFiles[$append];
|
||||
}
|
||||
$config = nexus_config('nexus');
|
||||
if (!empty($config['log_file']) && in_array($config['log_files'], ["/dev/stdout", "/dev/stderr"])) {
|
||||
return $logFiles[$append] = $config['log_files'];
|
||||
}
|
||||
$path = getenv('NEXUS_LOG_DIR', true);
|
||||
$fromEnv = true;
|
||||
if ($path === false) {
|
||||
@@ -500,12 +503,14 @@ function getSchemeAndHttpHost(bool $fromConfig = false)
|
||||
function getBaseUrl()
|
||||
{
|
||||
$url = getSchemeAndHttpHost();
|
||||
$requestUri = $_SERVER['REQUEST_URI'];
|
||||
$pos = strpos($requestUri, '?');
|
||||
if ($pos !== false) {
|
||||
$url .= substr($requestUri, 0, $pos);
|
||||
} else {
|
||||
$url .= $requestUri;
|
||||
if (!isRunningInConsole()) {
|
||||
$requestUri = $_SERVER['REQUEST_URI'];
|
||||
$pos = strpos($requestUri, '?');
|
||||
if ($pos !== false) {
|
||||
$url .= substr($requestUri, 0, $pos);
|
||||
} else {
|
||||
$url .= $requestUri;
|
||||
}
|
||||
}
|
||||
return trim($url, '/');
|
||||
}
|
||||
@@ -528,13 +533,10 @@ function api(...$args)
|
||||
$msg = $args[1];
|
||||
$data = $args[2];
|
||||
}
|
||||
if ($data instanceof \Illuminate\Http\Resources\Json\ResourceCollection || $data instanceof \Illuminate\Http\Resources\Json\JsonResource) {
|
||||
if ($data instanceof \Illuminate\Http\Resources\Json\JsonResource) {
|
||||
$data = $data->response()->getData(true);
|
||||
if (isset($data['data']) && count($data) == 1) {
|
||||
//单纯的集合,无分页等其数据
|
||||
$data = $data['data'];
|
||||
}
|
||||
}
|
||||
// dd($data);
|
||||
$time = (float)number_format(microtime(true) - nexus()->getStartTimestamp(), 3);
|
||||
$count = null;
|
||||
$resultKey = 'ret';
|
||||
@@ -630,7 +632,7 @@ function last_query($all = false)
|
||||
function format_datetime($datetime, $format = 'Y-m-d H:i')
|
||||
{
|
||||
if (empty($datetime)) {
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$carbonTime = \Carbon\Carbon::parse($datetime);
|
||||
@@ -1320,15 +1322,28 @@ function get_snatch_info($torrentId, $userId)
|
||||
*/
|
||||
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);
|
||||
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), 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);
|
||||
} else {
|
||||
$eventClass = \App\Enums\ModelEventEnum::$eventMaps[$name]['event'];
|
||||
$params = [$model];
|
||||
if ($oldModel) {
|
||||
$params[] = $oldModel;
|
||||
}
|
||||
call_user_func_array([$eventClass, "dispatch"], $params);
|
||||
publish_model_event($name, $model->id);
|
||||
}
|
||||
executeCommand("event:fire --name=$name --idKey=$idKey --idKeyOld=$idKeyOld", "string", true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ $lang_log = array
|
||||
'title_time_added' => "时间",
|
||||
'col_date' => "日期",
|
||||
'col_event' => "事件",
|
||||
'col_user' => '用户',
|
||||
'time_zone_note' => "<p>时间为北京时间。</p>\n",
|
||||
'text_daily_log' => " 常 规 日 志 ",
|
||||
'text_chronicle' => " 史 册 ",
|
||||
|
||||
@@ -12,6 +12,7 @@ $lang_log = array
|
||||
'title_time_added' => "時間",
|
||||
'col_date' => "日期",
|
||||
'col_event' => "事件",
|
||||
'col_user' => '用戶',
|
||||
'time_zone_note' => "<p>時間為北京時間。</p>\n",
|
||||
'text_daily_log' => " 常 規 日 志 ",
|
||||
'text_chronicle' => " 史 冊 ",
|
||||
|
||||
@@ -43,6 +43,7 @@ $lang_log = array
|
||||
'text_news_empty' => "<b>News is empty</b><br />",
|
||||
'col_title' => "Title",
|
||||
'col_body' => "Body",
|
||||
'col_user' => 'User',
|
||||
'std_delete_poll' => "Delete poll",
|
||||
'std_delete_poll_confirmation' => "Do you really want to delete a poll? Click ",
|
||||
'std_here_if_sure' => "<b>here</b></a> if you are sure.",
|
||||
|
||||
@@ -148,20 +148,25 @@ class TechnicalInformation
|
||||
public function renderOnDetailsPage()
|
||||
{
|
||||
global $lang_functions;
|
||||
$videos = [
|
||||
'Runtime' => $this->getRuntime(),
|
||||
'Resolution' => $this->getResolution(),
|
||||
'Bitrate' => $this->getBitrate(),
|
||||
'HDR' => $this->getHDRFormat(),
|
||||
'Bit depth' => $this->getBitDepth(),
|
||||
'Frame rate' => $this->getFramerate(),
|
||||
'Profile' => $this->getProfile(),
|
||||
'Ref.Frames' => $this->getRefFrame(),
|
||||
];
|
||||
$videos = array_filter($videos);
|
||||
$audios = $this->getAudios();
|
||||
$subtitles = $this->getSubtitles();
|
||||
// dd($videos, $audios, $subtitles);
|
||||
// $videos = [
|
||||
// 'Runtime' => $this->getRuntime(),
|
||||
// 'Resolution' => $this->getResolution(),
|
||||
// 'Bitrate' => $this->getBitrate(),
|
||||
// 'HDR' => $this->getHDRFormat(),
|
||||
// 'Bit depth' => $this->getBitDepth(),
|
||||
// 'Frame rate' => $this->getFramerate(),
|
||||
// 'Profile' => $this->getProfile(),
|
||||
// 'Ref.Frames' => $this->getRefFrame(),
|
||||
// ];
|
||||
// $videos = array_filter($videos);
|
||||
// $audios = $this->getAudios();
|
||||
// $subtitles = $this->getSubtitles();
|
||||
$summaryInfo = $this->getSummaryInfo();
|
||||
$videos = $summaryInfo['videos'] ?: [];
|
||||
$audios = $summaryInfo['audios'] ?: [];
|
||||
$subtitles = $summaryInfo['subtitles'] ?: [];
|
||||
|
||||
// dd($summaryInfo, $videos, $audios, $subtitles);
|
||||
if (empty($videos) && empty($audios) && empty($subtitles)) {
|
||||
return sprintf('<div class="nexus-media-info-raw"><pre>%s</pre></div>', $this->mediaInfo);
|
||||
}
|
||||
@@ -187,6 +192,24 @@ class TechnicalInformation
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getSummaryInfo(): array
|
||||
{
|
||||
$videos = [
|
||||
'Runtime' => $this->getRuntime(),
|
||||
'Resolution' => $this->getResolution(),
|
||||
'Bitrate' => $this->getBitrate(),
|
||||
'HDR' => $this->getHDRFormat(),
|
||||
'Bit depth' => $this->getBitDepth(),
|
||||
'Frame rate' => $this->getFramerate(),
|
||||
'Profile' => $this->getProfile(),
|
||||
'Ref.Frames' => $this->getRefFrame(),
|
||||
];
|
||||
$videos = array_filter($videos) ?: null;
|
||||
$audios = $this->getAudios() ?: null;
|
||||
$subtitles = $this->getSubtitles() ?: null;
|
||||
return compact('videos', 'audios', 'subtitles');
|
||||
}
|
||||
|
||||
private function buildTdTable(array $parts)
|
||||
{
|
||||
$table = '<table style="border: none;"><tbody>';
|
||||
|
||||
@@ -121,7 +121,7 @@ else {
|
||||
|
||||
list($pagertop, $pagerbottom, $limit) = pager($perpage, $count, "log.php?action=dailylog&".$addparam);
|
||||
|
||||
$res = sql_query("SELECT added, txt FROM sitelog $wherea ORDER BY added DESC $limit") or sqlerr(__FILE__, __LINE__);
|
||||
$res = sql_query("SELECT * FROM sitelog $wherea ORDER BY added DESC $limit") or sqlerr(__FILE__, __LINE__);
|
||||
if (mysql_num_rows($res) == 0)
|
||||
print($lang_log['text_log_empty']);
|
||||
else
|
||||
@@ -130,7 +130,11 @@ else {
|
||||
//echo $pagertop;
|
||||
|
||||
print("<table width=940 border=1 cellspacing=0 cellpadding=5>\n");
|
||||
print("<tr><td class=colhead align=center><img class=\"time\" src=\"pic/trans.gif\" alt=\"time\" title=\"".$lang_log['title_time_added']."\" /></td><td class=colhead align=left>".$lang_log['col_event']."</td></tr>\n");
|
||||
print("<tr><td class=colhead align=center><img class=\"time\" src=\"pic/trans.gif\" alt=\"time\" title=\"".$lang_log['title_time_added']."\" /></td><td class=colhead align=left>".$lang_log['col_event']);
|
||||
if (user_can('confilog')){
|
||||
print("<td class=colhead align=left>".$lang_log['col_user']."</td>");
|
||||
}
|
||||
print("</td></tr>\n");
|
||||
while ($arr = mysql_fetch_assoc($res))
|
||||
{
|
||||
$color = "";
|
||||
@@ -139,7 +143,11 @@ else {
|
||||
if (strpos($arr['txt'],'was added to the Request section')) $color = "purple";
|
||||
if (strpos($arr['txt'],'was edited by')) $color = "blue";
|
||||
if (strpos($arr['txt'],'settings updated by')) $color = "darkred";
|
||||
print("<tr><td class=\"rowfollow nowrap\" align=center>".gettime($arr['added'],true,false)."</td><td class=rowfollow align=left><font color='".$color."'>".htmlspecialchars($arr['txt'])."</font></td></tr>\n");
|
||||
print("<tr><td class=\"rowfollow nowrap\" align=center>".gettime($arr['added'],true,false)."</td><td class=rowfollow align=left><font color='".$color."'>".htmlspecialchars($arr['txt'])."</font></td>");
|
||||
if (user_can('confilog')){
|
||||
print("<td class=rowfollow align=left>".($arr['uid'] > 0 ? get_username($arr['uid']) : "System")."</td>");
|
||||
}
|
||||
print("</tr>\n");
|
||||
}
|
||||
print("</table>");
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST")
|
||||
failedlogins($lang_recover['std_missing_email_address'],true);
|
||||
if (!check_email($email))
|
||||
failedlogins($lang_recover['std_invalid_email_address'],true);
|
||||
$res = sql_query("SELECT * FROM users WHERE email=" . sqlesc($email) . " LIMIT 1") or sqlerr(__FILE__, __LINE__);
|
||||
$res = sql_query("SELECT * FROM users WHERE BINARY email=" . sqlesc($email) . " LIMIT 1") or sqlerr(__FILE__, __LINE__);
|
||||
$arr = mysql_fetch_assoc($res);
|
||||
if (!$arr) failedlogins($lang_recover['std_email_not_in_database'],true);
|
||||
if ($arr['status'] == "pending") failedlogins($lang_recover['std_user_account_unconfirmed'],true);
|
||||
|
||||
@@ -746,7 +746,7 @@ elseif ($action == 'torrentsettings')
|
||||
tr($lang_settings['row_tax_factor'],"<input type='number' name=tax_factor style=\"width: 100px\" value={$TORRENT['tax_factor']}> ".$lang_settings['text_tax_factor_note'], 1);
|
||||
tr($lang_settings['row_max_price'],"<input type='number' name=max_price style=\"width: 100px\" value={$TORRENT['max_price']}> ".$lang_settings['text_max_price_note'], 1);
|
||||
|
||||
yesorno($lang_settings['row_promotion_rules'], 'prorules', $TORRENT["prorules"], $lang_settings['text_promotion_rules_note']);
|
||||
// yesorno($lang_settings['row_promotion_rules'], 'prorules', $TORRENT["prorules"], $lang_settings['text_promotion_rules_note']);
|
||||
tr($lang_settings['row_random_promotion'], $lang_settings['text_random_promotion_note_one']."<ul><li><input type='text' style=\"width: 50px\" name=randomhalfleech value='".(isset($TORRENT["randomhalfleech"]) ? $TORRENT["randomhalfleech"] : 5 )."'>".$lang_settings['text_halfleech_chance_becoming']."</li><li><input type='text' style=\"width: 50px\" name=randomfree value='".(isset($TORRENT["randomfree"]) ? $TORRENT["randomfree"] : 2 )."'>".$lang_settings['text_free_chance_becoming']."</li><li><input type='text' style=\"width: 50px\" name=randomtwoup value='".(isset($TORRENT["randomtwoup"]) ? $TORRENT["randomtwoup"] : 2 )."'>".$lang_settings['text_twoup_chance_becoming']."</li><li><input type='text' style=\"width: 50px\" name=randomtwoupfree value='".(isset($TORRENT["randomtwoupfree"]) ? $TORRENT["randomtwoupfree"] : 1 )."'>".$lang_settings['text_freetwoup_chance_becoming']."</li><li><input type='text' style=\"width: 50px\" name=randomtwouphalfdown value='".(isset($TORRENT["randomtwouphalfdown"]) ? $TORRENT["randomtwouphalfdown"] : 0 )."'>".$lang_settings['text_twouphalfleech_chance_becoming']."</li><li><input type='text' style=\"width: 50px\" name=randomthirtypercentdown value='".(isset($TORRENT["randomthirtypercentdown"]) ? $TORRENT["randomthirtypercentdown"] : 0 )."'>".$lang_settings['text_thirtypercentleech_chance_becoming']."</li></ul>".$lang_settings['text_random_promotion_note_two'], 1);
|
||||
tr($lang_settings['row_large_torrent_promotion'], $lang_settings['text_torrent_larger_than']."<input type='text' style=\"width: 50px\" name=largesize value='".(isset($TORRENT["largesize"]) ? $TORRENT["largesize"] : 20 )."'>".$lang_settings['text_gb_promoted_to']."<select name=largepro>".promotion_selection((isset($TORRENT['largepro']) ? $TORRENT['largepro'] : 2), 1)."</select>".$lang_settings['text_by_system_upon_uploading']."<br />".$lang_settings['text_large_torrent_promotion_note'], 1);
|
||||
tr($lang_settings['row_promotion_timeout'], $lang_settings['text_promotion_timeout_note_one']."<ul>
|
||||
|
||||
@@ -83,7 +83,7 @@ if ($nfoaction == "update")
|
||||
$Cache->delete_value('nfo_block_torrent_id_'.$id);
|
||||
}
|
||||
elseif ($nfoaction == "remove"){
|
||||
$updateset[] = "nfo = ''";
|
||||
$extraUpdate["nfo"] = "";
|
||||
$Cache->delete_value('nfo_block_torrent_id_'.$id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ if ($useChallengeResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
$res = sql_query("SELECT id, passhash, secret, auth_key, enabled, status, two_step_secret, lang FROM users WHERE username = " . sqlesc($username));
|
||||
$res = sql_query("SELECT id, passhash, secret, auth_key, enabled, status, two_step_secret, lang FROM users WHERE BINARY username = " . sqlesc($username));
|
||||
$row = mysql_fetch_array($res);
|
||||
|
||||
if (!$row)
|
||||
|
||||
@@ -134,7 +134,7 @@ if ($_POST["rulesverify"] != "yes" || $_POST["faqverify"] != "yes" || $_POST["ag
|
||||
stderr($lang_takesignup['std_signup_failed'], $lang_takesignup['std_unqualified']);
|
||||
|
||||
// check if email addy is already in use
|
||||
$a = (@mysql_fetch_row(@sql_query("select count(*) from users where email='".mysql_real_escape_string($email)."'"))) or sqlerr(__FILE__, __LINE__);
|
||||
$a = (@mysql_fetch_row(@sql_query("select count(*) from users where BINARY email='".mysql_real_escape_string($email)."'"))) or sqlerr(__FILE__, __LINE__);
|
||||
if ($a[0] != 0)
|
||||
bark($lang_takesignup['std_email_address'].$email.$lang_takesignup['std_in_use']);
|
||||
|
||||
|
||||
@@ -275,34 +275,34 @@ else{ //ramdom torrent promotion
|
||||
else
|
||||
$sp_state = 1; //normal
|
||||
}
|
||||
|
||||
if ($altname_main == 'yes'){
|
||||
$cnname_part = unesc(trim($_POST["cnname"]));
|
||||
$size_part = str_replace(" ", "", mksize($totallen));
|
||||
$date_part = date("m.d.y");
|
||||
$category_part = get_single_value("categories","name","WHERE id = ".sqlesc($catid));
|
||||
$torrent = "【".$date_part."】".($_POST["name"] ? "[".$_POST["name"]."]" : "").($cnname_part ? "[".$cnname_part."]" : "");
|
||||
}
|
||||
//
|
||||
//if ($altname_main == 'yes'){
|
||||
//$cnname_part = unesc(trim($_POST["cnname"]));
|
||||
//$size_part = str_replace(" ", "", mksize($totallen));
|
||||
//$date_part = date("m.d.y");
|
||||
//$category_part = get_single_value("categories","name","WHERE id = ".sqlesc($catid));
|
||||
//$torrent = "【".$date_part."】".($_POST["name"] ? "[".$_POST["name"]."]" : "").($cnname_part ? "[".$cnname_part."]" : "");
|
||||
//}
|
||||
|
||||
// some ugly code of automatically promoting torrents based on some rules
|
||||
if ($prorules_torrent == 'yes'){
|
||||
foreach ($promotionrules_torrent as $rule)
|
||||
{
|
||||
if (!array_key_exists('catid', $rule) || in_array($catid, $rule['catid']))
|
||||
if (!array_key_exists('sourceid', $rule) || in_array($sourceid, $rule['sourceid']))
|
||||
if (!array_key_exists('mediumid', $rule) || in_array($mediumid, $rule['mediumid']))
|
||||
if (!array_key_exists('codecid', $rule) || in_array($codecid, $rule['codecid']))
|
||||
if (!array_key_exists('standardid', $rule) || in_array($standardid, $rule['standardid']))
|
||||
if (!array_key_exists('processingid', $rule) || in_array($processingid, $rule['processingid']))
|
||||
if (!array_key_exists('teamid', $rule) || in_array($teamid, $rule['teamid']))
|
||||
if (!array_key_exists('audiocodecid', $rule) || in_array($audiocodecid, $rule['audiocodecid']))
|
||||
if (!array_key_exists('pattern', $rule) || preg_match($rule['pattern'], $torrent))
|
||||
if (is_numeric($rule['promotion'])){
|
||||
$sp_state = $rule['promotion'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if ($prorules_torrent == 'yes'){
|
||||
//foreach ($promotionrules_torrent as $rule)
|
||||
//{
|
||||
// if (!array_key_exists('catid', $rule) || in_array($catid, $rule['catid']))
|
||||
// if (!array_key_exists('sourceid', $rule) || in_array($sourceid, $rule['sourceid']))
|
||||
// if (!array_key_exists('mediumid', $rule) || in_array($mediumid, $rule['mediumid']))
|
||||
// if (!array_key_exists('codecid', $rule) || in_array($codecid, $rule['codecid']))
|
||||
// if (!array_key_exists('standardid', $rule) || in_array($standardid, $rule['standardid']))
|
||||
// if (!array_key_exists('processingid', $rule) || in_array($processingid, $rule['processingid']))
|
||||
// if (!array_key_exists('teamid', $rule) || in_array($teamid, $rule['teamid']))
|
||||
// if (!array_key_exists('audiocodecid', $rule) || in_array($audiocodecid, $rule['audiocodecid']))
|
||||
// if (!array_key_exists('pattern', $rule) || preg_match($rule['pattern'], $torrent))
|
||||
// if (is_numeric($rule['promotion'])){
|
||||
// $sp_state = $rule['promotion'];
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
//}
|
||||
$dateTimeStringNow = \Carbon\Carbon::now()->toDateTimeString();
|
||||
|
||||
$torrentSavePath = getFullDirectory($torrent_dir);
|
||||
@@ -452,15 +452,15 @@ $torrentRep = new \App\Repositories\TorrentRepository();
|
||||
$torrentRep->addPiecesHashCache($id, $insert['pieces_hash']);
|
||||
|
||||
write_log("Torrent $id ($torrent) was uploaded by $anon");
|
||||
|
||||
$searchRep = new \App\Repositories\SearchRepository();
|
||||
$searchRep->addTorrent($id);
|
||||
|
||||
$meiliSearch = new \App\Repositories\MeiliSearchRepository();
|
||||
$meiliSearch->doImportFromDatabase($id);
|
||||
//move to event listener
|
||||
//$searchRep = new \App\Repositories\SearchRepository();
|
||||
//$searchRep->addTorrent($id);
|
||||
//
|
||||
//$meiliSearch = new \App\Repositories\MeiliSearchRepository();
|
||||
//$meiliSearch->doImportFromDatabase($id);
|
||||
|
||||
//trigger event
|
||||
fire_event("torrent_created", \App\Models\Torrent::query()->find($id));
|
||||
fire_event(\App\Enums\ModelEventEnum::TORRENT_CREATED, \App\Models\Torrent::query()->find($id));
|
||||
|
||||
//===notify people who voted on offer thanks CoLdFuSiOn :)
|
||||
if ($is_offer)
|
||||
|
||||
@@ -1150,7 +1150,7 @@ if ($res->count() > 0)
|
||||
$token .= sprintf('<td>%s</td>', $tokenRecord->name);
|
||||
$token .= sprintf('<td>%s</td>', $tokenRecord->abilitiesText);
|
||||
$token .= sprintf('<td>%s</td>', $tokenRecord->created_at);
|
||||
$token .= sprintf('<td><span style="cursor: pointer;margin-right: 10px" class="token-get" data-id="%s">获取</span><span style="cursor: pointer" title="%s" data-id="%s" class="token-del">删除</span></td>', $tokenRecord->id, $lang_functions['text_delete'], $tokenRecord->id);
|
||||
$token .= sprintf('<td><img style="cursor: pointer" class="staff_delete token-del" src="pic/trans.gif" alt="D" title="%s" data-id="%s"></td>', $lang_functions['text_delete'], $tokenRecord->id);
|
||||
$token .= "</tr>";
|
||||
}
|
||||
$token .= '</table>';
|
||||
@@ -1184,13 +1184,16 @@ jQuery('#add-token-box-btn').on('click', function () {
|
||||
jQuery('body').loading({stoppable: false});
|
||||
let params = jQuery('#token-box-form').serialize()
|
||||
jQuery.post('/web/token/add', params, function (response) {
|
||||
jQuery('body').loading('stop');
|
||||
console.log(response)
|
||||
if (response.ret != 0) {
|
||||
jQuery('body').loading('stop');
|
||||
layer.alert(response.msg)
|
||||
return
|
||||
} else {
|
||||
layer.alert(response.msg, function(index) {
|
||||
layer.close(index);
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
window.location.reload()
|
||||
}, 'json')
|
||||
}
|
||||
})
|
||||
@@ -1211,19 +1214,6 @@ jQuery('#token-table').on('click', '.token-del', function () {
|
||||
}, 'json')
|
||||
})
|
||||
});
|
||||
jQuery('#token-table').on('click', '.token-get', function () {
|
||||
let params = {id: jQuery(this).attr("data-id")}
|
||||
jQuery('body').loading({stoppable: false});
|
||||
jQuery.post('/web/token/get-plain', params, function (response) {
|
||||
console.log(response)
|
||||
jQuery('body').loading('stop');
|
||||
if (response.ret != 0) {
|
||||
layer.alert(response.msg)
|
||||
} else {
|
||||
layer.alert(response.data)
|
||||
}
|
||||
}, 'json')
|
||||
});
|
||||
JS;
|
||||
\Nexus\Nexus::js($tokenBoxJs, 'footer', false);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => 'Torrent be downloaded',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => 'Receive reward',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => 'Receive gift',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => 'Upload torrent',
|
||||
],
|
||||
'fields' => [
|
||||
'business_type' => 'Business type',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"label" => "Access token",
|
||||
"permission" => "Permission",
|
||||
];
|
||||
return array (
|
||||
'label' => 'Access Token',
|
||||
'permission' => 'Permissions',
|
||||
'maximum_allow_number_reached' => 'The number reaches the upper limit',
|
||||
'create_success_tip' => 'The token was created successfully, this data is displayed only once, please save it properly<br/><br/>:token',
|
||||
);
|
||||
|
||||
68
resources/lang/en/upload.php
Normal file
68
resources/lang/en/upload.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
return array (
|
||||
'invalid_price' => 'Invalid price: :price',
|
||||
'invalid_category' => 'Invalid type',
|
||||
'invalid_section' => 'Invalid section',
|
||||
'invalid_hr' => 'Invalid H&R value',
|
||||
'invalid_pos_state' => 'Invalid position: :pos_state',
|
||||
'invalid_pos_state_until' => 'Invalid position deadline',
|
||||
'not_supported_sub_category_field' => 'Unsupported subcategory fields: :field',
|
||||
'invalid_sub_category_value' => 'Subcategory field: :label(:field) value: :value invalid',
|
||||
'invalid_tag' => 'Invalid tag::tag',
|
||||
'invalid_pick_type' => 'Invalid recommendation::pick_type',
|
||||
'require_name' => 'The title cannot be empty',
|
||||
'price_too_much' => 'Price exceeds the allowable range',
|
||||
'approval_deny_reach_upper_limit' => 'The number of torrent rejected for the current review: %s reaches the upper limit and publishing is not allowed.',
|
||||
'special_section_not_enabled' => 'Special zone is not enabled.',
|
||||
'paid_torrent_not_enabled' => 'The paid torrent is not enabled.',
|
||||
'no_permission_to_set_torrent_hr' => 'No permission to set torrent H&R.',
|
||||
'no_permission_to_set_torrent_pos_state' => 'There is no permission to set torrent top.',
|
||||
'no_permission_to_set_torrent_price' => 'No permission to set torrent charges.',
|
||||
'no_permission_to_pick_torrent' => 'No permission to recommend videos.',
|
||||
'no_permission_to_be_anonymous' => 'No permission to publish anonymously.',
|
||||
'torrent_save_dir_not_exists' => 'The torrent save directory does not exist.',
|
||||
'torrent_save_dir_not_writable' => 'The torrent save directory is not writable.',
|
||||
'save_torrent_file_failed' => 'Saving the torrent file failed.',
|
||||
'upload_failed' => 'Upload failed!',
|
||||
'missing_form_data' => 'Please fill in the required items',
|
||||
'missing_torrent_file' => 'Missing torrent file',
|
||||
'empty_filename' => 'The file name cannot be empty!',
|
||||
'zero_byte_nfo' => 'NFO file is empty',
|
||||
'nfo_too_big' => 'The NFO file is too large! Maximum allowable by 65,535 bytes.',
|
||||
'nfo_upload_failed' => 'NFO file upload failed',
|
||||
'blank_description' => 'You must fill in the introduction!',
|
||||
'category_unselected' => 'You have to choose the type!',
|
||||
'invalid_filename' => 'Invalid file name!',
|
||||
'filename_not_torrent' => 'Invalid file name (not .torrent file).',
|
||||
'empty_file' => 'Empty file!',
|
||||
'not_bencoded_file' => 'What the hell are you doing? What you uploaded is not a Bencode file!',
|
||||
'not_a_dictionary' => 'Not a directory',
|
||||
'dictionary_is_missing_key' => 'Directory missing value',
|
||||
'invalid_entry_in_dictionary' => 'Invalid directory entry',
|
||||
'invalid_dictionary_entry_type' => 'Invalid directory item type',
|
||||
'invalid_pieces' => 'Invalid file block',
|
||||
'missing_length_and_files' => 'Missing length and file',
|
||||
'filename_errors' => 'Error file name',
|
||||
'uploaded_not_offered' => 'You can only upload torrent that pass the candidate. Please return to select the appropriate project in <b>your candidate</b> before uploading!',
|
||||
'unauthorized_upload_freely' => 'You do not have the permission to upload freely!',
|
||||
'torrent_existed' => 'The torrent already exists!id: :id',
|
||||
'torrent_file_too_big' => 'The torrent file is too large! Maximum allowable',
|
||||
'remake_torrent_note' => 'bytes. Please re-create the torrent file with a larger block size, or split the content into multiple torrent to publish.',
|
||||
'email_notification_body' => 'Hello,
|
||||
A new torrent has been uploaded.
|
||||
|
||||
Name::name
|
||||
Size::size
|
||||
Type::category
|
||||
Uploader::upload_by
|
||||
|
||||
Introduction:
|
||||
:description
|
||||
|
||||
View more detailed information and download it (you may need to log in), please click here: <b><a href=javascript:void(null) onclick=window.open(\':torrent_url\')>here</a></b>
|
||||
:torrent_url
|
||||
|
||||
:site_name Website',
|
||||
'email_notification_subject' => ':site_name New torrent notification',
|
||||
);
|
||||
@@ -28,6 +28,7 @@ return [
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => '种子被下载',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => '收到奖励',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => '收到礼物',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => '发布种子',
|
||||
],
|
||||
'fields' => [
|
||||
'business_type' => '业务类型',
|
||||
|
||||
@@ -223,4 +223,12 @@ return [
|
||||
'text' => '获取种子列表',
|
||||
'desc' => '获取种子列表',
|
||||
],
|
||||
'torrent:view' => [
|
||||
'text' => '查看种子详情',
|
||||
'desc' => '查看种子详情',
|
||||
],
|
||||
'user:view' => [
|
||||
'text' => '查看用户基本信息',
|
||||
'desc' => '查看用户基本信息',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -3,4 +3,6 @@
|
||||
return [
|
||||
"label" => "访问令牌",
|
||||
"permission" => "权限",
|
||||
"maximum_allow_number_reached" => "数量达到上限",
|
||||
"create_success_tip" => "token 创建成功,此数据只展示一次,请妥善保存<br/><br/>:token",
|
||||
];
|
||||
|
||||
56
resources/lang/zh_CN/upload.php
Normal file
56
resources/lang/zh_CN/upload.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'invalid_price' => '无效的价格::price',
|
||||
'invalid_category' => '无效的分类',
|
||||
'invalid_section' => '无效的分区',
|
||||
'invalid_hr' => '无效的 H&R 值',
|
||||
'invalid_pos_state' => '无效的位置::pos_state',
|
||||
'invalid_pos_state_until' => '无效的位置截止时间',
|
||||
'not_supported_sub_category_field' => '不支持的子分类字段::field',
|
||||
'invalid_sub_category_value' => '子分类字段::label(:field) 的值::value 无效',
|
||||
'invalid_tag' => '无效的标签::tag',
|
||||
'invalid_pick_type' => '无效的推荐::pick_type',
|
||||
'require_name' => '标题不能为空',
|
||||
'price_too_much' => '价格超过允许范围',
|
||||
'approval_deny_reach_upper_limit' => '当前审核被拒绝的种子数:%s 达到上限,不允许发布。',
|
||||
'special_section_not_enabled' => '特别区未启用。',
|
||||
'paid_torrent_not_enabled' => '收费种子未启用。',
|
||||
'no_permission_to_set_torrent_hr' => '没有权限设置种子 H&R。',
|
||||
'no_permission_to_set_torrent_pos_state' => '没有权限设置种子置顶。',
|
||||
'no_permission_to_set_torrent_price' => '没有权限设置种子收费。',
|
||||
'no_permission_to_pick_torrent' => '没有权限推荐影片。',
|
||||
'no_permission_to_be_anonymous' => '没有权限匿名发布。',
|
||||
'torrent_save_dir_not_exists' => '种子保存目录不存在。',
|
||||
'torrent_save_dir_not_writable' => '种子保存目录不可写。',
|
||||
'save_torrent_file_failed' => '保存种子文件失败。',
|
||||
|
||||
'upload_failed' => "上传失败!",
|
||||
'missing_form_data' => "请填写必填项目",
|
||||
'missing_torrent_file' => "缺少种子文件",
|
||||
'empty_filename' => "文件名不能为空!",
|
||||
'zero_byte_nfo' => "NFO文件为空",
|
||||
'nfo_too_big' => "NFO文件过大!最大允许65,535 bytes。",
|
||||
'nfo_upload_failed' => "NFO文件上传失败",
|
||||
'blank_description' => "你必须填写简介!",
|
||||
'category_unselected' => "你必须选择类型!",
|
||||
'invalid_filename' => "无效的文件名!",
|
||||
'filename_not_torrent' => "无效的文件名(不是.torrent文件).",
|
||||
'empty_file' => "空文件!",
|
||||
'not_bencoded_file' => "你在搞什么鬼?你上传的不是Bencode文件!",
|
||||
'not_a_dictionary' => "不是目录",
|
||||
'dictionary_is_missing_key' => "目录缺少值",
|
||||
'invalid_entry_in_dictionary' => "无效的目录项",
|
||||
'invalid_dictionary_entry_type' => "无效的目录项类型",
|
||||
'invalid_pieces' => "无效的文件块",
|
||||
'missing_length_and_files' => "缺少长度和文件",
|
||||
'filename_errors' => "文件名错误",
|
||||
'uploaded_not_offered' => "你只能上传通过候选的种子,请返回在<b>你的候选</b>中选择合适项目后再上传!",
|
||||
'unauthorized_upload_freely' => "你没有自由上传的权限!",
|
||||
'torrent_existed' => "该种子已存在!id: :id",
|
||||
'torrent_file_too_big' => "种子文件过大!最大允许",
|
||||
'remake_torrent_note' => " bytes。请使用更大的区块大小重新制作种子文件,或者将内容分为多个种子发布。",
|
||||
|
||||
'email_notification_body' => "你好,\n一个新的种子已经上传.\n\n名称::name\n大小::size\n类型::category\n上传者::upload_by\n\n简介:\n:description\n\n查看更为详细的信息并下载(你可能需要登录),请点击这里:<b><a href=javascript:void(null) onclick=window.open(':torrent_url')>这里</a></b>\n:torrent_url\n\n:site_name 网站",
|
||||
'email_notification_subject' => ':site_name 新种子通知',
|
||||
];
|
||||
@@ -26,6 +26,7 @@ return [
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => '種子被下載',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_REWARD => '收到獎勵',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_RECEIVE_GIFT => '收到禮物',
|
||||
\App\Models\BonusLogs::BUSINESS_TYPE_UPLOAD_TORRENT => '發布種子',
|
||||
],
|
||||
'fields' => [
|
||||
'business_type' => '業務類型',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"label" => "訪問令牌",
|
||||
"permission" => "權限",
|
||||
];
|
||||
return array (
|
||||
'label' => '訪問令牌',
|
||||
'permission' => '權限',
|
||||
'maximum_allow_number_reached' => '數量達到上限',
|
||||
'create_success_tip' => 'token 創建成功,此數據只展示一次,請妥善保存<br/><br/>:token',
|
||||
);
|
||||
|
||||
68
resources/lang/zh_TW/upload.php
Normal file
68
resources/lang/zh_TW/upload.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
return array (
|
||||
'invalid_price' => '無效的價格::price',
|
||||
'invalid_category' => '無效的分類',
|
||||
'invalid_section' => '無效的分區',
|
||||
'invalid_hr' => '無效的 H&R 值',
|
||||
'invalid_pos_state' => '無效的位置::pos_state',
|
||||
'invalid_pos_state_until' => '無效的位置截止時間',
|
||||
'not_supported_sub_category_field' => '不支持的子分類字段::field',
|
||||
'invalid_sub_category_value' => '子分類字段::label(:field) 的值::value 無效',
|
||||
'invalid_tag' => '無效的標籤::tag',
|
||||
'invalid_pick_type' => '無效的推薦::pick_type',
|
||||
'require_name' => '標題不能為空',
|
||||
'price_too_much' => '價格超過允許範圍',
|
||||
'approval_deny_reach_upper_limit' => '當前審核被拒絕的種子數:%s 達到上限,不允許發布。',
|
||||
'special_section_not_enabled' => '特別區未啟用。',
|
||||
'paid_torrent_not_enabled' => '收費種子未啟用。',
|
||||
'no_permission_to_set_torrent_hr' => '沒有權限設置種子 H&R。',
|
||||
'no_permission_to_set_torrent_pos_state' => '沒有權限設置種子置頂。',
|
||||
'no_permission_to_set_torrent_price' => '沒有權限設置種子收費。',
|
||||
'no_permission_to_pick_torrent' => '沒有權限推薦影片。',
|
||||
'no_permission_to_be_anonymous' => '沒有權限匿名發布。',
|
||||
'torrent_save_dir_not_exists' => '種子保存目錄不存在。',
|
||||
'torrent_save_dir_not_writable' => '種子保存目錄不可寫。',
|
||||
'save_torrent_file_failed' => '保存種子文件失敗。',
|
||||
'upload_failed' => '上傳失敗!',
|
||||
'missing_form_data' => '請填寫必填項目',
|
||||
'missing_torrent_file' => '缺少種子文件',
|
||||
'empty_filename' => '文件名不能為空!',
|
||||
'zero_byte_nfo' => 'NFO文件為空',
|
||||
'nfo_too_big' => 'NFO文件過大!最大允許65,535 bytes。',
|
||||
'nfo_upload_failed' => 'NFO文件上傳失敗',
|
||||
'blank_description' => '你必須填寫簡介!',
|
||||
'category_unselected' => '你必須選擇類型!',
|
||||
'invalid_filename' => '無效的文件名!',
|
||||
'filename_not_torrent' => '無效的文件名(不是.torrent文件).',
|
||||
'empty_file' => '空文件!',
|
||||
'not_bencoded_file' => '你在搞什麼鬼?你上傳的不是Bencode文件!',
|
||||
'not_a_dictionary' => '不是目錄',
|
||||
'dictionary_is_missing_key' => '目錄缺少值',
|
||||
'invalid_entry_in_dictionary' => '無效的目錄項',
|
||||
'invalid_dictionary_entry_type' => '無效的目錄項類型',
|
||||
'invalid_pieces' => '無效的文件塊',
|
||||
'missing_length_and_files' => '缺少長度和文件',
|
||||
'filename_errors' => '文件名錯誤',
|
||||
'uploaded_not_offered' => '你只能上傳通過候選的種子,請返回在<b>你的候選</b>中選擇合適項目後再上傳!',
|
||||
'unauthorized_upload_freely' => '你沒有自由上傳的權限!',
|
||||
'torrent_existed' => '該種子已存在!id: :id',
|
||||
'torrent_file_too_big' => '種子文件過大!最大允許',
|
||||
'remake_torrent_note' => 'bytes。請使用更大的區塊大小重新製作種子文件,或者將內容分為多個種子發布。',
|
||||
'email_notification_body' => '你好,
|
||||
一個新的種子已經上傳.
|
||||
|
||||
名稱::name
|
||||
大小::size
|
||||
類型::category
|
||||
上傳者::upload_by
|
||||
|
||||
簡介:
|
||||
:description
|
||||
|
||||
查看更為詳細的信息並下載(你可能需要登錄),請點擊這裡:<b><a href=javascript:void(null) onclick=window.open(\':torrent_url\')>這裡</a></b>
|
||||
:torrent_url
|
||||
|
||||
:site_name 網站',
|
||||
'email_notification_subject' => ':site_name 新種子通知',
|
||||
);
|
||||
156
routes/api.php
156
routes/api.php
@@ -17,87 +17,93 @@ use App\Enums\Permission\PermissionEnum;
|
||||
|
||||
Route::group(['middleware' => ['auth:sanctum']], function () {
|
||||
|
||||
Route::group(['middleware' => ['user']], function () {
|
||||
Route::post('logout', [\App\Http\Controllers\AuthenticateController::class, 'logout']);
|
||||
Route::group(['middleware' => []], function () {
|
||||
// Route::post('logout', [\App\Http\Controllers\AuthenticateController::class, 'logout']);
|
||||
|
||||
// Route::get('user-me',[\App\Http\Controllers\UserController::class, 'me'])->name('user.me');
|
||||
// Route::get('user-publish-torrent',[\App\Http\Controllers\UserController::class, 'publishTorrent']);
|
||||
// Route::get('user-seeding-torrent',[\App\Http\Controllers\UserController::class, 'seedingTorrent']);
|
||||
// Route::get('user-leeching-torrent',[\App\Http\Controllers\UserController::class, 'leechingTorrent']);
|
||||
// Route::get('user-finished-torrent',[\App\Http\Controllers\UserController::class, 'finishedTorrent']);
|
||||
// Route::get('user-not-finished-torrent',[\App\Http\Controllers\UserController::class, 'notFinishedTorrent']);
|
||||
// Route::resource('messages', \App\Http\Controllers\MessageController::class);
|
||||
// Route::get('messages-unread', [\App\Http\Controllers\MessageController::class, 'listUnread']);
|
||||
// Route::resource('torrents', \App\Http\Controllers\TorrentController::class);
|
||||
// Route::resource('comments', \App\Http\Controllers\CommentController::class);
|
||||
// Route::resource('peers', \App\Http\Controllers\PeerController::class);
|
||||
// Route::resource('files', \App\Http\Controllers\FileController::class);
|
||||
// Route::resource('thanks', \App\Http\Controllers\ThankController::class);
|
||||
// Route::resource('snatches', \App\Http\Controllers\SnatchController::class);
|
||||
// Route::resource('bookmarks', \App\Http\Controllers\BookmarkController::class);
|
||||
// Route::get('search-box', [\App\Http\Controllers\TorrentController::class, 'searchBox']);
|
||||
// Route::resource('news', \App\Http\Controllers\NewsController::class);
|
||||
// Route::get('attend', [\App\Http\Controllers\AttendanceController::class, 'attend']);
|
||||
// Route::get('news-latest', [\App\Http\Controllers\NewsController::class, 'latest']);
|
||||
// Route::resource('polls', \App\Http\Controllers\PollController::class);
|
||||
// Route::get('polls-latest', [\App\Http\Controllers\PollController::class, 'latest']);
|
||||
// Route::post('polls-vote', [\App\Http\Controllers\PollController::class, 'vote']);
|
||||
// Route::resource('rewards', \App\Http\Controllers\RewardController::class);
|
||||
// Route::get('notifications', [\App\Http\Controllers\ToolController::class, 'notifications']);
|
||||
// Route::resource('over-forums', \App\Http\Controllers\OverForumController::class);
|
||||
// Route::resource('forums', \App\Http\Controllers\ForumController::class);
|
||||
// Route::resource('topics', \App\Http\Controllers\TopicController::class);
|
||||
|
||||
Route::get('sections', [\App\Http\Controllers\UploadController::class, 'sections'])->middleware(ability(PermissionEnum::UPLOAD));
|
||||
Route::get('torrents/{section?}', [\App\Http\Controllers\TorrentController::class, 'index'])->middleware(ability(PermissionEnum::TORRENT_LIST));
|
||||
Route::post('upload', [\App\Http\Controllers\TorrentController::class, 'store'])->middleware(ability(PermissionEnum::UPLOAD));
|
||||
Route::get('detail/{id}', [\App\Http\Controllers\TorrentController::class, 'show'])->middleware(ability(PermissionEnum::TORRENT_VIEW));
|
||||
|
||||
Route::get('/profile/{id?}', [\App\Http\Controllers\UserController::class, 'show'])->middleware(ability(PermissionEnum::USER_VIEW));
|
||||
|
||||
Route::get('user-me',[\App\Http\Controllers\UserController::class, 'me'])->name('user.me');
|
||||
Route::get('user-publish-torrent',[\App\Http\Controllers\UserController::class, 'publishTorrent']);
|
||||
Route::get('user-seeding-torrent',[\App\Http\Controllers\UserController::class, 'seedingTorrent']);
|
||||
Route::get('user-leeching-torrent',[\App\Http\Controllers\UserController::class, 'leechingTorrent']);
|
||||
Route::get('user-finished-torrent',[\App\Http\Controllers\UserController::class, 'finishedTorrent']);
|
||||
Route::get('user-not-finished-torrent',[\App\Http\Controllers\UserController::class, 'notFinishedTorrent']);
|
||||
Route::resource('messages', \App\Http\Controllers\MessageController::class);
|
||||
Route::get('messages-unread', [\App\Http\Controllers\MessageController::class, 'listUnread']);
|
||||
Route::resource('torrents', \App\Http\Controllers\TorrentController::class);
|
||||
Route::resource('comments', \App\Http\Controllers\CommentController::class);
|
||||
Route::resource('peers', \App\Http\Controllers\PeerController::class);
|
||||
Route::resource('files', \App\Http\Controllers\FileController::class);
|
||||
Route::resource('thanks', \App\Http\Controllers\ThankController::class);
|
||||
Route::resource('snatches', \App\Http\Controllers\SnatchController::class);
|
||||
Route::resource('bookmarks', \App\Http\Controllers\BookmarkController::class);
|
||||
Route::get('search-box', [\App\Http\Controllers\TorrentController::class, 'searchBox']);
|
||||
Route::resource('news', \App\Http\Controllers\NewsController::class);
|
||||
Route::get('attend', [\App\Http\Controllers\AttendanceController::class, 'attend']);
|
||||
Route::get('news-latest', [\App\Http\Controllers\NewsController::class, 'latest']);
|
||||
Route::resource('polls', \App\Http\Controllers\PollController::class);
|
||||
Route::get('polls-latest', [\App\Http\Controllers\PollController::class, 'latest']);
|
||||
Route::post('polls-vote', [\App\Http\Controllers\PollController::class, 'vote']);
|
||||
Route::resource('rewards', \App\Http\Controllers\RewardController::class);
|
||||
Route::get('notifications', [\App\Http\Controllers\ToolController::class, 'notifications']);
|
||||
Route::resource('over-forums', \App\Http\Controllers\OverForumController::class);
|
||||
Route::resource('forums', \App\Http\Controllers\ForumController::class);
|
||||
Route::resource('topics', \App\Http\Controllers\TopicController::class);
|
||||
|
||||
Route::get('sections', [\App\Http\Controllers\UploadController::class, 'sections']);
|
||||
Route::post('upload', [\App\Http\Controllers\UploadController::class, 'upload'])->middleware(ability(PermissionEnum::UPLOAD));
|
||||
});
|
||||
|
||||
Route::group(['middleware' => ['admin']], function () {
|
||||
Route::resource('agent-allows', \App\Http\Controllers\AgentAllowController::class);
|
||||
Route::get('all-agent-allows', [\App\Http\Controllers\AgentAllowController::class, 'all']);
|
||||
Route::post('agent-check', [\App\Http\Controllers\AgentAllowController::class, 'check']);
|
||||
Route::resource('agent-denies', \App\Http\Controllers\AgentDenyController::class);
|
||||
|
||||
Route::resource('users', \App\Http\Controllers\UserController::class);
|
||||
Route::get('user-base', [\App\Http\Controllers\UserController::class, 'base']);
|
||||
Route::get('user-classes', [\App\Http\Controllers\UserController::class, 'classes']);
|
||||
Route::get('user-invite-info', [\App\Http\Controllers\UserController::class, 'inviteInfo']);
|
||||
Route::get('user-match-exams', [\App\Http\Controllers\UserController::class, 'matchExams']);
|
||||
Route::get('user-mod-comment', [\App\Http\Controllers\UserController::class, 'modComment']);
|
||||
Route::post('user-disable', [\App\Http\Controllers\UserController::class, 'disable']);
|
||||
Route::post('user-enable', [\App\Http\Controllers\UserController::class, 'enable']);
|
||||
Route::post('user-reset-password', [\App\Http\Controllers\UserController::class, 'resetPassword']);
|
||||
Route::put('user-increment-decrement', [\App\Http\Controllers\UserController::class, 'incrementDecrement']);
|
||||
Route::put('user-remove-two-step', [\App\Http\Controllers\UserController::class, 'removeTwoStepAuthentication']);
|
||||
|
||||
Route::resource('exams', \App\Http\Controllers\ExamController::class);
|
||||
Route::get('exams-all', [\App\Http\Controllers\ExamController::class, 'all']);
|
||||
Route::get('exam-indexes', [\App\Http\Controllers\ExamController::class, 'indexes']);
|
||||
|
||||
Route::resource('exam-users', \App\Http\Controllers\ExamUserController::class);
|
||||
Route::put('exam-users-avoid', [\App\Http\Controllers\ExamUserController::class, 'avoid']);
|
||||
Route::put('exam-users-recover', [\App\Http\Controllers\ExamUserController::class, 'recover']);
|
||||
Route::put('exam-users-avoid-bulk', [\App\Http\Controllers\ExamUserController::class, 'bulkAvoid']);
|
||||
Route::put('exam-users-delete-bulk', [\App\Http\Controllers\ExamUserController::class, 'bulkDelete']);
|
||||
|
||||
Route::get('dashboard/system-info', [\App\Http\Controllers\DashboardController::class, 'systemInfo']);
|
||||
Route::get('dashboard/stat-data', [\App\Http\Controllers\DashboardController::class, 'statData']);
|
||||
Route::get('dashboard/latest-user', [\App\Http\Controllers\DashboardController::class, 'latestUser']);
|
||||
Route::get('dashboard/latest-torrent', [\App\Http\Controllers\DashboardController::class, 'latestTorrent']);
|
||||
|
||||
Route::resource('settings', \App\Http\Controllers\SettingController::class);
|
||||
Route::resource('medals', \App\Http\Controllers\MedalController::class);
|
||||
Route::resource('user-medals', \App\Http\Controllers\UserMedalController::class);
|
||||
Route::resource('tags', \App\Http\Controllers\TagController::class);
|
||||
Route::resource('hr', \App\Http\Controllers\HitAndRunController::class);
|
||||
Route::get('hr-status', [\App\Http\Controllers\HitAndRunController::class, 'listStatus']);
|
||||
Route::put('hr-pardon/{id}', [\App\Http\Controllers\HitAndRunController::class, 'pardon']);
|
||||
Route::put('hr-delete', [\App\Http\Controllers\HitAndRunController::class, 'bulkDelete']);
|
||||
Route::put('hr-pardon', [\App\Http\Controllers\HitAndRunController::class, 'bulkPardon']);
|
||||
});
|
||||
// Route::group(['middleware' => ['admin']], function () {
|
||||
// Route::resource('agent-allows', \App\Http\Controllers\AgentAllowController::class);
|
||||
// Route::get('all-agent-allows', [\App\Http\Controllers\AgentAllowController::class, 'all']);
|
||||
// Route::post('agent-check', [\App\Http\Controllers\AgentAllowController::class, 'check']);
|
||||
// Route::resource('agent-denies', \App\Http\Controllers\AgentDenyController::class);
|
||||
//
|
||||
// Route::resource('users', \App\Http\Controllers\UserController::class);
|
||||
// Route::get('user-base', [\App\Http\Controllers\UserController::class, 'base']);
|
||||
// Route::get('user-classes', [\App\Http\Controllers\UserController::class, 'classes']);
|
||||
// Route::get('user-invite-info', [\App\Http\Controllers\UserController::class, 'inviteInfo']);
|
||||
// Route::get('user-match-exams', [\App\Http\Controllers\UserController::class, 'matchExams']);
|
||||
// Route::get('user-mod-comment', [\App\Http\Controllers\UserController::class, 'modComment']);
|
||||
// Route::post('user-disable', [\App\Http\Controllers\UserController::class, 'disable']);
|
||||
// Route::post('user-enable', [\App\Http\Controllers\UserController::class, 'enable']);
|
||||
// Route::post('user-reset-password', [\App\Http\Controllers\UserController::class, 'resetPassword']);
|
||||
// Route::put('user-increment-decrement', [\App\Http\Controllers\UserController::class, 'incrementDecrement']);
|
||||
// Route::put('user-remove-two-step', [\App\Http\Controllers\UserController::class, 'removeTwoStepAuthentication']);
|
||||
//
|
||||
// Route::resource('exams', \App\Http\Controllers\ExamController::class);
|
||||
// Route::get('exams-all', [\App\Http\Controllers\ExamController::class, 'all']);
|
||||
// Route::get('exam-indexes', [\App\Http\Controllers\ExamController::class, 'indexes']);
|
||||
//
|
||||
// Route::resource('exam-users', \App\Http\Controllers\ExamUserController::class);
|
||||
// Route::put('exam-users-avoid', [\App\Http\Controllers\ExamUserController::class, 'avoid']);
|
||||
// Route::put('exam-users-recover', [\App\Http\Controllers\ExamUserController::class, 'recover']);
|
||||
// Route::put('exam-users-avoid-bulk', [\App\Http\Controllers\ExamUserController::class, 'bulkAvoid']);
|
||||
// Route::put('exam-users-delete-bulk', [\App\Http\Controllers\ExamUserController::class, 'bulkDelete']);
|
||||
//
|
||||
// Route::get('dashboard/system-info', [\App\Http\Controllers\DashboardController::class, 'systemInfo']);
|
||||
// Route::get('dashboard/stat-data', [\App\Http\Controllers\DashboardController::class, 'statData']);
|
||||
// Route::get('dashboard/latest-user', [\App\Http\Controllers\DashboardController::class, 'latestUser']);
|
||||
// Route::get('dashboard/latest-torrent', [\App\Http\Controllers\DashboardController::class, 'latestTorrent']);
|
||||
//
|
||||
// Route::resource('settings', \App\Http\Controllers\SettingController::class);
|
||||
// Route::resource('medals', \App\Http\Controllers\MedalController::class);
|
||||
// Route::resource('user-medals', \App\Http\Controllers\UserMedalController::class);
|
||||
// Route::resource('tags', \App\Http\Controllers\TagController::class);
|
||||
// Route::resource('hr', \App\Http\Controllers\HitAndRunController::class);
|
||||
// Route::get('hr-status', [\App\Http\Controllers\HitAndRunController::class, 'listStatus']);
|
||||
// Route::put('hr-pardon/{id}', [\App\Http\Controllers\HitAndRunController::class, 'pardon']);
|
||||
// Route::put('hr-delete', [\App\Http\Controllers\HitAndRunController::class, 'bulkDelete']);
|
||||
// Route::put('hr-pardon', [\App\Http\Controllers\HitAndRunController::class, 'bulkPardon']);
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
Route::post('login', [\App\Http\Controllers\AuthenticateController::class, 'login']);
|
||||
//Route::post('login', [\App\Http\Controllers\AuthenticateController::class, 'login']);
|
||||
|
||||
|
||||
|
||||
1
storage/framework/.gitignore
vendored
1
storage/framework/.gitignore
vendored
@@ -7,3 +7,4 @@ routes.php
|
||||
routes.scanned.php
|
||||
schedule-*
|
||||
services.json
|
||||
lang*.json
|
||||
|
||||
Reference in New Issue
Block a user