mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-24 03:57:22 +08:00
Merge branch 'php8' into section
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,4 +15,12 @@ trait OptionsTrait
|
||||
$disableValue => __('label.disabled'),
|
||||
];
|
||||
}
|
||||
|
||||
private static function getYesNoOptions($yesValue = 1, $noValue = 0): array
|
||||
{
|
||||
return [
|
||||
$yesValue => 'Yes',
|
||||
$noValue => 'No',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
-3
@@ -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(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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'])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -154,7 +154,7 @@ class SeedBoxRepository extends BaseRepository
|
||||
|
||||
private function clearCache()
|
||||
{
|
||||
return true;
|
||||
NexusDB::cache_del('SEED_BOX_RECORD_APPROVAL_NONE');
|
||||
// SeedBoxRecordUpdated::dispatch();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user