You are here

social_private_message.module in Open Social 8.9

The Social Privagte Message module.

File

modules/social_features/social_private_message/social_private_message.module
View source
<?php

/**
 * @file
 * The Social Privagte Message module.
 */
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\private_message\Entity\PrivateMessageThreadInterface;
use Drupal\social_private_message\DeletedUser;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ViewExecutable;

/**
 * Implements hook_element_info_alter().
 */
function social_private_message_element_info_alter(array &$info) {
  if (isset($info['text_format']['#process'])) {
    $info['text_format']['#process'][] = 'social_private_message_filter_process_format';
  }
}

/**
 * Remove ability of selecting format on private message (use plain_text only).
 */
function social_private_message_filter_process_format($element) {

  // Fields listed here will have plain_text format only.
  $plain_text_fields = [
    'edit-message-0',
  ];
  if ($element['#type'] == 'text_format' && in_array($element['#id'], $plain_text_fields)) {
    $element['#format'] = 'plain_text';
    $element['format']['format']['#access'] = FALSE;
    $element['format']['format']['#value'] = 'plain_text';
    $element['format']['help']['#access'] = FALSE;
    $element['format']['format']['#options'] = [
      'plain_text' => 'Plain Text',
    ];
  }
  return $element;
}

/**
 * Implements hook_form_FORMID_alter().
 */
function social_private_message_form_private_message_thread_delete_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  // Redirect cancel to our inbox.
  $form['actions']['cancel']['#url'] = Url::fromRoute('social_private_message.inbox');

  // Redirect delete to our inbox.
  $form['actions']['submit']['#submit'][] = 'social_private_message_thread_delete_redirect';
}

/**
 * Implements hook_form_FORMID_alter().
 */
function social_private_message_form_private_message_add_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $form['members']['widget']['target_id']['#description'] = t('The member(s) of the private message thread. Add multiple members by seperating them with a comma.');

  // Add CTRL/CMD + Enter is submit.
  $form['#attached']['library'][] = 'social_post/keycode-submit';
  $form['#attached']['library'][] = 'social_private_message/validator';
  $form['#attached']['drupalSettings']['social_private_message']['validator'] = t('Please select an item in the list.');

  // Determine if the form is an PMT edit form.
  $form_is_edit_form = $form_state
    ->has('thread_members');

  // There's an alternative submit. We need to change its value.
  foreach ($form['actions'] as $key => &$actions) {

    // Must be pretty sure it's a submit button.
    if (!(is_array($actions) && isset($actions['#submit']) && is_array($actions['#submit']))) {
      continue;
    }

    // Key must have submit in it.
    if (strpos($key, 'submit-') === FALSE) {
      continue;
    }
    $actions['#value'] = t('Send');
    $actions['#submit'][] = 'social_private_message_redirect';
    unset($actions['#ajax']);
    if (!($form_is_edit_form && \Drupal::routeMatch()
      ->getRouteName() == 'entity.private_message_thread.canonical' && \Drupal::routeMatch()
      ->getParameter('private_message_thread')
      ->get('members')
      ->count() != count($form_state
      ->get('thread_members')))) {
      continue;
    }
    $callbacks = $actions['#submit'];
    $actions['#submit'] = [];
    foreach ($callbacks as $callback) {
      if ($callback == '::save') {
        $actions['#submit'][] = '_social_private_message_members';
      }
      $actions['#submit'][] = $callback;
    }
  }

  // Alter the users widget.
  $form['members']['widget']['#required'] = TRUE;
  $form['members']['widget']['#description'] = '';

  // Add the required tag.
  $form['members']['widget']['#attributes']['required'] = 'required';

  // Unset the empty and anonymous option.
  unset($form['members']['widget']['#options']['0']);
  unset($form['members']['widget']['#options']['_none']);

  // Unset current user.
  unset($form['members']['widget']['#options'][Drupal::currentUser()
    ->id()]);

  // Alter the message widget.
  // Add the required tag.
  $form['message']['widget']['#attributes']['required'] = 'required';

  // Normal submit button shoud say 'Next'.
  $form['actions']['submit']['#value'] = t('Send');
  $form['actions']['submit']['#submit'][] = 'social_private_message_redirect';

  // Add form class based on form type (create/edit).
  if ($form_is_edit_form) {
    $form_class = 'message__thread_create';

    // In edit mode, remove the label from the message field.
    $form['message']['widget'][0]['#title'] = '';
  }
  else {
    $form_class = 'message__thread_edit';
  }
  $form['#attributes']['class'][] = $form_class;
}

