Skip to content
Snippets Groups Projects
Commit 55a70b78 authored by Vadym Abramchuk's avatar Vadym Abramchuk Committed by Artem Sylchuk
Browse files

Private Message Blocking System

parent 8cd60c51
No related branches found
No related tags found
No related merge requests found
Showing
with 630 additions and 2 deletions
......@@ -6,6 +6,11 @@ hide_form_filter_tips: false
hide_recipient_field_when_prefilled: false
create_message_label: 'Create Private Message'
save_message_label: 'Send'
ban_mode: 'passive'
ban_message: 'User is unable to receive your message'
ban_label: 'Block'
unban_label: 'Unblock'
ban_page_label: 'Block / Unblock users'
autofocus_enable: true
keys_send: 'Enter, 13'
remove_css: false
......@@ -26,6 +26,21 @@ private_message.settings:
save_message_label:
type: label
label: 'The label of the button to send a new message.'
ban_mode:
type: string
label: 'The blocking mode.'
ban_message:
type: string
label: 'The message to show to the user when they are blocked.'
ban_label:
type: label
label: 'The label of the button to block a user.'
unban_label:
type: label
label: 'The label of the button to unblock a user.'
ban_page_label:
type: label
label: 'The label of the link to the block page.'
autofocus_enable:
type: boolean
label: 'Whether or not the message autofocus feature is enabled.'
......
(function($, Drupal) {
'use strict';
/**
* Override drupal selectHandler function
*/
function membersBanSelectHandler(event, ui) {
let valueField = $(event.target);
if ($(event.target).hasClass('private-message-ban-autocomplete')) {
const valueFieldName = 'banned_user';
if ($('input[name=' + valueFieldName + ']').length > 0) {
valueField = $('input[name=' + valueFieldName + ']');
// Update the labels too.
const labels = Drupal.autocomplete.splitValues(event.target.value);
labels.pop();
labels.push(ui.item.label);
event.target.value = labels.join(', ');
}
}
const terms = Drupal.autocomplete.splitValues(valueField.val());
// Remove the current input.
terms.pop();
// Add the selected item.
terms.push(ui.item.value);
valueField.val(terms.join(', '));
// Return false to tell jQuery UI that we've filled in the value already.
return false;
}
Drupal.behaviors.privateMessageBan = {
attach: function(context, settings) {
// Attach custom select handler to fields with class.
$('input.private-message-ban-autocomplete').autocomplete({
select: membersBanSelectHandler,
});
},
};
})(jQuery, Drupal);
......@@ -6,6 +6,7 @@
*/
use Drupal\Core\Database\IntegrityConstraintViolationException;
use Drupal\private_message\Form\ConfigForm as Config;
/**
* Implements hook_uninstall().
......@@ -359,3 +360,30 @@ function private_message_update_8010() {
// Leave this hook update empty because it already passed who applied patch
// from issue https://www.drupal.org/project/private_message/issues/3265901
}
/**
* Install the private_message_ban entity type.
*/
function private_message_update_8011() {
if (!\Drupal::database()->schema()->tableExists('private_message_ban')) {
\Drupal::entityTypeManager()->clearCachedDefinitions();
\Drupal::entityDefinitionUpdateManager()->installEntityType(\Drupal::entityTypeManager()->getDefinition('private_message_ban'));
}
else {
return 'Private Message Ban entity already exists';
}
}
/**
* Sets default values for the block functionality.
*/
function private_message_update_8012() {
\Drupal::configFactory()->getEditable('private_message.settings')
->set('ban_mode', Config::PASSIVE)
->set('ban_message', 'User is unable to receive your message')
->set('ban_label', 'Block')
->set('unban_label', 'Unblock')
->save();
}
......@@ -82,3 +82,10 @@ uninstall_page:
dependencies:
- core/jquery
- core/once
ban_autocomplete:
version: VERSION
js:
js/private_message_ban_user_autocomplete.js: {}
dependencies:
- core/jquery
entity.private_message_ban.add_form:
route_name: entity.private_message_ban.add_form
title: 'Add Private Message Ban'
appears_on:
- entity.private_message_ban.collection
......@@ -33,3 +33,11 @@ private_message.admin_config.uninstall:
description: 'Prepare the private message module for uninstallation'
route_name: private_message.admin_config.uninstall
parent: private_message.admin_config
# Private Message Ban menu items definition
entity.private_message_ban.collection:
title: 'Private Message Bans list'
route_name: entity.private_message_ban.collection
description: 'List Private Message Bans'
parent: private_message.admin.structure
weight: 100
# Private Message Ban routing definition
entity.private_message_ban.edit_form:
route_name: entity.private_message_ban.edit_form
base_route: entity.private_message_ban.edit_form
title: 'Edit'
entity.private_message_ban.delete_form:
route_name: entity.private_message_ban.delete_form
base_route: entity.private_message_ban.edit_form
title: 'Delete'
weight: 10
private_message.private_message_settings_tab:
route_name: private_message.private_message_settings
title: Settings
......
......@@ -89,6 +89,13 @@ function private_message_entity_extra_field_info() {
'weight' => 0,
'visible' => TRUE,
];
$fields['user'][$bundle]['display']['block_user'] = [
'label' => t('Block/Unblock user link'),
'description' => t('Link to block or unblock user'),
'weight' => 0,
'visible' => TRUE,
];
}
$node_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('node');
......@@ -221,6 +228,38 @@ function private_message_user_view(array &$build, EntityInterface $entity, Entit
}
}
if ($display->getComponent('block_user')) {
if (\Drupal::currentUser()->isAuthenticated() &&
$entity->id() != \Drupal::currentUser()->id()) {
/** @var \Drupal\private_message\Service\PrivateMessageBanManager $privateMessageBanManager */
$privateMessageBanManager = \Drupal::service('private_message.ban_manager');
$config = \Drupal::config('private_message.settings');
if ($privateMessageBanManager->isBanned($entity->id())) {
$block_label = $config->get('unban_label');
$block_url = Url::fromRoute('private_message.unban_user_form', ['user' => $entity->id()]);
}
else {
$block_label = $config->get('ban_label');
$block_url = Url::fromRoute('private_message.ban_user_form', ['user' => $entity->id()]);
}
$build['block_user'] = [
'#prefix' => '<div class="block-unblock">',
'#suffix' => '</div>',
'#type' => 'link',
'#url' => $block_url,
'#title' => $block_label,
'#cache' => [
// This should probably be optimized in future by introducing
// per-user bans cache tags.
'tags' => ['private_message_ban_list'],
],
];
}
}
\Drupal::service('private_message.service')->createRenderablePrivateMessageThreadLink($build, $entity, $display, $view_mode);
}
......
......@@ -24,3 +24,20 @@ administer private message module:
title: 'Administer private message module'
description: 'Allows administrators to manage all parts of the One Page Private Message module'
restrict access: true
add private message ban entities:
title: 'Create new Private Message Ban entities'
administer private message ban entities:
title: 'Administer Private Message Ban entities'
description: 'Allow to access the administration form to configure Private Message Ban entities.'
restrict access: true
delete private message ban entities:
title: 'Delete Private Message Ban entities'
edit private message ban entities:
title: 'Edit Private Message Ban entities'
view private message ban entities:
title: 'View Private Message Ban entities'
......@@ -138,3 +138,38 @@ private_message.ajax_callback:
_csrf_token: 'TRUE'
_user_is_logged_in: 'TRUE'
_permission: 'use private messaging system,access user profiles'
private_message.ban_page:
path: '/private-message/ban'
defaults:
_controller: '\Drupal\private_message\Controller\PrivateMessageController::banUnbanPage'
_title: 'Ban/Unban users'
requirements:
_permission: 'use private messaging system,access user profiles'
private_message.ban_autocomplete:
path: '/private-message/autocomplete/ban-members'
defaults:
_controller: '\Drupal\private_message\Controller\AjaxController::privateMessageBanMembersAutocomplete'
_format: json
requirements:
_user_is_logged_in: 'TRUE'
_permission: 'use private messaging system,access user profiles'
private_message.ban_user_form:
path: '/private-message/ban/{user}'
defaults:
_form: '\Drupal\private_message\Form\ConfirmBanUserForm'
_title: 'Ban User'
requirements:
_permission: 'use private messaging system,access user profiles'
_user_is_logged_in: 'TRUE'
private_message.unban_user_form:
path: '/private-message/unban/{user}'
defaults:
_form: '\Drupal\private_message\Form\ConfirmUnbanUserForm'
_title: 'Unban User'
requirements:
_permission: 'use private messaging system,access user profiles'
_user_is_logged_in: 'TRUE'
parameters:
private_message.mapper.class: 'Drupal\private_message\Mapper\PrivateMessageMapper'
private_message.service.class: 'Drupal\private_message\Service\PrivateMessageService'
private_message.ban_manager.class: 'Drupal\private_message\Service\PrivateMessageBanManager'
cache_context.private_message_thread.class: 'Drupal\private_message\Cache\Context\PrivateMessageThreadCacheContext'
private_message.thread_manager.class: 'Drupal\private_message\Service\PrivateMessageThreadManager'
private_message.private_message_config_form_manager.class: 'Drupal\private_message\PluginManager\PrivateMessageConfigFormManager'
......@@ -23,6 +24,14 @@ services:
- '@entity_type.manager'
- '@datetime.time'
private_message.ban_manager:
class: '%private_message.ban_manager.class%'
arguments:
- '@current_user'
- '@entity_type.manager'
- '@database'
- '@messenger'
cache_context.private_message_thread:
class: '%cache_context.private_message_thread.class%'
arguments:
......
......@@ -20,9 +20,13 @@ use Drupal\private_message\Ajax\PrivateMessageMembersAutocompleteResponseCommand
use Drupal\private_message\Ajax\PrivateMessageMemberUsernameValidatedCommand;
use Drupal\private_message\Ajax\PrivateMessageUpdateUnreadThreadCountCommand;
use Drupal\private_message\Entity\PrivateMessageThread;
use Drupal\private_message\Service\PrivateMessageBanManagerInterface;
use Drupal\private_message\Service\PrivateMessageServiceInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\Component\Utility\Tags;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
......@@ -81,6 +85,13 @@ class AjaxController extends ControllerBase implements AjaxControllerInterface {
*/
protected $privateMessageService;
/**
* The Private Message Ban manager.
*
* @var \Drupal\private_message\Service\PrivateMessageBanManagerInterface
*/
protected PrivateMessageBanManagerInterface $privateMessageBanManager;
/**
* The key/value storage collection.
*
......@@ -103,6 +114,10 @@ class AjaxController extends ControllerBase implements AjaxControllerInterface {
* The current user.
* @param \Drupal\private_message\Service\PrivateMessageServiceInterface $privateMessageService
* The private message service.
* @param \Drupal\private_message\Service\PrivateMessageBanManagerInterface $privateMessageBanManager
* The Private Message Ban manager.
* @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $keyValueStore
* The key/value storage collection.
*/
public function __construct(
RendererInterface $renderer,
......@@ -111,6 +126,7 @@ class AjaxController extends ControllerBase implements AjaxControllerInterface {
ConfigFactoryInterface $configFactory,
AccountProxyInterface $currentUser,
PrivateMessageServiceInterface $privateMessageService,
PrivateMessageBanManagerInterface $privateMessageBanManager,
KeyValueStoreInterface $keyValueStore
) {
$this->renderer = $renderer;
......@@ -120,6 +136,7 @@ class AjaxController extends ControllerBase implements AjaxControllerInterface {
$this->configFactory = $configFactory;
$this->currentUser = $currentUser;
$this->privateMessageService = $privateMessageService;
$this->privateMessageBanManager = $privateMessageBanManager;
$this->keyValueStore = $keyValueStore;
}
......@@ -134,6 +151,7 @@ class AjaxController extends ControllerBase implements AjaxControllerInterface {
$container->get('config.factory'),
$container->get('current_user'),
$container->get('private_message.service'),
$container->get('private_message.ban_manager'),
$container->get('keyvalue')->get('entity_autocomplete')
);
}
......@@ -215,7 +233,7 @@ class AjaxController extends ControllerBase implements AjaxControllerInterface {
$accounts = $this->privateMessageService->getUsersFromString($username, $match_limit);
$user_info = [];
foreach ($accounts as $account) {
if ($account->access('view', $this->currentUser) && $account->isActive()) {
if ($account->access('view', $this->currentUser) && $account->isActive() && !$this->privateMessageBanManager->isBanned($account->id())) {
$user_info[] = [
'uid' => $account->id(),
'username' => $account->getDisplayName(),
......@@ -228,6 +246,30 @@ class AjaxController extends ControllerBase implements AjaxControllerInterface {
return $response;
}
/**
* Handler for autocomplete request for banning people.
*/
public function privateMessageBanMembersAutocomplete(Request $request) {
$results = [];
if ($input = $request->query->get('q')) {
$typed_string = Tags::explode($input);
$typed_string = mb_strtolower(array_pop($typed_string));
$accounts = $this->privateMessageService->getUsersFromString($typed_string, self::AUTOCOMPLETE_COUNT);
foreach ($accounts as $account) {
if (!$this->privateMessageBanManager->isBanned($account->id())) {
$results[] = [
'value' => $account->id(),
'label' => $account->getDisplayName(),
];
}
}
}
return new JsonResponse($results);
}
/**
* Creates an Ajax Command containing new private message.
*
......
......@@ -3,10 +3,13 @@
namespace Drupal\private_message\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\private_message\Form\BanUserForm;
use Drupal\private_message\Service\PrivateMessageBanManagerInterface;
use Drupal\private_message\Service\PrivateMessageServiceInterface;
use Drupal\user\UserDataInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -100,6 +103,7 @@ class PrivateMessageController extends ControllerBase implements PrivateMessageC
public function privateMessagePage() {
$this->privateMessageService->updateLastCheckTime();
/** @var \Drupal\user\UserInterface $user */
$user = $this->userManager->load($this->currentUser->id());
$private_message_thread = $this->privateMessageService->getFirstThreadForUser($user);
......@@ -167,4 +171,20 @@ class PrivateMessageController extends ControllerBase implements PrivateMessageC
];
}
/**
* {@inheritdoc}
*/
public function banUnbanPage() {
return [
'#prefix' => '<div id="private_message_ban_page">',
'#suffix' => '</div>',
'content' => [
// @todo list banned users.
[
'form' => $this->formBuilder->getForm(BanUserForm::class),
],
],
];
}
}
......@@ -27,4 +27,9 @@ interface PrivateMessageControllerInterface {
*/
public function adminUninstallPage();
/**
* The page for banning and unbanning users.
*/
public function banUnbanPage();
}
<?php
namespace Drupal\private_message\Entity\Access;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;
/**
* Access controller for the Private Message Ban entities.
*
* @see \Drupal\private_message\Entity\PrivateMessageBan
*/
class PrivateMessageBanAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\private_message\Entity\PrivateMessageBanInterface $entity */
switch ($operation) {
case 'view':
return AccessResult::allowedIfHasPermission($account, 'view private message ban entities');
case 'update':
return AccessResult::allowedIfHasPermission($account, 'edit private message ban entities');
case 'delete':
return AccessResult::allowedIfHasPermission($account, 'delete private message ban entities');
}
// Unknown operation, no opinion.
return AccessResult::neutral();
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return AccessResult::allowedIfHasPermission($account, 'add private message ban entities');
}
}
<?php
namespace Drupal\private_message\Entity\Builder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Link;
/**
* Defines a class to build a listing of Private Message Ban entities.
*
* @ingroup private_message
*/
class PrivateMessageBanListBuilder extends EntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
// @todo: Make this useful by adding the ban owner and target fields.
$header['id'] = $this->t('Private Message Ban ID');
$header['name'] = $this->t('Name');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
/** @var \Drupal\private_message\Entity\PrivateMessageBanInterface $entity */
$row['id'] = $entity->id();
$row['name'] = Link::createFromRoute(
$entity->label(),
'entity.private_message_ban.edit_form',
['private_message_ban' => $entity->id()]
);
return $row + parent::buildRow($entity);
}
}
<?php
namespace Drupal\private_message\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
/**
* Defines the Private Message Ban entity type.
*
* This is a lightweight entity type used to store banned users.
*
* @ingroup private_message
*
* @ContentEntityType(
* id = "private_message_ban",
* label = @Translation("Private Message Ban"),
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\private_message\Entity\Builder\PrivateMessageBanListBuilder",
* "views_data" = "Drupal\views\EntityViewsData",
* "form" = {
* "default" = "Drupal\private_message\Form\PrivateMessageBanForm",
* "add" = "Drupal\private_message\Form\PrivateMessageBanForm",
* "edit" = "Drupal\private_message\Form\PrivateMessageBanForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
* },
* "access" = "Drupal\private_message\Entity\Access\PrivateMessageBanAccessControlHandler",
* },
* base_table = "private_message_ban",
* admin_permission = "administer private message ban entities",
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "uid" = "owner",
* },
* links = {
* "add-form" = "/admin/structure/private_message_ban/add",
* "edit-form" = "/admin/structure/private_message_ban/{private_message_ban}/edit",
* "delete-form" = "/admin/structure/private_message_ban/{private_message_ban}/delete",
* "collection" = "/admin/structure/private_message_ban",
* },
* constraints = {
* "UniquePrivateMessageBan" = {}
* }
* )
*/
class PrivateMessageBan extends ContentEntityBase implements PrivateMessageBanInterface {
/**
* {@inheritdoc}
*/
public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
parent::preCreate($storage_controller, $values);
$values += [
'owner' => \Drupal::currentUser()->id(),
];
}
/**
* {@inheritdoc}
*/
public function getCreatedTime(): int {
return $this->get('created')->value;
}
/**
* {@inheritdoc}
*/
public function setCreatedTime($timestamp) {
$this->set('created', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getOwner() {
return $this->get('owner')->entity;
}
/**
* {@inheritdoc}
*/
public function getOwnerId() {
return $this->get('owner')->target_id;
}
/**
* {@inheritdoc}
*/
public function setOwnerId($uid) {
$this->set('owner', $uid);
return $this;
}
/**
* {@inheritdoc}
*/
public function setOwner(UserInterface $account) {
$this->set('owner', $account->id());
return $this;
}
/**
* {@inheritDoc}
*/
public function getTarget(): User {
return $this->get('target')->entity;
}
/**
* {@inheritDoc}
*/
public function getTargetId(): int {
return $this->get('target')->target_id;
}
/**
* {@inheritDoc}
*/
public function setTarget(AccountInterface $user): PrivateMessageBanInterface {
return $this->set('target', $user->id());
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['owner'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Owned by'))
->setDescription(t('The ID of user who performed the ban.'))
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
->setRequired(TRUE)
->setDisplayOptions('form', [
'type' => 'entity_reference_autocomplete',
'weight' => 5,
'settings' => [
'match_operator' => 'CONTAINS',
'size' => '60',
'autocomplete_type' => 'tags',
'placeholder' => '',
],
]);
$fields['target'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Banned user'))
->setDescription(t('The ID of user being banned'))
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
->setRequired(TRUE)
->setDisplayOptions('form', [
'type' => 'entity_reference_autocomplete',
'weight' => 5,
'settings' => [
'match_operator' => 'CONTAINS',
'size' => '60',
'autocomplete_type' => 'tags',
'placeholder' => '',
],
]);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Created'))
->setDescription(t('The time that the entity was created.'));
return $fields;
}
/**
* {@inheritdoc}
*/
public function label() {
return new TranslatableMarkup('Private Message Ban by @username', ['@username' => $this->getOwner()->getDisplayName()]);
}
}
<?php
namespace Drupal\private_message\Entity;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\Entity\User;
use Drupal\user\EntityOwnerInterface;
use Drupal\user\UserInterface;
/**
* The Private Message Ban entity interface.
*
* @ingroup private_message
*/
interface PrivateMessageBanInterface extends ContentEntityInterface, EntityOwnerInterface {
/**
* Gets the Private Message Ban creation timestamp.
*
* @return int
* Creation timestamp of the Private Message Ban.
*/
public function getCreatedTime(): int;
/**
* Sets the Private Message Ban creation timestamp.
*
* @param int $timestamp
* The Private Message Ban creation timestamp.
*
* @return \Drupal\private_message\Entity\PrivateMessageBanInterface
* The called Private Message Ban entity.
*/
public function setCreatedTime($timestamp);
/**
* Gets banned user.
*/
public function getTarget(): User;
/**
* Gets target id.
*/
public function getTargetId(): int;
/**
* Sets banned user.
*/
public function setTarget(AccountInterface $user): self;
}
......@@ -115,7 +115,23 @@ class PrivateMessageThread extends ContentEntityBase implements PrivateMessageTh
* {@inheritdoc}
*/
public function getMessages() {
return $this->get('private_messages')->referencedEntities();
/** @var \Drupal\private_message\Entity\PrivateMessageInterface[] $raw_messages */
$raw_messages = $this->get('private_messages')->referencedEntities();
$messages = [];
$bannedUsers = \Drupal::service('private_message.ban_manager')
->getBannedUsers(\Drupal::currentUser()->id());
foreach ($raw_messages as $index => $message) {
if ($index == 0) {
// The first message is always displayed, regardless of the ban status.
// This is to ensure that the thread display is not broken.
$messages[] = $message;
}
elseif (!in_array($message->getOwnerId(), $bannedUsers)) {
$messages[] = $message;
}
}
return $messages;
}
/**
......
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