2021-04-17 19:01:33 +08:00
< ? php
namespace App\Repositories ;
2022-07-23 23:35:43 +08:00
use App\Exceptions\InsufficientPermissionException ;
2021-05-14 20:41:43 +08:00
use App\Exceptions\NexusException ;
2021-04-27 19:13:32 +08:00
use App\Http\Resources\ExamUserResource ;
2025-04-17 01:39:40 +07:00
use App\Http\Resources\TorrentResource ;
2021-04-27 19:13:32 +08:00
use App\Http\Resources\UserResource ;
use App\Models\ExamUser ;
2022-12-13 13:51:39 +08:00
use App\Models\Invite ;
2023-04-09 14:53:15 +08:00
use App\Models\LoginLog ;
2022-05-10 22:08:41 +08:00
use App\Models\Message ;
2021-04-17 19:01:33 +08:00
use App\Models\Setting ;
2024-12-17 13:12:58 +08:00
use App\Models\Snatch ;
2025-04-17 01:39:40 +07:00
use App\Models\Torrent ;
2021-04-17 19:01:33 +08:00
use App\Models\User ;
2021-05-14 02:11:57 +08:00
use App\Models\UserBanLog ;
2022-08-10 17:38:05 +08:00
use App\Models\UserMeta ;
2025-02-06 22:32:36 +08:00
use App\Models\UserModifyLog ;
2022-08-10 23:38:10 +08:00
use App\Models\UsernameChangeLog ;
2025-04-17 01:39:40 +07:00
use App\Utils\ApiQueryBuilder ;
2022-05-10 22:08:41 +08:00
use Carbon\Carbon ;
2025-04-17 01:39:40 +07:00
use Illuminate\Contracts\Auth\Authenticatable ;
2021-05-12 13:45:00 +08:00
use Illuminate\Database\Eloquent\Builder ;
2022-08-10 17:38:05 +08:00
use Illuminate\Support\Arr ;
2024-03-21 11:09:25 +08:00
use Illuminate\Support\Collection ;
2022-07-18 01:37:50 +08:00
use Illuminate\Support\Facades\Auth ;
2021-05-14 02:11:57 +08:00
use Illuminate\Support\Facades\DB ;
2024-04-26 03:21:35 +08:00
use Illuminate\Support\Str ;
2022-05-10 20:12:11 +08:00
use Nexus\Database\NexusDB ;
2021-04-17 19:01:33 +08:00
class UserRepository extends BaseRepository
{
public function getList ( array $params )
{
$query = User :: query ();
2022-02-25 18:09:31 +08:00
if ( ! empty ( $params [ 'id' ])) {
$query -> where ( 'id' , $params [ 'id' ]);
}
if ( ! empty ( $params [ 'username' ])) {
$query -> where ( 'username' , 'like' , " % { $params [ 'username' ] } % " );
}
if ( ! empty ( $params [ 'email' ])) {
$query -> where ( 'email' , 'like' , " % { $params [ 'email' ] } % " );
}
2022-05-23 01:09:06 +08:00
if ( isset ( $params [ 'class' ]) && $params [ 'class' ] !== '' ) {
2022-03-04 16:16:56 +08:00
$query -> where ( 'class' , $params [ 'class' ]);
}
2021-04-17 19:01:33 +08:00
list ( $sortField , $sortType ) = $this -> getSortFieldAndType ( $params );
$query -> orderBy ( $sortField , $sortType );
return $query -> paginate ();
}
2021-04-23 01:28:41 +08:00
public function getBase ( $id )
{
$user = User :: query () -> findOrFail ( $id , [ 'id' , 'username' , 'email' , 'avatar' ]);
return $user ;
}
2025-04-17 01:39:40 +07:00
public function getDetail ( $id , Authenticatable $currentUser )
2021-04-27 19:13:32 +08:00
{
2025-04-17 01:39:40 +07:00
//query this info default
$query = User :: query () -> with ([]);
$allowIncludes = [ 'inviter' , 'valid_medals' ];
$allowIncludeCounts = [];
$allowIncludeFields = [];
$apiQueryBuilder = ApiQueryBuilder :: for ( UserResource :: NAME , $query )
-> allowIncludes ( $allowIncludes )
-> allowIncludeCounts ( $allowIncludeCounts )
-> allowIncludeFields ( $allowIncludeFields )
;
$user = $apiQueryBuilder -> build () -> findOrFail ( $id );
return $this -> appendIncludeFields ( $apiQueryBuilder , $currentUser , $user );
}
2021-04-27 19:13:32 +08:00
2025-04-17 01:39:40 +07:00
private function appendIncludeFields ( ApiQueryBuilder $apiQueryBuilder , Authenticatable $currentUser , User $user ) : User
{
// $id = $torrent->id;
// if ($apiQueryBuilder->hasIncludeField('has_bookmarked')) {
// $torrent->has_bookmarked = (int)$user->bookmarks()->where('torrentid', $id)->exists();;
// }
return $user ;
2021-04-27 19:13:32 +08:00
}
2022-03-13 21:45:48 +08:00
/**
* create user
*
* @ param array $params must : username , email , password , password_confirmation . optional : id , class
* @ return User
*/
2021-04-17 19:01:33 +08:00
public function store ( array $params )
{
$password = $params [ 'password' ];
if ( $password != $params [ 'password_confirmation' ]) {
throw new \InvalidArgumentException ( " password confirmation != password " );
}
2022-03-13 21:45:48 +08:00
$username = $params [ 'username' ];
if ( ! validusername ( $username )) {
2022-08-10 17:38:05 +08:00
throw new \InvalidArgumentException ( " Invalid username: $username " );
2022-03-13 21:45:48 +08:00
}
$email = htmlspecialchars ( trim ( $params [ 'email' ]));
$email = safe_email ( $email );
if ( ! check_email ( $email )) {
2022-08-10 17:38:05 +08:00
throw new \InvalidArgumentException ( " Invalid email: $email " );
2022-03-13 21:45:48 +08:00
}
if ( User :: query () -> where ( 'email' , $email ) -> exists ()) {
throw new \InvalidArgumentException ( " The email address: $email is already in use " );
}
2022-05-15 02:31:05 +08:00
if ( User :: query () -> where ( 'username' , $username ) -> exists ()) {
throw new \InvalidArgumentException ( " The username: $username is already in use " );
}
2022-03-13 21:45:48 +08:00
if ( mb_strlen ( $password ) < 6 || mb_strlen ( $password ) > 40 ) {
2022-08-10 17:38:05 +08:00
throw new \InvalidArgumentException ( " Invalid password: $password , it should be more than 6 character and less than 40 character " );
2022-03-13 21:45:48 +08:00
}
2023-08-22 02:02:55 +08:00
if ( ! empty ( $params [ 'class' ])) {
$class = intval ( $params [ 'class' ]);
2023-09-25 01:52:45 +08:00
if ( ! IN_NEXUS ) {
$authUser = Auth :: user ();
if ( $authUser && $class >= $authUser -> class ) {
throw new InsufficientPermissionException ( 'No permission' );
}
2023-08-22 02:02:55 +08:00
}
} else {
$class = User :: CLASS_USER ;
}
2022-03-13 21:45:48 +08:00
if ( ! isset ( User :: $classes [ $class ])) {
throw new \InvalidArgumentException ( " Invalid user class: $class " );
}
2021-04-17 19:01:33 +08:00
$setting = Setting :: get ( 'main' );
$secret = mksecret ();
2025-04-05 20:16:09 +07:00
$passhash = hash ( 'sha256' , $secret . hash ( 'sha256' , $password ));
2021-04-17 19:01:33 +08:00
$data = [
2022-03-13 21:45:48 +08:00
'username' => $username ,
'email' => $email ,
2021-04-17 19:01:33 +08:00
'secret' => $secret ,
2025-04-05 21:43:37 +07:00
'auth_key' => mksecret (),
2021-04-17 19:01:33 +08:00
'editsecret' => '' ,
'passhash' => $passhash ,
'stylesheet' => $setting [ 'defstylesheet' ],
'added' => now () -> toDateTimeString (),
'status' => User :: STATUS_CONFIRMED ,
2024-10-10 21:04:35 +08:00
'class' => $class ,
'passkey' => md5 ( $username . date ( " Y-m-d H:i:s " ) . $passhash )
2021-04-17 19:01:33 +08:00
];
2022-03-13 21:45:48 +08:00
$user = new User ( $data );
2022-06-27 01:39:01 +08:00
if ( ! empty ( $params [ 'id' ])) {
2022-03-13 21:45:48 +08:00
if ( User :: query () -> where ( 'id' , $params [ 'id' ]) -> exists ()) {
throw new \InvalidArgumentException ( " uid: { $params [ 'id' ] } already exists. " );
}
do_log ( " [CREATE_USER], specific id: " . $params [ 'id' ]);
$user -> id = $params [ 'id' ];
}
$user -> save ();
2024-10-10 21:04:35 +08:00
fire_event ( " user_created " , $user );
2021-04-17 19:01:33 +08:00
return $user ;
}
2021-05-14 20:41:43 +08:00
public function resetPassword ( $id , $password , $passwordConfirmation )
2021-04-17 19:01:33 +08:00
{
if ( $password != $passwordConfirmation ) {
throw new \InvalidArgumentException ( " password confirmation != password " );
}
2022-07-18 01:37:50 +08:00
$user = User :: query () -> findOrFail ( $id , [ 'id' , 'username' , 'class' ]);
2022-09-12 22:02:57 +08:00
$operator = Auth :: user ();
if ( $operator ) {
$this -> checkPermission ( $operator , $user );
}
2021-04-17 19:01:33 +08:00
$secret = mksecret ();
2025-04-05 20:16:09 +07:00
$passhash = hash ( 'sha256' , $secret . hash ( 'sha256' , $password ));
2021-04-17 19:01:33 +08:00
$update = [
'secret' => $secret ,
'passhash' => $passhash ,
2025-04-05 21:43:37 +07:00
'auth_key' => mksecret (),
2021-04-17 19:01:33 +08:00
];
$user -> update ( $update );
2021-05-14 20:41:43 +08:00
return true ;
2021-04-17 19:01:33 +08:00
}
2021-04-19 20:13:21 +08:00
2022-08-19 15:30:16 +08:00
/**
* @ deprecated use User :: listClass () instead !
*
* @ return array
*/
2021-04-19 20:13:21 +08:00
public function listClass ()
{
2022-08-19 15:30:16 +08:00
return User :: listClass ();
2021-04-19 20:13:21 +08:00
}
2021-05-14 02:11:57 +08:00
2022-10-23 18:26:03 +08:00
public function disableUser ( User $operator , $uid , $reason = '' )
2021-05-14 02:11:57 +08:00
{
2022-05-23 13:09:41 +08:00
$targetUser = User :: query () -> findOrFail ( $uid , [ 'id' , 'enabled' , 'username' , 'class' ]);
2021-05-14 20:41:43 +08:00
if ( $targetUser -> enabled == User :: ENABLED_NO ) {
2022-05-23 13:09:41 +08:00
throw new NexusException ( 'Already disabled !' );
}
2022-10-23 18:26:03 +08:00
if ( empty ( $reason )) {
$reason = nexus_trans ( " user.disable_by_admin " );
}
2022-07-23 23:35:43 +08:00
$this -> checkPermission ( $operator , $targetUser );
2021-05-14 02:11:57 +08:00
$banLog = [
'uid' => $uid ,
'username' => $targetUser -> username ,
'reason' => $reason ,
'operator' => $operator -> id ,
];
2022-05-23 13:09:41 +08:00
$modCommentText = sprintf ( " %s - Disable by %s, reason: %s. " , now () -> format ( 'Y-m-d' ), $operator -> username , $reason );
2021-05-14 02:11:57 +08:00
DB :: transaction ( function () use ( $targetUser , $banLog , $modCommentText ) {
2021-05-14 20:41:43 +08:00
$targetUser -> updateWithModComment ([ 'enabled' => User :: ENABLED_NO ], $modCommentText );
2021-05-14 02:11:57 +08:00
UserBanLog :: query () -> insert ( $banLog );
});
2021-05-14 20:41:43 +08:00
do_log ( " user: $uid , $modCommentText " );
2022-07-30 15:06:51 +08:00
$this -> clearCache ( $targetUser );
2024-04-26 03:21:35 +08:00
fire_event ( " user_disabled " , $targetUser );
2021-05-14 20:41:43 +08:00
return true ;
}
2022-06-30 21:08:25 +08:00
public function enableUser ( User $operator , $uid , $reason = '' )
2021-05-14 20:41:43 +08:00
{
2021-05-15 12:59:59 +08:00
$targetUser = User :: query () -> findOrFail ( $uid , [ 'id' , 'enabled' , 'username' , 'class' ]);
2021-05-14 20:41:43 +08:00
if ( $targetUser -> enabled == User :: ENABLED_YES ) {
2022-05-23 13:09:41 +08:00
throw new NexusException ( 'Already enabled !' );
2021-05-14 20:41:43 +08:00
}
2022-07-23 23:35:43 +08:00
$this -> checkPermission ( $operator , $targetUser );
2021-05-15 12:59:59 +08:00
$update = [
'enabled' => User :: ENABLED_YES
];
if ( $targetUser -> class == User :: CLASS_PEASANT ) {
// warn users until 30 days
$until = now () -> addDays ( 30 ) -> toDateTimeString ();
$update [ 'leechwarn' ] = 'yes' ;
$update [ 'leechwarnuntil' ] = $until ;
} else {
$update [ 'leechwarn' ] = 'no' ;
$update [ 'leechwarnuntil' ] = null ;
}
2022-06-30 21:08:25 +08:00
$modCommentText = sprintf ( " %s - Enable by %s, reason: %s " , now () -> format ( 'Y-m-d' ), $operator -> username , $reason );
2021-05-15 12:59:59 +08:00
$targetUser -> updateWithModComment ( $update , $modCommentText );
do_log ( " user: $uid , $modCommentText , update: " . nexus_json_encode ( $update ));
2022-07-30 15:06:51 +08:00
$this -> clearCache ( $targetUser );
2024-04-26 03:21:35 +08:00
fire_event ( " user_enabled " , $targetUser );
2021-05-14 02:11:57 +08:00
return true ;
}
2021-05-14 20:41:43 +08:00
public function getInviteInfo ( $id )
{
$user = User :: query () -> findOrFail ( $id , [ 'id' ]);
return $user -> invitee_code () -> with ( 'inviter_user' ) -> first ();
}
public function getModComment ( $id )
{
$user = User :: query () -> findOrFail ( $id , [ 'modcomment' ]);
return $user -> modcomment ;
}
2022-01-19 23:54:55 +08:00
2022-05-10 20:12:11 +08:00
public function incrementDecrement ( User $operator , $uid , $action , $field , $value , $reason = '' ) : bool
{
$fieldMap = [
'uploaded' => 'uploaded' ,
'downloaded' => 'downloaded' ,
2022-06-30 21:08:25 +08:00
'seedbonus' => 'seedbonus' ,
2022-05-10 20:12:11 +08:00
'invites' => 'invites' ,
2022-05-29 15:19:16 +08:00
'attendance_card' => 'attendance_card' ,
2022-05-10 20:12:11 +08:00
];
if ( ! isset ( $fieldMap [ $field ])) {
throw new \InvalidArgumentException ( " Invalid field: $field , only support: " . implode ( ', ' , array_keys ( $fieldMap )));
}
$sourceField = $fieldMap [ $field ];
$targetUser = User :: query () -> findOrFail ( $uid , User :: $commonFields );
2022-07-23 23:35:43 +08:00
$this -> checkPermission ( $operator , $targetUser );
2022-05-10 20:12:11 +08:00
$old = $targetUser -> { $sourceField };
2022-05-10 22:08:41 +08:00
$valueAtomic = $value ;
2022-05-12 01:35:01 +08:00
$formatSize = false ;
2022-05-10 22:08:41 +08:00
if ( in_array ( $field , [ 'uploaded' , 'downloaded' ])) {
//Frontend unit: GB
$valueAtomic = $value * 1024 * 1024 * 1024 ;
2022-05-12 01:35:01 +08:00
$formatSize = true ;
2022-05-10 22:08:41 +08:00
}
2022-05-10 20:12:11 +08:00
if ( $action == 'Increment' ) {
2022-05-10 22:08:41 +08:00
$new = $old + abs ( $valueAtomic );
2022-05-10 20:12:11 +08:00
} elseif ( $action == 'Decrement' ) {
2022-05-10 22:08:41 +08:00
$new = $old - abs ( $valueAtomic );
2022-05-10 20:12:11 +08:00
} else {
throw new \InvalidArgumentException ( " Invalid action: $action . " );
}
2022-05-29 15:19:16 +08:00
if ( $new < 0 ) {
throw new NexusException ( " New value( $new ) lte 0 " );
}
2022-05-10 22:08:41 +08:00
//for administrator, use english
$modCommentText = nexus_trans ( 'message.field_value_change_message_body' , [
'field' => nexus_trans ( " user.labels. $sourceField " , [], 'en' ),
'operator' => $operator -> username ,
2022-05-12 01:35:01 +08:00
'old' => $formatSize ? mksize ( $old ) : $old ,
'new' => $formatSize ? mksize ( $new ) : $new ,
2022-05-10 22:08:41 +08:00
'reason' => $reason ,
], 'en' );
2022-05-12 01:35:01 +08:00
do_log ( " user: $uid , $modCommentText " , 'alert' );
2022-05-10 20:12:11 +08:00
$update = [
$sourceField => $new ,
2025-02-06 22:32:36 +08:00
// 'modcomment' => NexusDB::raw("if(modcomment = '', '$modCommentText', concat_ws('\n', '$modCommentText', modcomment))"),
2022-05-10 20:12:11 +08:00
];
2022-05-10 22:08:41 +08:00
$locale = $targetUser -> locale ;
$fieldLabel = nexus_trans ( " user.labels. $sourceField " , [], $locale );
$msg = nexus_trans ( 'message.field_value_change_message_body' , [
'field' => $fieldLabel ,
'operator' => $operator -> username ,
2022-05-12 01:35:01 +08:00
'old' => $formatSize ? mksize ( $old ) : $old ,
'new' => $formatSize ? mksize ( $new ) : $new ,
2022-05-10 22:08:41 +08:00
'reason' => $reason ,
], $locale );
$message = [
'sender' => 0 ,
'receiver' => $targetUser -> id ,
'subject' => nexus_trans ( " message.field_value_change_message_subject " , [ 'field' => $fieldLabel ], $locale ),
'msg' => $msg ,
'added' => Carbon :: now (),
];
2025-02-06 22:32:36 +08:00
NexusDB :: transaction ( function () use ( $uid , $sourceField , $old , $new , $update , $message , $modCommentText ) {
2022-05-10 22:08:41 +08:00
$affectedRows = User :: query ()
-> where ( 'id' , $uid )
-> where ( $sourceField , $old )
-> update ( $update )
;
if ( $affectedRows != 1 ) {
throw new \RuntimeException ( " Change fail, affected rows != 1( $affectedRows ) " );
}
Message :: query () -> insert ( $message );
2025-02-06 22:32:36 +08:00
UserModifyLog :: query () -> insert ([
'user_id' => $uid ,
'content' => $modCommentText ,
'created_at' => Carbon :: now (),
'updated_at' => Carbon :: now (),
]);
2022-05-10 22:08:41 +08:00
});
2022-07-30 15:06:51 +08:00
$this -> clearCache ( $targetUser );
2022-05-10 20:12:11 +08:00
return true ;
}
2022-05-13 15:56:09 +08:00
public function removeLeechWarn ( $operator , $uid ) : bool
{
2022-07-23 23:35:43 +08:00
$operator = $this -> getUser ( $operator );
2022-05-13 15:56:09 +08:00
$user = User :: query () -> findOrFail ( $uid , User :: $commonFields );
2022-07-23 23:35:43 +08:00
$this -> checkPermission ( $operator , $user );
2022-07-30 15:06:51 +08:00
$this -> clearCache ( $user );
2022-05-13 15:56:09 +08:00
$user -> leechwarn = 'no' ;
$user -> leechwarnuntil = null ;
return $user -> save ();
}
2022-05-13 17:55:49 +08:00
public function removeTwoStepAuthentication ( $operator , $uid ) : bool
{
if ( ! $operator -> canAccessAdmin ()) {
throw new \RuntimeException ( " No permission. " );
}
$user = User :: query () -> findOrFail ( $uid , User :: $commonFields );
2022-07-23 23:35:43 +08:00
$this -> checkPermission ( $operator , $user );
2022-07-30 15:06:51 +08:00
$this -> clearCache ( $user );
2022-05-13 17:55:49 +08:00
$user -> two_step_secret = '' ;
return $user -> save ();
}
2022-07-23 23:35:43 +08:00
2022-10-06 18:19:39 +08:00
public function updateDownloadPrivileges ( $operator , $user , $status , $disableReasonKey = null )
2022-07-23 23:35:43 +08:00
{
2022-07-30 15:06:51 +08:00
if ( ! in_array ( $status , [ 'yes' , 'no' ])) {
throw new \InvalidArgumentException ( " Invalid status: $status " );
}
$targetUser = $this -> getUser ( $user );
2022-07-23 23:35:43 +08:00
$operator = $this -> getUser ( $operator );
2022-07-30 15:06:51 +08:00
$operatorUsername = 'System' ;
if ( $operator ) {
$operatorUsername = $operator -> username ;
$this -> checkPermission ( $operator , $targetUser );
}
2022-07-23 23:35:43 +08:00
$message = [
'added' => now (),
'receiver' => $targetUser -> id ,
];
2022-07-30 15:06:51 +08:00
if ( $status == 'no' ) {
2022-07-23 23:35:43 +08:00
$update = [ 'downloadpos' => 'no' ];
2022-07-30 15:06:51 +08:00
$modComment = date ( 'Y-m-d' ) . " - Download disable by " . $operatorUsername ;
2022-10-06 18:19:39 +08:00
$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 );
2022-07-23 23:35:43 +08:00
} else {
$update = [ 'downloadpos' => 'yes' ];
2022-07-30 15:06:51 +08:00
$modComment = date ( 'Y-m-d' ) . " - Download enable by " . $operatorUsername ;
2022-07-23 23:35:43 +08:00
$message [ 'subject' ] = nexus_trans ( 'message.download_enable.subject' , [], $targetUser -> locale );
2022-07-30 15:06:51 +08:00
$message [ 'msg' ] = nexus_trans ( 'message.download_enable.body' , [ 'operator' => $operatorUsername ], $targetUser -> locale );
2022-07-23 23:35:43 +08:00
}
return NexusDB :: transaction ( function () use ( $targetUser , $update , $modComment , $message ) {
Message :: add ( $message );
2022-07-30 15:06:51 +08:00
$this -> clearCache ( $targetUser );
2022-07-23 23:35:43 +08:00
return $targetUser -> updateWithModComment ( $update , $modComment );
});
}
private function checkPermission ( $operator , User $user , $minAuthClass = 'authority.prfmanage' )
2022-05-13 17:55:49 +08:00
{
2022-07-23 23:35:43 +08:00
$operator = $this -> getUser ( $operator );
2022-08-10 23:38:10 +08:00
if ( $operator -> id == $user -> id ) {
return ;
}
2022-07-23 23:35:43 +08:00
$classRequire = Setting :: get ( $minAuthClass );
if ( $operator -> class < $classRequire || $operator -> class <= $user -> class ) {
throw new InsufficientPermissionException ();
2022-05-13 17:55:49 +08:00
}
}
2022-07-30 15:06:51 +08:00
private function clearCache ( User $user )
{
2022-08-10 17:38:05 +08:00
clear_user_cache ( $user -> id , $user -> passkey );
2022-07-30 15:06:51 +08:00
}
2022-08-10 17:38:05 +08:00
public function listMetas ( $uid , $metaKeys = [], $valid = true )
{
$query = UserMeta :: query () -> where ( 'uid' , $uid );
if ( ! empty ( $metaKeys )) {
$query -> whereIn ( 'meta_key' , Arr :: wrap ( $metaKeys ));
}
if ( $valid ) {
$query -> where ( 'status' , 0 ) -> where ( function ( Builder $query ) {
$query -> whereNull ( 'deadline' ) -> orWhere ( 'deadline' , '>=' , now ());
});
}
return $query -> get () -> groupBy ( 'meta_key' );
}
public function consumeBenefit ( $uid , array $params ) : bool
{
$metaKey = $params [ 'meta_key' ];
$records = $this -> listMetas ( $uid , $metaKey );
if ( ! $records -> has ( $metaKey )) {
throw new \RuntimeException ( " User do not has this metaKey: $metaKey " );
}
/** @var UserMeta $meta */
$meta = $records -> get ( $metaKey ) -> first ();
$user = User :: query () -> findOrFail ( $uid , User :: $commonFields );
if ( $metaKey == UserMeta :: META_KEY_CHANGE_USERNAME ) {
2022-08-11 22:55:17 +08:00
$changeLog = $user -> usernameChangeLogs () -> orderBy ( 'id' , 'desc' ) -> first ();
if ( $changeLog ) {
$miniDays = Setting :: get ( 'system.change_username_min_interval_in_days' , 365 );
2025-01-19 23:41:50 +08:00
if ( abs ( $changeLog -> created_at -> diffInDays ()) <= $miniDays ) {
2022-08-11 22:55:17 +08:00
$msg = nexus_trans ( 'user.change_username_lte_min_interval' , [ 'last_change_time' => $changeLog -> created_at , 'interval' => $miniDays ]);
throw new \RuntimeException ( $msg );
}
}
2022-08-10 17:38:05 +08:00
NexusDB :: transaction ( function () use ( $user , $meta , $params ) {
2022-08-11 18:26:11 +08:00
$this -> changeUsername (
$user , UsernameChangeLog :: CHANGE_TYPE_USER , $user , $params [ 'username' ],
Setting :: get ( 'system.change_username_card_allow_characters_outside_the_alphabets' ) == 'yes'
);
2022-08-10 17:38:05 +08:00
$meta -> delete ();
clear_user_cache ( $user -> id , $user -> passkey );
});
return true ;
}
throw new \InvalidArgumentException ( " Invalid meta_key: $metaKey " );
}
2022-08-11 18:26:11 +08:00
private function changeUsername ( $operator , $changeType , $targetUser , $newUsername , $allowOutsideAlphabets = false ) : bool
2022-08-10 17:38:05 +08:00
{
2022-08-10 23:38:10 +08:00
$operator = $this -> getUser ( $operator );
$targetUser = $this -> getUser ( $targetUser );
$this -> checkPermission ( $operator , $targetUser );
if ( $targetUser -> username == $newUsername ) {
2022-08-10 17:38:05 +08:00
throw new \RuntimeException ( " New username can not be the same with current username ! " );
}
2022-08-10 23:38:10 +08:00
$strWidth = mb_strwidth ( $newUsername );
if ( $strWidth < 4 || $strWidth > 20 ) {
throw new \InvalidArgumentException ( " Invalid username, maybe too long or too short " );
2022-08-10 17:38:05 +08:00
}
2022-08-11 18:26:11 +08:00
if ( ! $allowOutsideAlphabets && ! validusername ( $newUsername )) {
throw new \InvalidArgumentException ( " Invalid username, only support alphabets " );
}
2022-08-10 23:38:10 +08:00
if ( User :: query () -> where ( 'username' , $newUsername ) -> where ( 'id' , '!=' , $targetUser -> id ) -> exists ()) {
2022-08-10 17:38:05 +08:00
throw new \RuntimeException ( " Username: $newUsername already exists ! " );
}
2022-08-10 23:38:10 +08:00
$changeLog = [
'uid' => $targetUser -> id ,
'operator' => $operator -> username ,
'change_type' => $changeType ,
'username_old' => $targetUser -> username ,
'username_new' => $newUsername
];
NexusDB :: transaction ( function () use ( $operator , $changeType , $targetUser , $changeLog ) {
$targetUser -> usernameChangeLogs () -> create ( $changeLog );
$targetUser -> username = $changeLog [ 'username_new' ];
$targetUser -> save ();
2023-02-09 16:34:38 +08:00
$this -> clearCache ( $targetUser );
2022-08-10 17:38:05 +08:00
});
return true ;
}
2022-01-19 23:54:55 +08:00
2023-06-16 01:26:27 +08:00
public function changeClass ( $operator , $targetUser , $newClass , $reason = '' , array $extra = []) : bool
2023-01-07 19:34:59 +08:00
{
user_can ( 'user-change-class' , true );
$operator = $this -> getUser ( $operator );
$targetUser = $this -> getUser ( $targetUser );
2023-01-10 17:25:53 +08:00
if ( $operator ) {
if ( $operator -> class <= $targetUser -> class || $operator -> class <= $newClass )
2023-01-07 21:27:53 +08:00
throw new InsufficientPermissionException ();
}
2023-06-16 01:26:27 +08:00
if ( $targetUser -> class == $newClass && $newClass != User :: CLASS_VIP ) {
2023-01-07 19:34:59 +08:00
return true ;
}
$locale = $targetUser -> locale ;
$subject = nexus_trans ( 'user.edit_notifications.change_class.subject' , [], $locale );
$body = nexus_trans ( 'user.edit_notifications.change_class.body' , [
'action' => nexus_trans ( 'user.edit_notifications.change_class.' . ( $newClass > $targetUser -> class ? 'promote' : 'demote' )),
'new_class' => User :: getClassText ( $newClass ),
'operator' => $operator -> username ? ? '' ,
'reason' => $reason ,
], $locale );
$message = [
'sender' => 0 ,
'receiver' => $targetUser -> id ,
'subject' => $subject ,
'msg' => $body ,
'added' => Carbon :: now (),
];
2023-06-16 01:26:27 +08:00
$userUpdates = [
'class' => $newClass ,
];
if ( $newClass == User :: CLASS_VIP ) {
if ( ! empty ( $extra [ 'vip_added' ]) && in_array ( $extra [ 'vip_added' ], [ 'yes' , 'no' ])) {
$userUpdates [ 'vip_added' ] = $extra [ 'vip_added' ];
} else {
$userUpdates [ 'vip_added' ] = 'no' ;
}
if ( ! empty ( $extra [ 'vip_until' ])) {
$until = Carbon :: parse ( $extra [ 'vip_until' ]);
$userUpdates [ 'vip_until' ] = $until ;
} else {
$userUpdates [ 'vip_until' ] = null ;
}
} else {
$userUpdates [ 'vip_added' ] = 'no' ;
$userUpdates [ 'vip_until' ] = null ;
}
do_log ( " userUpdates: " . json_encode ( $userUpdates ));
NexusDB :: transaction ( function () use ( $targetUser , $userUpdates , $message ) {
2023-01-07 19:34:59 +08:00
$modComment = date ( 'Y-m-d' ) . " - " . $message [ 'msg' ];
2023-06-16 01:26:27 +08:00
if ( $targetUser -> class != $userUpdates [ 'class' ]) {
$targetUser -> updateWithModComment ( $userUpdates , $modComment );
Message :: add ( $message );
} else {
$targetUser -> update ( $userUpdates );
}
2023-02-09 16:34:38 +08:00
$this -> clearCache ( $targetUser );
2023-01-07 19:34:59 +08:00
});
return true ;
}
2023-01-05 18:29:31 +08:00
public function addMeta ( $user , array $metaData , array $keyExistsUpdates = [], $notify = true )
2022-08-11 17:12:36 +08:00
{
$user = $this -> getUser ( $user );
2022-11-08 19:06:37 +08:00
$locale = $user -> locale ;
2022-08-11 17:12:36 +08:00
$metaKey = $metaData [ 'meta_key' ];
2022-11-08 19:06:37 +08:00
$metaName = nexus_trans ( " label.user_meta.meta_keys. $metaKey " , [], $locale );
2022-08-11 17:12:36 +08:00
$allowMultiple = UserMeta :: $metaKeys [ $metaKey ][ 'multiple' ];
2022-11-08 19:06:37 +08:00
$log = " user: { $user -> id } , locale: $locale , metaKey: $metaKey , allowMultiple: $allowMultiple " ;
$message = [
'receiver' => $user -> id ,
'added' => now (),
'subject' => nexus_trans ( 'user.grant_props_notification.subject' , [ 'name' => $metaName ], $locale ),
];
if ( ! empty ( $keyExistsUpdates [ 'duration' ]) && $metaKey != UserMeta :: META_KEY_CHANGE_USERNAME ) {
$durationText = $keyExistsUpdates [ 'duration' ] . " Days " ;
} else {
$durationText = nexus_trans ( 'label.permanent' , [], $locale );
}
2023-01-05 18:29:31 +08:00
$operatorId = get_user_id ();
$operatorInfo = get_user_row ( $operatorId );
$message [ 'msg' ] = nexus_trans ( 'user.grant_props_notification.body' , [ 'name' => $metaName , 'operator' => $operatorInfo [ 'username' ], 'duration' => $durationText ], $locale );
2022-12-19 20:39:13 +08:00
if ( ! empty ( $metaData [ 'duration' ])) {
$metaData [ 'deadline' ] = now () -> addDays ( $metaData [ 'duration' ]);
}
2022-08-11 17:12:36 +08:00
if ( $allowMultiple ) {
//Allow multiple, just insert
$result = $user -> metas () -> create ( $metaData );
2022-10-10 01:36:35 +08:00
$log .= " , allowMultiple, just insert " ;
2022-08-11 17:12:36 +08:00
} else {
$metaExists = $user -> metas () -> where ( 'meta_key' , $metaKey ) -> first ();
2022-10-10 01:36:35 +08:00
$log .= " , metaExists: " . ( $metaExists -> id ? ? '' );
2022-08-11 17:12:36 +08:00
if ( ! $metaExists ) {
$result = $user -> metas () -> create ( $metaData );
2022-10-10 01:36:35 +08:00
$log .= " , meta not exists, just create " ;
2022-08-11 17:12:36 +08:00
} else {
2022-10-10 01:36:35 +08:00
$log .= " , meta exists " ;
$keyExistsUpdates [ 'updated_at' ] = now ();
if ( ! empty ( $keyExistsUpdates [ 'duration' ])) {
2023-01-06 20:19:21 +08:00
if ( $metaExists -> deadline === null ) {
throw new \RuntimeException ( nexus_trans ( 'user.metas.already_valid_forever' , [ 'meta_key_text' => $metaExists -> metaKeyText ]));
}
2022-10-10 01:36:35 +08:00
$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 ;
2022-08-11 17:12:36 +08:00
}
2022-10-10 01:36:35 +08:00
$log .= " , update: " . json_encode ( $keyExistsUpdates );
2022-08-11 17:12:36 +08:00
$result = $metaExists -> update ( $keyExistsUpdates );
}
}
if ( $result ) {
2023-02-09 16:34:38 +08:00
$this -> clearCache ( $user );
2023-01-05 18:29:31 +08:00
if ( $notify ) {
Message :: add ( $message );
}
2022-08-11 17:12:36 +08:00
}
2022-10-10 01:36:35 +08:00
do_log ( $log );
2022-08-11 17:12:36 +08:00
return $result ;
}
2022-01-19 23:54:55 +08:00
2022-09-12 22:41:45 +08:00
public function confirmUser ( $id ) : bool
{
$update = [
'status' => User :: STATUS_CONFIRMED ,
'editsecret' => '' ,
];
User :: query ()
-> whereIn ( 'id' , Arr :: wrap ( $id ))
-> where ( 'status' , User :: STATUS_PENDING )
-> update ( $update );
return true ;
}
2024-03-21 11:09:25 +08:00
public function destroy ( Collection | int $id , $reasonKey = 'user.destroy_by_admin' )
2022-09-13 04:09:23 +08:00
{
2022-10-23 18:26:03 +08:00
if ( ! isRunningInConsole ()) {
user_can ( 'user-delete' , true );
}
2024-03-21 11:09:25 +08:00
if ( is_int ( $id )) {
$uidArr = Arr :: wrap ( $id );
} else {
2024-04-26 03:21:35 +08:00
$uidArr = $id -> pluck ( 'id' ) -> toArray ();
2024-03-21 11:09:25 +08:00
}
2024-12-17 13:12:58 +08:00
$uidStr = implode ( ',' , $uidArr );
2024-04-26 03:21:35 +08:00
$users = User :: query () -> with ( 'language' ) -> whereIn ( 'id' , $uidArr ) -> get ();
2024-12-17 13:12:58 +08:00
if ( $users -> isEmpty ()) {
return true ;
2024-03-21 11:09:25 +08:00
}
2022-09-13 04:09:23 +08:00
$tables = [
'users' => 'id' ,
'hit_and_runs' => 'uid' ,
'claims' => 'uid' ,
'exam_users' => 'uid' ,
'exam_progress' => 'uid' ,
2023-06-30 02:55:53 +08:00
'user_metas' => 'uid' ,
'user_medals' => 'uid' ,
'attendance' => 'uid' ,
'attendance_logs' => 'uid' ,
'login_logs' => 'uid' ,
2024-03-21 11:09:25 +08:00
'oauth_access_tokens' => 'user_id' ,
'oauth_auth_codes' => 'user_id' ,
2022-09-13 04:09:23 +08:00
];
foreach ( $tables as $table => $key ) {
2024-12-17 13:12:58 +08:00
NexusDB :: statement ( sprintf ( " delete from `%s` where `%s` in (%s) " , $table , $key , $uidStr ));
2022-10-23 18:26:03 +08:00
}
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 );
2024-12-17 13:12:58 +08:00
//delete by user, make sure torrent is deleted
NexusDB :: statement ( sprintf ( 'DELETE FROM snatched WHERE userid IN (%s) and not exists (select 1 from torrents where id = snatched.torrentid)' , $uidStr ));
2024-03-21 11:09:25 +08:00
if ( is_int ( $id )) {
do_action ( " user_delete " , $id );
2024-04-26 03:21:35 +08:00
fire_event ( " user_destroyed " , $users -> first ());
2024-03-21 11:09:25 +08:00
}
2022-09-13 04:09:23 +08:00
return true ;
}
2023-01-05 18:29:31 +08:00
public function addTemporaryInvite ( User | null $operator , int $uid , string $action , int $count , int | null $days , string | null $reason = '' )
2022-12-13 13:51:39 +08:00
{
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 );
2023-01-05 18:29:31 +08:00
if ( $operator ) {
$this -> checkPermission ( $operator , $targetUser );
}
2022-12-13 13:51:39 +08:00
$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 ,
2023-01-05 18:29:31 +08:00
'operator' => $operator -> username ? ? '' ,
2022-12-13 13:51:39 +08:00
'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 (),
];
}
}
2023-01-05 18:29:31 +08:00
NexusDB :: transaction ( function () use ( $uid , $message , $inviteData , $count , $operator ) {
2022-12-13 13:51:39 +08:00
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 " );
}
2023-01-05 18:29:31 +08:00
if ( $operator ) {
Message :: add ( $message );
}
2022-12-13 13:51:39 +08:00
});
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' );
}
2023-04-09 14:53:15 +08:00
public function saveLoginLog ( int $uid , string $ip , string $client = '' , bool $notify = false )
{
$locationInfo = get_ip_location_from_geoip ( $ip );
$loginLog = LoginLog :: query () -> create ([
'ip' => $ip ,
'uid' => $uid ,
'country' => $locationInfo [ 'country_en' ] ? ? '' ,
'city' => $locationInfo [ 'city_en' ] ? ? '' ,
'client' => $client ,
]);
if ( $notify ) {
$command = sprintf ( " user:login_notify --this_id=%s " , $loginLog -> id );
do_log ( " [LOGIN_NOTIFY], user: $uid , $command " );
executeCommand ( $command , " string " , true , false );
}
return $loginLog ;
}
2021-04-17 19:01:33 +08:00
}