/**
 * Redirects the form to the inbox.
 *
 * @param array $form
 *   The form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form_state.
 */
function social_private_message_thread_delete_redirect(array $form, FormStateInterface $form_state) {

  // Set a nice message.
  \Drupal::messenger()
    ->addStatus(t('Your message has been deleted.'));

  // Force redirect to the inbox.
  $url = Url::fromRoute('social_private_message.inbox');
  $form_state
    ->setRedirectUrl($url);

  // Unset cache tags for user profiles.
  $build_info = $form_state
    ->getBuildInfo();
  if (isset($build_info['callback_object'])) {

    /** @var \Drupal\private_message\Form\PrivateMessageThreadDeleteForm $pm_thread_form */
    $pm_thread_form = $build_info['callback_object'];
    $pm_thread = $pm_thread_form
      ->getEntity();

    /** @var \Drupal\private_message\Entity\PrivateMessageThread $pm_thread */
    if ($pm_thread) {
      $members = $pm_thread
        ->getMembers();
      $cache_tags = [];
      foreach ($members as $member) {

        /** @var \Drupal\user\Entity\User $member */
        if ($member) {
          foreach ($member
            ->getCacheTagsToInvalidate() as $cache_tag) {
            $cache_tags[] = $cache_tag;
          }
        }
      }
      \Drupal::service('cache_tags.invalidator')
        ->invalidateTags($cache_tags);
    }
  }
}

/**
 * Redirects the form to the inbox.
 *
 * @param array $form
 *   The form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form_state.
 */
function social_private_message_redirect(array $form, FormStateInterface $form_state) {

  // Set a nice message.
  \Drupal::messenger()
    ->addStatus(t('Your message has been created.'));

  // Force redirect to the inbox.
  $url = Url::fromRoute('social_private_message.inbox');
  $form_state
    ->setRedirectUrl($url);
}

/**
 * Add deleted users to list of thread members.
 *
 * @param array $form
 *   The form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form_state.
 */
function _social_private_message_members(array $form, FormStateInterface $form_state) {
  $only_exist_members = $form_state
    ->get('thread_members');
  $members_with_deleted = \Drupal::routeMatch()
    ->getParameter('private_message_thread')
    ->get('members')
    ->getValue();
  $thread_members = [];
  foreach ($members_with_deleted as $value) {
    $found = FALSE;

    /** @var \Drupal\user\UserInterface $member */
    foreach ($only_exist_members as $member) {
      if ($value['target_id'] == $member
        ->id()) {
        $found = TRUE;
        break;
      }
    }
    if ($found) {
      $thread_members[] = $member;
    }
    else {
      $thread_members[] = new DeletedUser($value['target_id']);
    }
  }
  $form_state
    ->set('thread_members', $thread_members);
}

/**
 * Implements hook_js_alter().
 */
function social_private_message_js_alter(&$javascript, AttachedAssetsInterface $assets) {

  // Remove Js coming from the private_message module.
  if (isset($javascript['modules/contrib/private_message/js/private_message_inbox_block.js'])) {
    unset($javascript['modules/contrib/private_message/js/private_message_inbox_block.js']);
  }
}

/**
 * Implements hook_entity_view_alter().
 */
