diff --git a/app/Console/Commands/InviteAddTemporary.php b/app/Console/Commands/InviteAddTemporary.php
new file mode 100644
index 00000000..3956be1e
--- /dev/null
+++ b/app/Console/Commands/InviteAddTemporary.php
@@ -0,0 +1,39 @@
+argument('uid');
+ $days = $this->argument('days');
+ $count = $this->argument('count');
+ $this->info("uid: $uid, days: $days, count: $count");
+ $uidArr = preg_split('/[\s,]+/', $uid);
+ GenerateTemporaryInvite::dispatch($uidArr, $days, $count);
+ return Command::SUCCESS;
+ }
+}
diff --git a/app/Console/Commands/Test.php b/app/Console/Commands/Test.php
index 42e90efa..b80b23ac 100644
--- a/app/Console/Commands/Test.php
+++ b/app/Console/Commands/Test.php
@@ -12,6 +12,7 @@ use App\Models\Exam;
use App\Models\ExamProgress;
use App\Models\ExamUser;
use App\Models\HitAndRun;
+use App\Models\Invite;
use App\Models\Medal;
use App\Models\Peer;
use App\Models\SearchBox;
@@ -94,10 +95,9 @@ class Test extends Command
*/
public function handle()
{
- $rep = new WorkRepository();
- $rep->settleRole(4);
+ $uid = "2,3,4";
+ $uidArr = preg_split('/[\s,]+/', $uid);
+ dd($uidArr);
}
-
-
}
diff --git a/app/Filament/Resources/User/InviteResource.php b/app/Filament/Resources/User/InviteResource.php
new file mode 100644
index 00000000..e45f28e1
--- /dev/null
+++ b/app/Filament/Resources/User/InviteResource.php
@@ -0,0 +1,112 @@
+schema([
+ //
+ ]);
+ }
+
+ public static function table(Table $table): Table
+ {
+ return $table
+ ->columns([
+ Tables\Columns\TextColumn::make('id'),
+ Tables\Columns\TextColumn::make('inviter')
+ ->label(__('invite.fields.inviter'))
+ ->formatStateUsing(fn ($state) => username_for_admin($state))
+ ,
+ Tables\Columns\TextColumn::make('invitee')
+ ->label(__('invite.fields.invitee'))
+ ,
+ Tables\Columns\TextColumn::make('hash')
+ ,
+ Tables\Columns\TextColumn::make('time_invited')
+ ->label(__('invite.fields.time_invited'))
+ ,
+ Tables\Columns\IconColumn::make('valid')
+ ->label(__('invite.fields.valid'))
+ ->boolean()
+ ,
+ Tables\Columns\TextColumn::make('invitee_register_uid')
+ ->label(__('invite.fields.invitee_register_uid'))
+ ,
+ Tables\Columns\TextColumn::make('invitee_register_email')
+ ->label(__('invite.fields.invitee_register_email'))
+ ,
+ Tables\Columns\TextColumn::make('invitee_register_email')
+ ->label(__('invite.fields.invitee_register_email'))
+ ,
+ Tables\Columns\TextColumn::make('invitee_register_username')
+ ->label(__('invite.fields.invitee_register_username'))
+ ,
+ Tables\Columns\TextColumn::make('expired_at')
+ ->label(__('invite.fields.expired_at'))
+ ->formatStateUsing(fn ($state) => format_datetime($state))
+ ,
+ Tables\Columns\TextColumn::make('created_at')
+ ->label(__('label.created_at'))
+ ->formatStateUsing(fn ($state) => format_datetime($state))
+ ,
+ ])
+ ->filters([
+ //
+ ])
+ ->actions([
+// Tables\Actions\EditAction::make(),
+ ])
+ ->bulkActions([
+// Tables\Actions\DeleteBulkAction::make(),
+ ]);
+ }
+
+ public static function getRelations(): array
+ {
+ return [
+ //
+ ];
+ }
+
+ public static function getPages(): array
+ {
+ return [
+ 'index' => Pages\ListInvites::route('/'),
+ 'create' => Pages\CreateInvite::route('/create'),
+ 'edit' => Pages\EditInvite::route('/{record}/edit'),
+ ];
+ }
+}
diff --git a/app/Filament/Resources/User/InviteResource/Pages/CreateInvite.php b/app/Filament/Resources/User/InviteResource/Pages/CreateInvite.php
new file mode 100644
index 00000000..a46184df
--- /dev/null
+++ b/app/Filament/Resources/User/InviteResource/Pages/CreateInvite.php
@@ -0,0 +1,12 @@
+with(['inviter_user']);
+ }
+}
diff --git a/app/Filament/Resources/User/UserResource/Pages/UserProfile.php b/app/Filament/Resources/User/UserResource/Pages/UserProfile.php
index 5a45a121..c1f89c58 100644
--- a/app/Filament/Resources/User/UserResource/Pages/UserProfile.php
+++ b/app/Filament/Resources/User/UserResource/Pages/UserProfile.php
@@ -3,12 +3,14 @@
namespace App\Filament\Resources\User\UserResource\Pages;
use App\Filament\Resources\User\UserResource;
+use App\Models\Invite;
use App\Models\Medal;
use App\Models\User;
use App\Models\UserMeta;
use App\Repositories\ExamRepository;
use App\Repositories\MedalRepository;
use App\Repositories\UserRepository;
+use Carbon\Carbon;
use Filament\Resources\Pages\Concerns\HasRelationManagers;
use Filament\Resources\Pages\Concerns\InteractsWithRecord;
use Filament\Resources\Pages\Page;
@@ -129,20 +131,44 @@ class UserProfile extends ViewRecord
'invites' => __('label.user.invites'),
'seedbonus' => __('label.user.seedbonus'),
'attendance_card' => __('label.user.attendance_card'),
- ])->label(__('admin.resources.user.actions.change_bonus_etc_field_label'))->inline()->required(),
+ 'tmp_invites' => __('label.user.tmp_invites'),
+ ])
+ ->label(__('admin.resources.user.actions.change_bonus_etc_field_label'))
+ ->inline()
+ ->required()
+ ->reactive()
+ ,
Forms\Components\Radio::make('action')->options([
'Increment' => __("admin.resources.user.actions.change_bonus_etc_action_increment"),
'Decrement' => __("admin.resources.user.actions.change_bonus_etc_action_decrement"),
- ])->label(__('admin.resources.user.actions.change_bonus_etc_action_label'))->inline()->required(),
+ ])
+ ->label(__('admin.resources.user.actions.change_bonus_etc_action_label'))
+ ->inline()
+ ->required()
+ ,
Forms\Components\TextInput::make('value')->integer()->required()
->label(__('admin.resources.user.actions.change_bonus_etc_value_label'))
- ->helperText(__('admin.resources.user.actions.change_bonus_etc_value_help')),
- Forms\Components\Textarea::make('reason')->label(__('admin.resources.user.actions.change_bonus_etc_reason_label')),
+ ->helperText(__('admin.resources.user.actions.change_bonus_etc_value_help'))
+ ,
+
+ Forms\Components\TextInput::make('duration')->integer()
+ ->label(__('admin.resources.user.actions.change_bonus_etc_duration_label'))
+ ->helperText(__('admin.resources.user.actions.change_bonus_etc_duration_help'))
+ ->hidden(fn (\Closure $get) => $get('field') != 'tmp_invites')
+ ,
+
+ Forms\Components\Textarea::make('reason')
+ ->label(__('admin.resources.user.actions.change_bonus_etc_reason_label'))
+ ,
])
->action(function ($data) {
$userRep = $this->getRep();
try {
- $userRep->incrementDecrement(Auth::user(), $this->record->id, $data['action'], $data['field'], $data['value'], $data['reason']);
+ if ($data['field'] == 'tmp_invites') {
+ $userRep->addTemporaryInvite(Auth::user(), $this->record->id, $data['action'], $data['value'], $data['duration'], $data['reason']);
+ } else {
+ $userRep->incrementDecrement(Auth::user(), $this->record->id, $data['action'], $data['field'], $data['value'], $data['reason']);
+ }
$this->notify('success', 'Success!');
$this->emitSelf(self::EVENT_RECORD_UPDATED, $this->record->id);
} catch (\Exception $exception) {
@@ -298,6 +324,7 @@ class UserProfile extends ViewRecord
{
return [
'props' => $this->listUserProps(),
+ 'temporary_invite_count' => $this->countTemporaryInvite()
];
}
@@ -319,4 +346,13 @@ class UserProfile extends ViewRecord
}
return $props;
}
+
+ private function countTemporaryInvite()
+ {
+ return Invite::query()->where('inviter', $this->record->id)
+ ->where('invitee', '')
+ ->whereNotNull('expired_at')
+ ->where('expired_at', '>', Carbon::now())
+ ->count();
+ }
}
diff --git a/app/Jobs/GenerateTemporaryInvite.php b/app/Jobs/GenerateTemporaryInvite.php
new file mode 100644
index 00000000..234cc24e
--- /dev/null
+++ b/app/Jobs/GenerateTemporaryInvite.php
@@ -0,0 +1,81 @@
+uidArr = $uidArr;
+ $this->days = $days;
+ $this->count = $count;
+ }
+
+ /**
+ * Determine the time at which the job should timeout.
+ *
+ * @return \DateTime
+ */
+ public function retryUntil()
+ {
+ return now()->addHours(1);
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ $toolRep = new ToolRepository();
+ foreach ($this->uidArr as $uid) {
+ try {
+ $hashArr = $toolRep->generateUniqueInviteHash([], $this->count, $this->count);
+ $data = [];
+ foreach($hashArr as $hash) {
+ $data[] = [
+ 'inviter' => $uid,
+ 'invitee' => '',
+ 'hash' => $hash,
+ 'valid' => 0,
+ 'expired_at' => Carbon::now()->addDays($this->days),
+ 'created_at' => Carbon::now(),
+ ];
+ }
+ if (!empty($data)) {
+ Invite::query()->insert($data);
+ }
+ do_log("success add $this->count temporary invite ($this->days days) to $uid");
+ } catch (\Exception $exception) {
+ do_log("fail add $this->count temporary invite ($this->days days) to $uid: " . $exception->getMessage(), 'error');
+ }
+ }
+
+ }
+
+}
diff --git a/app/Models/Invite.php b/app/Models/Invite.php
index 91fc38f4..371aab30 100644
--- a/app/Models/Invite.php
+++ b/app/Models/Invite.php
@@ -9,6 +9,10 @@ class Invite extends NexusModel
const VALID_YES = 1;
const VALID_NO = 0;
+ protected $casts = [
+ 'expired_at' => 'datetime',
+ ];
+
public static $validInfo = [
self::VALID_NO => ['text' => 'No'],
self::VALID_YES => ['text' => 'Yes'],
diff --git a/app/Models/User.php b/app/Models/User.php
index a50b0d47..86b314a2 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -360,6 +360,15 @@ class User extends Authenticatable implements FilamentUser, HasName
return $this->belongsTo(User::class, 'invited_by');
}
+ public function temporary_invites()
+ {
+ return $this->hasMany(Invite::class, 'inviter')
+ ->where('invitee', '')
+ ->whereNotNull('expired_at')
+ ->where('expired_at', '>=', Carbon::now())
+ ;
+ }
+
public function send_messages()
{
return $this->hasMany(Message::class, 'sender');
diff --git a/app/Repositories/ClaimRepository.php b/app/Repositories/ClaimRepository.php
index 85716836..7f438912 100644
--- a/app/Repositories/ClaimRepository.php
+++ b/app/Repositories/ClaimRepository.php
@@ -153,6 +153,11 @@ class ClaimRepository extends BaseRepository
public function settleUser($uid, $force = false, $test = false): bool
{
+ $shouldDoSettle = apply_filter('user_should_do_claim_settle', true, $uid);
+ if (!$shouldDoSettle) {
+ do_log("uid: $uid, filter: user_should_do_claim_settle => false");
+ return false;
+ }
$user = User::query()->with('language')->findOrFail($uid);
$list = Claim::query()->where('uid', $uid)->with(['snatch', 'torrent' => fn ($query) => $query->select(Torrent::$commentFields)])->get();
$now = Carbon::now();
diff --git a/app/Repositories/ToolRepository.php b/app/Repositories/ToolRepository.php
index a63182ea..47d2aefa 100644
--- a/app/Repositories/ToolRepository.php
+++ b/app/Repositories/ToolRepository.php
@@ -1,6 +1,7 @@
10) {
+ throw new \RuntimeException("deep: $deep > 10");
+ }
+ if (count($hashArr) >= $total) {
+ return array_slice(array_values($hashArr), 0, $total);
+ }
+ for ($i = 0; $i < $left; $i++) {
+ $hash = Str::random(32);
+ $hashArr[$hash] = $hash;
+ }
+ $exists = Invite::query()->whereIn('hash', array_values($hashArr))->get(['id', 'hash']);
+ foreach($exists as $value) {
+ unset($hashArr[$value->hash]);
+ }
+ return $this->generateUniqueInviteHash($hashArr, $total, $total - count($hashArr), ++$deep);
+
+ }
}
diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php
index c1d4183a..0f1b1e9e 100644
--- a/app/Repositories/UserRepository.php
+++ b/app/Repositories/UserRepository.php
@@ -6,6 +6,7 @@ use App\Exceptions\NexusException;
use App\Http\Resources\ExamUserResource;
use App\Http\Resources\UserResource;
use App\Models\ExamUser;
+use App\Models\Invite;
use App\Models\Message;
use App\Models\Setting;
use App\Models\User;
@@ -566,4 +567,81 @@ class UserRepository extends BaseRepository
return true;
}
+ public function addTemporaryInvite(User $operator, int $uid, string $action, int $count, int|null $days, string|null $reason = '')
+ {
+ do_log("uid: $uid, action: $action, count: $count, days: $days, reason: $reason");
+ $action = strtolower($action);
+ if ($count <= 0 || ($action == 'increment' && $days <= 0)) {
+ throw new \InvalidArgumentException("days or count lte 0");
+ }
+ $targetUser = User::query()->findOrFail($uid, User::$commonFields);
+ $this->checkPermission($operator, $targetUser);
+ $toolRep = new ToolRepository();
+ $locale = $targetUser->locale;
+
+ $changeType = nexus_trans("nexus.$action", [], $locale);
+ $subject = nexus_trans('message.temporary_invite_change.subject', ['change_type' => $changeType], $locale);
+ $body = nexus_trans('message.temporary_invite_change.body', [
+ 'change_type' => $changeType,
+ 'count' => $count,
+ 'operator' => $operator->username,
+ 'reason' => $reason,
+ ], $locale);
+ $message = [
+ 'sender' => 0,
+ 'receiver' => $targetUser->id,
+ 'subject' => $subject,
+ 'msg' => $body,
+ 'added' => Carbon::now(),
+ ];
+ $inviteData = [];
+ if ($action == 'increment') {
+ $hashArr = $toolRep->generateUniqueInviteHash([], $count, $count);
+ foreach ($hashArr as $hash) {
+ $inviteData[] = [
+ 'inviter' => $uid,
+ 'invitee' => '',
+ 'hash' => $hash,
+ 'valid' => 0,
+ 'expired_at' => Carbon::now()->addDays($days),
+ 'created_at' => Carbon::now(),
+ ];
+ }
+ }
+ NexusDB::transaction(function () use ($uid, $message, $inviteData, $count) {
+ if (!empty($inviteData)) {
+ Invite::query()->insert($inviteData);
+ do_log("[INSERT TEMPORARY INVITE] to $uid, count: $count");
+ } else {
+ Invite::query()->where('inviter', $uid)
+ ->where('invitee', '')
+ ->orderBy('expired_at', 'asc')
+ ->limit($count)
+ ->delete()
+ ;
+ do_log("[DELETE TEMPORARY INVITE] of $uid, count: $count");
+ }
+ Message::add($message);
+ });
+ return true;
+ }
+
+ public function getInviteBtnText(int $uid)
+ {
+ if (Setting::get('main.invitesystem') != 'yes') {
+ throw new NexusException(nexus_trans('invite.send_deny_reasons.invite_system_closed'));
+ }
+ $permission = 'sendinvite';
+ if (!user_can($permission, false, $uid)) {
+ $requireClass = get_setting("authority.$permission");
+ throw new NexusException(nexus_trans('invite.send_deny_reasons.no_permission', ['class' => User::getClassText($requireClass)]));
+ }
+ $userInfo = User::query()->findOrFail($uid, User::$commonFields);
+ $temporaryInviteCount = $userInfo->temporary_invites()->count();
+ if ($userInfo->invites + $temporaryInviteCount < 1) {
+ throw new NexusException(nexus_trans('invite.send_deny_reasons.invite_not_enough'));
+ }
+ return nexus_trans('invite.send_allow_text');
+ }
+
}
diff --git a/database/migrations/2022_12_10_034926_add_expired_at_to_invites_table.php b/database/migrations/2022_12_10_034926_add_expired_at_to_invites_table.php
new file mode 100644
index 00000000..5f10cded
--- /dev/null
+++ b/database/migrations/2022_12_10_034926_add_expired_at_to_invites_table.php
@@ -0,0 +1,35 @@
+dateTime('expired_at')->nullable(true)->index();
+ $table->dateTime('created_at')->useCurrent();
+ $table->index(['inviter'], 'idx_inviter');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('invites', function (Blueprint $table) {
+ $table->dropColumn('expired_at', 'created_at');
+ $table->dropIndex('idx_inviter');
+ });
+ }
+};
diff --git a/database/migrations/2022_12_10_041706_change_invites_table_invitee_default_empty.php b/database/migrations/2022_12_10_041706_change_invites_table_invitee_default_empty.php
new file mode 100644
index 00000000..c747e63d
--- /dev/null
+++ b/database/migrations/2022_12_10_041706_change_invites_table_invitee_default_empty.php
@@ -0,0 +1,30 @@
+string('invitee')->default('')->change();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ //
+ }
+};
diff --git a/include/cleanup.php b/include/cleanup.php
index 54261a82..74990772 100644
--- a/include/cleanup.php
+++ b/include/cleanup.php
@@ -248,6 +248,7 @@ function docleanup($forceAll = 0, $printProgress = false) {
set_time_limit(0);
ignore_user_abort(1);
$now = time();
+ $nowStr = \Carbon\Carbon::now()->toDateTimeString();
do_log("start docleanup(), forceAll: $forceAll, printProgress: $printProgress, now: $now, " . date('Y-m-d H:i:s', $now));
//Priority Class 1: cleanup every 15 mins
@@ -592,7 +593,7 @@ function docleanup($forceAll = 0, $printProgress = false) {
//6.delete old invite codes
$secs = $invite_timeout*24*60*60; // when?
$dt = sqlesc(date("Y-m-d H:i:s",(TIMENOW - $secs))); // calculate date.
- sql_query("DELETE FROM invites WHERE time_invited < $dt") or sqlerr(__FILE__, __LINE__);
+ sql_query("DELETE FROM invites WHERE ((time_invited < $dt and time_invited is not null and invitee != '') or (invitee = '' and expired_at < '$nowStr' and expired_at is not null))") or sqlerr(__FILE__, __LINE__);
$log = "delete old invite codes";
do_log($log);
if ($printProgress) {
diff --git a/include/constants.php b/include/constants.php
index d7a096a5..261acffe 100644
--- a/include/constants.php
+++ b/include/constants.php
@@ -1,6 +1,6 @@
]
[]:
'.$lang_functions['text_attended'].'', $attendance->points, $CURUSER['attendance_card']); }else{ printf(' %s', $lang_functions['text_attendance']);}?>
- []:
+ []: where('inviter', $CURUSER['id'])->where('invitee', '')->where('expired_at', '>', now())->count())?>
= \App\Models\User::CLASS_ADMINISTRATOR) printf('[%s]', nexus_env('FILAMENT_PATH', 'nexusphp'), $lang_functions['text_management_system'])?>
diff --git a/lang/chs/lang_invite.php b/lang/chs/lang_invite.php
index 36e13820..b772d464 100644
--- a/lang/chs/lang_invite.php
+++ b/lang/chs/lang_invite.php
@@ -57,6 +57,11 @@ $lang_invite = array
'text_seed_torrent_bonus_per_hour_help' => '即做种积分,单纯做种的值,不考虑捐赠、官种、后宫等任何加成',
'text_seed_torrent_last_announce_at' => '最后做种汇报时间',
'text_enabled' => '启用',
+ 'text_tmp_status' => '临时邀请状态',
+ 'text_expired_at' => 'hash 过期时间',
+ 'text_permanent' => '永久有效邀请',
+ 'text_consume_invite' => '使用邀请',
+ 'text_temporary_left' => '%s个临时邀请',
);
?>
diff --git a/lang/chs/lang_takeinvite.php b/lang/chs/lang_takeinvite.php
index 3d111d9a..605dddc9 100644
--- a/lang/chs/lang_takeinvite.php
+++ b/lang/chs/lang_takeinvite.php
@@ -24,6 +24,9 @@ $lang_takeinvite = array
'std_no_invite' => "你没有剩余邀请名额。你怎么到这来的?",
'std_invitation_already_sent_to' => "发送失败!此邮箱",
'std_await_user_registeration' => "已收到过邀请函,请勿重复发送。请等待用户注册。",
+ 'std_must_select_invite' => "请选择要使用的邀请。",
+ 'hash_not_exists' => "hash 不存在。",
+ 'hash_expired' => "hash 已经过期。",
);
?>
diff --git a/lang/cht/lang_invite.php b/lang/cht/lang_invite.php
index 6351cdde..d12cce2e 100644
--- a/lang/cht/lang_invite.php
+++ b/lang/cht/lang_invite.php
@@ -57,6 +57,8 @@ $lang_invite = array
'text_seed_torrent_bonus_per_hour_help' => '即做種積分,單純做種的值,不考慮捐贈、官種、後宮等任何加成',
'text_seed_torrent_last_announce_at' => '最後做種匯報時間',
'text_enabled' => '啟用',
+ 'text_tmp_status' => '臨時邀請狀態',
+ 'text_expired_at' => '過期時間',
);
?>
diff --git a/lang/en/lang_invite.php b/lang/en/lang_invite.php
index 92a759c4..2a91905f 100644
--- a/lang/en/lang_invite.php
+++ b/lang/en/lang_invite.php
@@ -60,6 +60,8 @@ Best Regards,
'text_seed_torrent_bonus_per_hour_help' => 'That is, seed points, do not consider donations, official torrent, harems and any other additions',
'text_seed_torrent_last_announce_at' => 'Last announce time',
'text_enabled' => 'Enabled',
+ 'text_tmp_status' => 'Temporary invite status',
+ 'text_expired_at' => 'Expired at',
);
?>
diff --git a/public/increment-bulk.php b/public/increment-bulk.php
index dd60d4b0..14d1fea5 100644
--- a/public/increment-bulk.php
+++ b/public/increment-bulk.php
@@ -10,6 +10,7 @@ $validTypeMap = [
'attendance_card' => 'Attend card',
'invites' => 'Invite',
'uploaded' => 'Upload',
+ 'tmp_invites' => 'Temporary invite',
];
$type = $_REQUEST['type'] ?? '';
stdhead("Add Bonus/Attend card/Invite/upload", false);
@@ -49,6 +50,7 @@ $classes = array_chunk(\App\Models\User::$classes, 4, true);
| Amount | |
+ | Duration | Only required when Type = 'Temporary invite', Unit: Day |
| Add to Class |
diff --git a/public/invite.php b/public/invite.php
index db211012..5a0488ca 100644
--- a/public/invite.php
+++ b/public/invite.php
@@ -8,23 +8,31 @@ $id = intval($_GET["id"] ?? 0);
$type = unesc($_GET["type"] ?? '');
$menuSelected = $_REQUEST['menu'] ?? 'invitee';
$pageSize = 50;
+$userRep = new \App\Repositories\UserRepository();
function inviteMenu ($selected = "invitee") {
- global $lang_invite, $id, $CURUSER, $invitesystem;
+ global $lang_invite, $id, $CURUSER, $invitesystem, $userRep;
begin_main_frame("", false, "100%");
print ("");
+ print ("".$lang_invite['text_tmp_status']."");
+ try {
+ $sendBtnText = $userRep->getInviteBtnText($CURUSER['id']);
+ $disabled = '';
+ } catch (\Exception $exception) {
+ $sendBtnText = $exception->getMessage();
+ $disabled = ' disabled';
}
+ print ("");
end_main_frame();
}
-if (($CURUSER['id'] != $id && !user_can('viewinvite')) || !is_valid_id($id))
-stderr($lang_invite['std_sorry'],$lang_invite['std_permission_denied']);
$res = sql_query("SELECT username FROM users WHERE id = ".mysql_real_escape_string($id)) or sqlerr();
$user = mysql_fetch_assoc($res);
+if (!$user) {
+ stderr($lang_invite['std_sorry'], 'Invalid id');
+}
stdhead($lang_invite['head_invites']);
print("| ");
@@ -46,24 +54,34 @@ if ($inv["invites"] != 1){
}
if ($type == 'new'){
- if (!user_can('sendinvite'))
- stderr($lang_invite['std_sorry'],$lang_invite['std_only'].get_user_class_name($sendinvite_class,false,true,true).$lang_invite['std_or_above_can_invite'],false, false);
+ try {
+ $sendBtnText = $userRep->getInviteBtnText($CURUSER['id']);
+ } catch (\Exception $exception) {
+ stdmsg($lang_invite['std_sorry'],$exception->getMessage().
+ " ".$lang_invite['here_to_go_back'],false);
+ print(" | ");
+ stdfoot();
+ die;
+ }
registration_check('invitesystem',true,false);
- if ($CURUSER['invites'] <= 0) {
- stdmsg($lang_invite['std_sorry'],$lang_invite['std_no_invites_left'].
- "".$lang_invite['here_to_go_back'],false);
- print(" ");
- stdfoot();
- die;
- }
+ $temporaryInvites = \App\Models\Invite::query()->where('inviter', $CURUSER['id'])
+ ->where('invitee', '')
+ ->where('expired_at', '>', now())
+ ->orderBy('expired_at', 'asc')
+ ->get()
+ ;
$invitation_body = $lang_invite['text_invitation_body'].$CURUSER['username'];
//$invitation_body_insite = str_replace(" ","\n",$invitation_body);
+ $inviteSelectOptions = '';
+ foreach ($temporaryInvites as $tmp) {
+ $inviteSelectOptions .= sprintf('', $tmp->hash, $tmp->hash, $lang_invite['text_expired_at'], $tmp->expired_at);
+ }
print(" |
");
@@ -94,7 +112,9 @@ if ($type == 'new'){
$name, $_GET['status'] == $name ? ' selected' : '', $text
);
}
+
$resetText = nexus_trans('label.reset');
+ $submitText = nexus_trans('label.submit');
$filterForm = <<
@@ -220,22 +240,32 @@ JS;
}
print("");
print("$pagertop");
- } elseif ($menuSelected == 'sent') {
- $rul = sql_query("SELECT COUNT(*) FROM invites WHERE inviter =".mysql_real_escape_string($id)) or sqlerr();
+ } elseif (in_array($menuSelected, ['sent', 'tmp'])) {
+ $whereStr = "inviter = " . sqlesc($id);
+ if ($menuSelected == 'sent') {
+ $whereStr .= " and invitee != ''";
+ } elseif ($menuSelected == 'tmp') {
+ $whereStr .= " and invitee = '' and expired_at is not null";
+ }
+ $rul = sql_query("SELECT COUNT(*) FROM invites WHERE $whereStr");
$arre = mysql_fetch_row($rul);
$number1 = $arre[0];
-
print("");
if(!$number1){
- print("| ".$lang_invite['text_no_invitation_sent']." |
");
+ print("| ".$lang_functions['text_none']." |
");
} else {
list($pagertop, $pagerbottom, $limit) = pager($pageSize, $number1, "?id=$id&menu=$menuSelected&");
- $rer = sql_query("SELECT * FROM invites WHERE inviter = ".mysql_real_escape_string($id) . " $limit") or sqlerr();
+ $rer = sql_query("SELECT * FROM invites WHERE $whereStr $limit") or sqlerr();
$num1 = mysql_num_rows($rer);
- print("| ".$lang_invite['text_email']." | ".$lang_invite['text_hash']." | ".$lang_invite['text_send_date']." | ".$lang_invite['text_hash_status']." | ".$lang_invite['text_invitee_user']." |
");
+ print("| ".$lang_invite['text_email']." | ".$lang_invite['text_hash']." | ".$lang_invite['text_send_date']." | ".$lang_invite['text_hash_status']." | ".$lang_invite['text_invitee_user']." | ");
+ if ($menuSelected == 'tmp') {
+ print("".$lang_invite['text_expired_at']." | ");
+ print("".nexus_trans('label.created_at')." | ");
+ }
+ print("
");
for ($i = 0; $i < $num1; ++$i)
{
$arr1 = mysql_fetch_assoc($rer);
@@ -254,6 +284,10 @@ JS;
} else {
$tr .= " | ";
}
+ if ($menuSelected == 'tmp') {
+ $tr .= "{$arr1['expired_at']} | ";
+ $tr .= "{$arr1['created_at']} | ";
+ }
$tr .= "";
print($tr);
}
diff --git a/public/take-increment-bulk.php b/public/take-increment-bulk.php
index 9a570566..0c9e2e87 100644
--- a/public/take-increment-bulk.php
+++ b/public/take-increment-bulk.php
@@ -13,6 +13,7 @@ $validTypeMap = [
'attendance_card' => 'Attend card',
'invites' => 'Invite',
'uploaded' => 'Upload',
+ 'tmp_invites' => 'Temporary invite',
];
$sender_id = ($_POST['sender'] == 'system' ? 0 : (int)$CURUSER['id']);
$dt = sqlesc(date("Y-m-d H:i:s"));
@@ -29,8 +30,9 @@ if (!isset($validTypeMap[$type])) {
if ($type == 'uploaded') {
$amount = sqlesc(getsize_int($amount,"G"));
}
+$isTypeTmpInvite = $type == 'tmp_invites';
$subject = trim($_POST['subject']);
-$size = 10000;
+$size = 2000;
$page = 1;
set_time_limit(300);
$conditions = [];
@@ -41,7 +43,12 @@ $conditions = apply_filter("role_query_conditions", $conditions, $_POST);
if (empty($conditions)) {
stderr("Error","No valid filter");
}
+if ($isTypeTmpInvite && (empty($_POST['duration']) || $_POST['duration'] < 1)) {
+ stderr("Error","Invalid duration");
+}
$whereStr = implode(' OR ', $conditions);
+$phpPath = nexus_env('PHP_PATH', 'php');
+$webRoot = rtrim(ROOT_PATH, '/');
while (true) {
$msgValues = $idArr = [];
$offset = ($page - 1) * $size;
@@ -54,10 +61,19 @@ while (true) {
if (empty($idArr)) {
break;
}
- $idStr = implode(', ', $idArr);
+ $idStr = implode(',', $idArr);
+ if ($isTypeTmpInvite) {
+ $command = sprintf(
+ '%s %s/artisan invite:tmp %s %s %s',
+ $phpPath, $webRoot, $idStr, $_POST['duration'], $amount
+ );
+ $result = exec("$command 2>&1", $output, $result_code);
+ do_log(sprintf('command: %s, result_code: %s, result: %s, output: %s', $command, $result_code, $result, json_encode($output)));
+ } else {
+ sql_query("UPDATE users SET $type = $type + $amount WHERE id in ($idStr)");
+ }
$sql = "INSERT INTO messages (sender, receiver, added, subject, msg) VALUES " . implode(', ', $msgValues);
sql_query($sql);
- sql_query("UPDATE users SET $type = $type + $amount WHERE id in ($idStr)");
$page++;
}
diff --git a/public/takeinvite.php b/public/takeinvite.php
index d0d95303..3147f3f3 100644
--- a/public/takeinvite.php
+++ b/public/takeinvite.php
@@ -3,10 +3,12 @@ require_once("../include/bittorrent.php");
dbconn();
require_once(get_langfile_path());
registration_check('invitesystem', true, false);
-if (!user_can('sendinvite'))
-stderr($lang_takeinvite['std_error'],$lang_takeinvite['std_invite_denied']);
-if ($CURUSER['invites'] < 1)
- stderr($lang_takeinvite['std_error'],$lang_takeinvite['std_no_invite']);
+$userRep = new \App\Repositories\UserRepository();
+try {
+ $sendText = $userRep->getInviteBtnText($CURUSER['id']);
+} catch (\Exception $exception) {
+ stderr($lang_takeinvite['std_error'], $exception->getMessage());
+}
function bark($msg) {
stdhead();
stdmsg($lang_takeinvite['head_invitation_failed'], $msg);
@@ -43,7 +45,24 @@ if ($b[0] != 0)
$ret = sql_query("SELECT username FROM users WHERE id = ".sqlesc($id)) or sqlerr();
$arr = mysql_fetch_assoc($ret);
-$hash = md5(mt_rand(1,10000).$CURUSER['username'].TIMENOW.$CURUSER['passhash']);
+if (empty($_POST['hash'])) {
+ bark($lang_takeinvite['std_must_select_invite']);
+}
+if ($_POST['hash'] == 'permanent') {
+ $hash = md5(mt_rand(1,10000).$CURUSER['username'].TIMENOW.$CURUSER['passhash']);
+} else {
+ $hashRecord = \App\Models\Invite::query()->where('inviter', $CURUSER['id'])->where('hash', $_POST['hash'])->first();
+ if (!$hashRecord) {
+ bark($lang_takeinvite['hash_not_exists']);
+ }
+ if ($hashRecord->invitee != '') {
+ bark('hash '.$lang_takeinvite['std_is_in_use']);
+ }
+ if ($hashRecord->expired_at->lt(now())) {
+ bark($lang_takeinvite['hash_expired']);
+ }
+ $hash = $_POST['hash'];
+}
$title = $SITENAME.$lang_takeinvite['mail_tilte'];
@@ -60,8 +79,16 @@ EOD;
$sendResult = sent_mail($email,$SITENAME,$SITEEMAIL,$title,$message,"invitesignup",false,false,'');
//this email is sent only when someone give out an invitation
if ($sendResult === true) {
- sql_query("INSERT INTO invites (inviter, invitee, hash, time_invited) VALUES ('".mysql_real_escape_string($id)."', '".mysql_real_escape_string($email)."', '".mysql_real_escape_string($hash)."', " . sqlesc(date("Y-m-d H:i:s")) . ")");
- sql_query("UPDATE users SET invites = invites - 1 WHERE id = ".mysql_real_escape_string($id)) or sqlerr(__FILE__, __LINE__);
+ if (isset($hashRecord)) {
+ $hashRecord->update([
+ 'invitee' => $email,
+ 'time_invited' => now(),
+ 'valid' => 1,
+ ]);
+ } else {
+ sql_query("INSERT INTO invites (inviter, invitee, hash, time_invited) VALUES ('".mysql_real_escape_string($id)."', '".mysql_real_escape_string($email)."', '".mysql_real_escape_string($hash)."', " . sqlesc(date("Y-m-d H:i:s")) . ")");
+ sql_query("UPDATE users SET invites = invites - 1 WHERE id = ".mysql_real_escape_string($id)) or sqlerr(__FILE__, __LINE__);
+ }
}
header("Refresh: 0; url=invite.php?id=".htmlspecialchars($id)."&sent=1");
diff --git a/resources/lang/en/admin.php b/resources/lang/en/admin.php
index 408fab56..ec1000d5 100644
--- a/resources/lang/en/admin.php
+++ b/resources/lang/en/admin.php
@@ -32,6 +32,7 @@ return [
'category' => 'Categories',
'second_icon' => 'Second icons',
'torrent_operation_log' => 'Torrent operation logs',
+ 'invite' => 'Invites',
],
'resources' => [
'agent_allow' => [
@@ -76,6 +77,8 @@ return [
'grant_prop_form_duration' => 'Duration',
'grant_prop_form_duration_help' => 'Unit: days. If left blank, the user has it permanently. Note: There is no time limit for Name Change Card, ignore this value.' ,
'confirm_bulk' => 'Bulk confirm',
+ 'change_bonus_etc_duration_label' => 'Duration',
+ 'change_bonus_etc_duration_help' => 'Required when adding temporary invitation, in days',
]
],
'exam_user' => [
diff --git a/resources/lang/en/invite.php b/resources/lang/en/invite.php
index aa483fc4..e94e6ad6 100644
--- a/resources/lang/en/invite.php
+++ b/resources/lang/en/invite.php
@@ -2,4 +2,20 @@
return [
'invalid_inviter' => 'Invalid inviter! The invite code is banned!',
+ 'fields' => [
+ 'inviter' => 'Sender',
+ 'invitee' => 'Receive email',
+ 'time_invited' => 'Send time',
+ 'valid' => 'Valid',
+ 'invitee_register_uid' => 'Registered UID',
+ 'invitee_register_email' => 'Registered email',
+ 'invitee_register_username' => 'Registered username',
+ 'expired_at' => 'hash expired at',
+ ],
+ 'send_deny_reasons' => [
+ 'invite_system_closed' => 'Invite system is closed',
+ 'no_permission' => 'Require :class or above to send invitations',
+ 'invite_not_enough' => 'Invites not enough',
+ ],
+ 'send_allow_text' => 'Invite someone',
];
diff --git a/resources/lang/en/label.php b/resources/lang/en/label.php
index 56490bdd..24c620c6 100644
--- a/resources/lang/en/label.php
+++ b/resources/lang/en/label.php
@@ -102,6 +102,7 @@ return [
'downloadpos' => 'Download privileges',
'parked' => 'Parked',
'offer_allowed_count' => 'Offer allowed count',
+ 'tmp_invites' => 'Temporary invite',
],
'medal' => [
'label' => 'Medal',
diff --git a/resources/lang/en/message.php b/resources/lang/en/message.php
index 3d27859b..d1de2137 100644
--- a/resources/lang/en/message.php
+++ b/resources/lang/en/message.php
@@ -23,4 +23,8 @@ return [
'subject' => 'Download permission restored',
'body' => 'Your download privileges restored, you can now download torrents. By: :operator',
],
+ 'temporary_invite_change' => [
+ 'subject' => 'Temporary invite :change_type',
+ 'body' => 'Your temporary invite count had :change_type :count by :operator, reason: :reason.',
+ ],
];
diff --git a/resources/lang/en/nexus.php b/resources/lang/en/nexus.php
index 2adb7113..7c553ea8 100644
--- a/resources/lang/en/nexus.php
+++ b/resources/lang/en/nexus.php
@@ -10,4 +10,6 @@ return [
],
'select_all' => 'Select all',
'unselect_all' => 'Unselect all',
+ 'increment' => 'increment',
+ 'decrement' => 'decrement',
];
diff --git a/resources/lang/zh_CN/admin.php b/resources/lang/zh_CN/admin.php
index 4b53c6d3..3a4a4024 100644
--- a/resources/lang/zh_CN/admin.php
+++ b/resources/lang/zh_CN/admin.php
@@ -30,6 +30,7 @@ return [
'category' => '主分类',
'second_icon' => '第二图标',
'torrent_operation_log' => '种子操作记录',
+ 'invite' => '用户邀请',
],
'resources' => [
'agent_allow' => [
@@ -74,6 +75,8 @@ return [
'grant_prop_form_duration' => '有效时长',
'grant_prop_form_duration_help' => '单位:天。如果留空,用户永久拥有。注:改名卡没有时间限制,忽略该值。',
'confirm_bulk' => '批量确认',
+ 'change_bonus_etc_duration_label' => '有效期',
+ 'change_bonus_etc_duration_help' => '增加临时邀请时必须,单位:天',
]
],
'exam_user' => [
diff --git a/resources/lang/zh_CN/invite.php b/resources/lang/zh_CN/invite.php
index c44640da..5cd8d943 100644
--- a/resources/lang/zh_CN/invite.php
+++ b/resources/lang/zh_CN/invite.php
@@ -2,4 +2,20 @@
return [
'invalid_inviter' => '非法邀请者!此邀请码已被禁用!',
+ 'fields' => [
+ 'inviter' => '发邀者',
+ 'invitee' => '接收邮箱',
+ 'time_invited' => '发邀时间',
+ 'valid' => '是否有效',
+ 'invitee_register_uid' => '注册用户 UID',
+ 'invitee_register_email' => '注册用户邮箱',
+ 'invitee_register_username' => '注册用户名',
+ 'expired_at' => 'hash 过期时间',
+ ],
+ 'send_deny_reasons' => [
+ 'invite_system_closed' => '邀请系统已关闭',
+ 'no_permission' => ':class 或以上等级才可以发送邀请',
+ 'invite_not_enough' => '邀请数量不足',
+ ],
+ 'send_allow_text' => '邀请其他人',
];
diff --git a/resources/lang/zh_CN/label.php b/resources/lang/zh_CN/label.php
index f4968b80..ea118162 100644
--- a/resources/lang/zh_CN/label.php
+++ b/resources/lang/zh_CN/label.php
@@ -102,6 +102,7 @@ return [
'downloadpos' => '下载权限',
'parked' => '挂起',
'offer_allowed_count' => '候选通过数',
+ 'tmp_invites' => '临时邀请',
],
'medal' => [
'label' => '勋章',
diff --git a/resources/lang/zh_CN/message.php b/resources/lang/zh_CN/message.php
index f030b00e..e47f47c2 100644
--- a/resources/lang/zh_CN/message.php
+++ b/resources/lang/zh_CN/message.php
@@ -23,4 +23,8 @@ return [
'subject' => '下载权限恢复',
'body' => '你的下载权限恢复,你现在可以下载种子。By: :operator',
],
+ 'temporary_invite_change' => [
+ 'subject' => '临时邀请:change_type',
+ 'body' => '你的临时邀请被管理员 :operator :change_type :count 个,理由::reason。',
+ ],
];
diff --git a/resources/lang/zh_CN/nexus.php b/resources/lang/zh_CN/nexus.php
index 345c5645..24ce2f71 100644
--- a/resources/lang/zh_CN/nexus.php
+++ b/resources/lang/zh_CN/nexus.php
@@ -10,4 +10,6 @@ return [
],
'select_all' => '全选',
'unselect_all' => '全不选',
+ 'increment' => '增加',
+ 'decrement' => '减少',
];
diff --git a/resources/lang/zh_TW/admin.php b/resources/lang/zh_TW/admin.php
index ba39b69c..6f95043c 100644
--- a/resources/lang/zh_TW/admin.php
+++ b/resources/lang/zh_TW/admin.php
@@ -32,6 +32,7 @@ return [
'category' => '主分類',
'second_icon' => '第二圖標',
'torrent_operation_log' => '種子操作記錄',
+ 'invite' => '用戶邀請',
],
'resources' => [
'agent_allow' => [
@@ -76,6 +77,8 @@ return [
'grant_prop_form_duration' => '有效時長',
'grant_prop_form_duration_help' => '單位:天。如果留空,用戶永久擁有。註:改名卡沒有時間限製,忽略該值。',
'confirm_bulk' => '批量確認',
+ 'change_bonus_etc_duration_label' => '有效期',
+ 'change_bonus_etc_duration_help' => '增加臨時邀請時必須,單位:天',
]
],
'exam_user' => [
diff --git a/resources/lang/zh_TW/invite.php b/resources/lang/zh_TW/invite.php
index 888bbf5e..d2ab93ab 100644
--- a/resources/lang/zh_TW/invite.php
+++ b/resources/lang/zh_TW/invite.php
@@ -2,4 +2,20 @@
return [
'invalid_inviter' => '非法邀請者!此邀請碼已被禁用!',
+ 'fields' => [
+ 'inviter' => '發邀者',
+ 'invitee' => '接收郵箱',
+ 'time_invited' => '發邀時間',
+ 'valid' => '是否有效',
+ 'invitee_register_uid' => '註冊用戶 UID',
+ 'invitee_register_email' => '註冊用戶郵箱',
+ 'invitee_register_username' => '註冊用戶名',
+ 'expired_at' => 'hash 過期時間',
+ ],
+ 'send_deny_reasons' => [
+ 'invite_system_closed' => '邀請系統已關閉',
+ 'no_permission' => ':class 或以上等級才可以發送邀請',
+ 'invite_not_enough' => '邀請數量不足',
+ ],
+ 'send_allow_text' => '邀請其他人',
];
diff --git a/resources/lang/zh_TW/label.php b/resources/lang/zh_TW/label.php
index 7c6b348d..6573498c 100644
--- a/resources/lang/zh_TW/label.php
+++ b/resources/lang/zh_TW/label.php
@@ -102,6 +102,7 @@ return [
'downloadpos' => '下載權限',
'parked' => '掛起',
'offer_allowed_count' => '候選通過數',
+ 'tmp_invites' => '臨時邀請',
],
'medal' => [
'label' => '勛章',
diff --git a/resources/lang/zh_TW/message.php b/resources/lang/zh_TW/message.php
index 99aaceb2..bac6d977 100644
--- a/resources/lang/zh_TW/message.php
+++ b/resources/lang/zh_TW/message.php
@@ -22,4 +22,8 @@ return [
'subject' => '下載權限恢復',
'body' => '你的下載權限恢復,你現在可以下載種子。By: :operator',
],
+ 'temporary_invite_change' => [
+ 'subject' => '臨時邀請:change_type',
+ 'body' => '你的臨時邀請被管理員 :operator :change_type :count 個,理由::reason。',
+ ],
];
diff --git a/resources/lang/zh_TW/nexus.php b/resources/lang/zh_TW/nexus.php
index 65d92c0f..4151b129 100644
--- a/resources/lang/zh_TW/nexus.php
+++ b/resources/lang/zh_TW/nexus.php
@@ -10,4 +10,6 @@ return [
],
'select_all' => '全選',
'unselect_all' => '全不選',
+ 'increment' => '增加',
+ 'decrement' => '減少',
];
diff --git a/resources/views/filament/resources/user/user-resource/pages/user-profile.blade.php b/resources/views/filament/resources/user/user-resource/pages/user-profile.blade.php
index faf61d4a..1ce33599 100644
--- a/resources/views/filament/resources/user/user-resource/pages/user-profile.blade.php
+++ b/resources/views/filament/resources/user/user-resource/pages/user-profile.blade.php
@@ -87,7 +87,7 @@
| {{ __('label.user.invites') }} |
- {{$record->invites}} |
+ {{sprintf('%s(%s)', $record->invites, $temporary_invite_count)}} |
|