diff --git a/mod/regmod.php b/mod/regmod.php deleted file mode 100644 index df1020b9f4..0000000000 --- a/mod/regmod.php +++ /dev/null @@ -1,120 +0,0 @@ -. - * - */ - -use Friendica\App; -use Friendica\Core\Worker; -use Friendica\Database\DBA; -use Friendica\DI; -use Friendica\Model\Register; -use Friendica\Model\User; -use Friendica\Module\Security\Login; - -function user_allow($hash) -{ - $register = Register::getByHash($hash); - if (!DBA::isResult($register)) { - return false; - } - - $user = User::getById($register['uid']); - if (!DBA::isResult($user)) { - exit(); - } - - Register::deleteByHash($hash); - - DBA::update('user', ['blocked' => false, 'verified' => true], ['uid' => $register['uid']]); - - $profile = DBA::selectFirst('profile', ['net-publish'], ['uid' => $register['uid']]); - - if (DBA::isResult($profile) && $profile['net-publish'] && DI::config()->get('system', 'directory')) { - $url = DI::baseUrl() . '/profile/' . $user['nickname']; - Worker::add(PRIORITY_LOW, "Directory", $url); - } - - $l10n = DI::l10n()->withLang($register['language']); - - $res = User::sendRegisterOpenEmail( - $l10n, - $user, - DI::config()->get('config', 'sitename'), - DI::baseUrl()->get(), - ($register['password'] ?? '') ?: 'Sent in a previous email' - ); - - if ($res) { - info(DI::l10n()->t('Account approved.') . EOL); - return true; - } -} - -// This does not have to go through user_remove() and save the nickname -// permanently against re-registration, as the person was not yet -// allowed to have friends on this system -function user_deny($hash) -{ - $register = Register::getByHash($hash); - if (!DBA::isResult($register)) { - return false; - } - - $user = User::getById($register['uid']); - if (!DBA::isResult($user)) { - exit(); - } - - DBA::delete('user', ['uid' => $register['uid']]); - - Register::deleteByHash($register['hash']); - - notice(DI::l10n()->t('Registration revoked for %s', $user['username']) . EOL); - return true; -} - -function regmod_content(App $a) -{ - if (!local_user()) { - info(DI::l10n()->t('Please login.') . EOL); - return Login::form(DI::args()->getQueryString(), intval(DI::config()->get('config', 'register_policy')) === \Friendica\Module\Register::CLOSED ? 0 : 1); - } - - if (!is_site_admin() || !empty($_SESSION['submanage'])) { - notice(DI::l10n()->t('Permission denied.') . EOL); - return ''; - } - - if ($a->argc != 3) { - exit(); - } - - $cmd = $a->argv[1]; - $hash = $a->argv[2]; - - if ($cmd === 'deny') { - user_deny($hash); - DI::baseUrl()->redirect('admin/users/'); - } - - if ($cmd === 'allow') { - user_allow($hash); - DI::baseUrl()->redirect('admin/users/'); - } -} diff --git a/src/Console/NewPassword.php b/src/Console/NewPassword.php deleted file mode 100644 index 118428866c..0000000000 --- a/src/Console/NewPassword.php +++ /dev/null @@ -1,126 +0,0 @@ -. - * - */ - -namespace Friendica\Console; - -use Friendica\App; -use Friendica\Core\L10n; -use Friendica\Database\Database; -use Friendica\Model\User; -use RuntimeException; - -/** - * tool to set a new password for a user - * - * With this tool, you can set a new password for a user - */ -class NewPassword extends \Asika\SimpleConsole\Console -{ - protected $helpOptions = ['h', 'help', '?']; - - /** - * @var App\Mode - */ - private $appMode; - /** - * @var L10n - */ - private $l10n; - /** - * @var Database - */ - private $dba; - - protected function getHelp() - { - $help = << [] [-h|--help|-?] [-v] - -Description - Creates a new password for a user without using the "forgot password" functionality. - -Options - -h|--help|-? Show help information - -v Show more debug information. -HELP; - return $help; - } - - public function __construct(App\Mode $appMode, L10n $l10n, Database $dba, array $argv = null) - { - parent::__construct($argv); - - $this->appMode = $appMode; - $this->l10n = $l10n; - $this->dba = $dba; - } - - protected function doExecute() - { - if ($this->getOption('v')) { - $this->out('Class: ' . __CLASS__); - $this->out('Arguments: ' . var_export($this->args, true)); - $this->out('Options: ' . var_export($this->options, true)); - } - - if (count($this->args) == 0) { - $this->out($this->getHelp()); - return 0; - } - - if (count($this->args) > 2) { - throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments'); - } - - if ($this->appMode->isInstall()) { - throw new RuntimeException('Database isn\'t ready or populated yet'); - } - - $nick = $this->getArgument(0); - - $user = $this->dba->selectFirst('user', ['uid'], ['nickname' => $nick]); - if (!$this->dba->isResult($user)) { - throw new RuntimeException($this->l10n->t('User not found')); - } - - $password = $this->getArgument(1); - if (is_null($password)) { - $this->out($this->l10n->t('Enter new password: '), false); - $password = \Seld\CliPrompt\CliPrompt::hiddenPrompt(true); - } - - try { - $result = User::updatePassword($user['uid'], $password); - - if (!$this->dba->isResult($result)) { - throw new \Exception($this->l10n->t('Password update failed. Please try again.')); - } - - $this->out($this->l10n->t('Password changed.')); - } catch (\Exception $e) { - throw new RuntimeException($e->getMessage(), $e->getCode(), $e); - } - - return 0; - } -} diff --git a/src/Console/User.php b/src/Console/User.php new file mode 100644 index 0000000000..b12a3a6ad3 --- /dev/null +++ b/src/Console/User.php @@ -0,0 +1,423 @@ +. + * + */ + +namespace Friendica\Console; + +use Console_Table; +use Friendica\App; +use Friendica\Content\Pager; +use Friendica\Core\L10n; +use Friendica\Database\Database; +use Friendica\Model\Register; +use Friendica\Model\User as UserModel; +use Friendica\Util\Temporal; +use RuntimeException; +use Seld\CliPrompt\CliPrompt; + +/** + * tool to manage users of the current node + */ +class User extends \Asika\SimpleConsole\Console +{ + protected $helpOptions = ['h', 'help', '?']; + + /** + * @var App\Mode + */ + private $appMode; + /** + * @var L10n + */ + private $l10n; + /** + * @var Database + */ + private $dba; + + protected function getHelp() + { + $help = << [] [-h|--help|-?] [-v] + bin/console user add [ [ [ []]]] [-h|--help|-?] [-v] + bin/console user delete [] [-q] [-h|--help|-?] [-v] + bin/console user allow [] [-h|--help|-?] [-v] + bin/console user deny [] [-h|--help|-?] [-v] + bin/console user block [] [-h|--help|-?] [-v] + bin/console user unblock [] [-h|--help|-?] [-v] + bin/console user list pending [-s|--start=0] [-c|--count=50] [-h|--help|-?] [-v] + bin/console user list removed [-s|--start=0] [-c|--count=50] [-h|--help|-?] [-v] + bin/console user list active [-s|--start=0] [-c|--count=50] [-h|--help|-?] [-v] + bin/console user list all [-s|--start=0] [-c|--count=50] [-h|--help|-?] [-v] + bin/console user search id [-h|--help|-?] [-v] + bin/console user search nick [-h|--help|-?] [-v] + bin/console user search mail [-h|--help|-?] [-v] + bin/console user search guid [-h|--help|-?] [-v] + +Description + Modify user settings per console commands. + +Options + -h|--help|-? Show help information + -v Show more debug information. + -q Quiet mode (don't ask for a command). +HELP; + return $help; + } + + public function __construct(App\Mode $appMode, L10n $l10n, Database $dba, array $argv = null) + { + parent::__construct($argv); + + $this->appMode = $appMode; + $this->l10n = $l10n; + $this->dba = $dba; + } + + protected function doExecute() + { + if ($this->getOption('v')) { + $this->out('Class: ' . __CLASS__); + $this->out('Arguments: ' . var_export($this->args, true)); + $this->out('Options: ' . var_export($this->options, true)); + } + + if (count($this->args) == 0) { + $this->out($this->getHelp()); + return 0; + } + + if ($this->appMode->isInstall()) { + throw new RuntimeException('Database isn\'t ready or populated yet'); + } + + $command = $this->getArgument(0); + + switch ($command) { + case 'password': + return $this->password(); + case 'add': + return $this->addUser(); + case 'allow': + return $this->pendingUser(true); + case 'deny': + return $this->pendingUser(false); + case 'block': + return $this->blockUser(true); + case 'unblock': + return $this->blockUser(false); + case 'delete': + return $this->deleteUser(); + case 'list': + return $this->listUser(); + case 'search': + return $this->searchUser(); + default: + throw new \Asika\SimpleConsole\CommandArgsException('Wrong command.'); + } + } + + /** + * Sets a new password + * + * @return int Return code of this command + * + * @throws \Exception + */ + private function password() + { + $nick = $this->getArgument(1); + + $user = $this->dba->selectFirst('user', ['uid'], ['nickname' => $nick]); + if (!$this->dba->isResult($user)) { + throw new RuntimeException($this->l10n->t('User not found')); + } + + $password = $this->getArgument(2); + + if (is_null($password)) { + $this->out($this->l10n->t('Enter new password: '), false); + $password = CliPrompt::hiddenPrompt(true); + } + + try { + $result = UserModel::updatePassword($user['uid'], $password); + + if (!$this->dba->isResult($result)) { + throw new \Exception($this->l10n->t('Password update failed. Please try again.')); + } + + $this->out($this->l10n->t('Password changed.')); + } catch (\Exception $e) { + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + } + + return 0; + } + + /** + * Adds a new user based on given console arguments + * + * @return bool True, if the command was successful + * @throws \ErrorException + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + private function addUser() + { + $name = $this->getArgument(1); + $nick = $this->getArgument(2); + $email = $this->getArgument(3); + $lang = $this->getArgument(4); + + if (empty($name)) { + $this->out($this->l10n->t('Enter user name: ')); + $name = CliPrompt::prompt(); + if (empty($name)) { + throw new RuntimeException('A name must be set.'); + } + } + + if (empty($nick)) { + $this->out($this->l10n->t('Enter user nickname: ')); + $nick = CliPrompt::prompt(); + if (empty($nick)) { + throw new RuntimeException('A nick name must be set.'); + } + } + + if (empty($email)) { + $this->out($this->l10n->t('Enter user email address: ')); + $email = CliPrompt::prompt(); + if (empty($email)) { + throw new RuntimeException('A email address must be set.'); + } + } + + if (empty($lang)) { + $this->out($this->l10n->t('Enter a language (optional): ')); + $lang = CliPrompt::prompt(); + } + + if (empty($lang)) { + return UserModel::createMinimal($name, $email, $nick); + } else { + return UserModel::createMinimal($name, $email, $nick, $lang); + } + } + + /** + * Allows or denys a user based on it's nickname + * + * @param bool $allow True, if the pending user is allowed, false if denies + * + * @return bool True, if allow was successful + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private function pendingUser(bool $allow = true) + { + $nick = $this->getArgument(1); + + if (!$nick) { + $this->out($this->l10n->t('Enter user nickname: ')); + $nick = CliPrompt::prompt(); + if (empty($nick)) { + throw new RuntimeException('A nick name must be set.'); + } + } + + $user = $this->dba->selectFirst('user', ['uid'], ['nickname' => $nick]); + if (empty($user)) { + throw new RuntimeException($this->l10n->t('User not found')); + } + + $pending = Register::getPendingForUser($user['uid'] ?? 0); + if (empty($pending)) { + throw new RuntimeException($this->l10n->t('User is not pending.')); + } + + return ($allow) ? UserModel::allow($pending['hash']) : UserModel::deny($pending['hash']); + } + + /** + * Blocks/unblocks a user + * + * @param bool $block True, if the given user should get blocked + * + * @return bool True, if the command was successful + * @throws \Exception + */ + private function blockUser(bool $block = true) + { + $nick = $this->getArgument(1); + + if (!$nick) { + $this->out($this->l10n->t('Enter user nickname: ')); + $nick = CliPrompt::prompt(); + if (empty($nick)) { + throw new RuntimeException('A nick name must be set.'); + } + } + + $user = $this->dba->selectFirst('user', ['uid'], ['nickname' => $nick]); + if (empty($user)) { + throw new RuntimeException($this->l10n->t('User not found')); + } + + return $block ? UserModel::block($user['uid'] ?? 0) : UserModel::block($user['uid'] ?? 0, false); + } + + /** + * Deletes a user + * + * @return bool True, if the delete was successful + * @throws \Exception + */ + private function deleteUser() + { + $nick = $this->getArgument(1); + + if (!$nick) { + $this->out($this->l10n->t('Enter user nickname: ')); + $nick = CliPrompt::prompt(); + if (empty($nick)) { + throw new RuntimeException('A nick name must be set.'); + } + } + + $user = $this->dba->selectFirst('user', ['uid'], ['nickname' => $nick]); + if (empty($user)) { + throw new RuntimeException($this->l10n->t('User not found')); + } + + if (!$this->getOption('q')) { + $this->out($this->l10n->t('Type "yes" to delete %s', $nick)); + if (CliPrompt::prompt() !== 'yes') { + throw new RuntimeException('Delete abort.'); + } + } + + return UserModel::remove($user['uid'] ?? -1); + } + + /** + * List users of the current node + * + * @return bool True, if the command was successful + */ + private function listUser() + { + $subCmd = $this->getArgument(1); + $start = $this->getOption(['s', 'start'], 0); + $count = $this->getOption(['c', 'count'], Pager::ITEMS_PER_PAGE); + + $table = new Console_Table(); + + switch ($subCmd) { + case 'pending': + $table->setHeaders(['Nick', 'Name', 'URL', 'E-Mail', 'Register Date', 'Comment']); + $pending = Register::getPending($start, $count); + foreach ($pending as $contact) { + $table->addRow([ + $contact['nick'], + $contact['name'], + $contact['url'], + $contact['email'], + Temporal::getRelativeDate($contact['created']), + $contact['note'], + ]); + } + $this->out($table->getTable()); + return true; + case 'all': + case 'active': + case 'removed': + $table->setHeaders(['Nick', 'Name', 'URL', 'E-Mail', 'Register', 'Login', 'Last Item']); + $contacts = UserModel::getList($start, $count, $subCmd); + foreach ($contacts as $contact) { + $table->addRow([ + $contact['nick'], + $contact['name'], + $contact['url'], + $contact['email'], + Temporal::getRelativeDate($contact['created']), + Temporal::getRelativeDate($contact['login_date']), + Temporal::getRelativeDate($contact['lastitem_date']), + ]); + } + $this->out($table->getTable()); + return true; + default: + $this->out($this->getHelp()); + return false; + } + } + + /** + * Returns a user based on search parameter + * + * @return bool True, if the command was successful + */ + private function searchUser() + { + $fields = [ + 'uid', + 'guid', + 'username', + 'nickname', + 'email', + 'register_date', + 'login_date', + 'verified', + 'blocked', + ]; + + $subCmd = $this->getArgument(1); + $param = $this->getArgument(2); + + $table = new Console_Table(); + $table->setHeaders(['UID', 'GUID', 'Name', 'Nick', 'E-Mail', 'Register', 'Login', 'Verified', 'Blocked']); + + switch ($subCmd) { + case 'id': + $user = UserModel::getById($param, $fields); + break; + case 'guid': + $user = UserModel::getByGuid($param, $fields); + break; + case 'email': + $user = UserModel::getByEmail($param, $fields); + break; + case 'nick': + $user = UserModel::getByNickname($param, $fields); + break; + default: + $this->out($this->getHelp()); + return false; + } + + $table->addRow($user); + $this->out($table->getTable()); + + return true; + } +} diff --git a/src/Content/Pager.php b/src/Content/Pager.php index 5b4345a4c8..a5e61bbf9f 100644 --- a/src/Content/Pager.php +++ b/src/Content/Pager.php @@ -30,10 +30,13 @@ use Friendica\Util\Strings; */ class Pager { + /** @var int Default count of items per page */ + const ITEMS_PER_PAGE = 50; + /** @var integer */ private $page = 1; /** @var integer */ - protected $itemsPerPage = 50; + protected $itemsPerPage = self::ITEMS_PER_PAGE; /** @var string */ protected $baseQueryString = ''; diff --git a/src/Core/Console.php b/src/Core/Console.php index 70835db9c4..86178c209d 100644 --- a/src/Core/Console.php +++ b/src/Core/Console.php @@ -57,7 +57,7 @@ Commands: autoinstall Starts automatic installation of friendica based on values from htconfig.php lock Edit site locks maintenance Set maintenance mode for this node - newpassword Set a new password for a given user + user User management php2po Generate a messages.po file from a strings.php file po2php Generate a strings.php file from a messages.po file typo Checks for parse errors in Friendica files @@ -85,7 +85,7 @@ HELP; 'autoinstall' => Friendica\Console\AutomaticInstallation::class, 'lock' => Friendica\Console\Lock::class, 'maintenance' => Friendica\Console\Maintenance::class, - 'newpassword' => Friendica\Console\NewPassword::class, + 'user' => Friendica\Console\User::class, 'php2po' => Friendica\Console\PhpToPo::class, 'po2php' => Friendica\Console\PoToPhp::class, 'typo' => Friendica\Console\Typo::class, diff --git a/src/Core/L10n.php b/src/Core/L10n.php index cda83ac3f4..8e6ee171c8 100644 --- a/src/Core/L10n.php +++ b/src/Core/L10n.php @@ -33,6 +33,9 @@ use Psr\Log\LoggerInterface; */ class L10n { + /** @var string The default language */ + const DEFAULT = 'en'; + /** * A string indicating the current language used for translation: * - Two-letter ISO 639-1 code. @@ -64,7 +67,7 @@ class L10n $this->dba = $dba; $this->logger = $logger; - $this->loadTranslationTable(L10n::detectLanguage($server, $get, $config->get('system', 'language', 'en'))); + $this->loadTranslationTable(L10n::detectLanguage($server, $get, $config->get('system', 'language', self::DEFAULT))); $this->setSessionVariable($session); $this->setLangFromSession($session); } @@ -158,7 +161,7 @@ class L10n * * @return string The two-letter language code */ - public static function detectLanguage(array $server, array $get, string $sysLang = 'en') + public static function detectLanguage(array $server, array $get, string $sysLang = self::DEFAULT) { $lang_variable = $server['HTTP_ACCEPT_LANGUAGE'] ?? null; diff --git a/src/Model/Register.php b/src/Model/Register.php index fa8fb7bdbb..be00699bfa 100644 --- a/src/Model/Register.php +++ b/src/Model/Register.php @@ -21,6 +21,7 @@ namespace Friendica\Model; +use Friendica\Content\Pager; use Friendica\Database\DBA; use Friendica\Util\DateTimeFormat; use Friendica\Util\Strings; @@ -33,21 +34,46 @@ class Register /** * Return the list of pending registrations * + * @param int $start Start count (Default is 0) + * @param int $count Count of the items per page (Default is @see Pager::ITEMS_PER_PAGE) + * * @return array * @throws \Exception */ - public static function getPending() + public static function getPending($start = 0, $count = Pager::ITEMS_PER_PAGE) { $stmt = DBA::p( - "SELECT `register`.*, `contact`.`name`, `contact`.`url`, `contact`.`micro`, `user`.`email` + "SELECT `register`.*, `contact`.`name`, `contact`.`url`, `contact`.`micro`, `user`.`email`, `contact`.`nick` FROM `register` INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid` - INNER JOIN `user` ON `register`.`uid` = `user`.`uid`" + INNER JOIN `user` ON `register`.`uid` = `user`.`uid` + LIMIT ?, ?", $start, $count ); return DBA::toArray($stmt); } + /** + * Returns the pending user based on a given user id + * + * @param int $uid The user id + * + * @return array The pending user information + * + * @throws \Exception + */ + public static function getPendingForUser(int $uid) + { + return DBA::fetchFirst( + "SELECT `register`.*, `contact`.`name`, `contact`.`url`, `contact`.`micro`, `user`.`email` + FROM `register` + INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid` + INNER JOIN `user` ON `register`.`uid` = `user`.`uid` + WHERE `register`.uid = ?", + $uid + ); + } + /** * Returns the pending registration count * diff --git a/src/Model/User.php b/src/Model/User.php index e4ef07e473..351982e8ad 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -23,7 +23,9 @@ namespace Friendica\Model; use DivineOmega\PasswordExposed; use Exception; +use Friendica\Content\Pager; use Friendica\Core\Hook; +use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Core\Protocol; use Friendica\Core\System; @@ -31,6 +33,7 @@ use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\TwoFactor\AppSpecificPassword; +use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Object\Image; use Friendica\Util\Crypto; use Friendica\Util\DateTimeFormat; @@ -279,7 +282,7 @@ class User * @param string $network network name * * @return int group id - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws InternalServerErrorException */ public static function getDefaultGroup($uid, $network = '') { @@ -556,7 +559,7 @@ class User * * @param string $nickname The nickname that should be checked * @return boolean True is the nickname is blocked on the node - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws InternalServerErrorException */ public static function isNicknameBlocked($nickname) { @@ -593,7 +596,7 @@ class User * @param array $data * @return array * @throws \ErrorException - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws InternalServerErrorException * @throws \ImagickException * @throws Exception */ @@ -880,6 +883,166 @@ class User return $return; } + /** + * Sets block state for a given user + * + * @param int $uid The user id + * @param bool $block Block state (default is true) + * + * @return bool True, if successfully blocked + + * @throws Exception + */ + public static function block(int $uid, bool $block = true) + { + return DBA::update('user', ['blocked' => $block], ['uid' => $uid]); + } + + /** + * Allows a registration based on a hash + * + * @param string $hash + * + * @return bool True, if the allow was successful + * + * @throws InternalServerErrorException + * @throws Exception + */ + public static function allow(string $hash) + { + $register = Register::getByHash($hash); + if (!DBA::isResult($register)) { + return false; + } + + $user = User::getById($register['uid']); + if (!DBA::isResult($user)) { + return false; + } + + Register::deleteByHash($hash); + + DBA::update('user', ['blocked' => false, 'verified' => true], ['uid' => $register['uid']]); + + $profile = DBA::selectFirst('profile', ['net-publish'], ['uid' => $register['uid']]); + + if (DBA::isResult($profile) && $profile['net-publish'] && DI::config()->get('system', 'directory')) { + $url = DI::baseUrl() . '/profile/' . $user['nickname']; + Worker::add(PRIORITY_LOW, "Directory", $url); + } + + $l10n = DI::l10n()->withLang($register['language']); + + return User::sendRegisterOpenEmail( + $l10n, + $user, + DI::config()->get('config', 'sitename'), + DI::baseUrl()->get(), + ($register['password'] ?? '') ?: 'Sent in a previous email' + ); + } + + /** + * Denys a pending registration + * + * @param string $hash The hash of the pending user + * + * This does not have to go through user_remove() and save the nickname + * permanently against re-registration, as the person was not yet + * allowed to have friends on this system + * + * @return bool True, if the deny was successfull + * @throws Exception + */ + public static function deny(string $hash) + { + $register = Register::getByHash($hash); + if (!DBA::isResult($register)) { + return false; + } + + $user = User::getById($register['uid']); + if (!DBA::isResult($user)) { + return false; + } + + return DBA::delete('user', ['uid' => $register['uid']]) && + Register::deleteByHash($register['hash']); + } + + /** + * Creates a new user based on a minimal set and sends an email to this user + * + * @param string $name The user's name + * @param string $email The user's email address + * @param string $nick The user's nick name + * @param string $lang The user's language (default is english) + * + * @return bool True, if the user was created successfully + * @throws InternalServerErrorException + * @throws \ErrorException + * @throws \ImagickException + */ + public static function createMinimal(string $name, string $email, string $nick, string $lang = L10n::DEFAULT) + { + if (empty($name) || + empty($email) || + empty($nick)) { + throw new InternalServerErrorException('Invalid arguments.'); + } + + $result = self::create([ + 'username' => $name, + 'email' => $email, + 'nickname' => $nick, + 'verified' => 1, + 'language' => $lang + ]); + + $user = $result['user']; + $preamble = Strings::deindent(DI::l10n()->t(' + Dear %1$s, + the administrator of %2$s has set up an account for you.')); + $body = Strings::deindent(DI::l10n()->t(' + The login details are as follows: + + Site Location: %1$s + Login Name: %2$s + Password: %3$s + + You may change your password from your account "Settings" page after logging + in. + + Please take a few moments to review the other account settings on that page. + + You may also wish to add some basic information to your default profile + (on the "Profiles" page) so that other people can easily find you. + + We recommend setting your full name, adding a profile photo, + adding some profile "keywords" (very useful in making new friends) - and + perhaps what country you live in; if you do not wish to be more specific + than that. + + We fully respect your right to privacy, and none of these items are necessary. + If you are new and do not know anybody here, they may help + you to make some new and interesting friends. + + If you ever want to delete your account, you can do so at %1$s/removeme + + Thank you and welcome to %4$s.')); + + $preamble = sprintf($preamble, $user['username'], DI::config()->get('config', 'sitename')); + $body = sprintf($body, DI::baseUrl()->get(), $user['nickname'], $result['password'], DI::config()->get('config', 'sitename')); + + $email = DI::emailer() + ->newSystemMail() + ->withMessage(DI::l10n()->t('Registration details for %s', DI::config()->get('config', 'sitename')), $preamble, $body) + ->forUser($user) + ->withRecipient($user['email']) + ->build(); + return DI::emailer()->send($email); + } + /** * Sends pending registration confirmation email * @@ -888,7 +1051,7 @@ class User * @param string $siteurl * @param string $password Plaintext password * @return NULL|boolean from notification() and email() inherited - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws InternalServerErrorException */ public static function sendRegisterPendingEmail($user, $sitename, $siteurl, $password) { @@ -931,7 +1094,7 @@ class User * @param string $password Plaintext password * * @return NULL|boolean from notification() and email() inherited - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws InternalServerErrorException */ public static function sendRegisterOpenEmail(\Friendica\Core\L10n $l10n, $user, $sitename, $siteurl, $password) { @@ -988,11 +1151,11 @@ class User } /** - * @param object $uid user to remove + * @param int $uid user to remove * @return bool - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws InternalServerErrorException */ - public static function remove($uid) + public static function remove(int $uid) { if (!$uid) { return false; @@ -1154,4 +1317,47 @@ class User return $statistics; } + + /** + * Get all users of the current node + * + * @param int $start Start count (Default is 0) + * @param int $count Count of the items per page (Default is @see Pager::ITEMS_PER_PAGE) + * @param string $type The type of users, which should get (all, bocked, removed) + * @param string $order Order of the user list (Default is 'contact.name') + * @param string $order_direction Order direction (Default is ASC) + * + * @return array The list of the users + * @throws Exception + */ + public static function getList($start = 0, $count = Pager::ITEMS_PER_PAGE, $type = 'all', $order = 'contact.name', $order_direction = '+') + { + $sql_order = '`' . str_replace('.', '`.`', $order) . '`'; + $sql_order_direction = ($order_direction === '+') ? 'ASC' : 'DESC'; + + switch ($type) { + case 'active': + $sql_extra = 'AND `user`.`blocked` = 0'; + break; + case 'blocked': + $sql_extra = 'AND `user`.`blocked` = 1'; + break; + case 'removed': + $sql_extra = 'AND `user`.`account_removed` = 1'; + break; + case 'all': + default: + $sql_extra = ''; + break; + } + + $usersStmt = DBA::p("SELECT `user`.*, `contact`.`name`, `contact`.`url`, `contact`.`micro`, `user`.`account_expired`, `contact`.`last-item` AS `lastitem_date`, `contact`.`nick`, `contact`.`created` + FROM `user` + INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self` + WHERE `user`.`verified` $sql_extra + ORDER BY $sql_order $sql_order_direction LIMIT ?, ?", $start, $count + ); + + return DBA::toArray($usersStmt); + } } diff --git a/src/Module/Admin/Users.php b/src/Module/Admin/Users.php index f5e2d5ed37..3ef91aadf7 100644 --- a/src/Module/Admin/Users.php +++ b/src/Module/Admin/Users.php @@ -28,7 +28,6 @@ use Friendica\DI; use Friendica\Model\Register; use Friendica\Model\User; use Friendica\Module\BaseAdmin; -use Friendica\Util\Strings; use Friendica\Util\Temporal; class Users extends BaseAdmin @@ -48,71 +47,24 @@ class Users extends BaseAdmin if ($nu_name !== '' && $nu_email !== '' && $nu_nickname !== '') { try { - $result = User::create([ - 'username' => $nu_name, - 'email' => $nu_email, - 'nickname' => $nu_nickname, - 'verified' => 1, - 'language' => $nu_language - ]); + User::createMinimal($nu_name, $nu_email, $nu_nickname, $nu_language); } catch (\Exception $ex) { notice($ex->getMessage()); return; } - - $user = $result['user']; - $preamble = Strings::deindent(DI::l10n()->t(' - Dear %1$s, - the administrator of %2$s has set up an account for you.')); - $body = Strings::deindent(DI::l10n()->t(' - The login details are as follows: - - Site Location: %1$s - Login Name: %2$s - Password: %3$s - - You may change your password from your account "Settings" page after logging - in. - - Please take a few moments to review the other account settings on that page. - - You may also wish to add some basic information to your default profile - (on the "Profiles" page) so that other people can easily find you. - - We recommend setting your full name, adding a profile photo, - adding some profile "keywords" (very useful in making new friends) - and - perhaps what country you live in; if you do not wish to be more specific - than that. - - We fully respect your right to privacy, and none of these items are necessary. - If you are new and do not know anybody here, they may help - you to make some new and interesting friends. - - If you ever want to delete your account, you can do so at %1$s/removeme - - Thank you and welcome to %4$s.')); - - $preamble = sprintf($preamble, $user['username'], DI::config()->get('config', 'sitename')); - $body = sprintf($body, DI::baseUrl()->get(), $user['nickname'], $result['password'], DI::config()->get('config', 'sitename')); - - $email = DI::emailer() - ->newSystemMail() - ->withMessage(DI::l10n()->t('Registration details for %s', DI::config()->get('config', 'sitename')), $preamble, $body) - ->forUser($user) - ->withRecipient($user['email']) - ->build(); - return DI::emailer()->send($email); } if (!empty($_POST['page_users_block'])) { - // @TODO Move this to Model\User:block($users); - DBA::update('user', ['blocked' => 1], ['uid' => $users]); + foreach ($users as $uid) { + User::block($uid); + } notice(DI::l10n()->tt('%s user blocked', '%s users blocked', count($users))); } if (!empty($_POST['page_users_unblock'])) { - // @TODO Move this to Model\User:unblock($users); - DBA::update('user', ['blocked' => 0], ['uid' => $users]); + foreach ($users as $uid) { + User::block($uid, false); + } notice(DI::l10n()->tt('%s user unblocked', '%s users unblocked', count($users))); } @@ -129,17 +81,17 @@ class Users extends BaseAdmin } if (!empty($_POST['page_users_approve'])) { - require_once 'mod/regmod.php'; foreach ($pending as $hash) { - user_allow($hash); + User::allow($hash); } + notice(DI::l10n()->tt('%s user approved', '%s users approved', count($pending))); } if (!empty($_POST['page_users_deny'])) { - require_once 'mod/regmod.php'; foreach ($pending as $hash) { - user_deny($hash); + User::deny($hash); } + notice(DI::l10n()->tt('%s registration revoked', '%s registrations revoked', count($pending))); } DI::baseUrl()->redirect('admin/users'); @@ -176,16 +128,24 @@ class Users extends BaseAdmin break; case 'block': parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't'); - // @TODO Move this to Model\User:block([$uid]); - DBA::update('user', ['blocked' => 1], ['uid' => $uid]); + User::block($uid); notice(DI::l10n()->t('User "%s" blocked', $user['username'])); break; case 'unblock': parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't'); - // @TODO Move this to Model\User:unblock([$uid]); - DBA::update('user', ['blocked' => 0], ['uid' => $uid]); + User::block($uid, false); notice(DI::l10n()->t('User "%s" unblocked', $user['username'])); break; + case 'allow': + parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't'); + User::allow(Register::getPendingForUser($uid)['hash'] ?? ''); + notice(DI::l10n()->t('Account approved.')); + break; + case 'deny': + parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't'); + User::deny(Register::getPendingForUser($uid)['hash'] ?? ''); + notice(DI::l10n()->t('Registration revoked')); + break; } DI::baseUrl()->redirect('admin/users'); @@ -196,7 +156,6 @@ class Users extends BaseAdmin $pager = new Pager(DI::l10n(), DI::args()->getQueryString(), 100); - // @TODO Move below block to Model\User::getUsers($start, $count, $order = 'contact.name', $order_direction = '+') $valid_orders = [ 'contact.name', 'user.email', @@ -219,16 +178,8 @@ class Users extends BaseAdmin $order = $new_order; } } - $sql_order = '`' . str_replace('.', '`.`', $order) . '`'; - $sql_order_direction = ($order_direction === '+') ? 'ASC' : 'DESC'; - $usersStmt = DBA::p("SELECT `user`.*, `contact`.`name`, `contact`.`url`, `contact`.`micro`, `user`.`account_expired`, `contact`.`last-item` AS `lastitem_date` - FROM `user` - INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self` - WHERE `user`.`verified` - ORDER BY $sql_order $sql_order_direction LIMIT ?, ?", $pager->getStart(), $pager->getItemsPerPage() - ); - $users = DBA::toArray($usersStmt); + $users = User::getList($pager->getStart(), $pager->getItemsPerPage(), 'all', $order, $order_direction); $adminlist = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email'))); $_setup_users = function ($e) use ($adminlist) { diff --git a/view/templates/admin/users.tpl b/view/templates/admin/users.tpl index 6ca85fe7aa..8bbffbb7de 100644 --- a/view/templates/admin/users.tpl +++ b/view/templates/admin/users.tpl @@ -35,8 +35,8 @@ {{$u.email}} - - + + diff --git a/view/theme/frio/templates/admin/users.tpl b/view/theme/frio/templates/admin/users.tpl index 256d5cdaec..a9c5f003ee 100644 --- a/view/theme/frio/templates/admin/users.tpl +++ b/view/theme/frio/templates/admin/users.tpl @@ -49,8 +49,8 @@ {{$u.name}} {{$u.email}} - - + + {{if $u.note}} diff --git a/view/theme/quattro/templates/admin/users.tpl b/view/theme/quattro/templates/admin/users.tpl index 97875a0771..bd4e63ec75 100644 --- a/view/theme/quattro/templates/admin/users.tpl +++ b/view/theme/quattro/templates/admin/users.tpl @@ -35,8 +35,8 @@ {{$u.email}} - - + +