Skip to content
Snippets Groups Projects
Commit a466987f authored by Jay Friendly's avatar Jay Friendly
Browse files

Issue #2970938 by Jaypan: Spam prevention

parent e48eb19d
No related branches found
No related tags found
No related merge requests found
private_message_flood.role:
type: sequence
label: 'Private Message Flood role settings'
private_message_flood.role.*:
type: config_object
label: 'Private Message flood role setting'
mapping:
limit:
type: integer
label: 'Maximum number of messages'
duration:
type: duration
label: 'Duration'
weight:
type: integer
label: 'Role Weight'
name: 'Private Message Flood Protection'
description: 'Sets up flood protection for the Private Message module'
type: module
core: 8.x
package: Private Message
dependencies:
- private_message:private_message
- duration_field:duration_field
<?php
/**
* @file
* Holds hooks for the Private Message Flood Protection module.
*/
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_form_FORM_ID_alter().
*/
function private_message_flood_form_private_message_form_alter(array &$form, FormStateInterface $formState) {
// Adds a custom validation handler to the form.
array_unshift($form['#validate'], 'private_message_flood_form_private_message_form_validate');
}
/**
* Custom validation handler for private message form.
*
* Checks if a user has reached their flood limit.
*/
function private_message_flood_form_private_message_form_validate(array $form, FormStateInterface $formState) {
if (\Drupal::service('private_message_flood.service')->checkUserFlood()) {
$formState->setError($form['message'], t('Sorry, you have reached your limit for posting. Please try again later.'));
}
}
parameters:
private_message_flood.service.class: 'Drupal\private_message_flood\Service\PrivateMessageFloodService'
services:
private_message_flood.service:
class: '%private_message_flood.service.class%'
arguments:
- '@current_user'
- '@config.factory'
- '@datetime.time'
<?php
namespace Drupal\private_message_flood\Plugin\PrivateMessageConfigForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\private_message\Plugin\PrivateMessageConfigForm\PrivateMessageConfigFormBase;
use Drupal\private_message\Plugin\PrivateMessageConfigForm\PrivateMessageConfigFormPluginInterface;
/**
* Adds Private Message Flood settings to the Private Message config page.
*
* @PrivateMessageConfigForm(
* id = "private_message_flood_settings",
* name = @Translation("Private Message Flood Protection settings"),
* )
*/
class PrivateMessageFloodPrivateMessageConfigForm extends PrivateMessageConfigFormBase implements PrivateMessageConfigFormPluginInterface {
/**
* {@inheritdoc}
*/
public function buildForm(FormStateInterface $formState) {
$form['header'] = [
'#prefix' => '<p>',
'#suffix' => '</p>',
'#markup' => $this->t('Flood protection limits the number of post users can make in a given period of time. You can apply flood protection on a per-role basis below. Priority is given to roles from top to bottom. The highest priority role a user is found to have, will be checked against the flood protection settings for that role when determining if they have flooded the private message system.'),
];
$group_class = 'group-order-weight';
$roles = array_map(['\Drupal\Component\Utility\Html', 'escape'], user_role_names(TRUE));
$items = [];
foreach ($roles as $role_id => $role_name) {
$config = $this->configFactory->get('private_message_flood.role.' . $role_id);
$items[$role_id] = [
'label' => $role_name,
'weight' => $config->get('weight'),
'elements' => [
'limit' => [
'#type' => 'number',
'#title' => t('Maxium number of messages'),
'#description' => $this->t('Leave empty to disable flood protection for this role'),
'#default_value' => $config->get('limit'),
],
'duration' => [
'#type' => 'duration',
'#title' => t('Duration'),
'#granularity' => 'y:d:m:h:i:s',
'#default_value' => $config->get('duration'),
],
],
];
}
$form['roles'] = [
'#type' => 'table',
'#caption' => $this->t('Roles'),
'#header' => [
$this->t('Label'),
$this->t('Values'),
$this->t('Weight'),
],
'#tableselect' => FALSE,
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => $group_class,
],
],
];
// Build rows.
foreach ($items as $key => $value) {
$form['roles'][$key]['#attributes']['class'][] = 'draggable';
$form['roles'][$key]['#weight'] = $value['weight'];
// Label col.
$form['roles'][$key]['label'] = [
'#plain_text' => $value['label'],
];
// Values col.
$form['roles'][$key]['values'] = [
'details' => [
'#type' => 'details',
'#title' => $this->t('Click to expand'),
'#open' => FALSE,
'elements' => $value['elements'],
],
];
// Weight col.
$form['roles'][$key]['weight'] = [
'#type' => 'weight',
'#title' => $this->t('Weight for @title', ['@title' => $value['label']]),
'#title_display' => 'invisible',
'#default_value' => $value['weight'],
'#attributes' => ['class' => [$group_class]],
];
}
uasort($form['roles'], ['Drupal\Component\Utility\SortArray', 'sortByWeightProperty']);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array $values) {
foreach ($values['roles'] as $rid => $data) {
$this->configFactory->getEditable('private_message_flood.role.' . $rid)
->set('limit', $data['values']['details']['elements']['limit'])
->set('duration', $data['values']['details']['elements']['duration'])
->set('weight', $data['weight'])
->save();
}
}
}
<?php
namespace Drupal\private_message_flood\Service;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
/**
* Provides services for the Private Message Flood Protection module.
*/
class PrivateMessageFloodService implements PrivateMessageFloodServiceInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* The configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* Constructs a PrivateMessageFloodService object.
*
* @param \Drupal\Core\Session\AccountProxyInterface $currentUser
* The current user.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The configuration factory.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
*/
public function __construct(
AccountProxyInterface $currentUser,
ConfigFactoryInterface $configFactory,
TimeInterface $time
) {
$this->currentUser = $currentUser;
$this->configFactory = $configFactory;
$this->time = $time;
}
/**
* {@inheritdoc}
*/
public function checkUserFlood() {
$flood_info = $this->getFloodProtectionInfo();
// Check if we even need to check anything.
if ($limit = $flood_info['limit']) {
// Get the amount of time in which they are limited to make said number of
// posts.
$duration = new \DateInterval($flood_info['duration']);
// Get the current time.
$now = new \DateTime();
$now->setTimestamp($this->time->getRequestTime());
// Get the UNIX timestamp for time that is $duration ago from now. For
// example, if $duration is one year, then $since will be the exact date
// and time one year ago from now.
$since = $now->sub($duration)->format('U');
// Query the number of posts that the user has made since the given
// timestamp.
$post_count = db_select('private_messages', 'pm')
->fields('pm', ['id'])
->condition('owner', $this->currentUser->id())
->condition('created', $since, '>=')
->countQuery()
->execute()
->fetchField();
// Return TRUE if they've posted their limit already, FALSE if they have
// not.
return $post_count >= $limit;
}
// If there is no limit for the user, then they have no flood protection
// applied to them, and therefore are unable to flood.
return FALSE;
}
/**
* Gets flood protection info for the current user, based on their roles.
*
* Each role in the system can be assigned flood protection settings. Roles
* are also assigned a priority. This function fetches the flood protection
* data for the highest prioirty role that the current user has.
*
* @return array
* An array of flood protection data for the current user based on their
* roles.
*/
private function getFloodProtectionInfo() {
$user_roles = $this->currentUser->getRoles();
$role = array_intersect_key($this->getRoleInfo(), array_flip($user_roles));
$role_id = key($role);
$config = $this->configFactory->get('private_message_flood.role.' . $role_id);
if ($config) {
return $config->get();
}
return [
'limit' => 0,
];
}
/**
* Fetches an array of roles, ordered by priority.
*
* Each role in the system may have flood protection settings that determine
* how many posts a user can make in a given duration. Roles have priority,
* and the highest priority role is the one that a user is checked against
* when determining whether or not they have crossed the threshold for their
* flood settings.
*
* @return array
* An array of roles that have flood protection assigned to them, keyed by
* the role ID, with the value being the weight. Roles in the array are
* ordered highest to lowest priority.
*/
private function getRoleInfo() {
$roles = array_map(['\Drupal\Component\Utility\Html', 'escape'], user_role_names(TRUE));
$items = [];
foreach (array_keys($roles) as $role_id) {
$role_info = $this->configFactory->get('private_message_flood.role.' . $role_id)->get();
$items[$role_id] = $role_info['weight'];
}
uasort($items, [$this, 'sortByWeight']);
return $items;
}
/**
* Sorting callback to sort arrays by their 'weight' attribute.
*/
private function sortByWeight($a, $b) {
if ($a === $b) {
return 0;
}
return $a > $b ? 1 : -1;
}
}
<?php
namespace Drupal\private_message_flood\Service;
/**
* Provides services for the Private Message Flood Protection module.
*/
interface PrivateMessageFloodServiceInterface {
/**
* Determines whether a user has reached their post limit for messages.
*
* @return bool
* A boolean indicating whether or not they have reached their flood limit.
* TRUE means they have reached the limit, and should be stopped from
* posting. FALSE means they can post as normal.
*/
public function checkUserFlood();
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment