null, ]; $options += $defaults; $this->pdo = $options['Settings'] instanceof DB ? $options['Settings'] : new DB(); $this->password_hash_cost = defined('NN_PASSWORD_HASH_COST') ? NN_PASSWORD_HASH_COST : 11; } /** * 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 function checkPassword($password, $hash, $userID = -1): bool { if (password_verify($password, $hash) === false) { return false; } // Update the hash if it needs to be. if (is_numeric($userID) && $userID > 0 && password_needs_rehash($hash, PASSWORD_DEFAULT, ['cost' => $this->password_hash_cost])) { $hash = $this->hashPassword($password); if ($hash !== false) { User::query()->where('id', $userID)->update(['password' => $hash]); } } return true; } /** * @return array */ public function get(): array { return User::all()->all(); } /** * Get the users selected theme. * * @param string|int $userID The id of the user. * * @return array|bool The users selected theme. */ public function getStyle($userID) { $row = User::query()->where('id', $userID)->value('style'); return $row ?? 'None'; } /** * @param $id */ public function delete($id): void { $this->delCartForUser($id); $this->delUserCategoryExclusions($id); $this->delDownloadRequests($id); $this->delApiRequests($id); $rc = new ReleaseComments(); $rc->deleteCommentsForUser($id); $um = new UserMovies(); $um->delMovieForUser($id); $us = new UserSeries(); $us->delShowForUser($id); $forum = new Forum(); $forum->deleteUser($id); User::query()->where('id', $id)->delete(); } /** * @param $uid */ public function delCartForUser($uid): void { $this->pdo->queryExec(sprintf('DELETE FROM users_releases WHERE users_id = %d', $uid)); } /** * @param $uid */ public function delUserCategoryExclusions($uid): void { $this->pdo->queryExec(sprintf('DELETE FROM user_excluded_categories WHERE users_id = %d', $uid)); } /** * @param $userID */ public function delDownloadRequests($userID): void { $this->pdo->queryExec(sprintf('DELETE FROM user_downloads WHERE users_id = %d', $userID)); } /** * @param $userID */ public function delApiRequests($userID): void { $this->pdo->queryExec(sprintf('DELETE FROM user_requests WHERE users_id = %d', $userID)); } /** * Get all users / extra data from other tables. * * @param $start * @param $offset * @param $orderBy * @param string $userName * @param string $email * @param string $host * @param string $role * @param bool $apiRequests * * @return array */ public function getRange($start, $offset, $orderBy, $userName = '', $email = '', $host = '', $role = '', $apiRequests = false): array { if ($apiRequests) { $this->clearApiRequests(false); $query = " SELECT users.*, user_roles.name AS rolename, COUNT(user_requests.id) AS apirequests FROM users INNER JOIN user_roles ON user_roles.id = users.role 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.*, user_roles.name AS rolename FROM users INNER JOIN user_roles ON user_roles.id = users.role WHERE 1=1 %s %s %s %s ORDER BY %s %s %s' ; } $order = $this->getBrowseOrder($orderBy); return $this->pdo->query( sprintf( $query, ($userName !== '' ? ('AND users.username ' . $this->pdo->likeString($userName)) : ''), ($email !== '' ? ('AND users.email ' . $this->pdo->likeString($email)) : ''), ($host !== '' ? ('AND users.host ' . $this->pdo->likeString($host)) : ''), ($role !== '' ? ('AND users.role = ' . $role) : ''), $order[0], $order[1], ($start === false ? '' : ('LIMIT ' . $offset . ' OFFSET ' . $start)) ) ); } /** * @param string $orderBy * * @return array */ public function getBrowseOrder($orderBy): array { $order = ($orderBy === '' ? 'username_desc' : $orderBy); $orderArr = explode('_', $order); switch ($orderArr[0]) { case 'username': $orderField = 'username'; break; case 'email': $orderField = 'email'; break; case 'host': $orderField = 'host'; break; case 'createddate': $orderField = 'createddate'; break; case 'lastlogin': $orderField = 'lastlogin'; break; case 'apiaccess': $orderField = 'apiaccess'; break; case 'apirequests': $orderField = 'apirequests'; break; case 'grabs': $orderField = 'grabs'; break; case 'role': $orderField = 'role'; break; case 'rolechangedate': $orderField = 'rolechangedate'; break; default: $orderField = 'username'; break; } $orderSort = (isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1])) ? $orderArr[1] : 'desc'; return [$orderField, $orderSort]; } /** * @param string|int $role * * @return mixed */ public function getCount($role = '') { $res = $this->pdo->queryOneRow(sprintf("SELECT COUNT(id) as num FROM users WHERE email != 'sharing@nZEDb.com' %s", $role !== '' ? sprintf('AND role = %d', $role) : '')); return $res['num']; } /** * @param $id * @param $userName * @param $email * @param $grabs * @param $role * @param $notes * @param $invites * @param $movieview * @param $musicview * @param $gameview * @param $xxxview * @param $consoleview * @param $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 function update($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); $email = trim($email); if (!$this->isValidUsername($userName)) { return self::ERR_SIGNUP_BADUNAME; } if (!$this->isValidEmail($email)) { return self::ERR_SIGNUP_BADEMAIL; } $res = $this->getByUsername($userName); if ($res) { if ((int)$res['id'] !== (int)$id) { return self::ERR_SIGNUP_UNAMEINUSE; } } $res = $this->getByEmail($email); if ($res) { if ((int)$res['id'] !== (int)$id) { return self::ERR_SIGNUP_EMAILINUSE; } } $sql = []; $sql[] = sprintf('username = %s', $this->pdo->escapeString($userName)); $sql[] = sprintf('email = %s', $this->pdo->escapeString($email)); $sql[] = sprintf('grabs = %d', $grabs); $sql[] = sprintf('role = %d', $role); $sql[] = sprintf('notes = %s', $this->pdo->escapeString(substr($notes, 0, 255))); $sql[] = sprintf('invites = %d', $invites); $sql[] = sprintf('movieview = %d', $movieview); $sql[] = sprintf('musicview = %d', $musicview); $sql[] = sprintf('gameview = %d', $gameview); $sql[] = sprintf('xxxview = %d', $xxxview); $sql[] = sprintf('consoleview = %d', $consoleview); $sql[] = sprintf('bookview = %d', $bookview); $sql[] = sprintf('style = %s', $this->pdo->escapeString($style)); if ($queueType !== '') { $sql[] = sprintf('queuetype = %d', $queueType); } if ($nzbgetURL !== '') { $sql[] = sprintf('nzbgeturl = %s', $this->pdo->escapeString($nzbgetURL)); } $sql[] = sprintf('nzbgetusername = %s', $this->pdo->escapeString($nzbgetUsername)); $sql[] = sprintf('nzbgetpassword = %s', $this->pdo->escapeString($nzbgetPassword)); if ($saburl !== '') { $sql[] = sprintf('saburl = %s', $this->pdo->escapeString($saburl)); } if ($sabapikey !== '') { $sql[] = sprintf('sabapikey = %s', $this->pdo->escapeString($sabapikey)); } if ($sabpriority !== '') { $sql[] = sprintf('sabpriority = %d', $sabpriority); } if ($sabapikeytype !== '') { $sql[] = sprintf('sabapikeytype = %d', $sabapikeytype); } if ($nzbvortexServerUrl !== false) { $sql[] = sprintf("nzbvortex_server_url = '%s'", $nzbvortexServerUrl); } if ($nzbvortexApiKey !== false) { $sql[] = sprintf("nzbvortex_api_key = '%s'", $nzbvortexApiKey); } if ($cp_url !== false) { $sql[] = sprintf('cp_url = %s', $this->pdo->escapeString($cp_url)); } if ($cp_api !== false) { $sql[] = sprintf('cp_api = %s', $this->pdo->escapeString($cp_api)); } $this->pdo->queryExec(sprintf('UPDATE users SET %s WHERE id = %d', implode(', ', $sql), $id)); return self::SUCCESS; } /** * @param string $userName * * @return int */ public function isValidUsername(string $userName): int { return preg_match('/^[a-z][a-z0-9_]{2,}$/i', $userName); } /** * When a user is registering or updating their profile, check if the email is valid. * * @param string $email * * @return bool */ public function isValidEmail(string $email): bool { return (bool)preg_match('/^([\w\+-]+)(\.[\w\+-]+)*@([a-z0-9-]+\.)+[a-z]{2,6}$/i', $email); } /** * @param string $userName * * @return array|bool */ public function getByUsername(string $userName) { return $this->pdo->queryOneRow(sprintf('SELECT users.*, user_roles.name as rolename, user_roles.apirequests, user_roles.downloadrequests FROM users INNER JOIN user_roles on user_roles.id = users.role WHERE username = %s', $this->pdo->escapeString($userName))); } /** * @param string $email * * @return \Illuminate\Database\Eloquent\Model|static * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public function getByEmail(string $email) { return User::query()->where('email', '=', $email)->first(); } /** * @param int $uid * @param int $role * * @return int */ public function updateUserRole(int $uid, int $role): int { User::query()->where('id', $uid)->update(['role' => $role]); return self::SUCCESS; } /** * @param $uid * @param $date * * @return int */ public function updateUserRoleChangeDate($uid, $date): int { User::query()->where('id', $uid)->update(['rolechangedate' => $date]); return self::SUCCESS; } /** * @param $msgsubject * @param $msgbody * * @return int * @throws \Exception */ public function updateExpiredRoles($msgsubject, $msgbody): int { $data = User::query()->whereDate('rolechangedate', '<', (new \DateTime())->format('Y-m-d H:i:s'))->select(['id', 'email'])->get(); foreach ($data as $u) { Utility::sendEmail($u['email'], $msgsubject, $msgbody, Settings::value('site.main.email')); User::query()->where('id', $u['id'])->update(['role' => self::ROLE_USER, 'rolechangedate' => null]); } return self::SUCCESS; } /** * @param $uid * * @return int */ public function updateRssKey($uid): int { User::query()->where('id', $uid)->update(['rsstoken' => md5(uniqid('', true))]); return self::SUCCESS; } /** * @param $id * @param $guid * * @return int */ public function updatePassResetGuid($id, $guid): int { User::query()->where('id', $id)->update(['resetguid' => $guid]); return self::SUCCESS; } /** * @param int $id * @param string $password * * @return int */ public function updatePassword(int $id, string $password): int { User::query()->where('id', $id)->update(['password' => $this->hashPassword($password), 'userseed' => md5(Utility::generateUuid())]); return self::SUCCESS; } /** * Hash a password using crypt. * * @param string $password * * @return string|bool */ public function hashPassword($password) { return password_hash($password, PASSWORD_DEFAULT, ['cost' => $this->password_hash_cost]); } /** * @param string $string * * @return string */ public static function hashSHA1(string $string): string { return sha1($string); } /** * @param $guid * * @return \Illuminate\Database\Eloquent\Model|static * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public function getByPassResetGuid(string $guid) { return User::query()->where('resetguid', $guid)->first(); } /** * @param $id * @param int $num */ public function incrementGrabs(int $id, $num = 1): void { User::query()->where('id', $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 int $userID ID of the user. * @param string $rssToken API key. * * @return bool|array */ public function getByIdAndRssToken($userID, $rssToken) { $user = $this->getById($userID); if ($user === false) { return false; } return ($user['rsstoken'] !== $rssToken ? false : $user); } /** * @param $id * * @return array|bool */ public function getById($id) { $sql = sprintf('SELECT users.*, user_roles.name as rolename, user_roles.hideads, user_roles.canpreview, user_roles.apirequests, user_roles.downloadrequests, NOW() as now FROM users INNER JOIN user_roles on user_roles.id = users.role WHERE users.id = %d', $id); return $this->pdo->queryOneRow($sql); } /** * @param string $rssToken * * @return array|bool */ public function getByRssToken(string $rssToken) { return $this->pdo->queryOneRow(sprintf('SELECT users.*, user_roles.apirequests, user_roles.downloadrequests, NOW() as now FROM users INNER JOIN user_roles on user_roles.id = users.role WHERE users.rsstoken = %s', $this->pdo->escapeString($rssToken))); } /** * @return array */ public function getBrowseOrdering(): array { return ['username_asc', 'username_desc', 'email_asc', 'email_desc', 'host_asc', 'host_desc', 'createddate_asc', 'createddate_desc', 'lastlogin_asc', 'lastlogin_desc', 'apiaccess_asc', 'apiaccess_desc', 'apirequests_asc', 'apirequests_desc', 'grabs_asc', 'grabs_desc', 'role_asc', 'role_desc', 'rolechangedate_asc', 'rolechangedate_desc']; } /** * @param $username * * @return bool */ public function isDisabled($username): bool { return $this->roleCheck(self::ROLE_DISABLED, $username); } /** * @param $url * * @return bool */ public 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; } /** * Create a random username. * * @param string $email * * @return string */ public function generateUsername($email): string { $string = ''; if (preg_match('/[A-Za-z0-9]+/', $email, $matches)) { $string = $matches[0]; } return 'u' . substr(md5(uniqid('', true)), 0, 7) . $string; } /** * @return string */ public function generatePassword(): string { return substr(md5(uniqid('', true)), 0, 8); } /** * Register a new user. * * @param $userName * @param $password * @param $email * @param $host * @param int $role * @param $notes * @param int $invites * @param string $inviteCode * @param bool $forceInviteMode * * @return bool|int * @throws \Exception * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public function signup($userName, $password, $email, $host, $role = self::ROLE_USER, $notes, $invites = self::DEFAULT_INVITES, $inviteCode = '', $forceInviteMode = false) { $userName = trim($userName); $password = trim($password); $email = trim($email); if (!$this->isValidUsername($userName)) { return self::ERR_SIGNUP_BADUNAME; } if (!$this->isValidPassword($password)) { return self::ERR_SIGNUP_BADPASS; } if (!$this->isValidEmail($email)) { return self::ERR_SIGNUP_BADEMAIL; } $res = $this->getByUsername($userName); if ($res) { return self::ERR_SIGNUP_UNAMEINUSE; } $res = $this->getByEmail($email); if ($res) { return self::ERR_SIGNUP_EMAILINUSE; } // 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::value('..registerstatus') === Settings::REGISTER_STATUS_INVITE) { if ($inviteCode === '') { return self::ERR_SIGNUP_BADINVITECODE; } $invitedBy = $this->checkAndUseInvite($inviteCode); if ($invitedBy < 0) { return self::ERR_SIGNUP_BADINVITECODE; } } return $this->add($userName, $password, $email, $role, $notes, $host, $invites, $invitedBy); } /** * @param $password * * @return bool */ public function isValidPassword(string $password): bool { return (strlen($password) > 5); } /** * If a invite is used, decrement the person who invited's invite count. * * @param int $inviteCode * * @return int */ public function checkAndUseInvite($inviteCode): int { $invite = $this->getInvite($inviteCode); if (!$invite) { return -1; } User::query()->where('id', $invite['users_id'])->decrement('invites'); $this->deleteInvite($inviteCode); return $invite['users_id']; } /** * @param $inviteToken * * @return array|bool */ public function getInvite($inviteToken) { // // Tidy any old invites sent greater than DEFAULT_INVITE_EXPIRY_DAYS days ago. // $this->pdo->queryExec(sprintf('DELETE FROM invitations WHERE createddate < now() - INTERVAL %d DAY', self::DEFAULT_INVITE_EXPIRY_DAYS)); return $this->pdo->queryOneRow( sprintf( 'SELECT * FROM invitations WHERE guid = %s', $this->pdo->escapeString($inviteToken) ) ); } /** * @param $inviteToken */ public function deleteInvite(string $inviteToken): void { $this->pdo->queryExec(sprintf('DELETE FROM invitations WHERE guid = %s', $this->pdo->escapeString($inviteToken))); } /** * Add a new user. * * @param string $userName * @param string|bool $password * @param string $email * @param integer $role * @param $notes * @param $host * @param integer $invites * @param integer $invitedBy * * @return bool|int * @throws \Exception */ public function add($userName, $password, $email, $role, $notes, $host, $invites = self::DEFAULT_INVITES, $invitedBy = 0) { $password = $this->hashPassword($password); if (!$password) { return false; } return $this->pdo->queryInsert( sprintf(' INSERT INTO users (username, password, email, role, createddate, host, rsstoken, invites, invitedby, userseed, notes) VALUES (%s, %s, LOWER(%s), %d, NOW(), %s, MD5(%s), %d, %s, MD5(%s), %s)', $this->pdo->escapeString($userName), $this->pdo->escapeString((string)$password), $this->pdo->escapeString($email), $role, $this->pdo->escapeString((int)Settings::value('..storeuserips') === 1 ? $host : ''), $this->pdo->escapeString(uniqid('', true)), $invites, ((int)$invitedBy === 0 ? 'NULL' : $invitedBy), $this->pdo->escapeString($this->pdo->uuid()), $this->pdo->escapeString($notes) ) ); } /** * Verify if the user is logged in. * * @return bool * @throws \Exception */ public function isLoggedIn(): bool { if (isset($_SESSION['uid'])) { return true; } if (isset($_COOKIE['uid'], $_COOKIE['idh'])) { $u = $this->getById($_COOKIE['uid']); if ((int)$u['role'] !== self::ROLE_DISABLED && $_COOKIE['idh'] === self::hashSHA1($u['userseed'] . $_COOKIE['uid'])) { $this->login($_COOKIE['uid'], $_SERVER['REMOTE_ADDR']); } } return isset($_SESSION['uid']); } /** * Log in a user. * * @param int $userID ID of the user. * @param string $host * @param string $remember Save the user in cookies to keep them logged in. * * @throws \Exception */ public function login($userID, $host = '', $remember = ''): void { $_SESSION['uid'] = $userID; if ((int)Settings::value('..storeuserips') !== 1) { $host = ''; } $this->updateSiteAccessed($userID, $host); if ((int)$remember === 1) { $this->setCookies($userID); } } /** * When a user logs in, update the last time they logged in. * * @param int $userID ID of the user. * @param string $host */ public function updateSiteAccessed($userID, $host = ''): void { $this->pdo->queryExec( sprintf( 'UPDATE users SET lastlogin = NOW() %s WHERE id = %d', ($host === '' ? '' : (', host = ' . $this->pdo->escapeString($host))), $userID ) ); } /** * Set up cookies for a user. * * @param int $userID */ public function setCookies($userID): void { $user = $this->getById($userID); $secure_cookie = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? '1' : '0'); setcookie('uid', $userID, time() + 2592000, '/', null, $secure_cookie, true); setcookie('idh', self::hashSHA1($user['userseed'] . $userID), time() + 2592000, '/', null, $secure_cookie, true); } /** * Return the User ID of the user. * * @return int */ public function currentUserId(): int { return $_SESSION['uid'] ?? -1; } /** * Logout the user, destroying his cookies and session. */ public function logout(): void { session_unset(); session_destroy(); $secure_cookie = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? '1' : '0'); setcookie('uid', null, -1, '/', null, $secure_cookie, true); setcookie('idh', null, -1, '/', null, $secure_cookie, true); } /** * @param $uid */ public function updateApiAccessed($uid): void { User::query()->where('id', $uid)->update(['apiaccess' => date('Y-m-d h:m:s')]); } /** * @param $uid * @param $releaseid * * @return false|int|string */ public function addCart($uid, $releaseid) { $sql = sprintf('INSERT INTO users_releases (users_id, releases_id, createddate) VALUES (%d, %d, now())', $uid, $releaseid); return $this->pdo->queryInsert($sql); } /** * @param $uid * @param int|string $releaseId * * @return array */ public function getCart($uid, $releaseId = ''): array { if ($releaseId !== '') { $releaseId = ' AND releases.id = ' . $releaseId; } return $this->pdo->query(sprintf('SELECT users_releases.*, releases.searchname,releases.guid FROM users_releases INNER JOIN releases on releases.id = users_releases.releases_id WHERE users_id = %d %s', $uid, $releaseId)); } /** * @param array $guids * @param $userID * * @return bool */ public function delCartByGuid($guids, $userID): bool { if (!is_array($guids)) { return false; } $del = []; foreach ($guids as $guid) { $rel = $this->pdo->queryOneRow(sprintf('SELECT id FROM releases WHERE guid = %s', $this->pdo->escapeString($guid))); if ($rel) { $del[] = $rel['id']; } } return (bool)$this->pdo->queryExec( sprintf( 'DELETE FROM users_releases WHERE releases_id IN (%s) AND users_id = %d', implode(',', $del), $userID ) ); } /** * @param $guid * @param $uid */ public function delCartByUserAndRelease($guid, $uid): void { $rel = $this->pdo->queryOneRow(sprintf('SELECT id FROM releases WHERE guid = %s', $this->pdo->escapeString($guid))); if ($rel) { $this->pdo->queryExec(sprintf('DELETE FROM users_releases WHERE users_id = %d AND releases_id = %d', $uid, $rel['id'])); } } /** * @param $rid */ public function delCartForRelease($rid): void { $this->pdo->queryExec(sprintf('DELETE FROM users_releases WHERE releases_id = %d', $rid)); } /** * @param $uid * @param array $catids */ public function addCategoryExclusions($uid, array $catids): void { $this->delUserCategoryExclusions($uid); if (count($catids) > 0) { foreach ($catids as $catid) { $this->pdo->queryInsert(sprintf('INSERT INTO user_excluded_categories (users_id, categories_id, createddate) VALUES (%d, %d, now())', $uid, $catid)); } } } /** * @param $role * * @return array */ public function getRoleCategoryExclusion($role): array { $ret = []; $categories = $this->pdo->query(sprintf('SELECT categories_id FROM role_excluded_categories WHERE role = %d', $role)); foreach ($categories as $category) { $ret[] = $category['categories_id']; } return $ret; } /** * @param $role * @param $catids */ public function addRoleCategoryExclusions($role, array $catids): void { $this->delRoleCategoryExclusions($role); if (count($catids) > 0) { foreach ($catids as $catid) { $this->pdo->queryInsert(sprintf('INSERT INTO role_excluded_categories (role, categories_id, createddate) VALUES (%d, %d, now())', $role, $catid)); } } } /** * @param $role */ public function delRoleCategoryExclusions($role): void { $this->pdo->queryExec(sprintf('DELETE FROM role_excluded_categories WHERE role = %d', $role)); } /** * Get the list of categories the user has excluded. * * @param int $userID ID of the user. * * @return array */ public function getCategoryExclusion($userID): array { $ret = []; $categories = $this->pdo->query(sprintf('SELECT categories_id FROM user_excluded_categories WHERE users_id = %d', $userID)); foreach ($categories as $category) { $ret[] = $category['categories_id']; } return $ret; } /** * Get list of category names excluded by the user. * * @param int $userID ID of the user. * * @return array */ public function getCategoryExclusionNames($userID): array { $data = $this->getCategoryExclusion($userID); $category = new Category(['Settings' => $this->pdo]); $categories = $category->getByIds($data); $ret = []; if ($categories !== false) { foreach ($categories as $cat) { $ret[] = $cat['title']; } } return $ret; } /** * @param $uid * @param $catid */ public function delCategoryExclusion($uid, $catid): void { $this->pdo->queryExec(sprintf('DELETE FROM user_excluded_categories WHERE users_id = %d AND categories_id = %d', $uid, $catid)); } /** * @param $sitetitle * @param $siteemail * @param $serverurl * @param $uid * @param $emailto * * @return string * @throws \Exception */ public function sendInvite($sitetitle, $siteemail, $serverurl, $uid, $emailto): string { $sender = $this->getById($uid); $token = self::hashSHA1(uniqid('', true)); $subject = $sitetitle . ' Invitation'; $url = $serverurl . 'register?invitecode=' . $token; $contents = $sender['username'] . ' has sent an invite to join ' . $sitetitle . ' to this email address. To accept the invitation click the following link. ' . $url; Utility::sendEmail($emailto, $subject, $contents, $siteemail); $this->addInvite($uid, $token); return $url; } /** * @param $uid * @param $inviteToken */ public function addInvite(int $uid, string $inviteToken): void { $this->pdo->queryInsert(sprintf('INSERT INTO invitations (guid, users_id, createddate) VALUES (%s, %d, now())', $this->pdo->escapeString($inviteToken), $uid)); } /** * @return array */ public function getTopGrabbers(): array { return $this->pdo->query('SELECT id, username, SUM(grabs) as grabs FROM users GROUP BY id, username HAVING SUM(grabs) > 0 ORDER BY grabs DESC LIMIT 10' ); } /** * Get list of user signups by month. * * @return array */ public function getUsersByMonth(): array { return $this->pdo->query(" SELECT DATE_FORMAT(createddate, '%M %Y') AS mth, COUNT(id) AS num FROM users WHERE createddate IS NOT NULL AND createddate != '0000-00-00 00:00:00' GROUP BY mth ORDER BY createddate DESC" ); } /** * @return array * @throws \Exception */ public function getUsersByHostHash(): array { $ipsql = "('-1')"; if (Settings::value('..userhostexclusion') !== '') { $ipsql = ''; $ips = explode(',', Settings::value('..userhostexclusion')); foreach ($ips as $ip) { $ipsql .= $this->pdo->escapeString($this->getHostHash($ip, Settings::value('..siteseed'))) . ','; } $ipsql = "(" . $ipsql . " '-1')"; } $sql = sprintf("SELECT hosthash, group_concat(users_id) AS user_string, group_concat(username) AS user_names FROM ( SELECT hosthash, users_id, username FROM user_downloads LEFT OUTER JOIN users ON users.id = user_downloads.users_id WHERE hosthash IS NOT NULL AND hosthash NOT IN %s GROUP BY hosthash, users_id union distinct SELECT hosthash, users_id, username FROM user_requests LEFT OUTER JOIN users on users.id = user_requests.users_id WHERE hosthash IS NOT NULL AND hosthash NOT IN %s GROUP BY hosthash, users_id ) x GROUP BY hosthash HAVING CAST((LENGTH(group_concat(users_id)) - LENGTH(REPLACE(group_concat(users_id), ',', ''))) / LENGTH(',') AS UNSIGNED) < 9 ORDER BY CAST((LENGTH(group_concat(users_id)) - LENGTH(REPLACE(group_concat(users_id), ',', ''))) / LENGTH(',') AS UNSIGNED) DESC limit 10", $ipsql, $ipsql ); return $this->pdo->query($sql); } /** * @param $host * @param string|null $siteseed * * @return string * @throws \Exception */ public function getHostHash($host, string $siteseed = ''): string { if ($siteseed === '') { $siteseed = Settings::value('..siteseed'); } return self::hashSHA1($siteseed . $host . $siteseed); } /** * @return array */ public function getUsersByRole(): array { return $this->pdo->query('SELECT ur.name, COUNT(u.id) as num FROM users u INNER JOIN user_roles ur ON ur.id = u.role GROUP BY ur.name ORDER BY COUNT(u.id) DESC' ); } /** * @return array */ public function getLoginCountsByMonth(): array { return $this->pdo->query("SELECT 'Login' as type, sum(case when lastlogin > curdate() - INTERVAL 1 DAY then 1 else 0 end) as 1day, sum(case when lastlogin > curdate() - INTERVAL 7 DAY AND lastlogin < curdate() - INTERVAL 1 DAY then 1 else 0 end) as 7day, sum(case when lastlogin > curdate() - INTERVAL 1 MONTH AND lastlogin < curdate() - INTERVAL 7 DAY then 1 else 0 end) as 1month, sum(case when lastlogin > curdate() - INTERVAL 3 MONTH AND lastlogin < curdate() - INTERVAL 1 MONTH then 1 else 0 end) as 3month, sum(case when lastlogin > curdate() - INTERVAL 6 MONTH AND lastlogin < curdate() - INTERVAL 3 MONTH then 1 else 0 end) as 6month, sum(case when lastlogin < curdate() - INTERVAL 6 MONTH then 1 else 0 end) as 12month FROM users union SELECT 'Api' as type, sum(case when apiaccess > curdate() - INTERVAL 1 DAY then 1 else 0 end) as 1day, sum(case when apiaccess > curdate() - INTERVAL 7 DAY AND apiaccess < curdate() - INTERVAL 1 DAY then 1 else 0 end) as 7day, sum(case when apiaccess > curdate() - INTERVAL 1 MONTH AND apiaccess < curdate() - INTERVAL 7 DAY then 1 else 0 end) as 1month, sum(case when apiaccess > curdate() - INTERVAL 3 MONTH AND apiaccess < curdate() - INTERVAL 1 MONTH then 1 else 0 end) as 3month, sum(case when apiaccess > curdate() - INTERVAL 6 MONTH AND apiaccess < curdate() - INTERVAL 3 MONTH then 1 else 0 end) as 6month, sum(case when apiaccess < curdate() - INTERVAL 6 MONTH then 1 else 0 end) as 12month FROM users" ); } /** * @return array */ public function getRoles(): array { return $this->pdo->query('SELECT * FROM user_roles'); } /** * @param $id * * @return array|bool */ public function getRoleById($id) { $sql = sprintf('SELECT * FROM user_roles WHERE id = %d', $id); return $this->pdo->queryOneRow($sql); } /** * @param $name * @param $apirequests * @param $downloadrequests * @param $defaultinvites * @param $canpreview * @param $hideads * * @return false|int|string */ public function addRole($name, $apirequests, $downloadrequests, $defaultinvites, $canpreview, $hideads) { $sql = sprintf('INSERT INTO user_roles (name, apirequests, downloadrequests, defaultinvites, canpreview, hideads) VALUES (%s, %d, %d, %d, %d, %d)', $this->pdo->escapeString($name), $apirequests, $downloadrequests, $defaultinvites, $canpreview, $hideads); return $this->pdo->queryInsert($sql); } /** * @param $id * @param $name * @param $apirequests * @param $downloadrequests * @param $defaultinvites * @param $isdefault * @param $canpreview * @param $hideads * * @return bool|\PDOStatement */ public function updateRole($id, $name, $apirequests, $downloadrequests, $defaultinvites, $isdefault, $canpreview, $hideads) { if ((int)$isdefault === 1) { $this->pdo->queryExec('UPDATE user_roles SET isdefault = 0'); } return $this->pdo->queryExec(sprintf('UPDATE user_roles SET name = %s, apirequests = %d, downloadrequests = %d, defaultinvites = %d, isdefault = %d, canpreview = %d, hideads = %d WHERE id = %d', $this->pdo->escapeString($name), $apirequests, $downloadrequests, $defaultinvites, $isdefault, $canpreview, $hideads, $id)); } /** * @param $id * * @return bool|\PDOStatement */ public function deleteRole($id) { $res = $this->pdo->query(sprintf('SELECT id FROM users WHERE role = %d', $id)); if (count($res) > 0) { $userids = []; foreach ($res as $user) { $userids[] = $user['id']; } $defaultrole = $this->getDefaultRole(); $this->pdo->queryExec(sprintf('UPDATE users SET role = %d WHERE id IN (%s)', $defaultrole['id'], implode(',', $userids))); } return $this->pdo->queryExec(sprintf('DELETE FROM user_roles WHERE id = %d', $id)); } /** * @return array|bool */ public function getDefaultRole() { return $this->pdo->queryOneRow('SELECT * FROM user_roles WHERE isdefault = 1'); } /** * Get the quantity of API requests in the last day for the users_id. * * @param int $userID * * @return int */ public function getApiRequests($userID) { // Clear old requests. $this->clearApiRequests($userID); $requests = $this->pdo->queryOneRow( sprintf('SELECT COUNT(id) AS num FROM user_requests WHERE users_id = %d', $userID) ); return (!$requests ? 0 : (int)$requests['num']); } /** * If a user accesses the API, log it. * * @param int $userID ID of the user. * @param string $request The API request. * */ public function addApiRequest($userID, $request): void { $this->pdo->queryInsert( sprintf( 'INSERT INTO user_requests (users_id, request, timestamp) VALUES (%d, %s, NOW())', $userID, $this->pdo->escapeString($request) ) ); } /** * Delete api requests older than a day. * * @param int|bool $userID * int The users ID. * bool false do all user ID's.. * * @return void */ protected function clearApiRequests($userID): void { if ($userID === false) { $this->pdo->queryExec('DELETE FROM user_requests WHERE timestamp < DATE_SUB(NOW(), INTERVAL 1 DAY)'); } else { $this->pdo->queryExec( sprintf( 'DELETE FROM user_requests WHERE users_id = %d AND timestamp < DATE_SUB(NOW(), INTERVAL 1 DAY)', $userID ) ); } } /** * deletes old rows FROM the userrequest 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 */ public function pruneRequestHistory($days = 0) { if ($days === 0) { $days = 1; $this->pdo->queryExec('UPDATE user_downloads SET releases_id = null'); } $this->pdo->queryExec(sprintf('DELETE FROM user_requests WHERE timestamp < DATE_SUB(NOW(), INTERVAL %d DAY)', $days)); $this->pdo->queryExec(sprintf('DELETE FROM user_downloads WHERE timestamp < DATE_SUB(NOW(), INTERVAL %d DAY)', $days)); } /** * Get the COUNT of how many NZB's the user has downloaded in the past day. * * @param int $userID * * @return int */ public function getDownloadRequests($userID): int { // Clear old requests. $this->pdo->queryExec( sprintf( 'DELETE FROM user_downloads WHERE users_id = %d AND timestamp < DATE_SUB(NOW(), INTERVAL 1 DAY)', $userID ) ); $value = $this->pdo->queryOneRow( sprintf( 'SELECT COUNT(id) AS num FROM user_downloads WHERE users_id = %d AND timestamp > DATE_SUB(NOW(), INTERVAL 1 DAY)', $userID ) ); return ($value === false ? 0 : (int) $value['num']); } /** * @param $userID * * @return array */ public function getDownloadRequestsForUser($userID): array { return $this->pdo->query(sprintf('SELECT u.*, r.guid, r.searchname FROM user_downloads u LEFT OUTER JOIN releases r ON r.id = u.releases_id WHERE u.users_id = %d ORDER BY u.timestamp DESC', $userID ) ); } /** * If a user downloads a NZB, log it. * * @param int $userID id of the user. * * @param $releaseID * * @return bool|int */ public function addDownloadRequest($userID, $releaseID) { return $this->pdo->queryInsert( sprintf( 'INSERT INTO user_downloads (users_id, releases_id, timestamp) VALUES (%d, %d, NOW())', $userID, $releaseID ) ); } /** * @param $releaseID * * @return false|int|string */ public function delDownloadRequestsForRelease(int $releaseID) { return $this->pdo->queryInsert(sprintf('DELETE FROM user_downloads WHERE releases_id = %d', $releaseID)); } /** * Checks if a user is a specific role. * * @notes Uses type of $user to denote identifier. if string: username, if int: users_id * @param int $roleID * @param string|int $user * @return bool */ public function roleCheck($roleID, $user): bool { if (is_string($user) && strlen($user) > 0) { $user = $this->pdo->escapeString($user); $querySuffix = "username = $user"; } elseif (is_int($user) && $user >= 0) { $querySuffix = "id = $user"; } else { return false; } $result = $this->pdo->queryOneRow( sprintf( 'SELECT role FROM users WHERE %s', $querySuffix ) ); return $result['role'] === $roleID; } /** * Wrapper for roleCheck specifically for Admins. * * @param int $userID * @return bool */ public function isAdmin($userID) { return $this->roleCheck(self::ROLE_ADMIN, (int)$userID); } /** * Wrapper for roleCheck specifically for Moderators. * * @param int $userId * @return bool */ public function isModerator($userId) { return $this->roleCheck(self::ROLE_MODERATOR, (int)$userId); } }