Commit 2e0d6a21 authored by New Zeal's avatar New Zeal

fixes to permissions, navigation etc

parent fcdff29b
......@@ -6,4 +6,12 @@ Disable to field in message
Fix user page problem on install: https://www.drupal.org/node/2906376#comment-12245045
8.x-1.0-alpha4
Enable permissions for conversation tab outside of admin
\ No newline at end of file
Enable permissions for conversation tab outside of admin
8.x-1.0-alpha5
Fix access to conversation view
8.x-1.0-alpha6
Test and ensure permissions for users are correct
Add suitable breadcrumb for navigation
Modify contextual dropdowns for proper access
\ No newline at end of file
......@@ -2,7 +2,7 @@ langcode: en
status: true
dependencies: { }
template: conversation
label: Conversations
label: Messages
description: 'Message thread to accompany Private Messages'
message_template: private_message
view_display: null
......
......@@ -6,12 +6,12 @@ dependencies:
- field.storage.message_thread.field_thread_title
- message_thread.template.conversation
module:
- message_private
- message_thread
- user
_core:
default_config_hash: UqAY1HdOAH7JtNrXnK5OGCxXSIJ6uZhbgRMT488UMSw
id: conversations
label: Conversations
label: Messages
module: views
description: ''
tag: ''
......@@ -26,9 +26,8 @@ display:
position: 0
display_options:
access:
type: perm
options:
perm: 'create and receive conversation'
type: message_private
options: { }
cache:
type: tag
options: { }
......@@ -106,7 +105,7 @@ display:
relationship: none
group_type: group
admin_label: ''
label: ''
label: Conversation
exclude: false
alter:
alter_text: false
......@@ -236,7 +235,7 @@ display:
entity_field: template
plugin_id: bundle
sorts: { }
title: Conversations
title: Messages
header:
area:
id: area
......@@ -312,7 +311,7 @@ display:
path: user/%/conversations
menu:
type: 'default tab'
title: Conversations
title: Messages
description: ''
expanded: false
parent: user.page
......
This diff is collapsed.
.field--name-field-thread-participants .field__item {
display: inline-block
}
.message-thread-reply {
border-radius: 8px;
border: 1px solid #ccc;
padding: 4px;
margin: 6px 0;
display: inline-block;
}
\ No newline at end of file
name: 'Message Thread'
description: 'Enable messages to be grouped into threads or conversations.'
core: 8.x
# core: 8.x
package: Message
type: module
configure: message_thread.admin_settings
......
thread-styles:
version: 1.0
css:
component:
css/message-thread.css: { }
......@@ -8,6 +8,9 @@ entity.message_thread.canonical:
base_route: entity.message_thread.canonical
title: View
# Tabs for message management
## Provide dynamic local tasks.
message_thread.dynamic_tasks:
deriver: 'Drupal\message_thread\Plugin\Derivative\DynamicLocalTasks'
\ No newline at end of file
......@@ -16,6 +16,8 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\message\Entity\MessageTemplate;
use Drupal\user\Entity\User;
use Drupal\Core\Access\AccessResult;
use Drupal\message_thread\MessageThreadAccessControlHandler;
use Drupal\message_private\MessagePrivateAccessControlHandler;
/**
......@@ -38,6 +40,9 @@ function message_thread_help($route_name, RouteMatchInterface $arg) {
}
}
/*
* Implements hook_form_alter().
*/
function message_thread_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
// When a message is created we need to ensure that the participants are included
// This is best done in the validation hook to ensure participants are not missed from submit functions
......@@ -103,6 +108,41 @@ function message_thread_submit_message(array &$form, FormStateInterface $form_st
)->execute();
}
/**
* Implements hook_preprocess_links().
*
* @param $variables
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
function message_thread_preprocess_links(&$variables) {
if ($variables['theme_hook_original'] == 'links__dropbutton__operations') {
$account = \Drupal::currentUser();
foreach ($variables['links'] as $key => $link) {
$route_name = $link['link']['#url']->getRouteName();
$route_parameters = $link['link']['#url']->getRouteParameters();
$entity_type = key($route_parameters);
$entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($route_parameters[$entity_type]);
if (!$entity) {
continue;
}
$operation = strtolower($link['link']['#title']->__toString());
if (substr($route_name, 0, 15) == 'entity.message.') {
$message_access_manager = new MessagePrivateAccessControlHandler($entity->getEntityType());
$access = $message_access_manager->checkAccess($entity, $operation, $account);
// Disable link if user does not have access
if ($access instanceof Drupal\Core\Access\AccessResultForbidden) {
unset ($variables['links'][$key]);
}
}
}
}
}
/**
* Implements hook_entity_extra_field_info().
*/
......@@ -176,6 +216,7 @@ function message_thread_entity_view_alter(array &$build, EntityInterface $entity
* @return array|bool|null
*/
function message_thread_get_messages_display($settings, $entity) {
$view_name = $settings['view_id'];
$display_id = $settings['view_display_id'];
$view = Views::getView($view_name);
......@@ -189,6 +230,7 @@ function message_thread_get_messages_display($settings, $entity) {
$view->build($display_id);
$view->preExecute();
$view->execute($display_id);
$view->element['#attached']['library'][] = 'message_thread/thread-styles';
if (!empty($view->result) || !empty($view->empty)) {
return $view->buildRenderable($display_id);
......@@ -196,7 +238,19 @@ function message_thread_get_messages_display($settings, $entity) {
return FALSE;
}
/*
* Helper function to build the reply link
*/
function message_thread_reply_link($entity, $component) {
// Find out if any messages in this conversation
$query = \Drupal::database()->select('message_thread_index', 't');
$query->condition('t.thread_id', $entity->id());
$query->addExpression('COUNT(*)');
$count = $query->execute()->fetchField();
$label = t('Send a message');
if ($count > 0) {
$label = t('Reply');
}
// Todo template should reference actual template
$params = array(
'message_template' => 'private_message',
......@@ -211,7 +265,7 @@ function message_thread_reply_link($entity, $component) {
return array(
'#type' => 'link',
'#url' => $url,
'#title' => t('Reply'),
'#title' => $label,
'#weight' => $component['weight'],
'#attributes' => array(
'class' => array('message-thread-reply')
......@@ -268,28 +322,30 @@ function message_thread_entity_base_field_info_alter(&$fields, EntityTypeInterfa
}
/**
* Implements hook_message_thread_access_control().
*
* @param $params
*/
function message_thread_message_thread_access_control($params) {
$entity = $params[0];
$operation = $params[1];
$account = $params[2];
// Get the participants
$participants = $entity->get('field_thread_participants')->getValue();
foreach ($participants as $participant) {
if ($participant['target_id'] == $account->id()) {
return AccessResult::allowed();
}
}
}
/*
* Implements hook_local_tasks_alter().
*/
function message_thread_local_tasks_alter(&$local_tasks) {
//We no longer need the private message local task on the user page
unset($local_tasks['message_private.messages']);
}
\ No newline at end of file
}
/**
* Views Plugin access callback.
*
* @return \Drupal\Core\Access\AccessResult
*/
function message_thread_tab_access_check() {
$account = \Drupal::currentUser();
// Load the current node.
$uid = \Drupal::routeMatch()->getParameter('user');
// Check if the current user owns the inbox.
if (!empty($uid) && $account->id() == $uid) {
return AccessResult::allowed();
}
// Allow if the user has the bypass permission
return AccessResult::allowedIfHasPermission($account, 'bypass private message access control');
}
......@@ -54,8 +54,8 @@ message_thread.add_page:
_title: 'Add Message Thread'
_controller: '\Drupal\message_thread\Controller\MessageThreadController::addPage'
# appears_on: # Should we define a route for converting MENU_LOCAL_ACTION.
# requirements:
# _entity_create_access: 'message_thread'
requirements:
_entity_create_access: 'message_thread'
message_thread.add:
path: '/message-thread/add/{message_thread_template}'
......@@ -65,6 +65,10 @@ message_thread.add:
_title: 'Create Message Thread'
requirements:
_entity_create_access: 'message_thread:{message_thread_template}'
options:
parameters:
message_thread_template:
with_config_overrides: FALSE
entity.message_thread.edit_form:
path: '/message-thread/{message_thread}/edit'
......@@ -74,19 +78,15 @@ entity.message_thread.edit_form:
_title: 'Edit Message Thread'
requirements:
_entity_access: 'message_thread.edit'
options:
_admin_route: TRUE
entity.message_thread.delete_form:
path: '/message-thread/{message-thread}/delete'
path: '/message-thread/{message_thread}/delete'
defaults:
# Calls the form.delete controller, defined in the message entity alter hook.
_entity_form: message_thread.delete
_title: 'Delete Message Thread'
requirements:
_entity_access: 'message_thread.delete'
options:
_admin_route: TRUE
message_thread.threads:
path: '/user/{user}/threads'
......@@ -133,18 +133,17 @@ message_thread.reply:
requirements:
_entity_create_access: 'message:{message_template}'
options:
_admin_route: TRUE
parameters:
message_template:
with_config_overrides: FALSE
# The default View that comes with this module
message_thread.conversations:
path: '/user/{user}/conversations'
defaults:
_controller: '\Drupal\message_thread\Controller\MessageThreadController::inBox'
#message_thread.conversations:
# path: '/user/{user}/conversations'
# defaults:
# _controller: '\Drupal\message_thread\Controller\MessageThreadController::inBox'
# requirements:
# _permission: 'overview messages'
# _custom_access: '_message_thread_tab_access_check'
route_callbacks:
- 'Drupal\message_thread\Routing\MessageThreadRoutes::routes'
\ No newline at end of file
services:
# message_thread.route_subscriber:
# class: Drupal\message_thread\Routing\MessageThreadRouteSubscriber
# arguments: ['@entity_type.manager', '@entity_type.manager']
# tags:
# - { name: event_subscriber }
\ No newline at end of file
message_thread.breadcrumbs:
class: Drupal\message_thread\Plugin\Breadcrumbs\MessageThreadBreadcrumbs
tags:
- { name: breadcrumb_builder, priority: 1020 }
......@@ -49,7 +49,6 @@ class MessageForm extends MessageMessageForm {
/* @var $message Message */
$message = $this->entity;
$values = $form_state->getValues();
ksm($values);
return;
// Save the relationship between the thread and the message
db_insert('message_thread_index')
......
......@@ -6,6 +6,7 @@ use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\message_private\MessagePrivateAccessControlHandler;
/**
* Access controller for the comment entity.
......@@ -20,7 +21,7 @@ class MessageThreadAccessControlHandler extends EntityAccessControlHandler {
* Link the activities to the permissions. checkAccess is called with the
* $operation as defined in the routing.yml file.
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
// Return early if we have bypass or create any template permissions.
if ($account->hasPermission('bypass message thread access control') || $account->hasPermission($operation . ' any message thread template')) {
......@@ -28,7 +29,7 @@ class MessageThreadAccessControlHandler extends EntityAccessControlHandler {
}
$params = [$entity, $operation, $account];
ksm($params);
/** @var \Drupal\Core\Access\AccessResult[] $results */
$results = $this
->moduleHandler()
......@@ -42,7 +43,45 @@ class MessageThreadAccessControlHandler extends EntityAccessControlHandler {
return $result;
}
return AccessResult::allowedIfHasPermission($account, $operation . ' ' . $entity->bundle() . ' message thread')->cachePerPermissions();
$current_id = $account->id();
$allow = [];
// Allow all participants to view
if ($operation == 'view' && $entity->get('field_thread_participants')->getValue() != NULL) {
if (AccessResult::allowedIfHasPermission($account, 'view own messages')) {
$participants = $entity->get('field_thread_participants')->getValue();
foreach ($participants as $participant) {
$allow[] = $participant['target_id'];
}
}
}
// Allow author of thread to edit and delete
if ($entity->get('uid')->getValue() != NULL) {
switch ($operation) {
case 'view':
if (AccessResult::allowedIfHasPermission($account, 'view own messages')) {
$allow[] = $entity->get('uid')->getValue()[0]['target_id'];
}
break;
case 'edit':
if (AccessResult::allowedIfHasPermission($account, 'edit own messages')) {
$allow[] = $entity->get('uid')->getValue()[0]['target_id'];
}
break;
case 'delete':
if (AccessResult::allowedIfHasPermission($account, 'delete own messages')) {
$allow[] = $entity->get('uid')->getValue()[0]['target_id'];
}
break;
}
}
if (in_array($current_id, $allow)) {
return AccessResult::allowed();
}
else return AccessResult::forbidden();
}
/**
......@@ -58,7 +97,7 @@ class MessageThreadAccessControlHandler extends EntityAccessControlHandler {
}
/** @var \Drupal\Core\Access\AccessResult[] $results */
$results = $this->moduleHandler()->invokeAll('message_message thread_create_access_control', [$entity_bundle,
$results = $this->moduleHandler()->invokeAll('message_thread_create_access_control', [$entity_bundle,
$account]);
foreach ($results as $result) {
......@@ -66,19 +105,20 @@ class MessageThreadAccessControlHandler extends EntityAccessControlHandler {
continue;
}
// We only return this if a result is not neutral, meaning that this hook overrides the default
return $result;
}
// When we have a bundle, check access on that bundle.
if ($entity_bundle) {
return AccessResult::allowedIfHasPermission($account, 'create ' . $entity_bundle . ' message_thread')
return AccessResult::allowedIfHasPermission($account, 'create and receive ' . $entity_bundle . ' message threads')
->cachePerPermissions();
}
// With no bundle, e.g. on message thread/add, check access to any message thread bundle.
// @todo: perhaps change this method to a service as in NodeAddAccessCheck.
foreach (\Drupal::entityManager()->getStorage('message_thread_template')->loadMultiple() as $template) {
$access = AccessResult::allowedIfHasPermission($account, 'create ' . $template->id() . ' message_thread');
$access = AccessResult::allowedIfHasPermission($account, 'create and receive ' . $template->id() . ' message threads');
// If access is allowed to any of the existing bundles return allowed.
if ($access->isAllowed()) {
......
......@@ -41,7 +41,7 @@ class MessageThreadPermissions implements ContainerInjectionInterface {
}
/**
* Get permissions for Taxonomy Views Integrator.
* Get permissions for Message Thread .
*
* @return array
* Permissions array.
......@@ -51,8 +51,11 @@ class MessageThreadPermissions implements ContainerInjectionInterface {
foreach ($this->entityManager->getStorage('message_thread_template')->loadMultiple() as $template) {
$permissions += [
'create and receive ' . $template->id() => [
'create and receive ' . $template->id() . ' message threads' => [
'title' => $this->t('Able to participate in %thread threads', array('%thread' => $template->label())),
],
'view own ' . $template->id() . ' message thread tab' => [
'title' => $this->t('View own %thread tab', array('%thread' => $template->label()))
]
];
}
......
......@@ -38,8 +38,8 @@ class MessageThreadViewsData extends EntityViewsData implements EntityViewsDataI
'id' => 'standard',
'base' => 'message_thread_field_data',
'base field' => 'thread_id',
'title' => $this->t('Message Thread'),
'label' => $this->t('Link the message thread index to the thread.'),
'title' => $this->t('Message Thread to Thread'),
'description' => $this->t('Link the message thread index to the thread.'),
),
);
$data['message_thread_index']['mid'] = array(
......@@ -52,7 +52,7 @@ class MessageThreadViewsData extends EntityViewsData implements EntityViewsDataI
'base' => 'message_field_data',
'base field' => 'mid',
'title' => $this->t('Message'),
'label' => $this->t('Link the message thread index to the message.'),
'description' => $this->t('Link the message thread index to the message.'),
),
);
return $data;
......
<?php
namespace Drupal\message_thread\Plugin\Breadcrumbs;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Link;
use Drupal\message_thread\Entity\MessageThread;
class MessageThreadBreadcrumbs implements BreadcrumbBuilderInterface{
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $attributes) {
// ksm($attributes);
$parameters = $attributes->getParameters()->all();
if (!empty($parameters['message_thread'])) {
return TRUE;
}
if (!empty($parameters['message'])) {
return TRUE;
}
}
/**
* {@inheritdoc}
*/
public function build(RouteMatchInterface $route_match) {
// ksm($route_match);
$breadcrumb = new Breadcrumb();
$parameters = $route_match->getParameters()->all();
$user = \Drupal::currentUser();
if (!empty($parameters['message_thread'])) {
$message_thread = $parameters['message_thread'];
// Get the parent messages link
// We use the current user as a reference since user can only view own messages
$thread_template = $message_thread->getTemplate();
$breadcrumb->addLink(Link::createFromRoute(
'Messages',
'message_thread.' . $thread_template->id(),
['user' => $user->id()]
));
// $breadcrumb->addLink(Link::createFromRoute(
// $message_thread->get('field_thread_title')->getValue()[0]['value'],
// 'entity.message_thread.canonical', [
// 'message_thread' => $message_thread->id()
// ]));
}
if (!empty($parameters['message'])) {
$message = $parameters['message'];
if ($message->bundle()) {
$message_thread = $this->messageThreadRelationship($message->id());
$thread_template = $message_thread->getTemplate();
$breadcrumb->addLink(Link::createFromRoute(
$thread_template->label(),
'message_thread.' . $thread_template->id(),
['user' => $user->id()]
));
$breadcrumb->addLink(Link::createFromRoute(
$message_thread->get('field_thread_title')->getValue()[0]['value'],
'entity.message_thread.canonical', [
'message_thread' => $message_thread->id()
]
));
// Probably don't need the current message
// $label = isset( $message->get('field_message_private_subject')->getValue()[0]['value'])
// ? $message->get('field_message_private_subject')->getValue()[0]['value'] :
// 'Message';
//
// $breadcrumb->addLink(Link::createFromRoute(
// $label,
// 'entity.message.canonical', [
// 'message' => $message->id()
// ]));
}
}
return $breadcrumb;
}
/*
* Helper function to relate a message to its thread
*/
function messageThreadRelationship($mid) {
$thread_id = db_select('message_thread_index', 'mdi')
->condition('mdi.mid', $mid)
->fields('mdi', ['thread_id'])
->execute()
->fetchField();
return MessageThread::load($thread_id);
}
}
\ No newline at end of file
......@@ -71,7 +71,6 @@ class DynamicLocalTasks extends DeriverBase implements ContainerDeriverInterface
$settings = $template->getSettings();
// Thread page tabs
$view_route = 'view.' . $settings['thread_view_id'] . '.' . $settings['thread_view_display_id'];
$exists = count($this->routeProvider->getRoutesByNames([$view_route])) === 1;
if (!$exists) {
......
<?php
/**
* @file
* Contains \Drupal\message_private\Plugin\views\access\InboxPermission.
*/
namespace Drupal\message_thread\Plugin\views\access;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\views\access\AccessPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route;
/**
* Access plugin that provides user and permission-based access control.
*
* @ingroup views_access_plugins
*
* @ViewsAccess(
* id = "message_thread",
* title = @Translation("Message Thread Tab"),
* help = @Translation("Access will be granted for user's own tab / users with bypass permissions.")
* )
*/
class MessageThreadTabPermission extends AccessPluginBase implements CacheableDependencyInterface {
/**
* Constructs a Permission object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition
);
}
/**
* {@inheritdoc}
*/
public function access(AccountInterface $account) {
return _message_private_inbox_access();
}
/**
* {@inheritdoc}
*/
public function alterRouteDefinition(Route $route) {
$route->setRequirement('_custom_access', 'message_thread_tab_access_check');
}
public function summaryTitle() {
return $this->t('Tab Permission');
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return Cache::PERMANENT;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['user.permissions'];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return [];
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment