From f3f055758ceb7d270731995600b8c59ad2984710 Mon Sep 17 00:00:00 2001
From: Hypolite Petovan <hypolite@mrpetovan.com>
Date: Sun, 21 Apr 2019 19:14:31 -0400
Subject: [PATCH] Move admin/users to src/Module

- Add Module\Admin\Users class
- Add route for admin/users[/{action}/{uid}]
- Add users admin aside menu entry
- Remove admin_page_users and admin_page_users_post from mod/admin.php
---
 mod/admin.php                                | 312 ------------------
 src/App/Router.php                           |   2 +
 src/Module/Admin/Users.php                   | 319 +++++++++++++++++++
 src/Module/BaseAdminModule.php               |   1 +
 view/templates/admin/users.tpl               |   6 +-
 view/theme/frio/templates/admin/users.tpl    |  17 +-
 view/theme/quattro/templates/admin/users.tpl |   6 +-
 7 files changed, 343 insertions(+), 320 deletions(-)
 create mode 100644 src/Module/Admin/Users.php

diff --git a/mod/admin.php b/mod/admin.php
index 22fd5adf3e..d6b447c793 100644
--- a/mod/admin.php
+++ b/mod/admin.php
@@ -87,9 +87,6 @@ function admin_post(App $a)
 			case 'site':
 				admin_page_site_post($a);
 				break;
