Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions adm/style/banhammer_body.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ <h1>{L_ACP_BH_SETTINGS}</h1>
<dt><label for="move_group">{L_ACP_MOVE_GROUP}{L_COLON}</label><br /><span>{L_ACP_MOVE_GROUP_EXPLAIN}</span></dt>
<dd><select id="move_group" name="move_group">{MOVE_GROUP}</select></dd>
</dl>
<dl>
<dt><label for="restrict_group">{L_ACP_RESTRICT_GROUP}{L_COLON}</label><br /><span>{L_ACP_RESTRICT_GROUP_EXPLAIN}</span></dt>
<dd><select id="restrict_group" name="restrict_group">{RESTRICT_GROUP}</select></dd>
</dl>
<dl>
<dt><label for="sfs_api_key">{L_SFS_API_KEY}{L_COLON}</label><br /><span>{L_SFS_API_KEY_EXPLAIN}</span></dt>
<dd>
Expand Down
11 changes: 11 additions & 0 deletions config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ services:
- '%core.root_path%'
- '%core.php_ext%'
- '@service_container'
- '%core.table_prefix%banhammer_restrict'
tags:
- { name: event.listener }
phpbbmodders.banhammer.admin.controller:
Expand All @@ -36,3 +37,13 @@ services:
- '@language'
- '@request'
- '@user'
phpbbmodders.banhammer.cron.restriction_expiry:
class: phpbbmodders\banhammer\cron\task\restriction_expiry
arguments:
- '@config'
- '@dbal.conn'
- '%core.table_prefix%banhammer_restrict'
- '%core.root_path%'
- '%core.php_ext%'
tags:
- { name: cron.task }
4 changes: 3 additions & 1 deletion controller/admin_controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ public function display_options()
'DEL_POSTS' => (!empty($this->config['bh_del_posts'])) ? true : false,
'DEL_PROFILE' => (!empty($this->config['bh_del_profile'])) ? true : false,
'DEL_SIGNATURE' => (!empty($this->config['bh_del_signature'])) ? true : false,
'MOVE_GROUP' => $this->get_groups($this->request->variable('move_group', $this->config['bh_group_id'])),
'MOVE_GROUP' => $this->get_groups($this->request->variable('move_group', $this->config['bh_group_id'])),
'RESTRICT_GROUP' => $this->get_groups($this->request->variable('restrict_group', $this->config['bh_restrict_group_id'])),
'SFS_ALLOW_HTTP' => (!empty($this->config['bh_sfs_allow_http'])) ? true : false,
'SFS_API_KEY' => (!empty($this->config['bh_sfs_api_key'])) ? $this->config['bh_sfs_api_key'] : '',
'SFS_CURL' => (function_exists('curl_init')) ? true : false,
Expand All @@ -133,6 +134,7 @@ protected function set_options()
$this->config->set('bh_del_profile', $this->request->variable('del_profile', 0));
$this->config->set('bh_del_signature', $this->request->variable('del_signature', 0));
$this->config->set('bh_group_id', $this->request->variable('move_group', 0));
$this->config->set('bh_restrict_group_id', $this->request->variable('restrict_group', 0));
$this->config->set('bh_sfs_api_key', $this->request->variable('sfs_api_key', '', true));
$this->config->set('bh_sfs_allow_http', $this->request->variable('sfs_allow_http', 0));
$this->config->set('bh_ban_time', $this->request->variable('ban_time', 0));
Expand Down
133 changes: 133 additions & 0 deletions cron/task/restriction_expiry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php
/**
*
* Ban Hammer extension for the phpBB Forum Software package
*
* @copyright (c) 2026, phpBB Modders, https://www.phpbbmodders.com/
* @license GNU General Public License, version 2 (GPL-2.0)
*
*/

namespace phpbbmodders\banhammer\cron\task;