function social_private_message_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {

  // Threads.
  if ($entity
    ->getEntityTypeId() === 'private_message_thread') {

    /* @var \Drupal\private_message\Entity\PrivateMessageThread $thread */
    $thread =& $entity;
    $only_exist_members = $thread
      ->getMembers();

    // View mode inbox.
    if ($build['#view_mode'] === 'inbox') {
      $members_with_deleted = $thread
        ->get('members')
        ->getValue();
      $members_string = [];
      $display_name = '';
      foreach (array_keys($members_with_deleted) as $key) {
        if (isset($only_exist_members[$key])) {

          /** @var \Drupal\user\UserInterface $member */
          $member = $only_exist_members[$key];
          if (\Drupal::currentUser()
            ->id() === $member
            ->id()) {
            unset($members_with_deleted[$key], $only_exist_members[$key]);
          }
          else {
            $user_profile = \Drupal::entityTypeManager()
              ->getStorage('profile')
              ->loadByUser($member, 'profile');
            $content = \Drupal::entityTypeManager()
              ->getViewBuilder('profile')
              ->view($user_profile, 'name_raw');
            $members_string[] = $content;
          }
        }
        else {
          $members_string[] = t('Deleted user');
        }
      }

      // Count the amount of members.
      $member_count = count($members_with_deleted);
      $profile_picture = [];
      if ($member_count === 1) {
        $recipient = end($only_exist_members);
        if ($recipient instanceof UserInterface) {
          $user_profile = \Drupal::entityTypeManager()
            ->getStorage('profile')
            ->loadByUser($recipient, 'profile');
          $display_name = \Drupal::entityTypeManager()
            ->getViewBuilder('profile')
            ->view($user_profile, 'name_raw');
        }
        else {

          // Make this an array, since we no longer render in here.
          $display_name = [
            '#markup' => t('Deleted user'),
          ];
          $recipient = User::load(1);
        }

        // Load compact notification view mode of the attached profile.
        if ($recipient instanceof User) {
          $storage = \Drupal::entityTypeManager()
            ->getStorage('profile');
          if (!empty($storage)) {
            $user_profile = $storage
              ->loadByUser($recipient, 'profile');
            if ($user_profile) {
              $content = \Drupal::entityTypeManager()
                ->getViewBuilder('profile')
                ->view($user_profile, 'compact_notification');

              // Add to a new field, so twig can render it.
              $profile_picture = $content;
            }
          }
        }
      }

      // Add either the profile picture or the group picture.
      if ($member_count > 1) {
        $build['members']['#markup'] = '<div class="avatar-icon avatar-group-icon avatar-group-icon--medium"></div>';

        // Add members names.
        $build['membernames'] = $members_string;
        $build['membernames']['#prefix'] = '<strong>';
        $build['membernames']['#suffix'] = '</strong>';
      }
      elseif (!empty($profile_picture && $display_name)) {
        $build['members'] = $profile_picture;

        // Add members name.
        $build['membernames'] = $display_name;
        $build['membernames']['#prefix'] = '<strong>';
        $build['membernames']['#suffix'] = '</strong>';
      }
    }
    elseif ($build['#view_mode'] === 'full') {
      $socialPrivateMessageService = \Drupal::service('social_private_message.service');
      $socialPrivateMessageService
        ->updateLastThreadCheckTime($entity);
      $build['#prefix'] = '';
      $build['#suffix'] = '';
      if ($display
        ->getComponent('private_message_form') && count($only_exist_members) == 1) {
        $build['private_message_form'] = NULL;
      }
    }
  }
  elseif ($entity
    ->getEntityTypeId() === 'private_message') {

    // View mode inbox.
    if ($build['#view_mode'] === 'inbox') {

      // Remove prefix and suffix.
      $build['#prefix'] = '';
      $build['#suffix'] = '';
    }
    elseif ($build['#view_mode'] === 'full') {
      $wrapper_class = NULL;

      /* @var \Drupal\private_message\Entity\PrivateMessage $entity */
      if (\Drupal::currentUser()
        ->id() === $entity
        ->getOwnerId()) {
        $wrapper_class = 'message__by-me';

        // Current user is 'You'.
        $build['owner'][0]['#plain_text'] = t('You');
      }
      elseif (empty($entity
        ->getOwner())) {
        $wrapper_class = 'message__deleted';
      }
      if (!empty($wrapper_class)) {
        $build['#prefix'] = '<div class="' . $wrapper_class . '">' . $build['#prefix'];
        $build['#suffix'] = '</div>' . $build['#suffix'];
      }
    }
  }
}