-			case 'users':
-				admin_page_users_post($a);
-				break;
 			case 'themes':
 				if ($a->argc < 2) {
 					if ($a->isAjax()) {
@@ -228,9 +225,6 @@ function admin_content(App $a)
 			case 'site':
 				$o = admin_page_site($a);
 				break;
-			case 'users':
-				$o = admin_page_users($a);
-				break;
 			case 'themes':
 				$o = admin_page_themes($a);
 				break;
@@ -1337,312 +1331,6 @@ function admin_page_dbsync(App $a)
 	return $o;
 }
 
-/**
- * @brief Process data send by Users admin page
- *
- * @param App $a
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- */
-function admin_page_users_post(App $a)
-{
-	$pending     = defaults($_POST, 'pending'          , []);
-	$users       = defaults($_POST, 'user'             , []);
-	$nu_name     = defaults($_POST, 'new_user_name'    , '');
-	$nu_nickname = defaults($_POST, 'new_user_nickname', '');
-	$nu_email    = defaults($_POST, 'new_user_email'   , '');
-	$nu_language = Config::get('system', 'language');
-
-	BaseModule::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users');
-
-	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
-			]);
-		} catch (Exception $ex) {
-			notice($ex->getMessage());
-			return;
-		}
-
-		$user = $result['user'];
-		$preamble = Strings::deindent(L10n::t('
-			Dear %1$s,
-				the administrator of %2$s has set up an account for you.'));
-		$body = Strings::deindent(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
-			' . "\x28" . 'on the "Profiles" page' . "\x29" . ' so that other people can easily find you.
-
-			We recommend setting your full name, adding a profile photo,
-			adding some profile "keywords" ' . "\x28" . 'very useful in making new friends' . "\x29" . ' - 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'], Config::get('config', 'sitename'));
-		$body = sprintf($body, System::baseUrl(), $user['nickname'], $result['password'], Config::get('config', 'sitename'));
-
-		notification([
-			'type'     => SYSTEM_EMAIL,
-			'language' => $user['language'],
-			'to_name'  => $user['username'],
-			'to_email' => $user['email'],
-			'uid'      => $user['uid'],
-			'subject'  => L10n::t('Registration details for %s', Config::get('config', 'sitename')),
-			'preamble' => $preamble,
-			'body'     => $body]);
-	}
-
-	if (!empty($_POST['page_users_block'])) {
-		foreach ($users as $uid) {
-			q("UPDATE `user` SET `blocked` = 1-`blocked` WHERE `uid` = %s", intval($uid)
-			);
-		}
-		notice(L10n::tt("%s user blocked/unblocked", "%s users blocked/unblocked", count($users)));
-	}
-	if (!empty($_POST['page_users_delete'])) {
-		foreach ($users as $uid) {
-			if (local_user() != $uid) {
-				User::remove($uid);
-			} else {
-				notice(L10n::t('You can\'t remove yourself'));
-			}
-		}
-		notice(L10n::tt("%s user deleted", "%s users deleted", count($users)));
-	}
-
-	if (!empty($_POST['page_users_approve'])) {
-		require_once "mod/regmod.php";
-		foreach ($pending as $hash) {
-			user_allow($hash);
-		}
-	}
-	if (!empty($_POST['page_users_deny'])) {
-		require_once "mod/regmod.php";
-		foreach ($pending as $hash) {
-			user_deny($hash);
-		}
-	}
-	$a->internalRedirect('admin/users');
-	return; // NOTREACHED
-}
-
-/**
- * @brief Admin panel subpage for User management
- *
- * This function generates the admin panel page for user management of the
- * node. It offers functionality to add/block/delete users and offers some
- * statistics about the userbase.
- *
- * The returned string holds the HTML code of the page.
- *
- * @param App $a
- * @return string
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- */
-function admin_page_users(App $a)
-{
-	if ($a->argc > 2) {
-		$uid = $a->argv[3];
-		$user = DBA::selectFirst('user', ['username', 'blocked'], ['uid' => $uid]);
-		if (!DBA::isResult($user)) {
-			notice('User not found' . EOL);
-			$a->internalRedirect('admin/users');
-			return ''; // NOTREACHED
-		}
-		switch ($a->argv[2]) {
-			case "delete":
-				if (local_user() != $uid) {
-					BaseModule::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't');
-					// delete user
-					User::remove($uid);
-
-					notice(L10n::t("User '%s' deleted", $user['username']));
-				} else {
-					notice(L10n::t('You can\'t remove yourself'));
-				}
-				break;
-			case "block":
-				BaseModule::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't');
-				q("UPDATE `user` SET `blocked` = %d WHERE `uid` = %s",
-					intval(1 - $user['blocked']),
-					intval($uid)
-				);
-				notice(sprintf(($user['blocked'] ? L10n::t("User '%s' unblocked") : L10n::t("User '%s' blocked")), $user['username']) . EOL);
-				break;
-		}
-		$a->internalRedirect('admin/users');
-		return ''; // NOTREACHED
-	}
-
-	/* get pending */
-	$pending = Register::getPending();
-
-	$pager = new Pager($a->query_string, 100);
-
-	/* ordering */
-	$valid_orders = [
-		'contact.name',
-		'user.email',
-		'user.register_date',
-		'user.login_date',
-		'lastitem_date',
-		'user.page-flags'
-	];
-
-	$order = "contact.name";
-	$order_direction = "+";
-	if (!empty($_GET['o'])) {
-		$new_order = $_GET['o'];
-		if ($new_order[0] === "-") {
-			$order_direction = "-";
-			$new_order = substr($new_order, 1);
-		}
-
-		if (in_array($new_order, $valid_orders)) {
-			$order = $new_order;
-		}
-	}
-	$sql_order = "`" . str_replace('.', '`.`', $order) . "`";
-	$sql_order_direction = ($order_direction === "+") ? "ASC" : "DESC";
-
-	$users = q("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 %d, %d", $pager->getStart(), $pager->getItemsPerPage()
-	);
-
-	$adminlist = explode(",", str_replace(" ", "", Config::get('config', 'admin_email')));
-	$_setup_users = function ($e) use ($adminlist) {
-		$page_types = [
-			User::PAGE_FLAGS_NORMAL    => L10n::t('Normal Account Page'),
-			User::PAGE_FLAGS_SOAPBOX   => L10n::t('Soapbox Page'),
-			User::PAGE_FLAGS_COMMUNITY => L10n::t('Public Forum'),
-			User::PAGE_FLAGS_FREELOVE  => L10n::t('Automatic Friend Page'),
-			User::PAGE_FLAGS_PRVGROUP  => L10n::t('Private Forum')
-		];
-		$account_types = [
-			User::ACCOUNT_TYPE_PERSON       => L10n::t('Personal Page'),
-			User::ACCOUNT_TYPE_ORGANISATION => L10n::t('Organisation Page'),
-			User::ACCOUNT_TYPE_NEWS         => L10n::t('News Page'),
-			User::ACCOUNT_TYPE_COMMUNITY    => L10n::t('Community Forum'),
-			User::ACCOUNT_TYPE_RELAY        => L10n::t('Relay'),
-		];
-
-		$e['page_flags_raw'] = $e['page-flags'];
-		$e['page-flags'] = $page_types[$e['page-flags']];
-
-		$e['account_type_raw'] = ($e['page_flags_raw'] == 0) ? $e['account-type'] : -1;
-		$e['account-type'] = ($e['page_flags_raw'] == 0) ? $account_types[$e['account-type']] : "";
-
-		$e['register_date'] = Temporal::getRelativeDate($e['register_date']);
-		$e['login_date'] = Temporal::getRelativeDate($e['login_date']);
-		$e['lastitem_date'] = Temporal::getRelativeDate($e['lastitem_date']);
-		$e['is_admin'] = in_array($e['email'], $adminlist);
-		$e['is_deletable'] = (intval($e['uid']) != local_user());
-		$e['deleted'] = ($e['account_removed'] ? Temporal::getRelativeDate($e['account_expires_on']) : False);
-
-		return $e;
-	};
-
-	$users = array_map($_setup_users, $users);
-
-
-	// Get rid of dashes in key names, Smarty3 can't handle them
-	// and extracting deleted users
-
-	$tmp_users = [];
-	$deleted = [];
-
-	while (count($users)) {
-		$new_user = [];
-		foreach (array_pop($users) as $k => $v) {
-			$k = str_replace('-', '_', $k);
-			$new_user[$k] = $v;
-		}
-		if ($new_user['deleted']) {
-			array_push($deleted, $new_user);
-		} else {
-			array_push($tmp_users, $new_user);
-		}
-	}
-	//Reversing the two array, and moving $tmp_users to $users
-	array_reverse($deleted);
-	while (count($tmp_users)) {
-		array_push($users, array_pop($tmp_users));
-	}
-
-	$th_users = array_map(null, [L10n::t('Name'), L10n::t('Email'), L10n::t('Register date'), L10n::t('Last login'), L10n::t('Last item'), L10n::t('Type')], $valid_orders);
-
-	$t = Renderer::getMarkupTemplate('admin/users.tpl');
-	$o = Renderer::replaceMacros($t, [
-		// strings //
-		'$title' => L10n::t('Administration'),
-		'$page' => L10n::t('Users'),
-		'$submit' => L10n::t('Add User'),
-		'$select_all' => L10n::t('select all'),
-		'$h_pending' => L10n::t('User registrations waiting for confirm'),
-		'$h_deleted' => L10n::t('User waiting for permanent deletion'),
-		'$th_pending' => [L10n::t('Request date'), L10n::t('Name'), L10n::t('Email')],
-		'$no_pending' => L10n::t('No registrations.'),
-		'$pendingnotetext' => L10n::t('Note from the user'),
-		'$approve' => L10n::t('Approve'),
-		'$deny' => L10n::t('Deny'),
-		'$delete' => L10n::t('Delete'),
-		'$block' => L10n::t('Block'),
-		'$blocked' => L10n::t('User blocked'),
-		'$unblock' => L10n::t('Unblock'),
-		'$siteadmin' => L10n::t('Site admin'),
-		'$accountexpired' => L10n::t('Account expired'),
-
-		'$h_users' => L10n::t('Users'),
-		'$h_newuser' => L10n::t('New User'),
-		'$th_deleted' => [L10n::t('Name'), L10n::t('Email'), L10n::t('Register date'), L10n::t('Last login'), L10n::t('Last item'), L10n::t('Permanent deletion')],
-		'$th_users' => $th_users,
-		'$order_users' => $order,
-		'$order_direction_users' => $order_direction,
-
-		'$confirm_delete_multi' => L10n::t('Selected users will be deleted!\n\nEverything these users had posted on this site will be permanently deleted!\n\nAre you sure?'),
-		'$confirm_delete' => L10n::t('The user {0} will be deleted!\n\nEverything this user has posted on this site will be permanently deleted!\n\nAre you sure?'),
-
-		'$form_security_token' => BaseModule::getFormSecurityToken("admin_users"),
-
-		// values //
-		'$baseurl' => $a->getBaseURL(true),
-
-		'$pending' => $pending,
-		'deleted' => $deleted,
-		'$users' => $users,
-		'$newusername' => ['new_user_name', L10n::t("Name"), '', L10n::t("Name of the new user.")],
-		'$newusernickname' => ['new_user_nickname', L10n::t("Nickname"), '', L10n::t("Nickname of the new user.")],
-		'$newuseremail' => ['new_user_email', L10n::t("Email"), '', L10n::t("Email address of the new user."), '', '', 'email'],
-	]);
-	$o .= $pager->renderFull(DBA::count('user'));
-	return $o;
-}
-
 /**
  * @param array  $themes
  * @param string $th
diff --git a/src/App/Router.php b/src/App/Router.php
index 6c152eb8a0..30f48f7c56 100644
--- a/src/App/Router.php
+++ b/src/App/Router.php
@@ -129,6 +129,8 @@ class Router
 			$collector->addRoute(['GET', 'POST'], '/themes'                 , Module\Admin\Themes\Index::class);
 
 			$collector->addRoute(['GET', 'POST'], '/tos'                    , Module\Admin\Tos::class);
+
+			$collector->addRoute(['GET', 'POST'], '/users[/{action}/{uid}]' , Module\Admin\Users::class);
 		});
 	}
 
diff --git a/src/Module/Admin/Users.php b/src/Module/Admin/Users.php
new file mode 100644
index 0000000000..85567fd2ab
--- /dev/null
+++ b/src/Module/Admin/Users.php
@@ -0,0 +1,319 @@
+<?php
+
+namespace Friendica\Module\Admin;
+
+use Friendica\Content\Pager;
+use Friendica\Core\Config;
+use Friendica\Core\L10n;
+use Friendica\Core\Renderer;
+use Friendica\Core\System;
+use Friendica\Database\DBA;
+use Friendica\Model\Register;
+use Friendica\Model\User;
+use Friendica\Module\BaseAdminModule;
+use Friendica\Util\Strings;
+use Friendica\Util\Temporal;
+
+class Users extends BaseAdminModule
+{
+	public static function post()
+	{
+		parent::post();
+
+		$a = self::getApp();
+
+		$pending     = defaults($_POST, 'pending'          , []);
+		$users       = defaults($_POST, 'user'             , []);
+		$nu_name     = defaults($_POST, 'new_user_name'    , '');
+		$nu_nickname = defaults($_POST, 'new_user_nickname', '');
+		$nu_email    = defaults($_POST, 'new_user_email'   , '');
+		$nu_language = Config::get('system', 'language');
+
+		parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users');
+
+		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
+				]);
+			} catch (\Exception $ex) {
+				notice($ex->getMessage());
+				return;
+			}
+
+			$user = $result['user'];
+			$preamble = Strings::deindent(L10n::t('
+			Dear %1$s,
+				the administrator of %2$s has set up an account for you.'));
+			$body = Strings::deindent(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'], Config::get('config', 'sitename'));
+			$body = sprintf($body, System::baseUrl(), $user['nickname'], $result['password'], Config::get('config', 'sitename'));
+
+			notification([
+				'type'     => SYSTEM_EMAIL,
+				'language' => $user['language'],
+				'to_name'  => $user['username'],
+				'to_email' => $user['email'],
+				'uid'      => $user['uid'],
+				'subject'  => L10n::t('Registration details for %s', Config::get('config', 'sitename')),
+				'preamble' => $preamble,
+				'body'     => $body]);
+		}
+
+		if (!empty($_POST['page_users_block'])) {
+			DBA::update('user', ['blocked' => 1], ['uid' => $users]);
+			notice(L10n::tt('%s user blocked', '%s users blocked', count($users)));
+		}
+
+		if (!empty($_POST['page_users_unblock'])) {
+			DBA::update('user', ['blocked' => 0], ['uid' => $users]);
+			notice(L10n::tt('%s user unblocked', '%s users unblocked', count($users)));
+		}
+
+		if (!empty($_POST['page_users_delete'])) {
+			foreach ($users as $uid) {
+				if (local_user() != $uid) {
+					User::remove($uid);
+				} else {
+					notice(L10n::t('You can\'t remove yourself'));
+				}
+			}
+
+			notice(L10n::tt('%s user deleted', '%s users deleted', count($users)));
+		}
+
+		if (!empty($_POST['page_users_approve'])) {
+			require_once 'mod/regmod.php';
+			foreach ($pending as $hash) {
+				user_allow($hash);
+			}
+		}
+
+		if (!empty($_POST['page_users_deny'])) {
+			require_once 'mod/regmod.php';
+			foreach ($pending as $hash) {
+				user_deny($hash);
+			}
+		}
+
+		$a->internalRedirect('admin/users');
+	}
+
+	public static function content()
+	{
+		parent::content();
+
+		$a = self::getApp();
+
+		if ($a->argc > 3) {
+			// @TODO: Replace with parameter from router
+			$action = $a->argv[2];
+			$uid = $a->argv[3];
+			$user = DBA::selectFirst('user', ['username', 'blocked'], ['uid' => $uid]);
+			if (!DBA::isResult($user)) {
+				notice('User not found' . EOL);
+				$a->internalRedirect('admin/users');
+				return ''; // NOTREACHED
+			}
+
+			switch ($action) {
+				case 'delete':
+					if (local_user() != $uid) {
+						parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't');
+						// delete user
+						User::remove($uid);
+
+						notice(L10n::t('User "%s" deleted', $user['username']));
+					} else {
+						notice(L10n::t('You can\'t remove yourself'));
+					}
+					break;
+				case 'block':
+					parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't');
+					DBA::update('user', ['blocked' => 1], ['uid' => $uid]);
+					notice(L10n::t('User "%s" blocked', $user['username']));
+					break;
+				case 'unblock':
+					parent::checkFormSecurityTokenRedirectOnError('/admin/users', 'admin_users', 't');
+					DBA::update('user', ['blocked' => 0], ['uid' => $uid]);
+					notice(L10n::t('User "%s" unblocked', $user['username']));
+					break;
+			}
+
+			$a->internalRedirect('admin/users');
+		}
+
+		/* get pending */
+		$pending = Register::getPending();
+
+		$pager = new Pager($a->query_string, 100);
+
+		/* ordering */
+		$valid_orders = [
+			'contact.name',
+			'user.email',
+			'user.register_date',
+			'user.login_date',
+			'lastitem_date',
+			'user.page-flags'
+		];
+
+		$order = 'contact.name';
+		$order_direction = '+';
+		if (!empty($_GET['o'])) {
+			$new_order = $_GET['o'];
+			if ($new_order[0] === '-') {
+				$order_direction = '-';
+				$new_order = substr($new_order, 1);
+			}
+
+			if (in_array($new_order, $valid_orders)) {
+				$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);
+
+		$adminlist = explode(',', str_replace(' ', '', Config::get('config', 'admin_email')));
+		$_setup_users = function ($e) use ($adminlist) {
+			$page_types = [
+				User::PAGE_FLAGS_NORMAL    => L10n::t('Normal Account Page'),
+				User::PAGE_FLAGS_SOAPBOX   => L10n::t('Soapbox Page'),
+				User::PAGE_FLAGS_COMMUNITY => L10n::t('Public Forum'),
+				User::PAGE_FLAGS_FREELOVE  => L10n::t('Automatic Friend Page'),
+				User::PAGE_FLAGS_PRVGROUP  => L10n::t('Private Forum')
+			];
+			$account_types = [
+				User::ACCOUNT_TYPE_PERSON       => L10n::t('Personal Page'),
+				User::ACCOUNT_TYPE_ORGANISATION => L10n::t('Organisation Page'),
+				User::ACCOUNT_TYPE_NEWS         => L10n::t('News Page'),
+				User::ACCOUNT_TYPE_COMMUNITY    => L10n::t('Community Forum'),
+				User::ACCOUNT_TYPE_RELAY        => L10n::t('Relay'),
+			];
+
+			$e['page_flags_raw'] = $e['page-flags'];
+			$e['page-flags'] = $page_types[$e['page-flags']];
+
+			$e['account_type_raw'] = ($e['page_flags_raw'] == 0) ? $e['account-type'] : -1;
+			$e['account-type'] = ($e['page_flags_raw'] == 0) ? $account_types[$e['account-type']] : '';
+
+			$e['register_date'] = Temporal::getRelativeDate($e['register_date']);
+			$e['login_date'] = Temporal::getRelativeDate($e['login_date']);
+			$e['lastitem_date'] = Temporal::getRelativeDate($e['lastitem_date']);
+			$e['is_admin'] = in_array($e['email'], $adminlist);
+			$e['is_deletable'] = (intval($e['uid']) != local_user());
+			$e['deleted'] = ($e['account_removed'] ? Temporal::getRelativeDate($e['account_expires_on']) : False);
+
+			return $e;
+		};
+
+		$tmp_users = array_map($_setup_users, $users);
+
+		// Get rid of dashes in key names, Smarty3 can't handle them
+		// and extracting deleted users
+
+		$deleted = [];
+		$users = [];
+		foreach ($tmp_users as $user) {
+			foreach ($user as $k => $v) {
+				$newkey = str_replace('-', '_', $k);
+				$user[$newkey] = $v;
+			}
+
+			if ($user['deleted']) {
+				$deleted[] = $user;
+			} else {
+				$users[] = $user;
+			}
+		}
+
+		$th_users = array_map(null, [L10n::t('Name'), L10n::t('Email'), L10n::t('Register date'), L10n::t('Last login'), L10n::t('Last item'), L10n::t('Type')], $valid_orders);
+
+		$t = Renderer::getMarkupTemplate('admin/users.tpl');
+		$o = Renderer::replaceMacros($t, [
+			// strings //
+			'$title' => L10n::t('Administration'),
+			'$page' => L10n::t('Users'),
+			'$submit' => L10n::t('Add User'),
+			'$select_all' => L10n::t('select all'),
+			'$h_pending' => L10n::t('User registrations waiting for confirm'),
+			'$h_deleted' => L10n::t('User waiting for permanent deletion'),
+			'$th_pending' => [L10n::t('Request date'), L10n::t('Name'), L10n::t('Email')],
+			'$no_pending' => L10n::t('No registrations.'),
+			'$pendingnotetext' => L10n::t('Note from the user'),
+			'$approve' => L10n::t('Approve'),
+			'$deny' => L10n::t('Deny'),
+			'$delete' => L10n::t('Delete'),
+			'$block' => L10n::t('Block'),
+			'$blocked' => L10n::t('User blocked'),
+			'$unblock' => L10n::t('Unblock'),
+			'$siteadmin' => L10n::t('Site admin'),
+			'$accountexpired' => L10n::t('Account expired'),
+
+			'$h_users' => L10n::t('Users'),
+			'$h_newuser' => L10n::t('New User'),
+			'$th_deleted' => [L10n::t('Name'), L10n::t('Email'), L10n::t('Register date'), L10n::t('Last login'), L10n::t('Last item'), L10n::t('Permanent deletion')],
+			'$th_users' => $th_users,
+			'$order_users' => $order,
+			'$order_direction_users' => $order_direction,
+
+			'$confirm_delete_multi' => L10n::t('Selected users will be deleted!\n\nEverything these users had posted on this site will be permanently deleted!\n\nAre you sure?'),
+			'$confirm_delete' => L10n::t('The user {0} will be deleted!\n\nEverything this user has posted on this site will be permanently deleted!\n\nAre you sure?'),
+
+			'$form_security_token' => parent::getFormSecurityToken('admin_users'),
+
+			// values //
+			'$baseurl' => $a->getBaseURL(true),
+
+			'$pending' => $pending,
+			'deleted' => $deleted,
+			'$users' => $users,
+			'$newusername' => ['new_user_name', L10n::t('Name'), '', L10n::t('Name of the new user.')],
+			'$newusernickname' => ['new_user_nickname', L10n::t('Nickname'), '', L10n::t('Nickname of the new user.')],
+			'$newuseremail' => ['new_user_email', L10n::t('Email'), '', L10n::t('Email address of the new user.'), '', '', 'email'],
+		]);
+
+		$o .= $pager->renderFull(DBA::count('user'));
+
+		return $o;
+	}
+}
\ No newline at end of file
diff --git a/src/Module/BaseAdminModule.php b/src/Module/BaseAdminModule.php
index 44ef39113f..cf434537ec 100644
--- a/src/Module/BaseAdminModule.php
+++ b/src/Module/BaseAdminModule.php
@@ -53,6 +53,7 @@ abstract class BaseAdminModule extends BaseModule
 				'federation'   => ['admin/federation'  , L10n::t('Federation Statistics')   , 'federation']
 			]],
 			'configuration' => [L10n::t('Configuration'), [
+				'users'        => ['admin/users'       , L10n::t('Users')                   , 'users'],
 				'addons'       => ['admin/addons'      , L10n::t('Addons')                  , 'addons'],
 				'themes'       => ['admin/themes'      , L10n::t('Themes')                  , 'themes'],
 				'tos'          => ['admin/tos'         , L10n::t('Terms of Service')        , 'tos'],
diff --git a/view/templates/admin/users.tpl b/view/templates/admin/users.tpl
index 88feb6136a..6ca85fe7aa 100644
--- a/view/templates/admin/users.tpl
+++ b/view/templates/admin/users.tpl
@@ -105,7 +105,11 @@
 				</tbody>
 			</table>
 			<div class='selectall'><a href='#' onclick="return selectall('users_ckbx');">{{$select_all}}</a></div>
-			<div class="submit"><input type="submit" name="page_users_block" value="{{$block}}/{{$unblock}}" /> <input type="submit" name="page_users_delete" value="{{$delete}}" onclick="return confirm_delete_multi()" /></div>
+			<div class="submit">
+				<input type="submit" name="page_users_block" value="{{$block}}" />
+				<input type="submit" name="page_users_unblock" value="{{$unblock}}" />
+				<input type="submit" name="page_users_delete" value="{{$delete}}" onclick="return confirm_delete_multi()" />
+			</div>
 		{{else}}
 			NO USERS?!?
 		{{/if}}
diff --git a/view/theme/frio/templates/admin/users.tpl b/view/theme/frio/templates/admin/users.tpl
index 273eb81e68..f2d407845b 100644
--- a/view/theme/frio/templates/admin/users.tpl
+++ b/view/theme/frio/templates/admin/users.tpl
@@ -223,13 +223,15 @@
 									</td>
 									<td class="text-right">
 										{{if $u.is_deletable}}
-										<a href="{{$baseurl}}/admin/users/block/{{$u.uid}}?t={{$form_security_token}}" class="admin-settings-action-link"title="{{if $u.blocked}}{{$unblock}}{{else}}{{$block}}{{/if}}">
-											{{if $u.blocked == 0}}
-											<i class="fa fa-ban" aria-hidden="true"></i>
-											{{else}}
+											{{if $u.blocked}}
+										<a href="{{$baseurl}}/admin/users/unblock/{{$u.uid}}?t={{$form_security_token}}" class="admin-settings-action-link" title="{{$unblock}}">
 											<i class="fa fa-circle-o" aria-hidden="true"></i>
-											{{/if}}
 										</a>
+											{{else}}
+										<a href="{{$baseurl}}/admin/users/block/{{$u.uid}}?t={{$form_security_token}}" class="admin-settings-action-link" title="{{$block}}">
+											<i class="fa fa-ban" aria-hidden="true"></i>
+										</a>
+											{{/if}}
 										<a href="{{$baseurl}}/admin/users/delete/{{$u.uid}}?t={{$form_security_token}}" class="admin-settings-action-link" title="{{$delete}}" onclick="return confirm_delete('{{$confirm_delete}}','{{$u.name}}')">
 											<i class="fa fa-trash" aria-hidden="true"></i>
 										</a>
@@ -256,7 +258,10 @@
 							</div>
 							<div class="col-xs-9 admin-settings-footer-elements text-right">
 								<button type="submit" name="page_users_block" value="1" class="btn btn-warning">
-									<i class="fa fa-ban" aria-hidden="true"></i> {{$block}} / <i class="fa fa-circle-o" aria-hidden="true"></i> {{$unblock}}
+									<i class="fa fa-ban" aria-hidden="true"></i> {{$block}}
+								</button>
+								<button type="submit" name="page_users_unblock" value="1" class="btn btn-default">
+									<i class="fa fa-circle-o" aria-hidden="true"></i> {{$unblock}}
 								</button>
 								<button type="submit" name="page_users_delete" value="1" class="btn btn-danger" onclick="return confirm_delete('{{$confirm_delete_multi}}')">
 									<i class="fa fa-trash" aria-hidden="true"></i> {{$delete}}
diff --git a/view/theme/quattro/templates/admin/users.tpl b/view/theme/quattro/templates/admin/users.tpl
index ddb395db9c..97875a0771 100644
--- a/view/theme/quattro/templates/admin/users.tpl
+++ b/view/theme/quattro/templates/admin/users.tpl
@@ -105,7 +105,11 @@
 				</tbody>
 			</table>
 			<div class='selectall'><a href='#' onclick="return selectall('users_ckbx');">{{$select_all}}</a></div>
-			<div class="submit"><input type="submit" name="page_users_block" value="{{$block}}/{{$unblock}}" /> <input type="submit" name="page_users_delete" value="{{$delete}}" onclick="return confirm_delete_multi()" /></div>						
+			<div class="submit">
+				<input type="submit" name="page_users_block" value="{{$block}}" />
+				<input type="submit" name="page_users_unblock" value="{{$unblock}}" />
+				<input type="submit" name="page_users_delete" value="{{$delete}}" onclick="return confirm_delete_multi()" />
+			</div>
 		{{else}}
 			NO USERS?!?
 		{{/if}}
-- 
2.39.5