belongsTo(Role::class, 'roles_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function request() { return $this->hasMany(UserRequest::class, 'users_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function download() { return $this->hasMany(UserDownload::class, 'users_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function release() { return $this->hasMany(UsersRelease::class, 'users_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function series() { return $this->hasMany(UserSerie::class, 'users_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function invitation() { return $this->hasMany(Invitation::class, 'users_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function failedRelease() { return $this->hasMany(DnzbFailure::class, 'users_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function comment() { return $this->hasMany(ReleaseComment::class, 'users_id'); } /** * @param $id * @throws \Exception */ public static function deleteUser($id): void { self::find($id)->delete(); } /** * @param string $role * @param string $username * @param string $host * @param string $email * @return int */ public static function getCount($role = '', $username = '', $host = '', $email = ''): int { $res = self::query()->where('email', '<>', 'sharing@nZEDb.com'); if ($role !== '') { $res->where('roles_id', $role); } if ($username !== '') { $res->where('username', 'like', '%'.$username.'%'); } if ($host !== '') { $res->where('host', 'like', '%'.$host.'%'); } if ($email !== '') { $res->where('email', 'like', '%'.$email.'%'); } return $res->count(['id']); } /** * @param int $id * @param string $userName * @param string $email * @param int $grabs * @param int $role * @param string $notes * @param int $invites * @param int $movieview * @param int $musicview * @param int $gameview * @param int $xxxview * @param int $consoleview * @param int $bookview * @param string $queueType * @param string $nzbgetURL * @param string $nzbgetUsername * @param string $nzbgetPassword * @param string $saburl * @param string $sabapikey * @param string $sabpriority * @param string $sabapikeytype * @param bool $nzbvortexServerUrl * @param bool $nzbvortexApiKey * @param bool $cp_url * @param bool $cp_api * @param string $style * * @return int * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public static function updateUser($id, $userName, $email, $grabs, $role, $notes, $invites, $movieview, $musicview, $gameview, $xxxview, $consoleview, $bookview, $queueType = '', $nzbgetURL = '', $nzbgetUsername = '', $nzbgetPassword = '', $saburl = '', $sabapikey = '', $sabpriority = '', $sabapikeytype = '', $nzbvortexServerUrl = false, $nzbvortexApiKey = false, $cp_url = false, $cp_api = false, $style = 'None'): int { $userName = trim($userName); $rateLimit = Role::query()->where('id', $role)->first(); $sql = [ 'username' => $userName, 'grabs' => $grabs, 'roles_id' => $role, 'notes' => substr($notes, 0, 255), 'invites' => $invites, 'movieview' => $movieview, 'musicview' => $musicview, 'gameview' => $gameview, 'xxxview' => $xxxview, 'consoleview' => $consoleview, 'bookview' => $bookview, 'style' => $style, 'queuetype' => $queueType, 'nzbgeturl' => $nzbgetURL, 'nzbgetusername' => $nzbgetUsername, 'nzbgetpassword' => $nzbgetPassword, 'saburl' => $saburl, 'sabapikey' => $sabapikey, 'sabapikeytype' => $sabapikeytype, 'sabpriority' => $sabpriority, 'nzbvortex_server_url' => $nzbvortexServerUrl, 'nzbvortex_api_key' => $nzbvortexApiKey, 'cp_url' => $cp_url, 'cp_api' => $cp_api, 'rate_limit' => $rateLimit ? $rateLimit['rate_limit'] : 60, ]; if (! empty($email)) { $email = trim($email); $sql += ['email' => $email]; } $user = self::find($id); $user->update($sql); $user->syncRoles([$rateLimit['name']]); return self::SUCCESS; } /** * @param string $userName * @return \Illuminate\Database\Eloquent\Model|null|static */ public static function getByUsername(string $userName) { return self::whereUsername($userName)->first(); } /** * @param string $email * * @return \Illuminate\Database\Eloquent\Model|static * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public static function getByEmail(string $email) { return self::whereEmail($email)->first(); } /** * @param int $uid * @param int $role * * @return bool */ public static function updateUserRole(int $uid, int $role) { $roleQuery = Role::query()->where('id', $role)->first(); $roleName = $roleQuery->name; $user = self::find($uid); $user->syncRoles([$roleName]); return self::find($uid)->update(['roles_id' => $role]); } /** * @param int $uid * @param $date * @param int $addYear */ public static function updateUserRoleChangeDate($uid, $date = '', $addYear = 0): void { $user = self::find($uid); $currRoleExp = $user->rolechangedate ?? now()->toDateTimeString(); if (! empty($date)) { $user->update(['rolechangedate' => $date]); } if (empty($date) && ! empty($addYear)) { $user->update(['rolechangedate' => Carbon::createFromDate($currRoleExp)->addYears($addYear)]); } } public static function updateExpiredRoles(): void { $now = CarbonImmutable::now(); $period = [ 'day' => $now->addDay(), 'week' => $now->addWeek(), 'month' => $now->addMonth(), ]; foreach ($period as $value) { $users = self::query()->whereDate('rolechangedate', '=', $value)->get(); $days = $now->diffInDays($value); foreach ($users as $user) { SendAccountWillExpireEmail::dispatch($user, $days)->onQueue('emails'); } } foreach (self::query()->whereDate('rolechangedate', '<', $now)->get() as $expired) { $expired->update(['roles_id' => self::ROLE_USER, 'rolechangedate' => null]); $expired->syncRoles(['User']); SendAccountExpiredEmail::dispatch($expired)->onQueue('emails'); } } /** * @param $start * @param $offset * @param $orderBy * @param string $userName * @param string $email * @param string $host * @param string $role * @param bool $apiRequests * * @return \Illuminate\Database\Eloquent\Collection * @throws \Throwable */ public static function getRange($start, $offset, $orderBy, $userName = '', $email = '', $host = '', $role = '', $apiRequests = false) { if ($apiRequests) { UserRequest::clearApiRequests(false); $query = " SELECT users.*, roles.name AS rolename, COUNT(user_requests.id) AS apirequests FROM users INNER JOIN roles ON roles.id = users.roles_id LEFT JOIN user_requests ON user_requests.users_id = users.id WHERE users.id != 0 %s %s %s %s AND email != 'sharing@nZEDb.com' GROUP BY users.id ORDER BY %s %s %s "; } else { $query = ' SELECT users.*, roles.name AS rolename FROM users INNER JOIN roles ON roles.id = users.roles_id WHERE 1=1 %s %s %s %s ORDER BY %s %s %s'; } $order = self::getBrowseOrder($orderBy); return self::fromQuery( sprintf( $query, ! empty($userName) ? 'AND users.username '.'LIKE '.escapeString('%'.$userName.'%') : '', ! empty($email) ? 'AND users.email '.'LIKE '.escapeString('%'.$email.'%') : '', ! empty($host) ? 'AND users.host '.'LIKE '.escapeString('%'.$host.'%') : '', (! empty($role) ? ('AND users.roles_id = '.$role) : ''), $order[0], $order[1], ($start === false ? '' : ('LIMIT '.$offset.' OFFSET '.$start)) ) ); } /** * Get sort types for sorting users on the web page user list. * * @param $orderBy * * @return string[] */ public static function getBrowseOrder($orderBy): array { $order = (empty($orderBy) ? 'username_desc' : $orderBy); $orderArr = explode('_', $order); switch ($orderArr[0]) { case 'email': $orderField = 'email'; break; case 'host': $orderField = 'host'; break; case 'createdat': $orderField = 'created_at'; break; case 'lastlogin': $orderField = 'lastlogin'; break; case 'apiaccess': $orderField = 'apiaccess'; break; case 'grabs': $orderField = 'grabs'; break; case 'role': $orderField = 'rolename'; break; case 'rolechangedate': $orderField = 'rolechangedate'; break; case 'verification': $orderField = 'verified'; break; default: $orderField = 'username'; break; } $orderSort = (isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1])) ? $orderArr[1] : 'desc'; return [$orderField, $orderSort]; } /** * Verify a password against a hash. * * Automatically update the hash if it needs to be. * * @param string $password Password to check against hash. * @param string|bool $hash Hash to check against password. * @param int $userID ID of the user. * * @return bool */ public static function checkPassword($password, $hash, $userID = -1): bool { if (Hash::check($password, $hash) === false) { return false; } // Update the hash if it needs to be. if (is_numeric($userID) && $userID > 0 && Hash::needsRehash($hash)) { $hash = self::hashPassword($password); if ($hash !== false) { self::find($userID)->update(['password' => $hash]); } } return true; } /** * @param $uid * * @return int */ public static function updateRssKey($uid): int { self::find($uid)->update(['api_token' => md5(Password::getRepository()->createNewToken())]); return self::SUCCESS; } /** * @param $id * @param $guid * * @return int */ public static function updatePassResetGuid($id, $guid): int { self::find($id)->update(['resetguid' => $guid]); return self::SUCCESS; } /** * @param int $id * @param string $password * * @return int */ public static function updatePassword(int $id, string $password): int { self::find($id)->update(['password' => self::hashPassword($password), 'userseed' => md5(Str::uuid()->toString())]); return self::SUCCESS; } /** * @param $password * @return mixed */ public static function hashPassword($password) { return Hash::make($password); } /** * @param $guid * * @return \Illuminate\Database\Eloquent\Model|static * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public static function getByPassResetGuid(string $guid) { return self::whereResetguid($guid)->first(); } /** * @param $id * @param int $num */ public static function incrementGrabs(int $id, $num = 1): void { self::find($id)->increment('grabs', $num); } /** * Check if the user is in the database, and if their API key is good, return user data if so. * * * @param $userID * @param $rssToken * * @return bool|\Illuminate\Database\Eloquent\Model|null|static */ public static function getByIdAndRssToken($userID, $rssToken) { $user = self::query()->where(['id' => $userID, 'api_token' => $rssToken])->first(); if ($user === null) { return false; } return $user; } /** * @param string $rssToken * @return \Illuminate\Database\Eloquent\Model|null|static */ public static function getByRssToken(string $rssToken) { return self::whereApiToken($rssToken)->first(); } /** * @param $url * * @return bool */ public static function isValidUrl($url): bool { return (! preg_match('/^(http|https|ftp):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i', $url)) ? false : true; } /** * Generate a random username. * * * @return string */ public static function generateUsername(): string { return Str::random(); } /** * @param int $length * * @return string * @throws \Exception */ public static function generatePassword($length = 15): string { return \Token::random($length, true); } /** * Register a new user. * * @param $userName * @param $password * @param $email * @param $host * @param $notes * @param int $invites * @param string $inviteCode * @param bool $forceInviteMode * * @param int $role * @param bool $validate * @return bool|int|string * @throws \Exception */ public static function signUp($userName, $password, $email, $host, $notes, $invites = Invitation::DEFAULT_INVITES, $inviteCode = '', $forceInviteMode = false, $role = self::ROLE_USER, $validate = true) { $user = [ 'username' => trim($userName), 'password' => trim($password), 'email' => trim($email), ]; if ($validate) { $validator = Validator::make($user, [ 'username' => ['required', 'string', 'min:5', 'max:255', 'unique:users'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users', 'indisposable'], 'password' => ['required', 'string', 'min:8', 'confirmed', 'regex:/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/'], ]); if ($validator->fails()) { $error = implode('', Arr::collapse($validator->errors()->toArray())); return $error; } } // Make sure this is the last check, as if a further validation check failed, the invite would still have been used up. $invitedBy = 0; if (! $forceInviteMode && (int) Settings::settingValue('..registerstatus') === Settings::REGISTER_STATUS_INVITE) { if ($inviteCode === '') { return self::ERR_SIGNUP_BADINVITECODE; } $invitedBy = self::checkAndUseInvite($inviteCode); if ($invitedBy < 0) { return self::ERR_SIGNUP_BADINVITECODE; } } return self::add($user['username'], $user['password'], $user['email'], $role, $notes, $host, $invites, $invitedBy); } /** * If a invite is used, decrement the person who invited's invite count. * * @param string $inviteCode * * @return int */ public static function checkAndUseInvite(string $inviteCode): int { $invite = Invitation::getInvite($inviteCode); if (! $invite) { return -1; } self::query()->where('id', $invite['users_id'])->decrement('invites'); Invitation::deleteInvite($inviteCode); return $invite['users_id']; } /** * Add a new user. * * @param string $userName * @param string $password * @param string $email * @param int $role * @param string $notes * @param string $host * @param int $invites * @param int $invitedBy * * @return bool|int * @throws \Exception */ public static function add($userName, $password, $email, $role, $notes = '', $host = '', $invites = Invitation::DEFAULT_INVITES, $invitedBy = 0) { $password = self::hashPassword($password); if (! $password) { return false; } $storeips = (int) Settings::settingValue('..storeuserips') === 1 ? $host : ''; $user = self::create( [ 'username' => $userName, 'password' => $password, 'email' => $email, 'host' => $storeips, 'roles_id' => $role, 'invites' => $invites, 'invitedby' => (int) $invitedBy === 0 ? null : $invitedBy, 'notes' => $notes, ] ); return $user->id; } /** * Get the list of categories the user has excluded. * * @param int $userID ID of the user. * * @return array * @throws \Exception */ public static function getCategoryExclusionById($userID): array { $ret = []; $user = self::find($userID); $userAllowed = $user->getDirectPermissions()->pluck('name')->toArray(); $roleAllowed = $user->getAllPermissions()->pluck('name')->toArray(); $allowed = array_intersect($roleAllowed, $userAllowed); $cats = ['view console', 'view movies', 'view audio', 'view tv', 'view pc', 'view adult', 'view books', 'view other']; if (! empty($allowed)) { foreach ($cats as $cat) { if (! \in_array($cat, $allowed, false)) { switch ($cat) { case 'view console': $ret[] = 1000; continue 2; case 'view movies': $ret[] = 2000; continue 2; case 'view audio': $ret[] = 3000; continue 2; case 'view pc': $ret[] = 4000; continue 2; case 'view tv': $ret[] = 5000; continue 2; case 'view adult': $ret[] = 6000; continue 2; case 'view books': $ret[] = 7000; continue 2; case 'view other': $ret[] = 1; } } } } $exclusion = Category::query()->whereIn('root_categories_id', $ret)->pluck('id')->toArray(); return $exclusion; } /** * @param \Illuminate\Http\Request $request * @return array * @throws \Exception */ public static function getCategoryExclusionForApi(Request $request): array { $apiToken = $request->has('api_token') ? $request->input('api_token') : $request->input('apikey'); $user = self::getByRssToken($apiToken); return self::getCategoryExclusionById($user->id); } /** * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|static[] */ public static function getTopGrabbers() { return self::query()->selectRaw('id, username, SUM(grabs) as grabs')->groupBy('id', 'username')->having('grabs', '>', 0)->orderBy('grabs', 'desc')->limit(10)->get(); } /** * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|static[] */ public static function getUsersByMonth() { return self::query()->whereNotNull('created_at')->where('created_at', '<>', '0000-00-00 00:00:00')->selectRaw("DATE_FORMAT(created_at, '%M %Y') as mth, COUNT(id) as num")->groupBy(['mth'])->orderBy('created_at', 'desc')->get(); } /** * @param $serverUrl * @param $uid * @param $emailTo * * @return string * @throws \Exception */ public static function sendInvite($serverUrl, $uid, $emailTo): string { $user = self::find($uid); $token = Invite::invite($emailTo, $user->id); $url = $serverUrl.'/register?invitecode='.$token; Invitation::addInvite($uid, $token); SendInviteEmail::dispatch($emailTo, $user, $url)->onQueue('emails'); return $url; } /** * Deletes old rows FROM the user_requests and user_downloads tables. * if site->userdownloadpurgedays SET to 0 then all release history is removed but * the download/request rows must remain for at least one day to allow the role based * limits to apply. * * @param int $days * @throws \Exception */ public static function pruneRequestHistory($days = 0): void { if ($days === 0) { $days = 1; UserDownload::query()->update(['releases_id' => null]); } UserRequest::query()->where('timestamp', '<', now()->subDays($days))->delete(); UserDownload::query()->where('timestamp', '<', now()->subDays($days))->delete(); } /** * Deletes users that have not verified their accounts for 3 or more days. */ public static function deleteUnVerified(): void { static::whereVerified(0)->where('created_at', '<', now()->subDays(3))->delete(); } }