Merge branch 'php8' into section

This commit is contained in:
xiaomlove
2022-10-25 19:16:56 +08:00
168 changed files with 3072 additions and 621 deletions
+4 -2
View File
@@ -12,7 +12,7 @@ class BackupDatabase extends Command
*
* @var string
*/
protected $signature = 'backup:database';
protected $signature = 'backup:database {--transfer=}';
/**
* The console command description.
@@ -39,7 +39,9 @@ class BackupDatabase extends Command
public function handle()
{
$rep = new ToolRepository();
$result = $rep->backupDatabase();
$transfer = $this->option('transfer');
$this->info("transfer: $transfer");
$result = $rep->backupDatabase($transfer);
$log = sprintf('[%s], %s, result: %s', nexus()->getRequestId(), __METHOD__, var_export($result, true));
$this->info($log);
do_log($log);
+4 -3
View File
@@ -12,7 +12,7 @@ class BackupWeb extends Command
*
* @var string
*/
protected $signature = 'backup:web {--method=}';
protected $signature = 'backup:web {--method=} {--transfer=}';
/**
* The console command description.
@@ -39,9 +39,10 @@ class BackupWeb extends Command
public function handle()
{
$method = $this->option('method');
$this->info("method: $method");
$transfer = $this->option('transfer');
$this->info("method: $method, transfer: $transfer");
$rep = new ToolRepository();
$result = $rep->backupWeb($method);
$result = $rep->backupWeb($method, $transfer);
$log = sprintf('[%s], %s, result: %s', nexus()->getRequestId(), __METHOD__, var_export($result, true));
$this->info($log);
do_log($log);
@@ -44,8 +44,8 @@ class HitAndRunUpdateStatus extends Command
$rep = new HitAndRunRepository();
$result = $rep->cronjobUpdateStatus($uid, $torrentId, $ignoreTime);
$log = sprintf(
'[%s], %s, uid: %s, torrentId: %s, result: %s',
nexus()->getRequestId(), __METHOD__, $uid, $torrentId, var_export($result, true)
'[%s], %s, uid: %s, torrentId: %s, ignoreTime: %s, result: %s',
nexus()->getRequestId(), __METHOD__, $uid, $torrentId, $ignoreTime, var_export($result, true)
);
$this->info($log);
do_log($log);
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Console\Commands;
use App\Repositories\PluginRepository;
use Illuminate\Console\Command;
use Nexus\Plugin\BasePlugin;
class PluginCronjob extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'plugin:cronjob {--action=} {--id=} {--force=}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Plugin install / update / delete cronjob handler';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$action = $this->option('action');
$id = $this->option('id');
$force = $this->option('force');
$pluginRep = new PluginRepository();
$pluginRep->cronjob($action, $id, $force);
$log = sprintf("[%s], action: %s, id: %s, force: %s run done !", nexus()->getRequestId(), $action, $id, $force);
$this->info($log);
do_log($log);
return 0;
}
}
+4 -2
View File
@@ -14,15 +14,18 @@ use App\Models\HitAndRun;
use App\Models\Medal;
use App\Models\Peer;
use App\Models\SearchBox;
use App\Models\Setting;
use App\Models\Snatch;
use App\Models\Tag;
use App\Models\Torrent;
use App\Models\TorrentOperationLog;
use App\Models\User;
use App\Models\UserBanLog;
use App\Repositories\AgentAllowRepository;
use App\Repositories\AttendanceRepository;
use App\Repositories\ExamRepository;
use App\Repositories\HitAndRunRepository;
use App\Repositories\PluginRepository;
use App\Repositories\SearchBoxRepository;
use App\Repositories\SearchRepository;
use App\Repositories\TagRepository;
@@ -87,8 +90,7 @@ class Test extends Command
*/
public function handle()
{
$rep = new SearchBoxRepository();
$rep->migrateToModeRelated();
}
+2
View File
@@ -35,6 +35,8 @@ class Kernel extends ConsoleKernel
$schedule->command('claim:settle')->hourly()->when(function () {
return Carbon::now()->format('d') == '01';
})->withoutOverlapping();
// $schedule->command('plugin:cronjob')->everyMinute()->withoutOverlapping();
}
/**
+8
View File
@@ -15,4 +15,12 @@ trait OptionsTrait
$disableValue => __('label.disabled'),
];
}
private static function getYesNoOptions($yesValue = 1, $noValue = 0): array
{
return [
$yesValue => 'Yes',
$noValue => 'No',
];
}
}
+6
View File
@@ -3,6 +3,7 @@
namespace App\Filament;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Filters\Layout;
use Illuminate\Database\Eloquent\Model;
class PageList extends ListRecords
@@ -15,4 +16,9 @@ class PageList extends ListRecords
return null;
};
}
protected function getTableFiltersLayout(): ?string
{
return Layout::AboveContent;
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace App\Filament;
use Filament\Resources\Pages\ManageRecords;
use Filament\Tables\Filters\Layout;
class PageListSingle extends ManageRecords
{
protected ?string $maxContentWidth = 'full';
protected function getTableFiltersLayout(): ?string
{
return Layout::AboveContent;
}
}
@@ -85,7 +85,7 @@ class ExamResource extends Resource
Forms\Components\Section::make(__('label.exam.section_time'))->schema([
Forms\Components\DateTimePicker::make('begin')->label(__('label.begin')),
Forms\Components\DateTimePicker::make('end')->label(__('label.begin')),
Forms\Components\DateTimePicker::make('end')->label(__('label.end')),
Forms\Components\TextInput::make('duration')
->integer()
->columnSpan(['sm' => 2])
@@ -118,7 +118,7 @@ class ExamResource extends Resource
Tables\Columns\TextColumn::make('name')->searchable()->label(__('label.name')),
Tables\Columns\TextColumn::make('indexFormatted')->label(__('label.exam.index_formatted'))->html(),
Tables\Columns\TextColumn::make('begin')->label(__('label.begin')),
Tables\Columns\TextColumn::make('end')->label(__('label.begin')),
Tables\Columns\TextColumn::make('end')->label(__('label.end')),
Tables\Columns\TextColumn::make('durationText')->label(__('label.duration')),
Tables\Columns\TextColumn::make('filterFormatted')->label(__('label.exam.filter_formatted'))->html(),
Tables\Columns\BooleanColumn::make('is_discovered')->label(__('label.exam.is_discovered')),
@@ -0,0 +1,114 @@
<?php
namespace App\Filament\Resources\System;
use App\Filament\Resources\System\PluginResource\Pages;
use App\Filament\Resources\System\PluginResource\RelationManagers;
use App\Models\Plugin;
use Filament\Forms;
use Filament\Resources\Form;
use Filament\Resources\Resource;
use Filament\Resources\Table;
use Filament\Tables;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\HtmlString;
class PluginResource extends Resource
{
protected static ?string $model = Plugin::class;
protected static ?string $navigationIcon = 'heroicon-o-plus-circle';
protected static ?string $navigationGroup = 'System';
protected static ?int $navigationSort = 99;
protected static bool $shouldRegisterNavigation = false;
protected static function getNavigationLabel(): string
{
return __('admin.sidebar.plugin');
}
public static function getBreadcrumb(): string
{
return self::getNavigationLabel();
}
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('package_name')->label(__('label.plugin.package_name')),
Forms\Components\TextInput::make('remote_url')->label(__('label.plugin.remote_url')),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('id'),
Tables\Columns\TextColumn::make('package_name')->label(__('plugin.labels.package_name')),
Tables\Columns\TextColumn::make('remote_url')->label(__('plugin.labels.remote_url')),
Tables\Columns\TextColumn::make('installed_version')->label(__('plugin.labels.installed_version')),
Tables\Columns\TextColumn::make('statusText')->label(__('label.status')),
])
->filters([
//
])
->actions(self::getActions())
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ManagePlugins::route('/'),
];
}
private static function getActions()
{
$actions = [];
$actions[] = Tables\Actions\EditAction::make();
$actions[] = self::buildInstallAction();
$actions[] = self::buildUpdateAction();
$actions[] = Tables\Actions\DeleteAction::make('delete')
->hidden(fn ($record) => $record->status == Plugin::STATUS_NOT_INSTALLED)
->using(function ($record) {
$record->update(['status' => Plugin::STATUS_PRE_DELETE]);
});
return $actions;
}
private static function buildInstallAction()
{
return Tables\Actions\Action::make('install')
->label(__('plugin.actions.install'))
->icon('heroicon-o-arrow-down')
->requiresConfirmation()
->hidden(fn ($record) => $record->status == Plugin::STATUS_NORMAL)
->action(function ($record) {
$record->update(['status' => Plugin::STATUS_PRE_INSTALL]);
})
;
}
private static function buildUpdateAction()
{
return Tables\Actions\Action::make('update')
->label(__('plugin.actions.update'))
->icon('heroicon-o-arrow-up')
->requiresConfirmation()
->hidden(fn ($record) => in_array($record->status, [Plugin::STATUS_NOT_INSTALLED, Plugin::STATUS_PRE_UPDATE]))
->action(function ($record) {
$record->update(['status' => Plugin::STATUS_PRE_UPDATE]);
})
;
}
}
@@ -0,0 +1,21 @@
<?php
namespace App\Filament\Resources\System\PluginResource\Pages;
use App\Filament\Resources\System\PluginResource;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ManageRecords;
class ManagePlugins extends ManageRecords
{
protected static string $resource = PluginResource::class;
protected ?string $maxContentWidth = 'full';
protected function getActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}
@@ -15,6 +15,7 @@ use Filament\Tables;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use phpDocumentor\Reflection\DocBlock\Tags\See;
use PhpIP\IP;
class SeedBoxRecordResource extends Resource
{
@@ -55,12 +56,23 @@ class SeedBoxRecordResource extends Resource
->columns([
Tables\Columns\TextColumn::make('id'),
Tables\Columns\TextColumn::make('typeText')->label(__('label.seed_box_record.type')),
Tables\Columns\TextColumn::make('uid')->searchable(),
Tables\Columns\TextColumn::make('user.username')->label(__('label.username'))->searchable(),
Tables\Columns\TextColumn::make('operator')->label(__('label.seed_box_record.operator'))->searchable(),
Tables\Columns\TextColumn::make('bandwidth')->label(__('label.seed_box_record.bandwidth')),
Tables\Columns\TextColumn::make('ip')
->label(__('label.seed_box_record.ip'))
->searchable()
->searchable(true, function (Builder $query, $search) {
try {
$ip = IP::create($search);
$ipNumeric = $ip->numeric();
return $query->orWhere(function (Builder $query) use ($ipNumeric) {
return $query->where('ip_begin_numeric', '<=', $ipNumeric)->where('ip_end_numeric', '>=', $ipNumeric);
});
} catch (\Exception $exception) {
do_log("Invalid IP: $search, error: " . $exception->getMessage());
}
})
->formatStateUsing(fn ($record) => $record->ip ?: sprintf('%s ~ %s', $record->ip_begin, $record->ip_end)),
Tables\Columns\TextColumn::make('comment')->label(__('label.comment')),
Tables\Columns\BadgeColumn::make('status')
@@ -73,6 +85,16 @@ class SeedBoxRecordResource extends Resource
->label(__('label.seed_box_record.status')),
])
->filters([
Tables\Filters\Filter::make('uid')
->form([
Forms\Components\TextInput::make('uid')
->label('UID')
->placeholder('UID')
,
])->query(function (Builder $query, array $data) {
return $query->when($data['uid'], fn (Builder $query, $uid) => $query->where("uid", $uid));
})
,
Tables\Filters\SelectFilter::make('type')->options(SeedBoxRecord::listTypes('text'))->label(__('label.seed_box_record.type')),
Tables\Filters\SelectFilter::make('status')->options(SeedBoxRecord::listStatus('text'))->label(__('label.seed_box_record.status')),
])
@@ -6,6 +6,7 @@ use App\Filament\OptionsTrait;
use App\Filament\Resources\System\SettingResource;
use App\Models\HitAndRun;
use App\Models\Setting;
use App\Models\Tag;
use Filament\Facades\Filament;
use Filament\Forms\ComponentContainer;
use Filament\Forms\Concerns\InteractsWithForms;
@@ -58,9 +59,6 @@ class EditSetting extends Page implements Forms\Contracts\HasForms
$data = [];
foreach ($formData as $prefix => $parts) {
foreach ($parts as $name => $value) {
if (is_null($value)) {
continue;
}
if (in_array($name, $notAutoloadNames)) {
$autoload = 'no';
} else {
@@ -78,6 +76,7 @@ class EditSetting extends Page implements Forms\Contracts\HasForms
}
}
Setting::query()->upsert($data, ['name'], ['value']);
do_action("nexus_setting_update");
clear_setting_cache();
Filament::notify('success', __('filament::resources/pages/edit-record.messages.saved'), true);
}
@@ -87,13 +86,9 @@ class EditSetting extends Page implements Forms\Contracts\HasForms
$tabs = [];
$tabs[] = Forms\Components\Tabs\Tab::make(__('label.setting.hr.tab_header'))
->id('hr')
->schema([
Forms\Components\Radio::make('hr.mode')->options(HitAndRun::listModes(true))->inline(true)->label(__('label.setting.hr.mode')),
Forms\Components\TextInput::make('hr.inspect_time')->helperText(__('label.setting.hr.inspect_time_help'))->label(__('label.setting.hr.inspect_time'))->integer(),
Forms\Components\TextInput::make('hr.seed_time_minimum')->helperText(__('label.setting.hr.seed_time_minimum_help'))->label(__('label.setting.hr.seed_time_minimum'))->integer(),
Forms\Components\TextInput::make('hr.ignore_when_ratio_reach')->helperText(__('label.setting.hr.ignore_when_ratio_reach_help'))->label(__('label.setting.hr.ignore_when_ratio_reach'))->integer(),
Forms\Components\TextInput::make('hr.ban_user_when_counts_reach')->helperText(__('label.setting.hr.ban_user_when_counts_reach_help'))->label(__('label.setting.hr.ban_user_when_counts_reach'))->integer(),
])->columns(2);
->schema($this->getHitAndRunSchema())
->columns(2)
;
$tabs[] = Forms\Components\Tabs\Tab::make(__('label.setting.backup.tab_header'))
->id('backup')
@@ -117,6 +112,7 @@ class EditSetting extends Page implements Forms\Contracts\HasForms
Forms\Components\TextInput::make('seed_box.not_seed_box_max_speed')->label(__('label.setting.seed_box.not_seed_box_max_speed'))->helperText(__('label.setting.seed_box.not_seed_box_max_speed_help'))->integer(),
Forms\Components\Radio::make('seed_box.no_promotion')->options(self::$yesOrNo)->inline(true)->label(__('label.setting.seed_box.no_promotion'))->helperText(__('label.setting.seed_box.no_promotion_help')),
Forms\Components\TextInput::make('seed_box.max_uploaded')->label(__('label.setting.seed_box.max_uploaded'))->helperText(__('label.setting.seed_box.max_uploaded_help'))->integer(),
Forms\Components\TextInput::make('seed_box.max_uploaded_duration')->label(__('label.setting.seed_box.max_uploaded_duration'))->helperText(__('label.setting.seed_box.max_uploaded_duration_help'))->integer(),
])->columns(2);
$tabs[] = Forms\Components\Tabs\Tab::make(__('label.setting.system.tab_header'))
@@ -137,4 +133,16 @@ class EditSetting extends Page implements Forms\Contracts\HasForms
return $tabs;
}
private function getHitAndRunSchema()
{
$default = [
Forms\Components\Radio::make('hr.mode')->options(HitAndRun::listModes(true))->inline(true)->label(__('label.setting.hr.mode')),
Forms\Components\TextInput::make('hr.inspect_time')->helperText(__('label.setting.hr.inspect_time_help'))->label(__('label.setting.hr.inspect_time'))->integer(),
Forms\Components\TextInput::make('hr.seed_time_minimum')->helperText(__('label.setting.hr.seed_time_minimum_help'))->label(__('label.setting.hr.seed_time_minimum'))->integer(),
Forms\Components\TextInput::make('hr.ignore_when_ratio_reach')->helperText(__('label.setting.hr.ignore_when_ratio_reach_help'))->label(__('label.setting.hr.ignore_when_ratio_reach'))->integer(),
Forms\Components\TextInput::make('hr.ban_user_when_counts_reach')->helperText(__('label.setting.hr.ban_user_when_counts_reach_help'))->label(__('label.setting.hr.ban_user_when_counts_reach'))->integer(),
];
return apply_filter("hit_and_run_setting_schema", $default);
}
}
@@ -12,6 +12,7 @@ use Filament\Resources\Table;
use Filament\Tables;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\HtmlString;
class UsernameChangeLogResource extends Resource
{
@@ -50,13 +51,30 @@ class UsernameChangeLogResource extends Resource
Tables\Columns\TextColumn::make('uid')->searchable(),
Tables\Columns\TextColumn::make('user.username')->searchable()->label(__('label.username')),
Tables\Columns\TextColumn::make('username_old')->searchable()->label(__('username-change-log.labels.username_old')),
Tables\Columns\TextColumn::make('username_new')->searchable()->label(__('username-change-log.labels.username_new')),
Tables\Columns\TextColumn::make('operator')->searchable()->label(__('label.operator')),
Tables\Columns\TextColumn::make('username_new')
->searchable()
->label(__('username-change-log.labels.username_new'))
->formatStateUsing(fn ($record) => new HtmlString(get_username($record->uid, false, true, true, true)))
,
Tables\Columns\TextColumn::make('operator')
->searchable()
->label(__('label.operator'))
,
Tables\Columns\TextColumn::make('created_at')->label(__('label.created_at'))->formatStateUsing(fn ($state) => format_datetime($state)),
])
->defaultSort('id', 'desc')
->filters([
Tables\Filters\Filter::make('uid')
->form([
Forms\Components\TextInput::make('uid')
->label('UID')
->placeholder('UID')
,
])->query(function (Builder $query, array $data) {
return $query->when($data['uid'], fn (Builder $query, $uid) => $query->where("uid", $uid));
})
,
Tables\Filters\SelectFilter::make('change_type')->options(UsernameChangeLog::listChangeType())->label(__('username-change-log.labels.change_type')),
])
->actions([
@@ -2,14 +2,13 @@
namespace App\Filament\Resources\System\UsernameChangeLogResource\Pages;
use App\Filament\PageListSingle;
use App\Filament\Resources\System\UsernameChangeLogResource;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ManageRecords;
class ManageUsernameChangeLogs extends ManageRecords
class ManageUsernameChangeLogs extends PageListSingle
{
protected ?string $maxContentWidth = 'full';
protected static string $resource = UsernameChangeLogResource::class;
protected function getActions(): array
@@ -9,6 +9,7 @@ use App\Models\Setting;
use App\Models\Tag;
use App\Models\Torrent;
use App\Models\TorrentTag;
use App\Models\User;
use App\Repositories\TagRepository;
use App\Repositories\TorrentRepository;
use Filament\Facades\Filament;
@@ -24,6 +25,7 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class TorrentResource extends Resource
{
@@ -83,10 +85,20 @@ class TorrentResource extends Resource
})->label(__('label.name'))->searchable(),
Tables\Columns\TextColumn::make('posStateText')->label(__('label.torrent.pos_state')),
Tables\Columns\TextColumn::make('spStateText')->label(__('label.torrent.sp_state')),
Tables\Columns\TextColumn::make('size')->label(__('label.torrent.size'))->formatStateUsing(fn ($state) => mksize($state)),
Tables\Columns\TextColumn::make('seeders')->label(__('label.torrent.seeders')),
Tables\Columns\TextColumn::make('leechers')->label(__('label.torrent.leechers')),
// Tables\Columns\TextColumn::make('times_completed')->label(__('label.torrent.times_completed')),
Tables\Columns\TextColumn::make('pickInfoText')
->label(__('label.torrent.picktype'))
->formatStateUsing(fn ($record) => $record->pickInfo['text'])
,
Tables\Columns\BooleanColumn::make('hr')
->label(__('label.torrent.hr'))
,
Tables\Columns\TextColumn::make('size')
->label(__('label.torrent.size'))
->formatStateUsing(fn ($state) => mksize($state))
->sortable()
,
Tables\Columns\TextColumn::make('seeders')->label(__('label.torrent.seeders'))->sortable(),
Tables\Columns\TextColumn::make('leechers')->label(__('label.torrent.leechers'))->sortable(),
Tables\Columns\BadgeColumn::make('approval_status')
->visible($showApproval)
->label(__('label.torrent.approval_status'))
@@ -95,12 +107,22 @@ class TorrentResource extends Resource
Tables\Columns\TextColumn::make('added')->label(__('label.added'))->dateTime(),
Tables\Columns\TextColumn::make('user.username')
->label(__('label.torrent.owner'))
->url(fn ($record) => sprintf('/userdetails.php?id=%s', $record->owner))
->openUrlInNewTab(true)
->formatStateUsing(fn ($record) => new HtmlString(get_username($record->owner, false, true, true, true)))
,
])
->defaultSort('id', 'desc')
->filters([
Tables\Filters\Filter::make('owner')
->form([
Forms\Components\TextInput::make('owner')
->label(__('label.torrent.owner'))
->placeholder('UID')
,
])->query(function (Builder $query, array $data) {
return $query->when($data['owner'], fn (Builder $query, $owner) => $query->where("owner", $owner));
})
,
Tables\Filters\SelectFilter::make('visible')
->options(self::$yesOrNo)
->label(__('label.torrent.visible')),
@@ -113,10 +135,18 @@ class TorrentResource extends Resource
->options(Torrent::listPromotionTypes(true))
->label(__('label.torrent.sp_state')),
Tables\Filters\SelectFilter::make('picktype')
->options(Torrent::listPickInfo(true))
->label(__('label.torrent.picktype')),
Tables\Filters\SelectFilter::make('approval_status')
->options(Torrent::listApprovalStatus(true))
->visible($showApproval)
->label(__('label.torrent.approval_status')),
Tables\Filters\SelectFilter::make('hr')
->options(self::getYesNoOptions())
->label(__('label.torrent.hr')),
])
->actions(self::getActions())
->bulkActions(self::getBulkActions());
@@ -146,6 +176,7 @@ class TorrentResource extends Resource
private static function getBulkActions(): array
{
$user = Auth::user();
$actions = [];
if (user_can('torrentsticky')) {
$actions[] = Tables\Actions\BulkAction::make('posState')
@@ -155,13 +186,77 @@ class TorrentResource extends Resource
->label(__('label.torrent.pos_state'))
->options(Torrent::listPosStates(true))
->required()
,
Forms\Components\DateTimePicker::make('pos_state_until')
->label(__('label.deadline'))
,
])
->icon('heroicon-o-arrow-circle-up')
->action(function (Collection $records, array $data) {
$idArr = $records->pluck('id')->toArray();
try {
$torrentRep = new TorrentRepository();
$torrentRep->setPosState($idArr, $data['pos_state']);
$torrentRep->setPosState($idArr, $data['pos_state'], $data['pos_state_until']);
} catch (\Exception $exception) {
do_log($exception->getMessage() . $exception->getTraceAsString(), 'error');
Filament::notify('danger', class_basename($exception));
}
})
->deselectRecordsAfterCompletion();
}
if (user_can('torrentonpromotion')) {
$actions[] = Tables\Actions\BulkAction::make('sp_state')
->label(__('admin.resources.torrent.bulk_action_sp_state'))
->form([
Forms\Components\Select::make('sp_state')
->label(__('label.torrent.sp_state'))
->options(Torrent::listPromotionTypes(true))
->required()
,
Forms\Components\Select::make('promotion_time_type')
->label(__('label.torrent.promotion_time_type'))
->options(Torrent::listPromotionTimeTypes(true))
->required()
,
Forms\Components\DateTimePicker::make('promotion_until')
->label(__('label.deadline'))
,
])
->icon('heroicon-o-speakerphone')
->action(function (Collection $records, array $data) {
$idArr = $records->pluck('id')->toArray();
try {
$torrentRep = new TorrentRepository();
$torrentRep->setSpState($idArr, $data['sp_state'], $data['promotion_time_type'], $data['promotion_until']);
} catch (\Exception $exception) {
do_log($exception->getMessage() . $exception->getTraceAsString(), 'error');
Filament::notify('danger', $exception->getMessage());
}
})
->deselectRecordsAfterCompletion();
}
if (user_can('torrentmanage') && ($user->picker == 'yes' || $user->class >= User::CLASS_SYSOP)) {
$actions[] = Tables\Actions\BulkAction::make('recommend')
->label(__('admin.resources.torrent.bulk_action_recommend'))
->form([
Forms\Components\Radio::make('picktype')
->label(__('admin.resources.torrent.bulk_action_recommend'))
->inline()
->options(Torrent::listPickInfo(true))
->required(),
])
->icon('heroicon-o-fire')
->action(function (Collection $records, array $data) {
if (empty($data['picktype'])) {
return;
}
$idArr = $records->pluck('id')->toArray();
try {
$torrentRep = new TorrentRepository();
$torrentRep->setPickType($idArr, $data['picktype']);
} catch (\Exception $exception) {
do_log($exception->getMessage() . $exception->getTraceAsString(), 'error');
Filament::notify('danger', class_basename($exception));
@@ -213,6 +308,38 @@ class TorrentResource extends Resource
}
})
->deselectRecordsAfterCompletion();
$actions[] = Tables\Actions\BulkAction::make('hr')
->label(__('admin.resources.torrent.bulk_action_hr'))
->form([
Forms\Components\Radio::make('hr')
->label(__('admin.resources.torrent.bulk_action_hr'))
->inline()
->options(self::getYesNoOptions())
->required(),
])
->icon('heroicon-o-sparkles')
->action(function (Collection $records, array $data) {
if (empty($data['hr'])) {
return;
}
$idArr = $records->pluck('id')->toArray();
try {
$torrentRep = new TorrentRepository();
$torrentRep->setHr($idArr, $data['hr']);
} catch (\Exception $exception) {
do_log($exception->getMessage() . $exception->getTraceAsString(), 'error');
Filament::notify('danger', class_basename($exception));
}
})
->deselectRecordsAfterCompletion();
}
if (user_can('torrent-delete')) {
$actions[] = Tables\Actions\DeleteBulkAction::make('bulk-delete')->using(function (Collection $records) {
deletetorrent($records->pluck('id')->toArray());
});
}
return $actions;
@@ -244,7 +371,7 @@ class TorrentResource extends Resource
});
}
if (user_can('torrentmanage')) {
if (user_can('torrent-delete')) {
$actions[] = Tables\Actions\DeleteAction::make('delete')->using(function ($record) {
deletetorrent($record->id);
});
@@ -7,6 +7,7 @@ use App\Filament\Resources\Torrent\TorrentResource;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ListRecords;
class ListTorrents extends PageList
{
protected static string $resource = TorrentResource::class;
@@ -17,4 +18,6 @@ class ListTorrents extends PageList
// Actions\CreateAction::make(),
];
}
}
+26 -2
View File
@@ -13,6 +13,7 @@ use Filament\Tables;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\HtmlString;
class ClaimResource extends Resource
{
@@ -48,7 +49,11 @@ class ClaimResource extends Resource
->columns([
Tables\Columns\TextColumn::make('id')->sortable(),
Tables\Columns\TextColumn::make('uid')->searchable(),
Tables\Columns\TextColumn::make('user.username')->label(__('label.user.label'))->searchable(),
Tables\Columns\TextColumn::make('user.username')
->label(__('label.user.label'))
->searchable()
->formatStateUsing(fn ($record) => new HtmlString(get_username($record->uid, false, true, true, true)))
,
Tables\Columns\TextColumn::make('torrent.name')->limit(40)->label(__('label.torrent.label'))->searchable(),
Tables\Columns\TextColumn::make('torrent.size')->label(__('label.torrent.size'))->formatStateUsing(fn (Model $record) => mksize($record->torrent->size)),
Tables\Columns\TextColumn::make('torrent.added')->label(__('label.torrent.ttl'))->formatStateUsing(fn (Model $record) => mkprettytime($record->torrent->added->diffInSeconds())),
@@ -60,7 +65,26 @@ class ClaimResource extends Resource
])
->defaultSort('id', 'desc')
->filters([
//
Tables\Filters\Filter::make('uid')
->form([
Forms\Components\TextInput::make('uid')
->label('UID')
->placeholder('UID')
,
])->query(function (Builder $query, array $data) {
return $query->when($data['uid'], fn (Builder $query, $uid) => $query->where("uid", $uid));
})
,
Tables\Filters\Filter::make('torrent_id')
->form([
Forms\Components\TextInput::make('torrent_id')
->label(__('claim.fields.torrent_id'))
->placeholder(__('claim.fields.torrent_id'))
,
])->query(function (Builder $query, array $data) {
return $query->when($data['torrent_id'], fn (Builder $query, $value) => $query->where("torrent_id", $value));
})
,
])
->actions([
// Tables\Actions\EditAction::make(),
@@ -4,6 +4,7 @@ namespace App\Filament\Resources\User;
use App\Filament\Resources\User\ExamUserResource\Pages;
use App\Filament\Resources\User\ExamUserResource\RelationManagers;
use App\Models\Exam;
use App\Models\ExamUser;
use App\Repositories\ExamRepository;
use App\Repositories\HitAndRunRepository;
@@ -16,6 +17,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\HtmlString;
class ExamUserResource extends Resource
{
@@ -51,7 +53,11 @@ class ExamUserResource extends Resource
->columns([
Tables\Columns\TextColumn::make('id')->sortable(),
Tables\Columns\TextColumn::make('uid')->searchable(),
Tables\Columns\TextColumn::make('user.username')->label(__('label.username'))->searchable(),
Tables\Columns\TextColumn::make('user.username')
->label(__('label.username'))
->searchable()
->formatStateUsing(fn ($record) => new HtmlString(get_username($record->uid, false, true, true, true)))
,
Tables\Columns\TextColumn::make('exam.name')->label(__('label.exam.label')),
Tables\Columns\TextColumn::make('begin')->label(__('label.begin'))->dateTime(),
Tables\Columns\TextColumn::make('end')->label(__('label.end'))->dateTime(),
@@ -61,6 +67,20 @@ class ExamUserResource extends Resource
])
->defaultSort('id', 'desc')
->filters([
Tables\Filters\Filter::make('uid')
->form([
Forms\Components\TextInput::make('uid')
->label('UID')
->placeholder('UID')
,
])->query(function (Builder $query, array $data) {
return $query->when($data['uid'], fn (Builder $query, $uid) => $query->where("uid", $uid));
})
,
Tables\Filters\SelectFilter::make('exam_id')
->options(Exam::query()->pluck('name', 'id')->toArray())
->label(__('exam.label'))
,
Tables\Filters\SelectFilter::make('status')->options(ExamUser::listStatus(true))->label(__("label.status")),
Tables\Filters\SelectFilter::make('is_done')->options(['0' => 'No', '1' => 'yes'])->label(__('label.exam_user.is_done')),
])
@@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\HtmlString;
class HitAndRunResource extends Resource
{
@@ -42,7 +43,12 @@ class HitAndRunResource extends Resource
->columns([
Tables\Columns\TextColumn::make('id')->sortable(),
Tables\Columns\TextColumn::make('uid')->searchable(),
Tables\Columns\TextColumn::make('user.username')->searchable()->label(__('label.username')),
Tables\Columns\TextColumn::make('user.username')
->searchable()
->label(__('label.username'))
->formatStateUsing(fn ($record) => new HtmlString(get_username($record->uid, false, true, true, true)))
,
Tables\Columns\TextColumn::make('torrent.name')->limit(30)->label(__('label.torrent.label')),
Tables\Columns\TextColumn::make('snatch.uploadText')->label(__('label.uploaded')),
Tables\Columns\TextColumn::make('snatch.downloadText')->label(__('label.downloaded')),
@@ -53,6 +59,16 @@ class HitAndRunResource extends Resource
])
->defaultSort('id', 'desc')
->filters([
Tables\Filters\Filter::make('uid')
->form([
Forms\Components\TextInput::make('uid')
->label('UID')
->placeholder('UID')
,
])->query(function (Builder $query, array $data) {
return $query->when($data['uid'], fn (Builder $query, $uid) => $query->where("uid", $uid));
})
,
Tables\Filters\SelectFilter::make('status')->options(HitAndRun::listStatus(true))->label(__('label.status')),
])
->actions([
@@ -72,7 +88,7 @@ class HitAndRunResource extends Resource
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()->with(['user', 'torrent', 'snatch']);
return parent::getEloquentQuery()->with(['user', 'torrent', 'snatch', 'torrent.basic_category']);
}
public static function getRelations(): array
@@ -4,6 +4,7 @@ namespace App\Filament\Resources\User;
use App\Filament\Resources\User\UserMedalResource\Pages;
use App\Filament\Resources\User\UserMedalResource\RelationManagers;
use App\Models\Medal;
use App\Models\UserMedal;
use Filament\Forms;
use Filament\Resources\Form;
@@ -12,6 +13,7 @@ use Filament\Resources\Table;
use Filament\Tables;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\HtmlString;
class UserMedalResource extends Resource
{
@@ -47,14 +49,31 @@ class UserMedalResource extends Resource
->columns([
Tables\Columns\TextColumn::make('id')->sortable(),
Tables\Columns\TextColumn::make('uid')->searchable(),
Tables\Columns\TextColumn::make('user.username')->label(__('label.username'))->searchable(),
Tables\Columns\TextColumn::make('user.username')
->label(__('label.username'))
->searchable()
->formatStateUsing(fn ($record) => new HtmlString(get_username($record->uid, false, true, true, true)))
,
Tables\Columns\TextColumn::make('medal.name')->label(__('label.medal.label'))->searchable(),
Tables\Columns\ImageColumn::make('medal.image_large')->label(__('label.image')),
Tables\Columns\TextColumn::make('expire_at')->label(__('label.expire_at'))->dateTime(),
])
->defaultSort('id', 'desc')
->filters([
Tables\Filters\Filter::make('uid')
->form([
Forms\Components\TextInput::make('uid')
->label('UID')
->placeholder('UID')
,
])->query(function (Builder $query, array $data) {
return $query->when($data['uid'], fn (Builder $query, $uid) => $query->where("uid", $uid));
})
,
Tables\Filters\SelectFilter::make('medal_id')
->options(Medal::query()->pluck('name', 'id')->toArray())
->label(__('medal.label'))
,
])
->actions([
Tables\Actions\DeleteAction::make(),
+10 -1
View File
@@ -61,7 +61,7 @@ class UserResource extends Resource
->columns([
Tables\Columns\TextColumn::make('id')->sortable()->searchable(),
Tables\Columns\TextColumn::make('username')->searchable()->label(__("label.user.username"))
->formatStateUsing(fn ($record) => new HtmlString(get_username($record->id, false, true, false, true))),
->formatStateUsing(fn ($record) => new HtmlString(get_username($record->id, false, true, true, true))),
Tables\Columns\TextColumn::make('email')->searchable()->label(__("label.email")),
Tables\Columns\TextColumn::make('class')->label('Class')
->formatStateUsing(fn(Tables\Columns\Column $column) => $column->getRecord()->classText)
@@ -80,6 +80,15 @@ class UserResource extends Resource
])
->defaultSort('added', 'desc')
->filters([
Tables\Filters\Filter::make('id')
->form([
Forms\Components\TextInput::make('id')
->placeholder('UID')
,
])->query(function (Builder $query, array $data) {
return $query->when($data['id'], fn (Builder $query, $id) => $query->where("id", $id));
})
,
Tables\Filters\SelectFilter::make('class')->options(array_column(User::$classes, 'text'))->label(__('label.user.class')),
Tables\Filters\SelectFilter::make('status')->options(['confirmed' => 'confirmed', 'pending' => 'pending'])->label(__('label.user.status')),
Tables\Filters\SelectFilter::make('enabled')->options(self::$yesOrNo)->label(__('label.user.enabled')),
@@ -7,6 +7,7 @@ use App\Filament\Resources\User\UserResource;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Model;
use Filament\Tables\Filters\Layout;
class ListUsers extends PageList
{
@@ -25,6 +26,9 @@ class ListUsers extends PageList
// }
protected function getTableFiltersLayout(): ?string
{
return Layout::AboveContent;
}
}
@@ -278,11 +278,6 @@ class UserProfile extends ViewRecord
->action(function ($data) {
$rep = $this->getRep();
try {
if (!empty($data['duration'])) {
$data['deadline'] = now()->addDays($data['duration']);
} else {
$data['deadline'] = null;
}
$rep->addMeta($this->record, $data, $data);
$this->notify('success', 'Success!');
$this->emitSelf(self::EVENT_RECORD_UPDATED, $this->record->id);
+2
View File
@@ -25,6 +25,7 @@ class BonusLogs extends NexusModel
const BUSINESS_TYPE_NO_AD = 11;
const BUSINESS_TYPE_GIFT_TO_LOW_SHARE_RATIO = 12;
const BUSINESS_TYPE_LUCKY_DRAW = 13;
const BUSINESS_TYPE_EXCHANGE_DOWNLOAD = 14;
public static array $businessTypes = [
self::BUSINESS_TYPE_CANCEL_HIT_AND_RUN => ['text' => 'Cancel H&R'],
@@ -40,6 +41,7 @@ class BonusLogs extends NexusModel
self::BUSINESS_TYPE_NO_AD => ['text' => 'No ad'],
self::BUSINESS_TYPE_GIFT_TO_LOW_SHARE_RATIO => ['text' => 'Gift to low share ratio'],
self::BUSINESS_TYPE_LUCKY_DRAW => ['text' => 'Lucky draw'],
self::BUSINESS_TYPE_EXCHANGE_DOWNLOAD => ['text' => 'Exchange download'],
];
public static function getBonusForCancelHitAndRun()
+59 -5
View File
@@ -2,6 +2,8 @@
namespace App\Models;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidArgumentException;
use Illuminate\Database\Eloquent\Casts\Attribute;
class HitAndRun extends NexusModel
@@ -17,13 +19,18 @@ class HitAndRun extends NexusModel
const STATUS_UNREACHED = 3;
const STATUS_PARDONED = 4;
public static $status = [
public static array $status = [
self::STATUS_INSPECTING => ['text' => 'Inspecting'],
self::STATUS_REACHED => ['text' => 'Reached'],
self::STATUS_UNREACHED => ['text' => 'Unreached'],
self::STATUS_PARDONED => ['text' => 'Pardoned'],
];
const CAN_PARDON_STATUS = [
self::STATUS_INSPECTING,
self::STATUS_UNREACHED,
];
const MODE_DISABLED = 'disabled';
const MODE_MANUAL = 'manual';
const MODE_GLOBAL = 'global';
@@ -39,17 +46,37 @@ class HitAndRun extends NexusModel
protected function seedTimeRequired(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => $this->status == self::STATUS_INSPECTING ? mkprettytime(3600 * Setting::get('hr.seed_time_minimum') - $this->snatch->seedtime) : '---'
get: fn($value, $attributes) => $this->doGetSeedTimeRequired()
);
}
protected function inspectTimeLeft(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => $this->status == self::STATUS_INSPECTING ? mkprettytime(\Carbon\Carbon::now()->diffInSeconds($this->snatch->completedat->addHours(Setting::get('hr.inspect_time')))) : '---'
get: fn($value, $attributes) => $this->doGetInspectTimeLeft()
);
}
private function doGetInspectTimeLeft(): string
{
if ($this->status != self::STATUS_INSPECTING) {
return '---';
}
$inspectTime = HitAndRun::getConfig('inspect_time', $this->torrent->basic_category->mode);
$diffInSeconds = Carbon::now()->diffInSeconds($this->snatch->completedat->addHours($inspectTime));
return mkprettytime($diffInSeconds);
}
private function doGetSeedTimeRequired(): string
{
if ($this->status != self::STATUS_INSPECTING) {
return '---';
}
$seedTimeMinimum = HitAndRun::getConfig('seed_time_minimum', $this->torrent->basic_category->mode);
$diffInSeconds = 3600 * $seedTimeMinimum - $this->snatch->seedtime;
return mkprettytime($diffInSeconds);
}
public function getStatusTextAttribute()
{
return nexus_trans('hr.status_' . $this->status);
@@ -87,8 +114,35 @@ class HitAndRun extends NexusModel
public static function getIsEnabled(): bool
{
$result = Setting::get('hr.mode');
return $result && in_array($result, [self::MODE_GLOBAL, self::MODE_MANUAL]);
$enableSpecialSection = Setting::get('main.spsct') == 'yes';
$browseMode = self::getConfig('mode', Setting::get('main.browsecat'));
$browseEnabled = $browseMode && in_array($browseMode, [self::MODE_GLOBAL, self::MODE_MANUAL]);
if (!$enableSpecialSection) {
do_log("Not enable special section, browseEnabled: $browseEnabled");
return $browseEnabled;
}
$specialMode = self::getConfig('mode', Setting::get('main.specialcat'));
$specialEnabled = $specialMode && in_array($specialMode, [self::MODE_GLOBAL, self::MODE_MANUAL]);
$result = $browseEnabled || $specialEnabled;
do_log("Enable special section, browseEnabled: $browseEnabled, specialEnabled: $specialEnabled, result: $result");
return $result;
}
public static function getConfig($name, $searchBoxId)
{
if ($name == '*') {
$key = "hr";
} else {
$key = "hr.$name";
}
$default = Setting::get($key);
return apply_filter("nexus_setting_get", $default, $name, ['mode' => $searchBoxId]);
}
public static function diffInSection(): bool
{
$enableSpecialSection = Setting::get('main.spsct') == 'yes';
return $enableSpecialSection && apply_filter("hit_and_run_diff_in_section", false);
}
public function torrent(): \Illuminate\Database\Eloquent\Relations\BelongsTo
+15
View File
@@ -51,4 +51,19 @@ class NexusModel extends Model
return sprintf('%s: %s', nexus_trans('label.deadline'), $raw);
}
public static function listStaticProps($dataSource, $textTransPrefix, $onlyKeyValue = false, $valueField = 'text'): array
{
$result = $dataSource;
$keyValue = [];
foreach ($result as $key => &$info) {
$text = $textTransPrefix ? nexus_trans("$textTransPrefix.$key") : $info['text'];
$info['text'] = $text;
$keyValue[$key] = $info[$valueField];
}
if ($onlyKeyValue) {
return $keyValue;
}
return $result;
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Plugin extends NexusModel
{
protected $fillable = ['display_name', 'package_name', 'remote_url', 'installed_version', 'status', 'description', 'status_result'];
const STATUS_NOT_INSTALLED = -1;
const STATUS_NORMAL = 0;
const STATUS_PRE_INSTALL = 1;
const STATUS_INSTALLING = 2;
const STATUS_INSTALL_FAILED = 3;
const STATUS_PRE_UPDATE = 11;
const STATUS_UPDATING = 12;
const STATUS_UPDATE_FAILED = 13;
const STATUS_PRE_DELETE = 101;
const STATUS_DELETING = 102;
const STATUS_DELETE_FAILED = 103;
public function statusText(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => __('plugin.status.' . $attributes['status'])
);
}
}
+37
View File
@@ -24,6 +24,13 @@ class SearchBox extends NexusModel
];
const EXTRA_TAXONOMY_LABELS = 'taxonomy_labels';
const SECTION_BROWSE = 'browse';
const SECTION_SPECIAL = 'special';
public static array $sections = [
self::SECTION_BROWSE => ['text' => 'Browse'],
self::SECTION_SPECIAL => ['text' => 'Special'],
];
const EXTRA_DISPLAY_COVER_ON_TORRENT_LIST = 'display_cover_on_torrent_list';
const EXTRA_DISPLAY_SEED_BOX_ICON_ON_TORRENT_LIST = 'display_seed_box_icon_on_torrent_list';
@@ -89,6 +96,36 @@ class SearchBox extends NexusModel
return array_combine(array_keys(self::$taxonomies), array_keys(self::$taxonomies));
}
public static function listSections($field = null): array
{
$result = [];
foreach (self::$sections as $key => $value) {
$value['text'] = nexus_trans("searchbox.sections.$key");
$value['mode'] = Setting::get("main.{$key}cat");
if ($field !== null && isset($value[$field])) {
$result[$key] = $value[$field];
} else {
$result[$key] = $value;
}
}
return $result;
}
public function getCustomFieldsAttribute($value): array
{
if (!is_array($value)) {
return explode(',', $value);
}
}
public function setCustomFieldsAttribute($value)
{
if (is_array($value)) {
$this->attributes['custom_fields'] = implode(',', $value);
}
}
public function categories(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Category::class, 'mode');
+8
View File
@@ -48,6 +48,14 @@ class Tag extends NexusModel
],
];
public static function listSpecial(): array
{
return array_filter([
Setting::get('system.official_tag'),
Setting::get('system.zero_bonus_tag'),
]);
}
public function torrents(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Torrent::class, 'torrent_tags', 'tag_id', 'torrent_id');
+56 -11
View File
@@ -3,9 +3,8 @@
namespace App\Models;
use App\Repositories\TagRepository;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Casts\Attribute;
use JeroenG\Explorer\Application\Explored;
use Laravel\Scout\Searchable;
class Torrent extends NexusModel
{
@@ -15,11 +14,9 @@ class Torrent extends NexusModel
'size', 'added', 'type', 'numfiles', 'owner', 'nfo', 'sp_state', 'promotion_time_type',
'promotion_until', 'anonymous', 'url', 'pos_state', 'cache_stamp', 'picktype', 'picktime',
'last_reseed', 'pt_gen', 'technical_info', 'leechers', 'seeders', 'cover', 'last_action',
'times_completed', 'approval_status', 'banned', 'visible',
'times_completed', 'approval_status', 'banned', 'visible', 'pos_state_until',
];
private static $globalPromotionState;
const VISIBLE_YES = 'yes';
const VISIBLE_NO = 'no';
@@ -30,11 +27,13 @@ class Torrent extends NexusModel
'added' => 'datetime',
'pt_gen' => 'array',
'promotion_until' => 'datetime',
'pos_state_until' => 'datetime',
];
public static $commentFields = [
'id', 'name', 'added', 'visible', 'banned', 'owner', 'sp_state', 'pos_state', 'hr', 'picktype', 'picktime',
'last_action', 'leechers', 'seeders', 'times_completed', 'views', 'size', 'cover', 'anonymous', 'approval_status'
'last_action', 'leechers', 'seeders', 'times_completed', 'views', 'size', 'cover', 'anonymous', 'approval_status',
'pos_state_until', 'category'
];
public static $basicRelations = [
@@ -129,6 +128,16 @@ class Torrent extends NexusModel
self::PICK_RECOMMENDED => ['text' => self::PICK_RECOMMENDED, 'color' => '#820084'],
];
const PROMOTION_TIME_TYPE_GLOBAL = 0;
const PROMOTION_TIME_TYPE_PERMANENT = 1;
const PROMOTION_TIME_TYPE_DEADLINE = 2;
public static array $promotionTimeTypes = [
self::PROMOTION_TIME_TYPE_GLOBAL => ['text' => 'Global'],
self::PROMOTION_TIME_TYPE_PERMANENT => ['text' => 'Permanent'],
self::PROMOTION_TIME_TYPE_DEADLINE => ['text' => 'Until'],
];
const BONUS_REWARD_VALUES = [50, 100, 200, 500, 1000];
const APPROVAL_STATUS_NONE = 0;
@@ -153,6 +162,14 @@ class Torrent extends NexusModel
],
];
const NFO_VIEW_STYLE_DOS = 'magic';
const NFO_VIEW_STYLE_WINDOWS = 'latin-1';
public static array $nfoViewStyles = [
self::NFO_VIEW_STYLE_DOS => ['text' => 'DOS-vy'],
self::NFO_VIEW_STYLE_WINDOWS => ['text' => 'Windows-vy'],
];
public function getPickInfoAttribute()
{
$info = self::$pickTypes[$this->picktype] ?? null;
@@ -193,11 +210,18 @@ class Torrent extends NexusModel
return $spState;
}
protected function posStateText(): Attribute
protected function getPosStateTextAttribute()
{
return new Attribute(
get: fn($value, $attributes) => nexus_trans('torrent.pos_state_' . $attributes['pos_state'])
);
$text = nexus_trans('torrent.pos_state_' . $this->pos_state);
if ($this->pos_state != Torrent::POS_STATE_STICKY_NONE) {
if ($this->pos_state_until) {
$append = format_datetime($this->pos_state_until);
} else {
$append = nexus_trans('label.permanent');
}
$text .= "($append)";
}
return $text;
}
protected function approvalStatusText(): Attribute
@@ -256,9 +280,30 @@ class Torrent extends NexusModel
return $result;
}
public static function listPromotionTimeTypes($onlyKeyValue = false, $valueField = 'text'): array
{
return self::listStaticProps(self::$promotionTimeTypes, 'torrent.promotion_time_types', $onlyKeyValue, $valueField);
}
public static function listPickInfo($onlyKeyValue = false, $valueField = 'text'): array
{
$result = self::$pickTypes;
$keyValue = [];
foreach ($result as $status => &$info) {
$text = nexus_trans('torrent.pick_info.' . $status);
$info['text'] = $text;
$keyValue[$status] = $info[$valueField];
}
if ($onlyKeyValue) {
return $keyValue;
}
return $result;
}
public function getHrAttribute(): string
{
$hrMode = Setting::get('hr.mode');
// $hrMode = Setting::get('hr.mode');
$hrMode = HitAndRun::getConfig('mode', $this->basic_category->mode);
if ($hrMode == HitAndRun::MODE_GLOBAL) {
return self::HR_YES;
}
+5 -1
View File
@@ -2,13 +2,14 @@
namespace App\Models;
use Nexus\Database\NexusDB;
class TorrentCustomField extends NexusModel
{
protected $table = 'torrents_custom_fields';
protected $fillable = [
'name', 'label', 'type', 'required', 'is_single_row', 'options', 'help'
'name', 'label', 'type', 'required', 'is_single_row', 'options', 'help', 'display', 'priority'
];
public static function getCheckboxOptions(): array
@@ -20,4 +21,7 @@ class TorrentCustomField extends NexusModel
}
return $result;
}
public $timestamps = true;
}
+11
View File
@@ -2,12 +2,23 @@
namespace App\Models;
<<<<<<< HEAD
=======
use Nexus\Database\NexusDB;
>>>>>>> php8
class TorrentCustomFieldValue extends NexusModel
{
protected $table = 'torrents_custom_field_values';
<<<<<<< HEAD
protected $fillable = [
'torrent_id', 'custom_field_id', 'custom_field_value',
];
=======
public $timestamps = true;
protected $fillable = ['torrent_id', 'custom_field_id', 'custom_field_value', ];
>>>>>>> php8
}
+3 -1
View File
@@ -211,7 +211,7 @@ class User extends Authenticatable implements FilamentUser, HasName
'id', 'username', 'email', 'class', 'status', 'added', 'avatar',
'uploaded', 'downloaded', 'seedbonus', 'seedtime', 'leechtime',
'invited_by', 'enabled', 'seed_points', 'last_access', 'invites',
'lang', 'attendance_card', 'privacy', 'noad', 'downloadpos',
'lang', 'attendance_card', 'privacy', 'noad', 'downloadpos', 'donoruntil', 'donor'
];
public static function getDefaultUserAttributes(): array
@@ -535,4 +535,6 @@ class User extends Authenticatable implements FilamentUser, HasName
}
}
+19
View File
@@ -7,4 +7,23 @@ class UserBanLog extends NexusModel
protected $table = 'user_ban_logs';
protected $fillable = ['uid', 'username', 'operator', 'reason'];
public static function clearUserBanLogDuplicate()
{
$lists = UserBanLog::query()
->selectRaw("min(id) as id, uid, count(*) as counts")
->groupBy('uid')
->having("counts", ">", 1)
->get();
if ($lists->isEmpty()) {
do_log("sql: " . last_query() . ", no data to delete");
return;
}
$idArr = $lists->pluck("id")->toArray();
$uidArr = $lists->pluck('uid')->toArray();
$result = UserBanLog::query()->whereIn("uid", $uidArr)->whereNotIn("id", $idArr)->delete();
do_log("sql: " . last_query() . ", result: $result");
}
}
+20 -4
View File
@@ -22,14 +22,14 @@ class BaseRepository
protected function handleAnonymous($username, User $user, User $authenticator, Torrent $torrent = null)
{
$canViewAnonymousClass = Setting::get('authority.viewanonymous');
if($user->privacy == "strong" || ($torrent && $torrent->anonymous == 'yes' && $user->id == $torrent->owner)) {
//用户强私密,或者种子作者匿名而当前项作者刚好为种子作者
if($authenticator->class >= $canViewAnonymousClass || $user->id == $authenticator->id) {
$anonymousText = nexus_trans('label.anonymous');
if(user_can('viewanonymous', false, $authenticator->id) || $user->id == $authenticator->id) {
//但当前用户权限可以查看匿名者,或当前用户查看自己的数据,显示个匿名,后边加真实用户名
return sprintf('匿名(%s)', $username);
return sprintf('%s(%s)', $anonymousText, $username);
} else {
return '匿名';
return $anonymousText;
}
} else {
return $username;
@@ -55,4 +55,20 @@ class BaseRepository
return User::query()->findOrFail(intval($user), $fields);
}
protected function executeCommand($command, $format = 'string'): string|array
{
$append = " 2>&1";
if (!str_ends_with($command, $append)) {
$command .= $append;
}
do_log("command: $command");
$result = exec($command, $output, $result_code);
$outputString = implode("\n", $output);
do_log(sprintf('result_code: %s, result: %s, output: %s', $result_code, $result, $outputString));
if ($result_code != 0) {
throw new \RuntimeException($outputString);
}
return $format == 'string' ? $outputString : $output;
}
}
+128 -49
View File
@@ -3,13 +3,16 @@ namespace App\Repositories;
use App\Models\HitAndRun;
use App\Models\Message;
use App\Models\SearchBox;
use App\Models\Setting;
use App\Models\User;
use App\Models\UserBanLog;
use Carbon\Carbon;
use Elasticsearch\Endpoints\Search;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Nexus\Database\NexusDB;
class HitAndRunRepository extends BaseRepository
{
@@ -87,12 +90,31 @@ class HitAndRunRepository extends BaseRepository
return $query;
}
public function cronjobUpdateStatus($uid = null, $torrentId = null, $ignoreTime = false): bool|int
public function cronjobUpdateStatus($uid = null, $torrentId = null, $ignoreTime = false)
{
$diffInSection = HitAndRun::diffInSection();
$browseMode = Setting::get('main.browsecat');
$setting = HitAndRun::getConfig('*', $browseMode);
$setting['diff_in_section'] = $diffInSection;
$setting['search_box_id'] = $browseMode;
$this->doCronjobUpdateStatus($setting, $uid, $torrentId, $ignoreTime);
$this->checkAndDisableUser($setting);
$specialMode = Setting::get('main.specialcat');
if ($diffInSection && $browseMode != $specialMode) {
$setting = HitAndRun::getConfig('*', $specialMode);
$setting['diff_in_section'] = $diffInSection;
$setting['search_box_id'] = $specialMode;
$this->doCronjobUpdateStatus($setting, $uid, $torrentId, $ignoreTime);
$this->checkAndDisableUser($setting);
}
}
private function doCronjobUpdateStatus(array $setting, $uid = null, $torrentId = null, $ignoreTime = false)
{
do_log("uid: $uid, torrentId: $torrentId, ignoreTime: " . var_export($ignoreTime, true));
$size = 1000;
$page = 1;
$setting = Setting::get('hr');
if (empty($setting['mode'])) {
do_log("H&R not set.");
return false;
@@ -108,7 +130,7 @@ class HitAndRunRepository extends BaseRepository
$query = HitAndRun::query()
->where('status', HitAndRun::STATUS_INSPECTING)
->with([
'torrent' => function ($query) {$query->select(['id', 'size', 'name']);},
'torrent' => function ($query) {$query->select(['id', 'size', 'name', 'category']);},
'snatch',
'user' => function ($query) {$query->select(['id', 'username', 'lang', 'class', 'donoruntil', 'enabled']);},
'user.language',
@@ -122,6 +144,12 @@ class HitAndRunRepository extends BaseRepository
if (!$ignoreTime) {
$query->where('created_at', '<', Carbon::now()->subHours($setting['inspect_time']));
}
if ($setting['diff_in_section']) {
$query->whereHas('torrent.basic_category', function (Builder $query) use ($setting) {
return $query->where('mode', $setting['search_box_id']);
});
}
$successCounts = 0;
$disabledUsers = [];
while (true) {
@@ -164,7 +192,7 @@ class HitAndRunRepository extends BaseRepository
$requireSeedTime = bcmul($setting['seed_time_minimum'], 3600);
do_log("$currentLog, targetSeedTime: $targetSeedTime, requireSeedTime: $requireSeedTime");
if ($targetSeedTime >= $requireSeedTime) {
$result = $this->reachedBySeedTime($row);
$result = $this->reachedBySeedTime($row, $setting);
if ($result) {
$successCounts++;
}
@@ -176,7 +204,7 @@ class HitAndRunRepository extends BaseRepository
$requireShareRatio = $setting['ignore_when_ratio_reach'];
do_log("$currentLog, targetShareRatio: $targetShareRatio, requireShareRatio: $requireShareRatio");
if ($targetShareRatio >= $requireShareRatio) {
$result = $this->reachedByShareRatio($row);
$result = $this->reachedByShareRatio($row, $setting);
if ($result) {
$successCounts++;
}
@@ -185,7 +213,7 @@ class HitAndRunRepository extends BaseRepository
//unreached
if ($row->created_at->addHours($setting['inspect_time'])->lte(Carbon::now())) {
$result = $this->unreached($row, !isset($disabledUsers[$row->uid]));
$result = $this->unreached($row, $setting, !isset($disabledUsers[$row->uid]));
if ($result) {
$successCounts++;
$disabledUsers[$row->uid] = true;
@@ -212,15 +240,15 @@ class HitAndRunRepository extends BaseRepository
];
}
private function reachedByShareRatio(HitAndRun $hitAndRun): bool
private function reachedByShareRatio(HitAndRun $hitAndRun, array $setting): bool
{
do_log(__METHOD__);
$comment = nexus_trans('hr.reached_by_share_ratio_comment', [
'now' => Carbon::now()->toDateTimeString(),
'seed_time_minimum' => Setting::get('hr.seed_time_minimum'),
'seed_time_minimum' => $setting['seed_time_minimum'],
'seed_time' => bcdiv($hitAndRun->snatch->seedtime, 3600, 1),
'share_ratio' => get_hr_ratio($hitAndRun->snatch->uploaded, $hitAndRun->snatch->downloaded),
'ignore_when_ratio_reach' => Setting::get('hr.ignore_when_ratio_reach'),
'ignore_when_ratio_reach' => $setting['ignore_when_ratio_reach'],
], $hitAndRun->user->locale);
$update = [
'comment' => $comment
@@ -228,13 +256,13 @@ class HitAndRunRepository extends BaseRepository
return $this->inspectingToReached($hitAndRun, $update, __FUNCTION__);
}
private function reachedBySeedTime(HitAndRun $hitAndRun): bool
private function reachedBySeedTime(HitAndRun $hitAndRun, array $setting): bool
{
do_log(__METHOD__);
$comment = nexus_trans('hr.reached_by_seed_time_comment', [
'now' => Carbon::now()->toDateTimeString(),
'seed_time' => bcdiv($hitAndRun->snatch->seedtime, 3600, 1),
'seed_time_minimum' => Setting::get('hr.seed_time_minimum')
'seed_time_minimum' => $setting['seed_time_minimum'],
], $hitAndRun->user->locale);
$update = [
'comment' => $comment
@@ -271,16 +299,16 @@ class HitAndRunRepository extends BaseRepository
return true;
}
private function unreached(HitAndRun $hitAndRun, $disableUser = true): bool
private function unreached(HitAndRun $hitAndRun, array $setting, $disableUser = true): bool
{
do_log(sprintf('hitAndRun: %s, disableUser: %s', $hitAndRun->toJson(), var_export($disableUser, true)));
$comment = nexus_trans('hr.unreached_comment', [
'now' => Carbon::now()->toDateTimeString(),
'seed_time' => bcdiv($hitAndRun->snatch->seedtime, 3600, 1),
'seed_time_minimum' => Setting::get('hr.seed_time_minimum'),
'seed_time_minimum' => $setting['seed_time_minimum'],
'share_ratio' => get_hr_ratio($hitAndRun->snatch->uploaded, $hitAndRun->snatch->downloaded),
'torrent_size' => mksize($hitAndRun->torrent->size),
'ignore_when_ratio_reach' => Setting::get('hr.ignore_when_ratio_reach')
'ignore_when_ratio_reach' => $setting['ignore_when_ratio_reach']
], $hitAndRun->user->locale);
$update = [
'status' => HitAndRun::STATUS_UNREACHED,
@@ -307,31 +335,43 @@ class HitAndRunRepository extends BaseRepository
];
Message::query()->insert($message);
if (!$disableUser) {
do_log("[DO_NOT_DISABLE_USER], return");
return true;
return true;
}
private function checkAndDisableUser(array $setting): void
{
$disableCounts = HitAndRun::getConfig('ban_user_when_counts_reach', $setting['search_box_id']);
$query = HitAndRun::query()
->selectRaw("count(*) as counts, uid")
->where('status', HitAndRun::STATUS_UNREACHED)
->groupBy('uid')
->having("counts", '>=', $disableCounts)
;
if ($setting['diff_in_section']) {
$query->whereHas('torrent.basic_category', function (Builder $query) use ($setting) {
return $query->where('mode', $setting['search_box_id']);
});
}
if ($hitAndRun->user->enabled == 'no') {
do_log("[USER_ALREADY_DISABLED], return");
return true;
$result = $query->get();
if ($result->isEmpty()) {
do_log("No user to disable");
return;
}
//disable user
/** @var User $user */
$user = $hitAndRun->user;
$counts = $user->hitAndRuns()->where('status', HitAndRun::STATUS_UNREACHED)->count();
$disableCounts = Setting::get('hr.ban_user_when_counts_reach');
do_log("user: {$user->id}, H&R counts: $counts, disableCounts: $disableCounts", 'notice');
if ($counts >= $disableCounts) {
do_log("[DISABLE_USER_DUE_TO_H&R_UNREACHED]", 'notice');
$comment = nexus_trans('hr.unreached_disable_comment', [], $user->locale);
$user->updateWithModComment(['enabled' => User::ENABLED_NO], $comment);
$users = User::query()
->with('language')
->where('enabled', User::ENABLED_YES)
->find($result->pluck('id')->toArray(), ['id', 'username', 'lang']);
foreach ($users as $user) {
$locale = $user->locale;
$comment = nexus_trans('hr.unreached_disable_comment', [], $locale);
$user->updateWithModComment(['enabled' => User::ENABLED_NO], sprintf('%s - %s', date('Y-m-d'), $comment));
$message = [
'receiver' => $hitAndRun->uid,
'receiver' => $user->id,
'added' => Carbon::now()->toDateTimeString(),
'subject' => $comment,
'msg' => nexus_trans('hr.unreached_disable_message_content', [
'ban_user_when_counts_reach' => Setting::get('hr.ban_user_when_counts_reach'),
], $hitAndRun->user->locale),
], $locale),
];
Message::query()->insert($message);
$userBanLog = [
@@ -340,30 +380,59 @@ class HitAndRunRepository extends BaseRepository
'reason' => $comment
];
UserBanLog::query()->insert($userBanLog);
do_log("Disable user: " . nexus_json_encode($userBanLog));
}
return true;
}
public function getStatusStats($uid, $formatted = true)
{
$results = HitAndRun::query()->where('uid', $uid)
->selectRaw('status, count(*) as counts')
->groupBy('status')
->get()
->pluck('counts', 'status');
if ($formatted) {
return sprintf(
'%s/%s/%s',
$results->get(HitAndRun::STATUS_INSPECTING, 0),
$results->get(HitAndRun::STATUS_UNREACHED, 0),
Setting::get('hr.ban_user_when_counts_reach')
);
$diffInSection = HitAndRun::diffInSection();
if ($diffInSection) {
$sql = "select hit_and_runs.status, categories.mode, count(*) as counts from hit_and_runs left join torrents on torrents.id = hit_and_runs.torrent_id left join categories on categories.id = torrents.category where hit_and_runs.uid = $uid group by hit_and_runs.status, categories.mode";
} else {
$sql = "select hit_and_runs.status, count(*) as counts from hit_and_runs where uid = $uid group by status";
}
$results = NexusDB::select($sql);
do_log("user: $uid, sql: $sql, results: " . json_encode($results));
if (!$formatted) {
return $results;
}
if ($diffInSection) {
$grouped = [];
foreach ($results as $item) {
$grouped[$item['mode']][$item['status']] = $item['counts'];
}
$out = [];
foreach (SearchBox::listSections() as $key => $info) {
$out[] = sprintf(
'%s: %s/%s/%s',
$info['text'],
$grouped[$info['mode']][HitAndRun::STATUS_INSPECTING] ?? 0,
$grouped[$info['mode']][HitAndRun::STATUS_UNREACHED] ?? 0,
HitAndRun::getConfig('ban_user_when_counts_reach', $info['mode'])
);
}
return implode(" ", $out);
} else {
$grouped = [];
foreach ($results as $item) {
$grouped[$item['status']] = $item['counts'];
}
foreach (SearchBox::listSections() as $key => $info) {
if ($key == SearchBox::SECTION_BROWSE) {
return sprintf(
'%s/%s/%s',
$grouped[HitAndRun::STATUS_INSPECTING] ?? 0,
$grouped[HitAndRun::STATUS_UNREACHED] ?? 0,
HitAndRun::getConfig('ban_user_when_counts_reach', $info['mode'])
);
}
}
}
return $results;
}
public function listStatus(): array
{
$results = [];
@@ -407,6 +476,16 @@ class HitAndRunRepository extends BaseRepository
private function getCanPardonStatus(): array
{
return [HitAndRun::STATUS_INSPECTING, HitAndRun::STATUS_UNREACHED];
return HitAndRun::CAN_PARDON_STATUS;
}
public function renderOnUploadPage($value, $searchBoxId): string
{
if (HitAndRun::getConfig('mode', $searchBoxId) == \App\Models\HitAndRun::MODE_MANUAL && user_can('torrent_hr')) {
$hrRadio = sprintf('<label><input type="radio" name="hr[%s]" value="0"%s />NO</label>', $searchBoxId, $value == 0 ? ' checked' : '');
$hrRadio .= sprintf('<label><input type="radio" name="hr[%s]" value="1"%s />YES</label>', $searchBoxId, $value == 1 ? ' checked' : '');
return tr('H&R', $hrRadio, 1, "mode_$searchBoxId", true);
}
return '';
}
}
+187
View File
@@ -0,0 +1,187 @@
<?php
namespace App\Repositories;
use App\Models\Plugin;
class PluginRepository extends BaseRepository
{
public function cronjob($action = null, $id = null, $force = false)
{
if ($action == 'install' || $action === null) {
$this->doCronjob('install', $id, $force, Plugin::STATUS_PRE_INSTALL, Plugin::STATUS_INSTALLING);
}
if ($action == 'delete' || $action === null) {
$this->doCronjob('delete', $id, $force, Plugin::STATUS_PRE_DELETE, Plugin::STATUS_DELETING);
}
if ($action == 'update' || $action === null) {
$this->doCronjob('update', $id, $force, Plugin::STATUS_PRE_UPDATE, Plugin::STATUS_UPDATING);
}
}
private function doCronjob($action, $id, $force, $preStatus, $doingStatus)
{
$query = Plugin::query();
if (!$force) {
$query->where('status', $preStatus);
}
if ($id !== null) {
$query->where("id", $id);
}
$list = $query->get();
if ($list->isEmpty()) {
do_log("No plugin need to be $action...");
return;
}
$idArr = $list->pluck('id')->toArray();
Plugin::query()->whereIn('id', $idArr)->update(['status' => $doingStatus]);
foreach ($list as $item) {
match ($action) {
'install' => $this->doInstall($item),
'update' => $this->doUpdate($item),
'delete' => $this->doDelete($item),
default => throw new \InvalidArgumentException("Invalid action: $action")
};
}
}
public function doInstall(Plugin $plugin)
{
$packageName = $plugin->package_name;
try {
$this->execComposerConfig($plugin);
$this->execComposerRequire($plugin);
$output = $this->execPluginInstall($plugin);
$version = $this->getInstalledVersion($packageName);
do_log("success install plugin: $packageName version: $version");
$update = [
'status' => Plugin::STATUS_NORMAL,
'status_result' => $output,
'installed_version' => $version
];
} catch (\Throwable $throwable) {
$update = [
'status' => Plugin::STATUS_INSTALL_FAILED,
'status_result' => $throwable->getMessage()
];
do_log("fail install plugin: " . $packageName);
} finally {
$this->updateResult($plugin, $update);
}
}
public function doDelete(Plugin $plugin)
{
$packageName = $plugin->package_name;
$removeSuccess = true;
try {
$output = $this->execComposerRemove($plugin);
do_log("success remove plugin: $packageName");
$update = [
'status' => Plugin::STATUS_NOT_INSTALLED,
'status_result' => $output,
'installed_version' => null,
];
} catch (\Throwable $throwable) {
$update = [
'status' => Plugin::STATUS_DELETE_FAILED,
'status_result' => $throwable->getMessage()
];
$removeSuccess = false;
do_log("fail remove plugin: " . $packageName);
} finally {
if ($removeSuccess) {
$plugin->delete();
} else {
$this->updateResult($plugin, $update);
}
}
}
public function doUpdate(Plugin $plugin)
{
$packageName = $plugin->package_name;
try {
$output = $this->execComposerUpdate($plugin);
$this->execPluginInstall($plugin);
$version = $this->getInstalledVersion($packageName);
do_log("success update plugin: $packageName to version: $version");
$update = [
'status' => Plugin::STATUS_NORMAL,
'status_result' => $output,
'installed_version' => $version,
];
} catch (\Throwable $throwable) {
$update = [
'status' => Plugin::STATUS_UPDATE_FAILED,
'status_result' => $throwable->getMessage()
];
do_log("fail update plugin: " . $packageName);
} finally {
$this->updateResult($plugin, $update);
}
}
private function getRepositoryKey(Plugin $plugin)
{
return str_replace("xiaomlove/nexusphp-", "", $plugin->package_name);
}
private function execComposerConfig(Plugin $plugin)
{
$command = sprintf("composer config repositories.%s git %s", $this->getRepositoryKey($plugin), $plugin->remote_url);
do_log("[COMPOSER_CONFIG]: $command");
return $this->executeCommand($command);
}
private function execComposerRequire(Plugin $plugin)
{
$command = sprintf("composer require %s", $plugin->package_name);
do_log("[COMPOSER_REQUIRE]: $command");
return $this->executeCommand($command);
}
private function execComposerRemove(Plugin $plugin)
{
$command = sprintf("composer remove %s", $plugin->package_name);
do_log("[COMPOSER_REMOVE]: $command");
return $this->executeCommand($command);
}
private function execComposerUpdate(Plugin $plugin)
{
$command = sprintf("composer update %s", $plugin->package_name);
do_log("[COMPOSER_UPDATE]: $command");
return $this->executeCommand($command);
}
private function execPluginInstall(Plugin $plugin)
{
$command = sprintf("php artisan plugin install %s", $plugin->package_name);
do_log("[PLUGIN_INSTALL]: $command");
return $this->executeCommand($command);
}
private function updateResult(Plugin $plugin, array $update)
{
$update['status_result'] = $update['status_result'] . "\n\nREQUEST_ID: " . nexus()->getRequestId();
do_log("[UPDATE]: " . json_encode($update));
$plugin->update($update);
}
public function getInstalledVersion($packageName)
{
$command = sprintf('composer info |grep -E %s', $packageName);
$result = $this->executeCommand($command);
$parts = preg_split("/[\s]+/", trim($result));
$version = $parts[1];
if (str_contains($version, 'dev')) {
$version .= " $parts[2]";
}
return $version;
}
}
+1 -1
View File
@@ -154,7 +154,7 @@ class SeedBoxRepository extends BaseRepository
private function clearCache()
{
return true;
NexusDB::cache_del('SEED_BOX_RECORD_APPROVAL_NONE');
// SeedBoxRecordUpdated::dispatch();
}
+45 -14
View File
@@ -11,6 +11,8 @@ class TagRepository extends BaseRepository
{
private static $orderByFieldIdString;
private static $allTags;
public function getList(array $params)
{
$query = $this->createBasicQuery();
@@ -48,10 +50,14 @@ class TagRepository extends BaseRepository
return Tag::query()->orderBy('priority', 'desc')->orderBy('id', 'desc');
}
public function renderCheckbox(array $checked = []): string
public function renderCheckbox(array $checked = [], $ignorePermission = false): string
{
$html = '';
$results = $this->createBasicQuery()->get();
$results = $this->listAll();
if (!$ignorePermission && !user_can('torrent-set-special-tag')) {
$specialTags = Tag::listSpecial();
$results = $results->filter(fn ($item) => !in_array($item->id, $specialTags));
}
foreach ($results as $value) {
$html .= sprintf(
'<label><input type="checkbox" name="tags[]" value="%s"%s />%s</label>',
@@ -61,20 +67,22 @@ class TagRepository extends BaseRepository
return $html;
}
public function renderSpan(Collection $tagKeyById, array $renderIdArr = [], $withFilterLink = false): string
public function renderSpan(array $renderIdArr = [], $withFilterLink = false): string
{
$html = '';
foreach ($renderIdArr as $tagId) {
$value = $tagKeyById->get($tagId);
if ($value) {
$item = sprintf(
"<span style=\"background-color:%s;color:%s;border-radius:%s;font-size:%s;margin:%s;padding:%s\">%s</span>",
$value->color, $value->font_color, $value->border_radius, $value->font_size, $value->margin, $value->padding, $value->name
);
if ($withFilterLink) {
$html .= sprintf('<a href="?tag_id=%s">%s</a>', $tagId, $item);
} else {
$html .= $item;
foreach ($this->listAll() as $value) {
if (in_array($value->id, $renderIdArr) || (isset($renderIdArr[0]) && $renderIdArr[0] == '*')) {
$tagId = $value->id;
if ($value) {
$item = sprintf(
"<span style=\"background-color:%s;color:%s;border-radius:%s;font-size:%s;margin:%s;padding:%s\">%s</span>",
$value->color, $value->font_color, $value->border_radius, $value->font_size, $value->margin, $value->padding, $value->name
);
if ($withFilterLink) {
$html .= sprintf('<a href="?tag_id=%s">%s</a>', $tagId, $item);
} else {
$html .= $item;
}
}
}
}
@@ -141,5 +149,28 @@ class TagRepository extends BaseRepository
return self::$orderByFieldIdString;
}
public function listAll()
{
if (empty(self::$allTags)) {
self::$allTags = self::createBasicQuery()->get();
}
return self::$allTags;
}
public function buildSelect($name, $value): string
{
$list = $this->listAll();
$select = sprintf('<select name="%s"><option value="">%s</option>', $name, nexus_trans('nexus.select_one_please'));
foreach ($list as $item) {
$selected = '';
if ($item->id == $value) {
$selected = ' selected';
}
$select .= sprintf('<option value="%s"%s>%s</option>', $item->id, $selected, $item->name);
}
$select .= '</select>';
return $select;
}
}
+33 -19
View File
@@ -24,7 +24,7 @@ class ToolRepository extends BaseRepository
{
const BACKUP_EXCLUDES = ['vendor', 'node_modules', '.git', '.idea', '.settings', '.DS_Store', '.github'];
public function backupWeb($method = null): array
public function backupWeb($method = null, $transfer = false): array
{
$webRoot = base_path();
$dirName = basename($webRoot);
@@ -76,10 +76,13 @@ class ToolRepository extends BaseRepository
$result_code = 0;
do_log("No tar command, use zip.");
}
return compact('result_code', 'filename');
if (!$transfer) {
return compact('result_code', 'filename');
}
return $this->transfer($filename, $result_code);
}
public function backupDatabase(): array
public function backupDatabase($transfer = false): array
{
$connectionName = config('database.default');
$config = config("database.connections.$connectionName");
@@ -93,10 +96,13 @@ class ToolRepository extends BaseRepository
"command: %s, output: %s, result_code: %s, result: %s, filename: %s",
$command, json_encode($output), $result_code, $result, $filename
));
return compact('result_code', 'filename');
if (!$transfer) {
return compact('result_code', 'filename');
}
return $this->transfer($filename, $result_code);
}
public function backupAll($method = null): array
public function backupAll($method = null, $transfer = false): array
{
$backupWeb = $this->backupWeb($method);
if ($backupWeb['result_code'] != 0) {
@@ -134,8 +140,10 @@ class ToolRepository extends BaseRepository
$result_code = 0;
do_log("No tar command, use zip.");
}
return compact('result_code', 'filename');
if (!$transfer) {
return compact('result_code', 'filename');
}
return $this->transfer($filename, $result_code);
}
/**
@@ -178,27 +186,33 @@ class ToolRepository extends BaseRepository
}
$backupResult = $this->backupAll();
do_log("Backup all result: " . json_encode($backupResult));
if ($backupResult['result_code'] != 0) {
throw new \RuntimeException("Backup all fail.");
}
$filename = $backupResult['filename'];
$transferResult = $this->transfer($backupResult['filename'], $backupResult['result_code'], $setting);
$backupResult['transfer_result'] = $transferResult;
do_log("[BACKUP_ALL_DONE]: " . json_encode($backupResult));
return $backupResult;
}
public function transfer($filename, $result_code, $setting = null): array
{
if ($result_code != 0) {
throw new \RuntimeException("file: $filename backup fail!");
}
$result = compact('filename', 'result_code');
if (empty($setting)) {
$setting = Setting::get('backup');
}
$saveResult = $this->saveToGoogleDrive($setting, $filename);
do_log("[BACKUP_GOOGLE_DRIVE]: $saveResult");
$backupResult['google_drive'] = $saveResult;
$result['google_drive'] = $saveResult;
$saveResult = $this->saveToFtp($setting, $filename);
do_log("[BACKUP_FTP]: $saveResult");
$backupResult['ftp'] = $saveResult;
$result['ftp'] = $saveResult;
$saveResult = $this->saveToSftp($setting, $filename);
do_log("[BACKUP_SFTP]: $saveResult");
$backupResult['sftp'] = $saveResult;
do_log("[BACKUP_ALL_DONE]: " . json_encode($backupResult));
return $backupResult;
$result['sftp'] = $saveResult;
return $result;
}
private function saveToGoogleDrive(array $setting, $filename): bool|string
+63 -2
View File
@@ -598,11 +598,72 @@ class TorrentRepository extends BaseRepository
}
public function setPosState($id, $posState): int
public function setPosState($id, $posState, $posStateUntil = null): int
{
user_can('torrentsticky', true);
if ($posState == Torrent::POS_STATE_STICKY_NONE) {
$posStateUntil = null;
}
if ($posStateUntil && Carbon::parse($posStateUntil)->lte(now())) {
$posState = Torrent::POS_STATE_STICKY_NONE;
$posStateUntil = null;
}
$update = [
'pos_state' => $posState,
'pos_state_until' => $posStateUntil,
];
$idArr = Arr::wrap($id);
return Torrent::query()->whereIn('id', $idArr)->update(['pos_state' => $posState]);
return Torrent::query()->whereIn('id', $idArr)->update($update);
}
public function setPickType($id, $pickType): int
{
user_can('torrentmanage', true);
if (!isset(Torrent::$pickTypes[$pickType])) {
throw new \InvalidArgumentException("Invalid pickType: $pickType");
}
$update = [
'picktype' => $pickType,
'picktime' => now(),
];
$idArr = Arr::wrap($id);
return Torrent::query()->whereIn('id', $idArr)->update($update);
}
public function setHr($id, $hrStatus): int
{
user_can('torrentmanage', true);
if (!isset(Torrent::$hrStatus[$hrStatus])) {
throw new \InvalidArgumentException("Invalid hrStatus: $hrStatus");
}
$update = [
'hr' => $hrStatus,
];
$idArr = Arr::wrap($id);
return Torrent::query()->whereIn('id', $idArr)->update($update);
}
public function setSpState($id, $spState, $promotionTimeType, $promotionUntil = null): int
{
user_can('torrentonpromotion', true);
if (!isset(Torrent::$promotionTypes[$spState])) {
throw new \InvalidArgumentException("Invalid spState: $spState");
}
if (!isset(Torrent::$promotionTimeTypes[$promotionTimeType])) {
throw new \InvalidArgumentException("Invalid promotionTimeType: $promotionTimeType");
}
if (in_array($promotionTimeType, [Torrent::PROMOTION_TIME_TYPE_GLOBAL, Torrent::PROMOTION_TIME_TYPE_PERMANENT])) {
$promotionUntil = null;
} elseif (!$promotionUntil || Carbon::parse($promotionUntil)->lte(now())) {
throw new \InvalidArgumentException("Invalid promotionUntil: $promotionUntil");
}
$update = [
'sp_state' => $spState,
'promotion_time_type' => $promotionTimeType,
'promotion_until' => $promotionUntil,
];
$idArr = Arr::wrap($id);
return Torrent::query()->whereIn('id', $idArr)->update($update);
}
public function buildUploadFieldInput($name, $value, $noteText, $btnText): string
+3 -2
View File
@@ -593,7 +593,7 @@ class TrackerRepository extends BaseRepository
$notSeedBoxMaxSpeedMbps = Setting::get('seed_box.not_seed_box_max_speed');
do_log("upSpeedMbps: $upSpeedMbps, notSeedBoxMaxSpeedMbps: $notSeedBoxMaxSpeedMbps");
if ($upSpeedMbps > $notSeedBoxMaxSpeedMbps) {
(new \App\Repositories\UserRepository())->updateDownloadPrivileges(null, $user, 'no');
(new \App\Repositories\UserRepository())->updateDownloadPrivileges(null, $user, 'no', 'upload_over_speed');
do_log("user: {$user->id} downloading privileges have been disabled! (over speed)", 'error');
throw new TrackerException("Your downloading privileges have been disabled! (over speed)");
}
@@ -1092,7 +1092,8 @@ class TrackerRepository extends BaseRepository
if ($user->isDonating()) {
return;
}
$hrMode = Setting::get('hr.mode');
// $hrMode = Setting::get('hr.mode');
$hrMode = HitAndRun::getConfig('mode', $torrent->basic_category->mode);
if ($hrMode == HitAndRun::MODE_DISABLED) {
return;
}
+48 -10
View File
@@ -162,12 +162,15 @@ class UserRepository extends BaseRepository
return User::listClass();
}
public function disableUser(User $operator, $uid, $reason)
public function disableUser(User $operator, $uid, $reason = '')
{
$targetUser = User::query()->findOrFail($uid, ['id', 'enabled', 'username', 'class']);
if ($targetUser->enabled == User::ENABLED_NO) {
throw new NexusException('Already disabled !');
}
if (empty($reason)) {
$reason = nexus_trans("user.disable_by_admin");
}
$this->checkPermission($operator, $targetUser);
$banLog = [
'uid' => $uid,
@@ -326,7 +329,7 @@ class UserRepository extends BaseRepository
}
public function updateDownloadPrivileges($operator, $user, $status)
public function updateDownloadPrivileges($operator, $user, $status, $disableReasonKey = null)
{
if (!in_array($status, ['yes', 'no'])) {
throw new \InvalidArgumentException("Invalid status: $status");
@@ -345,8 +348,12 @@ class UserRepository extends BaseRepository
if ($status == 'no') {
$update = ['downloadpos' => 'no'];
$modComment = date('Y-m-d') . " - Download disable by " . $operatorUsername;
$message['subject'] = nexus_trans('message.download_disable.subject', [], $targetUser->locale);
$message['msg'] = nexus_trans('message.download_disable.body', ['operator' => $operatorUsername], $targetUser->locale);
$msgTransPrefix = "message.download_disable";
if ($disableReasonKey !== null) {
$msgTransPrefix .= "_$disableReasonKey";
}
$message['subject'] = nexus_trans("$msgTransPrefix.subject", [], $targetUser->locale);
$message['msg'] = nexus_trans("$msgTransPrefix.body", ['operator' => $operatorUsername], $targetUser->locale);
} else {
$update = ['downloadpos' => 'yes'];
$modComment = date('Y-m-d') . " - Download enable by " . $operatorUsername;
@@ -463,23 +470,41 @@ class UserRepository extends BaseRepository
$user = $this->getUser($user);
$metaKey = $metaData['meta_key'];
$allowMultiple = UserMeta::$metaKeys[$metaKey]['multiple'];
$log = "user: {$user->id}, metaKey: $metaKey, allowMultiple: $allowMultiple";
if ($allowMultiple) {
//Allow multiple, just insert
$result = $user->metas()->create($metaData);
$log .= ", allowMultiple, just insert";
} else {
$metaExists = $user->metas()->where('meta_key', $metaKey)->first();
$log .= ", metaExists: " . ($metaExists->id ?? '');
if (!$metaExists) {
$result = $user->metas()->create($metaData);
$log .= ", meta not exists, just create";
} else {
if (empty($keyExistsUpdates)) {
$keyExistsUpdates = ['updated_at' => now()];
$log .= ", meta exists";
$keyExistsUpdates['updated_at'] = now();
if (!empty($keyExistsUpdates['duration'])) {
$log .= ", has duration: {$keyExistsUpdates['duration']}";
if ($metaExists->deadline && $metaExists->deadline->gte(now())) {
$log .= ", not expire";
$keyExistsUpdates['deadline'] = $metaExists->deadline->addDays($keyExistsUpdates['duration']);
} else {
$log .= ", expired or not set";
$keyExistsUpdates['deadline'] = now()->addDays($keyExistsUpdates['duration']);
}
unset($keyExistsUpdates['duration']);
} else {
$keyExistsUpdates['deadline'] = null;
}
$log .= ", update: " . json_encode($keyExistsUpdates);
$result = $metaExists->update($keyExistsUpdates);
}
}
if ($result) {
clear_user_cache($user->id, $user->passkey);
}
do_log($log);
return $result;
}
@@ -497,9 +522,13 @@ class UserRepository extends BaseRepository
return true;
}
public function destroy($id)
public function destroy($id, $reasonKey = 'user.destroy_by_admin')
{
user_can('user-delete', true);
if (!isRunningInConsole()) {
user_can('user-delete', true);
}
$uidArr = Arr::wrap($id);
$users = User::query()->with('language')->whereIn('id', $uidArr)->get(['id', 'username', 'lang']);
$tables = [
'users' => 'id',
'hit_and_runs' => 'uid',
@@ -508,9 +537,18 @@ class UserRepository extends BaseRepository
'exam_progress' => 'uid',
];
foreach ($tables as $table => $key) {
\Nexus\Database\NexusDB::table($table)->where($key, $id)->delete();
\Nexus\Database\NexusDB::table($table)->whereIn($key, $uidArr)->delete();
}
do_log("[DESTROY_USER]: $id", 'error');
do_log("[DESTROY_USER]: " . json_encode($uidArr), 'error');
$userBanLogs = [];
foreach ($users as $user) {
$userBanLogs[] = [
'uid' => $user->id,
'username' => $user->username,
'reason' => nexus_trans($reasonKey, [], $user->locale)
];
}
UserBanLog::query()->insert($userBanLogs);
return true;
}