/**
 * Implements hook_thread_view_alter().
 */
function social_private_message_thread_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
  $current_user = Drupal::currentUser();
  if ($display
    ->getComponent('delete_link') && $current_user
    ->hasPermission('use private messaging system') && $current_user
    ->hasPermission('delete private messages thread')) {
    $url = Url::fromRoute('entity.private_message_thread.delete_form', [
      'private_message_thread' => $entity
        ->id(),
    ]);
    $build['delete_link'] = [
      '#prefix' => '',
      '#suffix' => '',
      '#type' => 'link',
      '#url' => $url,
      '#title' => t('Delete thread'),
    ];
  }
  else {
    unset($build['delete_link']);
  }
  if (!$current_user
    ->hasPermission('use private messaging system') || !$current_user
    ->hasPermission('reply to private messages thread')) {
    unset($build['private_message_form']);
  }

  // Also add the back to inbox link
  // but just the link since it's a drop down with icon.
  $build['back_to_inbox']['#markup'] = Url::fromRoute('social_private_message.inbox')
    ->toString();
}

/**
 * Implements hook_preprocess_field().
 */
function social_private_message_preprocess_field(&$variables) {
  if (strpos($variables['entity_type'], 'private_message') === FALSE) {
    return;
  }
  $element =& $variables['element'];
  if (isset($element['#view_mode']) && $element['#view_mode'] == 'full' && $variables['field_name'] == 'message') {
    $variables['view_mode'] = 'full';
    if (empty($element['#object']
      ->getOwner())) {
      foreach ($variables['items'] as &$item) {
        $item['content'] = t("This message was deleted together with the user’s account.");
      }
    }
  }
}

/**
 * Implements hook_user_cancel().
 */
function social_private_message_user_cancel($edit, $account, $method) {
  if ($method == 'user_cancel_reassign') {
    \Drupal::database()
      ->update('private_messages')
      ->fields([
      'message__value' => ' ',
    ])
      ->condition('owner', $account
      ->id())
      ->execute();
  }
}

/**
 * Implements hook_menu_local_actions_alter().
 */
function social_private_message_menu_local_actions_alter(&$local_actions) {
  unset($local_actions['private_message.private_message_add']);
}

/**
 * When the user creates a message, mark this thread as read for the author.
 *
 * Implements hook_ENTITY_TYPE_insert().
 */
function social_private_message_private_message_thread_insert(EntityInterface $entity) {
  $socialPrivateMessageService = Drupal::service('social_private_message.service');
  $socialPrivateMessageService
    ->updateLastThreadCheckTime($entity);
}

/**
 * Implements hook_ENTITY_TYPE_update().
 */
function social_private_message_pm_thread_delete_time_update(EntityInterface $entity) {
  $socialPrivateMessageService = Drupal::service('social_private_message.service');
  $socialPrivateMessageService
    ->deleteUserDataThreadInfo($entity);
}

/**
 * Implements hook_user_cancel_methods_alter().
 */
function social_private_message_user_cancel_methods_alter(&$methods) {
  $methods['user_cancel_reassign']['title'] = t('Delete your account, delete private messages, and anonymize all other content.');
}

/**
 * Implements hook_activity_send_email_notifications_alter().
 */
function social_private_message_activity_send_email_notifications_alter(array &$items, array $email_message_templates) {

  // If our create_private_message template is enabled for email then we add it
  // to the "Message to Me" section.
  if (isset($email_message_templates['create_private_message'])) {
    $items['message_to_me']['templates'][] = 'create_private_message';
  }
}

