Merge pull request #8324 from nupplaphil/feat/8245-user_mgmt

Introduce user management per console
This commit is contained in:
Hypolite Petovan 2020-02-29 11:32:40 -05:00 committed by GitHub
commit 5ca98c17b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 707 additions and 341 deletions

View file

@ -1,120 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2020, Friendica
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
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/');
}
}

View file

@ -1,126 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2020, Friendica
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
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 = <<<HELP
console newpassword - Creates a new password for a given user
Usage
bin/console newpassword <nickname> [<password>] [-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;
}
}

423
src/Console/User.php Normal file
View file

@ -0,0 +1,423 @@
<?php
/**
* @copyright Copyright (C) 2020, Friendica
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
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 = <<<HELP
console user - Modify user settings per console commands.
Usage
bin/console user password <nickname> [<password>] [-h|--help|-?] [-v]
bin/console user add [<name> [<nickname> [<email> [<language>]]]] [-h|--help|-?] [-v]
bin/console user delete [<nickname>] [-q] [-h|--help|-?] [-v]
bin/console user allow [<nickname>] [-h|--help|-?] [-v]
bin/console user deny [<nickname>] [-h|--help|-?] [-v]
bin/console user block [<nickname>] [-h|--help|-?] [-v]
bin/console user unblock [<nickname>] [-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 <UID> [-h|--help|-?] [-v]
bin/console user search nick <nick> [-h|--help|-?] [-v]
bin/console user search mail <mail> [-h|--help|-?] [-v]
bin/console user search guid <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;
}
}

View file

@ -30,10 +30,13 @@ use Friendica\Util\Strings;
*/ */
class Pager class Pager
{ {
/** @var int Default count of items per page */
const ITEMS_PER_PAGE = 50;
/** @var integer */ /** @var integer */
private $page = 1; private $page = 1;
/** @var integer */ /** @var integer */
protected $itemsPerPage = 50; protected $itemsPerPage = self::ITEMS_PER_PAGE;
/** @var string */ /** @var string */
protected $baseQueryString = ''; protected $baseQueryString = '';

View file

@ -57,7 +57,7 @@ Commands:
autoinstall Starts automatic installation of friendica based on values from htconfig.php autoinstall Starts automatic installation of friendica based on values from htconfig.php
lock Edit site locks lock Edit site locks
maintenance Set maintenance mode for this node 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 php2po Generate a messages.po file from a strings.php file
po2php Generate a strings.php file from a messages.po file po2php Generate a strings.php file from a messages.po file
typo Checks for parse errors in Friendica files typo Checks for parse errors in Friendica files
@ -85,7 +85,7 @@ HELP;
'autoinstall' => Friendica\Console\AutomaticInstallation::class, 'autoinstall' => Friendica\Console\AutomaticInstallation::class,
'lock' => Friendica\Console\Lock::class, 'lock' => Friendica\Console\Lock::class,
'maintenance' => Friendica\Console\Maintenance::class, 'maintenance' => Friendica\Console\Maintenance::class,
'newpassword' => Friendica\Console\NewPassword::class, 'user' => Friendica\Console\User::class,
'php2po' => Friendica\Console\PhpToPo::class, 'php2po' => Friendica\Console\PhpToPo::class,
'po2php' => Friendica\Console\PoToPhp::class, 'po2php' => Friendica\Console\PoToPhp::class,
'typo' => Friendica\Console\Typo::class, 'typo' => Friendica\Console\Typo::class,

View file

@ -33,6 +33,9 @@ use Psr\Log\LoggerInterface;
*/ */
class L10n class L10n
{ {
/** @var string The default language */
const DEFAULT = 'en';
/** /**
* A string indicating the current language used for translation: * A string indicating the current language used for translation:
* - Two-letter ISO 639-1 code. * - Two-letter ISO 639-1 code.
@ -64,7 +67,7 @@ class L10n
$this->dba = $dba; $this->dba = $dba;
$this->logger = $logger; $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->setSessionVariable($session);
$this->setLangFromSession($session); $this->setLangFromSession($session);
} }
@ -158,7 +161,7 @@ class L10n
* *
* @return string The two-letter language code * @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; $lang_variable = $server['HTTP_ACCEPT_LANGUAGE'] ?? null;

View file

@ -21,6 +21,7 @@
namespace Friendica\Model; namespace Friendica\Model;
use Friendica\Content\Pager;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings; use Friendica\Util\Strings;
@ -33,21 +34,46 @@ class Register
/** /**
* Return the list of pending registrations * 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 * @return array
* @throws \Exception * @throws \Exception
*/ */
public static function getPending() public static function getPending($start = 0, $count = Pager::ITEMS_PER_PAGE)
{ {
$stmt = DBA::p( $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` FROM `register`
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid` 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); 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 * Returns the pending registration count
* *

View file

@ -23,7 +23,9 @@ namespace Friendica\Model;
use DivineOmega\PasswordExposed; use DivineOmega\PasswordExposed;
use Exception; use Exception;
use Friendica\Content\Pager;
use Friendica\Core\Hook; use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\Protocol; use Friendica\Core\Protocol;
use Friendica\Core\System; use Friendica\Core\System;
@ -31,6 +33,7 @@ use Friendica\Core\Worker;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\TwoFactor\AppSpecificPassword; use Friendica\Model\TwoFactor\AppSpecificPassword;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Object\Image; use Friendica\Object\Image;
use Friendica\Util\Crypto; use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
@ -279,7 +282,7 @@ class User
* @param string $network network name * @param string $network network name
* *
* @return int group id * @return int group id
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws InternalServerErrorException
*/ */
public static function getDefaultGroup($uid, $network = '') public static function getDefaultGroup($uid, $network = '')
{ {
@ -556,7 +559,7 @@ class User
* *
* @param string $nickname The nickname that should be checked * @param string $nickname The nickname that should be checked
* @return boolean True is the nickname is blocked on the node * @return boolean True is the nickname is blocked on the node
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws InternalServerErrorException
*/ */
public static function isNicknameBlocked($nickname) public static function isNicknameBlocked($nickname)
{ {
@ -593,7 +596,7 @@ class User
* @param array $data * @param array $data
* @return array * @return array
* @throws \ErrorException * @throws \ErrorException
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws InternalServerErrorException
* @throws \ImagickException * @throws \ImagickException
* @throws Exception * @throws Exception
*/ */
@ -880,6 +883,166 @@ class User
return $return; 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 * Sends pending registration confirmation email
* *
@ -888,7 +1051,7 @@ class User
* @param string $siteurl * @param string $siteurl
* @param string $password Plaintext password * @param string $password Plaintext password
* @return NULL|boolean from notification() and email() inherited * @return NULL|boolean from notification() and email() inherited
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws InternalServerErrorException
*/ */
public static function sendRegisterPendingEmail($user, $sitename, $siteurl, $password) public static function sendRegisterPendingEmail($user, $sitename, $siteurl, $password)
{ {
@ -931,7 +1094,7 @@ class User
* @param string $password Plaintext password * @param string $password Plaintext password
* *
* @return NULL|boolean from notification() and email() inherited * @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) 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 * @return bool
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws InternalServerErrorException
*/ */
public static function remove($uid) public static function remove(int $uid)
{ {
if (!$uid) { if (!$uid) {
return false; return false;
@ -1154,4 +1317,47 @@ class User
return $statistics; 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);
}
} }

View file

@ -28,7 +28,6 @@ use Friendica\DI;
use Friendica\Model\Register; use Friendica\Model\Register;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Module\BaseAdmin; use Friendica\Module\BaseAdmin;
use Friendica\Util\Strings;
use Friendica\Util\Temporal; use Friendica\Util\Temporal;
class Users extends BaseAdmin class Users extends BaseAdmin
@ -48,71 +47,24 @@ class Users extends BaseAdmin
if ($nu_name !== '' && $nu_email !== '' && $nu_nickname !== '') { if ($nu_name !== '' && $nu_email !== '' && $nu_nickname !== '') {
try { try {
$result = User::create([ User::createMinimal($nu_name, $nu_email, $nu_nickname, $nu_language);
'username' => $nu_name,
'email' => $nu_email,
'nickname' => $nu_nickname,
'verified' => 1,
'language' => $nu_language
]);
} catch (\Exception $ex) { } catch (\Exception $ex) {
notice($ex->getMessage()); notice($ex->getMessage());
return; 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'])) { if (!empty($_POST['page_users_block'])) {
// @TODO Move this to Model\User:block($users); foreach ($users as $uid) {
DBA::update('user', ['blocked' => 1], ['uid' => $users]); User::block($uid);
}
notice(DI::l10n()->tt('%s user blocked', '%s users blocked', count($users))); notice(DI::l10n()->tt('%s user blocked', '%s users blocked', count($users)));
} }
if (!empty($_POST['page_users_unblock'])) { if (!empty($_POST['page_users_unblock'])) {
// @TODO Move this to Model\User:unblock($users); foreach ($users as $uid) {
DBA::update('user', ['blocked' => 0], ['uid' => $users]); User::block($uid, false);
}
notice(DI::l10n()->tt('%s user unblocked', '%s users unblocked', count($users))); 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'])) { if (!empty($_POST['page_users_approve'])) {
require_once 'mod/regmod.php';
foreach ($pending as $hash) { 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'])) { if (!empty($_POST['page_users_deny'])) {
require_once 'mod/regmod.php';
foreach ($pending as $hash) { 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'); DI::baseUrl()->redirect('admin/users');
@ -176,16 +128,24 @@ class Users extends BaseAdmin
break; break;
case 'block': case 'block':
parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't'); parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't');
// @TODO Move this to Model\User:block([$uid]); User::block($uid);
DBA::update('user', ['blocked' => 1], ['uid' => $uid]);
notice(DI::l10n()->t('User "%s" blocked', $user['username'])); notice(DI::l10n()->t('User "%s" blocked', $user['username']));
break; break;
case 'unblock': case 'unblock':
parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't'); parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't');
// @TODO Move this to Model\User:unblock([$uid]); User::block($uid, false);
DBA::update('user', ['blocked' => 0], ['uid' => $uid]);
notice(DI::l10n()->t('User "%s" unblocked', $user['username'])); notice(DI::l10n()->t('User "%s" unblocked', $user['username']));
break; 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'); DI::baseUrl()->redirect('admin/users');
@ -196,7 +156,6 @@ class Users extends BaseAdmin
$pager = new Pager(DI::l10n(), DI::args()->getQueryString(), 100); $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 = [ $valid_orders = [
'contact.name', 'contact.name',
'user.email', 'user.email',
@ -219,16 +178,8 @@ class Users extends BaseAdmin
$order = $new_order; $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` $users = User::getList($pager->getStart(), $pager->getItemsPerPage(), 'all', $order, $order_direction);
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);
$adminlist = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email'))); $adminlist = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email')));
$_setup_users = function ($e) use ($adminlist) { $_setup_users = function ($e) use ($adminlist) {

View file

@ -35,8 +35,8 @@
<td class="email">{{$u.email}}</td> <td class="email">{{$u.email}}</td>
<td class="checkbox"><input type="checkbox" class="pending_ckbx" id="id_pending_{{$u.hash}}" name="pending[]" value="{{$u.hash}}" /></td> <td class="checkbox"><input type="checkbox" class="pending_ckbx" id="id_pending_{{$u.hash}}" name="pending[]" value="{{$u.hash}}" /></td>
<td class="tools"> <td class="tools">
<a href="{{$baseurl}}/regmod/allow/{{$u.hash}}" title='{{$approve}}'><span class='icon like'></span></a> <a href="{{$baseurl}}/admin/users/allow/{{$u.uid}}?t={{$form_security_token}}" title='{{$approve}}'><span class='icon like'></span></a>
<a href="{{$baseurl}}/regmod/deny/{{$u.hash}}" title='{{$deny}}'><span class='icon dislike'></span></a> <a href="{{$baseurl}}/admin/users/deny/{{$u.uid}}?t={{$form_security_token}}" title='{{$deny}}'><span class='icon dislike'></span></a>
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -49,8 +49,8 @@
<td>{{$u.name}}</td> <td>{{$u.name}}</td>
<td>{{$u.email}}</td> <td>{{$u.email}}</td>
<td> <td>
<a href="{{$baseurl}}/regmod/allow/{{$u.hash}}" class="admin-settings-action-link" title="{{$approve}}"><i class="fa fa-check" aria-hidden="true"></i></a> <a href="{{$baseurl}}/admin/users/allow/{{$u.uid}}?t={{$form_security_token}}" class="admin-settings-action-link" title="{{$approve}}"><i class="fa fa-check" aria-hidden="true"></i></a>
<a href="{{$baseurl}}/regmod/deny/{{$u.hash}}" class="admin-settings-action-link" title="{{$deny}}"><i class="fa fa-trash-o" aria-hidden="true"></i></a> <a href="{{$baseurl}}/admin/users/deny/{{$u.uid}}?t={{$form_security_token}}" class="admin-settings-action-link" title="{{$deny}}"><i class="fa fa-trash-o" aria-hidden="true"></i></a>
</td> </td>
</tr> </tr>
{{if $u.note}} {{if $u.note}}

View file

@ -35,8 +35,8 @@
<td class="email">{{$u.email}}</td> <td class="email">{{$u.email}}</td>
<td class="checkbox"><input type="checkbox" class="pending_ckbx" id="id_pending_{{$u.hash}}" name="pending[]" value="{{$u.hash}}" /></td> <td class="checkbox"><input type="checkbox" class="pending_ckbx" id="id_pending_{{$u.hash}}" name="pending[]" value="{{$u.hash}}" /></td>
<td class="tools"> <td class="tools">
<a href="{{$baseurl}}/regmod/allow/{{$u.hash}}" title='{{$approve}}'><span class='icon like'></span></a> <a href="{{$baseurl}}/admin/users/allow/{{$u.uid}}?t={{$form_security_token}}" title='{{$approve}}'><span class='icon like'></span></a>
<a href="{{$baseurl}}/regmod/deny/{{$u.hash}}" title='{{$deny}}'><span class='icon dislike'></span></a> <a href="{{$baseurl}}/admin/users/deny/{{$u.uid}}?t={{$form_security_token}}" title='{{$deny}}'><span class='icon dislike'></span></a>
</td> </td>
</tr> </tr>
<tr> <tr>