message_thread-8.x-1.x-dev/message_thread.module
message_thread.module
<?php
/**
* @file
* Module functions and hooks for message_thread.
*/
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\message_thread\Entity\MessageThreadTemplate;
use Drupal\message_thread\Entity\MessageThread;
use Drupal\views\Views;
use Drupal\Core\Form\FormStateInterface;
use Drupal\message\Entity\MessageTemplate;
use Drupal\user\Entity\User;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Render\Element;
use Drupal\Component\Utility\Xss;
use Drupal\Component\Utility\Html;
use Drupal\Core\Access\AccessResultForbidden;
use Drupal\Core\Entity\​EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\message\Entity\Message;
/**
* Implements hook_installed().
*
* Flush cache after install to ensure that all routes are read.
*/
function message_thread_modules_installed() {
drupal_flush_all_caches();
}
/**
* Implements hook_help().
*/
function message_thread_help($route_name, RouteMatchInterface $arg) {
switch ($route_name) {
case 'help.page.message_thread':
$output = file_get_contents(drupal_get_path('module', 'message_thread') . '/README.md');
return \Drupal::moduleHandler()->moduleExists('markdown') ? Xss::filterAdmin(\Drupal::moduleHandler()->invoke('markdown', 'filter', [
'process', 0, -1, $output,
])) : '<h3>Message Private README</h3><pre>' . Html::escape($output) . '</pre>';
}
}
/**
* Implements hook_form_alter().
*/
function message_thread_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
if (substr($form_id, 0, 8) != 'message_') {
return;
}
// 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.
$thread_templates = \Drupal::entityTypeManager()->getListBuilder('message_thread_template')->load();
foreach ($thread_templates as $name => $template) {
$settings = $template->getSettings();
$message_template = MessageTemplate::load($settings['message_template']);
$message_form_id = 'message_' . $message_template->getTemplate() . '_form';
if ($form_id == $message_form_id) {
$thread_id = NULL;
$parameters = \Drupal::routeMatch()->getParameters();
if ($parameters->has('message_thread')) {
if (is_object($parameters->get('message_thread'))) {
$thread_id = $parameters->get('message_thread')->id();
}
else {
$thread_id = $parameters->get('message_thread');
}
}
if ($form_state->get('thread_id') != NULL) {
$thread_id = $form_state->get('thread_id');
}
$owner = $form['uid']['widget'][0]['target_id']['#default_value'];
if ($thread_id) {
$form['message_thread'] = [
'#type' => 'hidden',
'#value' => $thread_id,
];
$message_thread = MessageThread::load($thread_id);
$participants = $message_thread->get('field_thread_participants')->getValue();
$widget_base = $form['field_message_private_to_user']['widget'][0];
foreach ($participants as $key => $participant) {
// Don't include the sender.
if ($participant['target_id'] == $owner->id()) {
unset($form['field_message_private_to_user']['widget'][$key]);
continue;
}
$form['field_message_private_to_user']['widget'][$key] = $widget_base;
$form['field_message_private_to_user']['widget'][$key]['target_id']['#default_value'] = User::load($participant['target_id']);
}
// We also should disable the to field
// because this is controlled from the thread.
$form['field_message_private_to_user']['#disabled'] = TRUE;
foreach (array_keys($form['actions']) as $action) {
if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
$form['actions'][$action]['#submit'][] = 'message_thread_submit_message';
}
}
break;
// Todo If there is no message thread parameter then we abort.
}
}
}
}
/**
* Submit message callback.
*/
function message_thread_submit_message(array &$form, FormStateInterface $form_state) {
$thread_id = $form_state->getValue('message_thread');
// Save the relationship between the thread and the message.
\Drupal::database()->insert('message_thread_index')
->fields(
[
'mid' => $form_state->getValue('mid'),
'thread_id' => $thread_id,
'created' => \Drupal::time()->getRequestTime(),
]
)->execute();
// Update statistics.
$entity = $form_state->getFormObject()->getEntity();
\Drupal::service('message.statistics')->update($entity);
// Redirect to the thread not the message.
$params = [
'message_thread' => $thread_id,
];
$url = Url::fromRoute('entity.message_thread.canonical', $params);
$form_state->setRedirectUrl($url);
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function message_thread_form_message_private_message_edit_form_alter(array &$form, FormStateInterface $form_state) {
// Redirect to the thread not the message.
$mid = $form_state->getFormObject()->getEntity()->id();
if (message_thread_relationship($mid)) {
$params = [
'message_thread' => message_thread_relationship($mid),
];
$url = Url::fromRoute('entity.message_thread.canonical', $params);
$form_state->setRedirectUrl($url);
$link = [
'#type' => 'link',
'#url' => $url,
'#title' => t('Cancel'),
];
$form['actions']['cancel'] = $link;
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function message_thread_form_message_private_message_form_alter(array &$form, FormStateInterface $form_state) {
// Get the message thread id from the url.
if (isset($_GET['destination'])) {
$parts = explode('/', $_GET['destination']);
$thread_id = array_pop($parts);
if (is_numeric($thread_id)) {
$params = [
'message_thread' => $thread_id,
];
$url = Url::fromRoute('entity.message_thread.canonical', $params);
$form_state->setRedirectUrl($url);
$link = [
'#type' => 'link',
'#url' => $url,
'#title' => t('Cancel'),
];
$form['actions']['cancel'] = $link;
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function message_thread_form_message_thread_conversation_edit_form_alter(array &$form, FormStateInterface $form_state) {
$form['field_thread_participants']['widget']['add_more']['#value'] = new TranslatableMarkup('Add another user');
$form['owner']['#access'] = \Drupal::currentUser()->hasPermission('bypass private message access control');
$form['advanced']['#access'] = \Drupal::currentUser()->hasPermission('bypass private message access control');
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function message_thread_form_message_thread_conversation_form_alter(array &$form, FormStateInterface $form_state) {
$form['field_thread_participants']['widget']['add_more']['#value'] = new TranslatableMarkup('Add another user');
$form['owner']['#access'] = \Drupal::currentUser()->hasPermission('bypass private message access control');
$form['advanced']['#access'] = \Drupal::currentUser()->hasPermission('bypass private message access control');
}
/**
* Implements hook_preprocess_links().
*
* Todo should use this hook: message_ui_message_ui_views_contextual_links_info.
*/
function message_thread_preprocess_links(&$variables) {
if (!message_thread_is_message_route()) {
return;
}
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 = ​EntityTypeManagerInterface::getStorage($entity_type)->load($route_parameters[$entity_type]);
if (!$entity) {
continue;
}
$operation = mb_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);.
$access = message_private_message_access($entity, $operation, $account);
// Disable link if user does not have access.
if ($access instanceof AccessResultForbidden) {
unset($variables['links'][$key]);
}
}
}
}
}
/**
* Test if the current route is a message thread or message route.
*/
function message_thread_is_message_route() {
$message = \Drupal::routeMatch()->getParameter('message');
if ($message) {
return TRUE;
}
$message_thread = \Drupal::routeMatch()->getParameter('message>_thread');
if ($message_thread) {
return TRUE;
}
return FALSE;
}
/**
* Implements hook_theme().
*/
function message_thread_theme() {
return [
'message_thread' => [
'render element' => 'elements',
],
];
}
/**
* Prepares variables for message_thread templates.
*
* Default template: message_thread.html.twig.
*
* Most themes use their own copy of message_thread.html.twig.
* The default is located inside
* "/core/modules/message_thread/templates/message_thread.html.twig".
* Look in there for the full list of variables.
*
* @param array $variables
* An associative array containing:
* - elements: An array of elements to display in view mode.
* - message_thread: The message_thread object.
* - view_mode: View mode; e.g., 'full', 'teaser', etc.
*/
function template_preprocess_message_thread(array &$variables) {
$variables['view_mode'] = $variables['elements']['#view_mode'];
// Provide a distinct $teaser boolean.
$variables['teaser'] = $variables['view_mode'] == 'teaser';
$variables['message_thread'] = $variables['elements']['#message_thread'];
/** @var \Drupal\message_thread\messageThreadInterface $message_thread */
$message_thread = $variables['message_thread'];
$variables['date'] = \Drupal::service('renderer')->render($variables['elements']['created']);
$variables['author_name'] = \Drupal::service('renderer')->render($variables['elements']['uid']);
$variables['url'] = $message_thread->EntityBase::toUrl('canonical', [
'language' => $message_thread->language(),
]);
// Helpful $content variable for templates.
$variables += ['content' => []];
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
// Add article ARIA role.
$variables['attributes']['role'] = 'article';
}
/**
* Implements hook_entity_extra_field_info().
*/
function message_thread_entity_extra_field_info() {
$bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('message_thread');
foreach ($bundles as $machine_name => $bundle) {
$extra_fields['message_thread'][$machine_name]['display']['message_thread_messages'] = [
'label' => t('Messages'),
'description' => t('Show all messages inside a thread'),
'weight' => 0,
'visible' => FALSE,
];
$extra_fields['message_thread'][$machine_name]['display']['message_thread_reply'] = [
'label' => t('Reply'),
'description' => t('Reply to this thread'),
'weight' => 0,
'visible' => FALSE,
];
$extra_fields['message_thread'][$machine_name]['display']['message_thread_reply_form'] = [
'label' => t('Reply Form'),
'description' => t('Reply to this thread using embedded form'),
'weight' => 0,
'visible' => FALSE,
];
}
return $extra_fields;
}
/**
* Implements hook_entity_view_alter().
*/
function message_thread_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
// Add the link to all message thread types.
// Maybe this should be controlled through settings
// but let's see if there are other uses for this module first.
// Other than as conversation threads.
if ($entity->getEntityTypeId() === 'message_thread') {
if ($component = $display->getComponent('message_thread_messages')) {
$template = $entity->bundle();
$config_entity = MessageThreadTemplate::load($template);
$settings = $config_entity->getSettings();
$build['message_thread_messages'] = [
0 => message_thread_get_messages_display($settings, $entity),
'#weight' => $component['weight'],
];
}
if ($component = $display->getComponent('message_thread_reply')) {
$build['reply'] = message_thread_reply_link($entity, $component);
}
if ($component = $display->getComponent('message_thread_reply_form')) {
$build['message_thread_reply_form'] = message_thread_reply_form($entity, $component);
}
}
}
/**
* Helper function to display the messages as a View.
*
* @param array $settings
* Message thread settings.
*
* @return array|bool|null
* The view or False.
*/
function message_thread_get_messages_display(array $settings, $entity) {
$view_name = $settings['view_id'];
$display_id = $settings['view_display_id'];
$view = Views::getView($view_name);
// Someone may have deleted the View.
if (!is_object($view)) {
return FALSE;
}
$view->setDisplay($display_id);
$view->setArguments([$entity->id()]);
$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);
}
return FALSE;
}
/**
* Helper function to build the reply link.
*/
function message_thread_reply_link($entity, $component) {
// Find out if any messages in this conversation.
$label = t('Send a message');
$count = message_thread_message_count($entity);
if ($count > 0) {
$label = t('Reply');
}
// Todo template should reference actual template.
$params = [
'message_template' => 'private_message',
'message_thread' => $entity->id(),
];
$options = [
'query' => [
'destination' => '/message/thread/' . $entity->id(),
],
];
$url = Url::fromRoute('message_thread.reply', $params, $options);
return [
'#type' => 'link',
'#url' => $url,
'#title' => $label,
'#weight' => $component['weight'],
'#attributes' => [
'class' => ['message-thread-reply'],
],
];
}
/**
* Helper function to build the reply form.
*/
function message_thread_reply_form($entity, $component) {
$user = \Drupal::currentUser();
$account = User::load($user->id());
$message = \Drupal::entityTypeManager()->getStorage('message')->create([
'template' => 'private_message',
'uid' => $account->id(),
]);
$args = [
'thread_id' => $entity->id(),
];
$form = \Drupal::service('entity.form_builder')->getForm($message, 'default', $args);
return $form;
}
/**
* Implements hook_ENTITY_TYPE_delete().
*/
function message_thread_message_delete(EntityInterface $entity) {
// Delete from index when deleting a private message.
switch ($entity->getTemplate()->id()) {
case 'private_message':
\Drupal::messenger()->addStatus(t('Deleting index'));
\Drupal::database()->delete('message_thread_index', ['mid' => $entity->id()]);
break;
}
}
/**
* Implements hook_ENTITY_TYPE_delete().
*/
function message_thread_message_thread_delete(EntityInterface $entity) {
// Delete all associated messages.
$mids = message_thread_get_messages($entity->id());
foreach ($mids as $mid) {
db_delete('message_thread_index', ['mid' => $mid]);
$message = Message::load($mid);
$message->delete();
}
}
/**
* Helper function to return number of messages attached to a thread.
*/
function message_thread_message_count($entity) {
$query = \Drupal::database()->select('message_thread_index', 't');
$query->condition('t.thread_id', $entity->id());
$query->addExpression('COUNT(*)');
return $query->execute()->fetchField();
}
/**
* Message submit callback.
*
* See Drupal\message_thread\Controller\MessageThreadController.php.
*/
function message_thread_add_message_form_submit(&$form, FormStateInterface &$form_state) {
$values = $form_state->getValues();
}
/**
* Implements hook_entity_base_field_info_alter().
*
* Extend the message entity type's field by providing display handlers.
*/
function message_thread_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
// Alter the uid and created field to include display settings.
if ($entity_type->id() != 'message_thread') {
return;
}
if (!empty($fields['uid'])) {
/* @var Drupal\Core\Field\BaseFieldDefinition $fields['uid'] */
$fields['uid']
->setDisplayOptions('form', [
'type' => 'entity_reference_autocomplete',
'weight' => 5,
'#group' => 'advanced',
'settings' => [
'match_operator' => 'CONTAINS',
'size' => '60',
'placeholder' => '',
],
])
->setDisplayConfigurable('form', TRUE)
->setRequired(TRUE);
}
if (!empty($fields['created'])) {
$fields['created']
->setDisplayOptions('form', [
'type' => 'datetime_timestamp',
'weight' => 10,
])
->setDisplayConfigurable('form', TRUE);
}
}
/**
* 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']);
}
/**
* Views Plugin access callback.
*
* @return \Drupal\Core\Access\AccessResult
* Whether or not to allow access.
*/
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');
}
/**
* Helper function to relate a message to its thread.
*/
function message_thread_relationship($mid) {
$thread_id = \Drupal::database()->select('message_thread_index', 'mdi')
->condition('mdi.mid', $mid)
->fields('mdi', ['thread_id'])
->execute()
->fetchField();
return $thread_id;
}
/**
* Helper function to get all message ids in a thread.
*/
function message_thread_get_messages($thread_id) {
$result = \Drupal::database()->select('message_thread_index', 'mdi')
->condition('mdi.thread_id', $thread_id)
->fields('mdi', ['mid'])
->execute()
->fetchAll();
$messages = [];
foreach ($result as $record) {
$messages[] = $record->mid;
}
return $messages;
}
/**
* Implements hook_ENTITY_TYPE_view_alter() for message thread entities.
*/
function message_history_message_thread_view_alter(array &$build,
EntityInterface $message_thread,
EntityViewDisplayInterface $display) {
// Update the message_history table,
// stating that this user viewed all messages in this thread.
if (!\Drupal::service('module_handler')->moduleExists('message_history')) {
return;
}
if (!in_array($display->getOriginalMode(), ['default', 'full'])) {
return;
}
$build['#cache']['contexts'][] = 'user.roles:authenticated';
if (!\Drupal::currentUser()->isAuthenticated()) {
return;
}
// Find all messages in this thread and mark them read.
$messages = message_thread_get_messages($message_thread->id());
foreach ($messages as $mid) {
$build['#attached']['drupalSettings']['message_history']['itemsToMarkAsRead'][$mid] = TRUE;
}
$build['#attached']['library'][] = 'message_history/mark-as-read';
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function message_thread_theme_suggestions_message_thread(array $variables) {
$suggestions = [];
/** @var \Drupal\message\MessageInterface $message */
$message_thread = $variables['elements']['#message_thread'];
$sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
$suggestions[] = 'message_thread__' . $sanitized_view_mode;
$suggestions[] = 'message_thread__' . $message_thread->bundle();
$suggestions[] = 'message_thread__' . $message_thread->bundle() . '__' . $sanitized_view_mode;
$suggestions[] = 'message_thread__' . $message_thread->id();
$suggestions[] = 'message_thread__' . $message_thread->id() . '__' . $sanitized_view_mode;
return $suggestions;
}
/**
* Implements hook_entity_insert().
*/
function message_thread_entity_insert(EntityInterface $entity) {
// On insert of a message thread create the statistics entry.
switch ($entity->getEntityType()->id()) {
case 'message_thread':
if (!\Drupal::state()->get('message.maintain_entity_statistics')) {
return;
}
\Drupal::service('message.statistics')->create($entity);
break;
}
}
/**
* Implements hook_entity_predelete().
*/
function message_thread_entity_predelete(EntityInterface $entity) {
switch ($entity->getEntityTypeId()) {
case 'message_thread':
\Drupal::service('message.statistics')->delete($entity);
break;
case 'message':
\Drupal::service('message.statistics')->update($entity);
break;
}
}