/**
 * Implements hook_social_user_account_header_items().
 *
 * Adds the Private Message button and indicator to the account header block if
 * it's enabled by the site manager.
 */
function social_private_message_social_user_account_header_items(array $context) {
  if (\Drupal::config('social_user.navigation.settings')
    ->get('display_social_private_message_icon') !== 1) {
    return [];
  }

  // We require a logged in user for this button.
  if (empty($context['user']) || !$context['user']
    ->isAuthenticated() || !$context['user']
    ->hasPermission('use private messaging system')) {
    return [];
  }

  // Fetch the amount of unread items.
  $num_account_messages = \Drupal::service('social_private_message.service')
    ->updateUnreadCount();
  return [
    'messages' => [
      '#type' => 'account_header_element',
      '#wrapper_attributes' => [
        'class' => [
          'desktop',
        ],
      ],
      '#title' => new TranslatableMarkup('Inbox'),
      '#url' => Url::fromRoute('social_private_message.inbox'),
      '#icon' => $num_account_messages > 0 ? 'mail' : 'mail_outline',
      '#label' => new TranslatableMarkup('Inbox'),
      '#notification_count' => $num_account_messages,
    ],
  ];
}

/**
 * Implements hook_social_user_account_header_account_links().
 *
 * Adds the mobile indicator for private messages under the profile icon menu.
 */
function social_private_message_social_user_account_header_account_links(array $context) {
  if (\Drupal::config('social_user.navigation.settings')
    ->get('display_social_private_message_icon') !== 1) {
    return [];
  }

  // We require a logged in user for this indicator.
  if (empty($context['user']) || !$context['user']
    ->isAuthenticated()) {
    return [];
  }

  // Fetch the amount of unread items.
  $num_account_messages = \Drupal::service('social_private_message.service')
    ->updateUnreadCount();

  // Default icon values.
  $label_classes = 'hidden';

  // Override icons when there are unread items.
  if ($num_account_messages > 0) {
    $label_classes = 'badge badge-accent badge--pill';
  }
  return [
    'messages_mobile' => [
      '#type' => 'link',
      '#wrapper_attributes' => [
        'class' => [
          'mobile',
        ],
      ],
      '#weight' => 200,
      '#attributes' => [
        'title' => new TranslatableMarkup('Inbox'),
      ],
      '#title' => [
        '#type' => 'inline_template',
        '#template' => '<span>{% trans %}Inbox{% endtrans %}</span><span{{ attributes }}>{{ icon }}</span>',
        '#context' => [
          'attributes' => new Attribute([
            'class' => $label_classes,
          ]),
          'icon' => (string) $num_account_messages,
        ],
      ],
    ] + Url::fromRoute('social_private_message.inbox')
      ->toRenderArray(),
  ];
}

/**
 * Implements hook_social_user_account_header_items().
 *
 * Adds an indicator to the user account menu on mobile.
 */
function social_private_message_social_user_account_header_items_alter(array &$menu_links, array $context) {
  if (\Drupal::config('social_user.navigation.settings')
    ->get('display_social_private_message_icon') !== 1) {
    return;
  }

  // We require a logged in user for this indicator.
  if (empty($context['user']) || !$context['user']
    ->isAuthenticated()) {
    return;
  }

  // If the account_box link was removed we have nothing to do.
  if (!isset($menu_links['account_box'])) {
    return;
  }

  // Fetch the amount of unread items.
  $num_account_messages = \Drupal::service('social_private_message.service')
    ->updateUnreadCount();
  if ($num_account_messages > 0) {
    $menu_links['account_box']['#wrapper_attributes']['class'][] = 'has-alert';
  }
}

/**
 * Implements hook_views_data_alter().
 */