/**
* Restores a restricted user's original group once their restriction period
* ends. Not a ban, so phpBB's own ban-expiry handling never sees these users,
* this is the extension's own equivalent for the "restrict instead" action.
*/
class restriction_expiry extends \phpbb\cron\task\base
{
/** @var \phpbb\config\config */
protected $config;

/** @var \phpbb\db\driver\driver_interface */
protected $db;

/** @var string */
protected $restrict_table;

/** @var string phpBB root path */
protected $root_path;

/** @var string phpEx */
protected $php_ext;

/**
* Constructor
*
* @param \phpbb\config\config $config Config object
* @param \phpbb\db\driver\driver_interface $db Database object
* @param string $restrict_table Restriction tracking table
* @param string $root_path phpBB root path
* @param string $php_ext PHP file extension
* @access public
*/
public function __construct(
\phpbb\config\config $config,
\phpbb\db\driver\driver_interface $db,
$restrict_table,
$root_path,
$php_ext
)
{
$this->config = $config;
$this->db = $db;
$this->restrict_table = $restrict_table;
$this->root_path = $root_path;
$this->php_ext = $php_ext;
}

/**
* Restore every restriction whose time has passed.
*
* @return void
* @access public
*/
public function run()
{
$this->config->set('bh_restrict_last_run', time(), false);

$sql = 'SELECT restrict_id, user_id, original_group_id
FROM ' . $this->restrict_table . '
WHERE restrict_until > 0
AND restrict_until <= ' . time();
$result = $this->db->sql_query($sql);

$expired = array();

while ($row = $this->db->sql_fetchrow($result))
{
$expired[] = $row;
}
$this->db->sql_freeresult($result);

if (empty($expired))
{
return;
}

if (!function_exists('group_user_add') || !function_exists('group_user_del'))
{
include($this->root_path . 'includes/functions_user.' . $this->php_ext);
}

$restrict_group_id = (int) $this->config['bh_restrict_group_id'];

foreach ($expired as $row)
{
$user_id = (int) $row['user_id'];
$original_group_id = (int) $row['original_group_id'];

if ($restrict_group_id)
{
group_user_del($restrict_group_id, array($user_id));
}

if ($original_group_id)
{
group_user_add($original_group_id, array($user_id), false, false, true);
}

$this->db->sql_query('DELETE FROM ' . $this->restrict_table . ' WHERE restrict_id = ' . (int) $row['restrict_id']);
}
}

/**
* {@inheritdoc}
*/
public function is_runnable()
{
return true;
}

/**
* At most once every five minutes, restoring a group a little late is
* harmless and this should not run on every page load.
*
* {@inheritdoc}
*/
public function should_run()
{
return (int) $this->config['bh_restrict_last_run'] < (time() - 300);
}
}
144 changes: 142 additions & 2 deletions event/banhammer_listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class banhammer_listener implements EventSubscriberInterface
/** @var ContainerInterface */
protected $container;

/** @var string */
protected $restrict_table;

public function __construct(
\phpbb\auth\auth $auth,
\phpbb\cache\driver\driver_interface $cache,
Expand All @@ -75,7 +78,8 @@ public function __construct(
\phpbbmodders\banhammer\core\bantime $bantime,
$root_path,
$phpExt,
ContainerInterface $container
ContainerInterface $container,
$restrict_table
)
{
$this->auth = $auth;
Expand All @@ -89,12 +93,16 @@ public function __construct(
$this->root_path = $root_path;
$this->php_ext = $phpExt;
$this->container = $container;
$this->restrict_table = $restrict_table;
}

static public function getSubscribedEvents()
{
return(array(
'core.memberlist_view_profile' => 'do_ban_hammer_stuff',
'core.memberlist_view_profile' => array(
array('do_ban_hammer_stuff'),
array('do_restrict_stuff'),
),
'core.session_set_custom_ban' => 'undo_bh_group',
'core.mcp_queue_approve_details_template' => 'add_mcp_queue_banhammer_link',
));
Expand Down Expand Up @@ -450,6 +458,138 @@ public function do_ban_hammer_stuff($event)
redirect($url);
}

/**
* Move a user into a restricted group for a set time, instead of banning
* them. They keep the ability to log in, just with whatever reduced
* permissions the admin has given the restricted group.
*
* Answers CDB topic 240381 ("would it be possible to slightly change
* this to avoid the ban... add the user from profile to a different
* group, severely limited, for a given time").
*
* @param \phpbb\event\data $event The event object
* @return void
* @access public
*/
public function do_restrict_stuff($event)
{
$data = $event['member'];
$user_id = (int) $data['user_id'];
$restrict_group_id = (int) $this->config['bh_restrict_group_id'];

if (!$this->auth->acl_get('m_ban') || $data['user_type'] == USER_FOUNDER || $user_id == $this->user->data['user_id'] || !$restrict_group_id)
{
// Nothing to see here, move on. No group configured in the ACP
// means this feature is simply off.
return;
}

// A banned user can't log in regardless of group, so restricting
// them on top of a ban is meaningless - same check do_ban_hammer_stuff
// already makes before showing its own options.
if (!function_exists('phpbb_get_banned_user_ids'))
{
include($this->root_path . 'includes/functions_user.' . $this->php_ext);
}

if (!empty(phpbb_get_banned_user_ids(array($user_id))))
{
return;
}

$this->user->add_lang_ext('phpbbmodders/banhammer', 'banhammer');
$this->user->add_lang('acp/ban');

if ($this->active_restriction($user_id) !== null)
{
$this->template->assign_var('RESTRICT_MESSAGE', $this->user->lang['BH_ALREADY_RESTRICTED']);

return;
}

if (!$this->request->is_set('restrict') || ($this->request->is_set('restrict') && $this->request->is_set('confirm_key') && !confirm_box(true)))
{
$params = array(
'mode' => 'viewprofile',
'u' => $user_id,
'restrict' => 1,
);

$this->template->assign_vars(array(
'RESTRICT_TIME' => $this->bantime->display_ban_time(0),
'S_SHOW_RESTRICT' => true,
'U_RESTRICT_USER' => append_sid($this->root_path . 'memberlist.' . $this->php_ext, $params),
));

return;
}

if (!confirm_box(true))
{
$hidden_fields = build_hidden_fields(array(
'restrict_time' => $this->request->variable('restrict_time', 0),
'mode' => 'viewprofile',
));

$ban_length_options = $this->bantime->ban_length_options();
$length = isset($ban_length_options[$this->request->variable('restrict_time', 0)]) ? $ban_length_options[$this->request->variable('restrict_time', 0)] : '';

$message = sprintf($this->user->lang['BH_SURE_RESTRICT'], $data['username']);
$message .= ($length) ? '<br><br>' . $this->user->lang('BH_RESTRICT_FOR', $length) : '<br><br>' . $this->user->lang['BH_RESTRICT_PERM'];

confirm_box(false, $message, $hidden_fields);
}

if (!function_exists('group_user_add'))
{
include($this->root_path . 'includes/functions_user.' . $this->php_ext);
}

$restrict_time = $this->request->variable('restrict_time', 0);
// $restrict_time is minutes (ban_length_options()'s keys, same unit
// user_ban() expects), not days.
$restrict_until = ($restrict_time > 0) ? time() + ($restrict_time * 60) : 0;
$original_group_id = (int) $data['group_id'];

$sql_ary = array(
'user_id' => $user_id,
'original_group_id' => $original_group_id,
'restrict_until' => $restrict_until,
);
$sql = 'INSERT INTO ' . $this->restrict_table . ' ' . $this->db->sql_build_array('INSERT', $sql_ary);
$this->db->sql_query($sql);

group_user_add($restrict_group_id, array($user_id), false, false, true);

$args = array(
'mode' => 'viewprofile',
'u' => $user_id,
);

$url = append_sid(generate_board_url() . '/memberlist.' . $this->php_ext, $args);

redirect($url);
}

/**
* Whether a user currently has an active restriction.
*
* @param int $user_id
* @return array|null The restriction row, or null when there is none.
* @access protected
*/
protected function active_restriction($user_id)
{
$sql = 'SELECT restrict_id
FROM ' . $this->restrict_table . '
WHERE user_id = ' . (int) $user_id;
$result = $this->db->sql_query_limit($sql, 1);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);

return ($row) ?: null;
}

// Once a ban is cleared try and remove the user from the banned group set in the ACP of the extension
public function undo_bh_group($event)
{
Expand Down
6 changes: 6 additions & 0 deletions language/en/banhammer.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@

'BH_THIS_USER' => 'Ban Hammer this user',

'BH_ALREADY_RESTRICTED' => 'This user already has an active restriction',
'BH_RESTRICT_FOR' => 'Restrict this user for %s', // %s will be a duration
'BH_RESTRICT_PERM' => 'Restrict this user permanently',
'BH_RESTRICT_THIS_USER' => 'Restrict this user',
'BH_SURE_RESTRICT' => 'Are you sure you want to restrict <strong>%s</strong>?', // %s will be a username

'SFS_REPORT' => 'Report this user to Stop Forum Spam',
'SURE_BAN' => 'Are you sure you want to ban <strong>%s</strong>?', // %s will be a username.

Expand Down
2 changes: 2 additions & 0 deletions language/en/banhammer_acp.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@

'ACP_MOVE_GROUP' => 'Move to group',
'ACP_MOVE_GROUP_EXPLAIN' => 'Name of the group to which banned users should be moved. This will also be their default group.<br /><strong>If nothing but <em>“No group specified.”</em> is in the drop down then you have not set up any groups.</strong>',
'ACP_RESTRICT_GROUP' => 'Restrict to group',
'ACP_RESTRICT_GROUP_EXPLAIN' => 'Name of the group moderators can move a user into instead of banning them, for a set amount of time. This will also be their default group while restricted; their original group is restored automatically once the restriction expires.<br /><strong>If nothing but <em>“No group specified.”</em> is in the drop down then you have not set up any groups, and the restrict option will not appear on member profiles.</strong>',
'BAN_LENGTH_EXPLAIN' => 'If either of the ban options is set then the user will be banned for the amount of time as set here. This is also able to be set when ban hammering the user.',
'SFS_ALLOW_HTTP' => 'Allow HTTP for Stop Forum Spam reports',
'SFS_ALLOW_HTTP_EXPLAIN' => 'Reports are sent over HTTPS by default. <strong>Only enable this if your server cannot make outbound HTTPS requests</strong> - your API key and the reported user\'s username, IP address, and email are sent in clear text over HTTP.',
Expand Down
Loading