diff --git a/phpBB/phpbb/ban/exception/ban_insert_failed_exception.php b/phpBB/phpbb/ban/exception/ban_insert_failed_exception.php new file mode 100644 index 0000000000..1c1a2fee7f --- /dev/null +++ b/phpBB/phpbb/ban/exception/ban_insert_failed_exception.php @@ -0,0 +1,20 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\exception; + +use phpbb\exception\runtime_exception; + +class ban_insert_failed_exception extends runtime_exception +{ +} diff --git a/phpBB/phpbb/ban/exception/no_items_specified_exception.php b/phpBB/phpbb/ban/exception/no_items_specified_exception.php new file mode 100644 index 0000000000..8b68c08ded --- /dev/null +++ b/phpBB/phpbb/ban/exception/no_items_specified_exception.php @@ -0,0 +1,20 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\exception; + +use phpbb\exception\runtime_exception; + +class no_items_specified_exception extends runtime_exception +{ +} diff --git a/phpBB/phpbb/ban/manager.php b/phpBB/phpbb/ban/manager.php index 221dbef99d..180d5a2561 100644 --- a/phpBB/phpbb/ban/manager.php +++ b/phpBB/phpbb/ban/manager.php @@ -13,12 +13,15 @@ namespace phpbb\ban; +use phpbb\ban\exception\ban_insert_failed_exception; use phpbb\ban\exception\invalid_length_exception; +use phpbb\ban\exception\no_items_specified_exception; use phpbb\ban\exception\type_not_found_exception; class manager { - const CACHE_KEY = '_ban_info'; + const CACHE_KEY_INFO = '_ban_info'; + const CACHE_KEY_USERS = '_banned_users'; const CACHE_TTL = 3600; protected $ban_table; @@ -65,13 +68,14 @@ class manager { throw new type_not_found_exception(); // TODO } + $this->tidy(); $ban_items = $ban_mode->prepare_for_storage($items); // Prevent duplicate bans $sql = 'DELETE FROM ' . $this->ban_table . " WHERE ban_mode = '" . $this->db->sql_escape($mode) . "' - AND " . $this->db->sql_in_set('ban_item', $ban_items); // TODO (what if empty?) + AND " . $this->db->sql_in_set('ban_item', $ban_items, false, true); $this->db->sql_query($sql); $insert_array = []; @@ -89,14 +93,13 @@ class manager if (empty($insert_array)) { - return; // TODO + throw new no_items_specified_exception(); // TODO } $result = $this->db->sql_multi_insert($this->ban_table, $insert_array); if ($result === false) { - // Something went wrong - // TODO throw exception + throw new ban_insert_failed_exception(); // TODO } if ($ban_mode->get_ban_log_string() !== false) @@ -173,10 +176,11 @@ class manager } } - $this->cache->destroy(self::CACHE_KEY); + $this->cache->destroy(self::CACHE_KEY_INFO); + $this->cache->destroy(self::CACHE_KEY_USERS); } - public function unban($mode, array $items) + public function unban($mode, array $items, $logging = true) { /** @var \phpbb\ban\type\type_interface $ban_mode */ $ban_mode = $this->find_type($mode); @@ -184,6 +188,7 @@ class manager { throw new type_not_found_exception(); // TODO } + $this->tidy(); $sql_ids = array_map('intval', $items); $sql = 'SELECT ban_item @@ -202,7 +207,7 @@ class manager WHERE ' . $this->db->sql_in_set('ban_id', $sql_ids); $this->db->sql_query($sql); - if ($ban_mode->get_unban_log_string() !== false) + if ($logging && $ban_mode->get_unban_log_string() !== false) { $unban_items_log = implode(', ', $unbanned_items); @@ -216,10 +221,12 @@ class manager $unban_data = [ 'items' => $unbanned_items, + 'logging' => $logging, ]; $ban_mode->after_unban($unban_data); - $this->cache->destroy(self::CACHE_KEY); + $this->cache->destroy(self::CACHE_KEY_INFO); + $this->cache->destroy(self::CACHE_KEY_USERS); } public function check(array $user_data = []) @@ -229,33 +236,7 @@ class manager $user_data = $this->user->data; } - $ban_info = $this->cache->get(self::CACHE_KEY); - if ($ban_info === false) - { - $sql = 'SELECT ban_mode, ban_item, ban_end, ban_reason_display - FROM ' . $this->ban_table . ' - WHERE 1'; - $result = $this->db->sql_query($sql); - - $ban_info = []; - - while ($row = $this->db->sql_fetchrow($result)) - { - if (!isset($ban_info[$row['ban_mode']])) - { - $ban_info[$row['ban_mode']] = []; - } - - $ban_info[$row['ban_mode']][] = [ - 'item' => $row['ban_item'], - 'end' => $row['ban_end'], - 'reason' => $row['ban_reason_display'], - ]; - } - $this->db->sql_freeresult($result); - - $this->cache->put(self::CACHE_KEY, $ban_info, self::CACHE_TTL); - } + $ban_info = $this->get_info_cache(); foreach ($ban_info as $mode => $ban_rows) { @@ -305,6 +286,100 @@ class manager return false; } + public function get_bans($mode) + { + /** @var \phpbb\ban\type\type_interface $ban_mode */ + $ban_mode = $this->find_type($mode); + if ($ban_mode === false) + { + throw new type_not_found_exception(); // TODO + } + $this->tidy(); + + $sql = 'SELECT ban_id, ban_item, ban_start, ban_end, ban_reason, ban_reason_display + FROM ' . $this->ban_table . " + WHERE ban_mode = '" . $this->db->sql_escape($mode) . "' + AND (ban_end <= 0 OR ban_end >= " . (int) time() . ')'; + $result = $this->db->sql_query($sql); + $rowset = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rowset; + } + + public function get_banned_users() + { + $banned_users = $this->cache->get(self::CACHE_KEY_USERS); + if ($banned_users === false) + { + $manual_modes = []; + $where_array = []; + + /** @var \phpbb\ban\type\type_interface $ban_mode */ + foreach ($this->types as $ban_mode) + { + $user_column = $ban_mode->get_user_column(); + if (empty($user_column)) + { + $manual_modes[] = $ban_mode; + continue; + } + $where_array[] = ['AND', + [ + ['b.ban_item', '=', 'u.' . $user_column], + ['b.ban_mode', '=', $ban_mode->get_type()], + ], + ]; + } + + $sql_array = [ + 'SELECT' => 'u.user_id, b.ban_end', + 'FROM' => [ + $this->ban_table => 'b', + $this->users_table => 'u', + ], + 'WHERE' => ['OR', + $where_array, + ], + ]; + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query($sql); + + $banned_users = []; + while ($row = $this->db->sql_fetchrow($result)) + { + $user_id = (int) $row['user_id']; + $end = (int) $row['ban_end']; + if (!isset($banned_users[$user_id]) || ($banned_users[$user_id] > 0 && $banned_users[$user_id] < $end)) + { + $banned_users[$user_id] = $end; + } + } + $this->db->sql_freeresult($result); + + /** @var \phpbb\ban\type\type_interface $manual_mode */ + foreach ($manual_modes as $manual_mode) + { + $mode_banned_users = $manual_mode->get_banned_users(); + foreach ($mode_banned_users as $user_id => $end) + { + $user_id = (int) $user_id; + $end = (int) $end; + if (!isset($banned_users[$user_id]) || ($banned_users[$user_id] > 0 && $banned_users[$user_id] < $end)) + { + $banned_users[$user_id] = $end; + } + } + } + + $this->cache->put(self::CACHE_KEY_USERS, $banned_users, self::CACHE_TTL); + } + + return array_filter($banned_users, function ($end) { + return $end <= 0 || $end > time(); + }); + } + public function tidy() { // Delete stale bans @@ -332,4 +407,37 @@ class manager return false; } + + protected function get_info_cache() + { + $ban_info = $this->cache->get(self::CACHE_KEY_INFO); + if ($ban_info === false) + { + $sql = 'SELECT ban_mode, ban_item, ban_end, ban_reason_display + FROM ' . $this->ban_table . ' + WHERE 1'; + $result = $this->db->sql_query($sql); + + $ban_info = []; + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!isset($ban_info[$row['ban_mode']])) + { + $ban_info[$row['ban_mode']] = []; + } + + $ban_info[$row['ban_mode']][] = [ + 'item' => $row['ban_item'], + 'end' => $row['ban_end'], + 'reason' => $row['ban_reason_display'], + ]; + } + $this->db->sql_freeresult($result); + + $this->cache->put(self::CACHE_KEY_INFO, $ban_info, self::CACHE_TTL); + } + + return $ban_info; + } } diff --git a/phpBB/phpbb/ban/type/base.php b/phpBB/phpbb/ban/type/base.php index 5771b02da1..7dc1c396c1 100644 --- a/phpBB/phpbb/ban/type/base.php +++ b/phpBB/phpbb/ban/type/base.php @@ -64,6 +64,14 @@ abstract class base implements type_interface { } + /** + * {@inheritDoc} + */ + public function get_banned_users() + { + return []; + } + /** * Queries users that are excluded from banning (like founders) * from the database and saves them in $this->excluded array. diff --git a/phpBB/phpbb/ban/type/type_interface.php b/phpBB/phpbb/ban/type/type_interface.php index 143d6a3041..4dfc79595f 100644 --- a/phpBB/phpbb/ban/type/type_interface.php +++ b/phpBB/phpbb/ban/type/type_interface.php @@ -96,6 +96,18 @@ interface type_interface */ public function check(array $ban_rows, array $user_data); + /** + * In case get_user_column() returns no string, this method will be called + * when a list of banned users is retrieved. + * Returns a list of banned users. + * The result is cached and is not used for ban checking, so the accuracy + * of the results is not as important as when *really* checking in check() + * + * @return array An array of banned users, where the user ids are the keys + * and the value is the end of the ban (or 0 if permanent) + */ + public function get_banned_users(); + /** * Prepares the given ban items before saving them in the database * diff --git a/phpBB/phpbb/ban/type/user.php b/phpBB/phpbb/ban/type/user.php index fe0e2adbe1..3c650a5ea8 100644 --- a/phpBB/phpbb/ban/type/user.php +++ b/phpBB/phpbb/ban/type/user.php @@ -94,6 +94,11 @@ class user extends base */ public function after_unban(array $data) { + if (empty($data['logging'])) + { + return; + } + $user_ids = array_map('intval', $data['items']); $sql = 'SELECT user_id, username