function social_private_message_views_data_alter(array &$data) {
  $data['private_message_threads']['social_private_message_deleted_threads_filter'] = [
    'title' => t('Filter deleted threads'),
    'filter' => [
      'title' => t('Filter deleted threads'),
      'help' => t('Do not show threads a user deleted.'),
      'field' => 'id',
      'id' => 'social_private_message_deleted_threads',
    ],
  ];
}

/**
 * Implements hook_views_query_alter().
 */
function social_private_message_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
  if ($view
    ->id() == 'inbox') {

    // Current user.
    $current_user_id = \Drupal::currentUser()
      ->id();

    // Add join definition.
    $definition = [
      'table' => 'pm_thread_delete_time',
      'field' => 'id',
      'left_table' => 'private_message_thread__last_delete_time',
      'left_field' => 'last_delete_time_target_id',
      'operator' => '=',
      'extra' => 'pm_thread_delete_time.delete_time <= private_messages_private_message_thread__private_messages.created',
    ];

    // Create a join stement from plugin.
    $join = Drupal::service('plugin.manager.views.join')
      ->createInstance('standard', $definition);

    // Add join to the query.
    $query
      ->addRelationship('pm_thread_delete_time', $join, 'delete_time');

    // Add some extra where statements.
    $query
      ->addWhere(NULL, 'private_message_thread__members.members_target_id', $current_user_id, '=');
    $query
      ->addWhere(NULL, 'pm_thread_delete_time.owner', $current_user_id, '=');
  }
}

/**
 * Implements hook_private_message_thread_access().
 */
function social_private_message_private_message_thread_access(PrivateMessageThreadInterface $entity, $operation, AccountInterface $account) {
  if ($operation === 'delete') {
    if ($account
      ->hasPermission('use private messaging system') && $account
      ->hasPermission('delete private messages thread')) {
      return AccessResult::allowed();
    }
    return AccessResult::forbidden();
  }
}

/**
 * Implements hook_private_message_create_access().
 */
function social_private_message_private_message_create_access(AccountInterface $account, array $context, $entity_bundle) {
  return AccessResult::forbiddenIf(!($account
    ->hasPermission('use private messaging system') && $account
    ->hasPermission('create private messages thread')));
}

Functions

Namesort descending Description
social_private_message_activity_send_email_notifications_alter Implements hook_activity_send_email_notifications_alter().
social_private_message_element_info_alter Implements hook_element_info_alter().
social_private_message_entity_view_alter Implements hook_entity_view_alter().
social_private_message_filter_process_format Remove ability of selecting format on private message (use plain_text only).
social_private_message_form_private_message_add_form_alter Implements hook_form_FORMID_alter().
social_private_message_form_private_message_thread_delete_form_alter Implements hook_form_FORMID_alter().
social_private_message_js_alter Implements hook_js_alter().
social_private_message_menu_local_actions_alter Implements hook_menu_local_actions_alter().
social_private_message_pm_thread_delete_time_update Implements hook_ENTITY_TYPE_update().
social_private_message_preprocess_field Implements hook_preprocess_field().
social_private_message_private_message_create_access Implements hook_private_message_create_access().
social_private_message_private_message_thread_access Implements hook_private_message_thread_access().
social_private_message_private_message_thread_insert When the user creates a message, mark this thread as read for the author.
social_private_message_redirect Redirects the form to the inbox.
social_private_message_social_user_account_header_account_links Implements hook_social_user_account_header_account_links().
social_private_message_social_user_account_header_items Implements hook_social_user_account_header_items().
social_private_message_social_user_account_header_items_alter Implements hook_social_user_account_header_items().
social_private_message_thread_delete_redirect Redirects the form to the inbox.
social_private_message_thread_view_alter Implements hook_thread_view_alter().
social_private_message_user_cancel Implements hook_user_cancel().
social_private_message_user_cancel_methods_alter Implements hook_user_cancel_methods_alter().
social_private_message_views_data_alter Implements hook_views_data_alter().
social_private_message_views_query_alter Implements hook_views_query_alter().
_social_private_message_members Add deleted users to list of thread members.