ptalk-8.x-0.x-dev/ptalk.module
ptalk.module
<?php
/**
* @file
* Allows start private conversation between multiple users.
*/
use Drupal\ptalk\MessageInterface;
use Drupal\ptalk\ThreadInterface;
use Drupal\node\NodeInterface;
use Drupal\user\Entity\User;
use Drupal\comment\CommentInterface;
use Drupal\ptalk\Entity\Message;
use Drupal\ptalk\Entity\Thread;
use Drupal\Core\Url;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\PluginBase;
use Drupal\Core\Entity\EntityInterface;
/**
* Status constant for read messages.
*/
define('PTALK_READ', \Drupal::time()->getRequestTime());
/**
* Status constant for unread messages.
*/
define('PTALK_UNREAD', 1);
/**
* Status constant for deleted messages.
*/
define('PTALK_DELETED', \Drupal::time()->getRequestTime());
/**
* Status constant for undeleted messages.
*/
define('PTALK_UNDELETED', 0);
/**
* Format an array of user objects.
*
* @param $part_array
* Array with user objects, for example the one returned by
* ptalk_generate_user_array.
* @param $limit
* Limit the number of user objects which should be displayed.
* @param $show_username
* Show the username for the current user or a replacer 'Youyou'.
* @param $no_text
* When TRUE, don't display the Participants/From text.
*
* @return
* String with formatted user objects, like user1, user2.
*/
function ptalk_format_participants($part_array, $show_username = FALSE, $limit = NULL, $no_text = FALSE) {
$current_user = \Drupal::currentUser();
$renderer = \Drupal::service('renderer');
if (count($part_array) > 0) {
$to = array();
$limited = FALSE;
if (!$show_username && in_array($current_user->id(), array_keys($part_array))) {
$to[] = $no_text ? t('You') : t('you');
unset($part_array[$current_user->id()]);
}
foreach ($part_array as $account) {
if (is_int($limit) && count($to) >= $limit) {
$limited = TRUE;
break;
}
$to[] = ptalk_participant_format($account);
}
$limit_string = '';
if ($limited) {
$limit_string = ' ' . t('and others');
}
if ($no_text) {
$to = implode(', ', $to);
$to = [
'#markup' => $to,
];
$to = $renderer->render($to);
return $to . $limit_string;
}
if (count($to) == 1) { // Only one participant
return t("From @last", array('@last' => array_pop($to)));
}
else { // Multiple participants..
if (!$limited) {
$last = array_pop($to);
}
$participants = implode(', ', $to);
$participants = [
'#markup' => $participants,
];
$participants = $renderer->render($participants);
if ($limited) {
return t('Between @participants' . $limit_string, array('@participants' => $participants));
}
else {
return t('Between @participants and @last', array('@participants' => $participants, '@last' => $last));
}
}
}
return '';
}
/**
* Define if message was read or not and build sentence about it.
*
* @param $message
* ptalk_message entity.
*
* @return string
* String with status of the message for the current message.
*/
function _ptalk_define_message_status($message) {
// String which will be returned.
$msg_status = '';
// Array keyed by users name with status of the message.
$message_status = [];
if ($message->index->status) {
$status_info = explode(',', $message->index->status);
foreach ($status_info as $info) {
list($participant_name, $status) = explode(':', $info);
$message_status[$participant_name] = $status;
}
}
if ($message_status) {
// Sort message status by last updated timestamp.
asort($message_status);
}
// If current user is an author of the message.
if ($message->isCurrentUserOwner()) {
// If there are recipients who read the message.
if ($message_status) {
foreach (array_keys($message_status) as $recipient_name) {
$read_by[] = $recipient_name;
}
$count = count($read_by);
$msg_status = t('Read by') . ' ';
$last_one = array_pop($read_by);
if (count($read_by) == 0) {
$msg_status .= t('@recipient:', array('@recipient' => $last_one));
}
else {
$recipients = implode(', ', $read_by);
$msg_status .= t('@recipients and @last_one:', array('@recipients' => $recipients, '@last_one' => $last_one));
}
// Get the last updated status of the message.
$msg_status .= ' ' . \Drupal::service('date.formatter')->format(array_pop($message_status));
}
else {
$msg_status = t('Is unread.');
}
}
// If current user is a potential recipient of the message.
else {
if ($message_status) {
// If current user is a recipient of the message.
if ($timeread = $message_status[$message->getCurrentUserName()]) {
$msg_status = t('Was read:') . ' ' . \Drupal::service('date.formatter')->format($timeread);
}
}
else {
return;
}
}
if ($msg_status) {
return $msg_status;
}
}
/**
* Format a single participant.
*
* @param $participant
* The participant object to format.
*
* @ingroup types.
*/
function ptalk_participant_format($participant) {
$username = [
'#theme' => 'username',
'#account' => $participant,
];
return \Drupal::service('renderer')->render($username);
}
/**
* Generate array of user objects based on a string.
*
* @param $string
* A string with user id, for example 1,2,4. Returned by the list query.
*
* @return
* Array with user objects.
*/
function ptalk_generate_user_array($string, $slice = NULL) {
// Convert user uid list (uid1,uid2,uid3) into an array. If $slice is not NULL
// pass that as argument to array_slice(). For example, -4 will only load the
// last four users.
// This is done to avoid loading user objects that are not displayed, for
// obvious performance reasons.
$users = explode(',', rtrim($string, ','));
if (!is_null($slice)) {
$users = array_slice($users, $slice);
}
$participants = [];
foreach ($users as $uid) {
$account = User::load($uid);
$participants[$uid] = $account;
}
return $participants;
}
/**
* Changes the read/new status of a single message.
*
* @param \Drupal\ptalk\MessageInterface $ptalk_message
* The ptalk_message entity.
* @param $status
* Either PTALK_READ or PTALK_UNREAD
* @param \Drupal\Core\Session\AccountInterface $account
* The account object, defaults to the current user
*/
function ptalk_message_change_status($mids, $status, $account = NULL) {
if (!$account) {
$user = Drupal::currentUser();
$account = $user;
}
\Drupal::database()->update('ptalk_message_index')
->fields([
'status' => $status
])
->condition('mid', $mids, 'IN')
->condition('recipient', $account->id())
->execute();
// Allows modules to respond to the status change.
$module_handler = Drupal::moduleHandler();
$module_handler->invokeAll('ptalk_message_status_changed', [$mids, $status, $account]);
}
/**
* Delete or restore a message.
*
* @param \Drupal\ptalk\MessageInterface $ptalk_message
* The ptalk_message entity.
* @param $delete
* Either deletes or restores the message (PTALK_DELETED => delete, PTALK_UNDELETED => restore)
* @param \Drupal\Core\Session\AccountInterface $account
* The account object for which the delete action should be carried out.
* Set to NULL to delete for all users.
*/
function ptalk_message_change_delete($message, $delete, $account = NULL) {
$update = \Drupal::database()->update('ptalk_message_index')
->fields(['deleted' => $delete])
->condition('mid', $message->id());
if ($account) {
$update
->condition('recipient', $account->id());
}
$update->execute();
$module_handler = Drupal::moduleHandler();
$module_handler->invokeAll('ptalk_message_delete_changed', [$message, $delete, $account]);
}
/**
* Implements hook_ptalk_message_delete_changed().
*/
function ptalk_ptalk_message_delete_changed($message, $delete, $account) {
$thread_manager = \Drupal::service('ptalk_thread.manager');
// If deletion done only for particular account, actually for the current user.
if ($account) {
// If message was deleted and this is the last message, update delete index.
if ($delete > 1 && $thread_manager->countMessages($message->getThread(), $account->id()) < 1) {
$thread_manager->deleteIndex($message->getThread(), $delete, $account->id());
}
}
else {
// Get all recipients of the message.
$rids = \Drupal::database()->select('ptalk_message_index', 'pmi');
$rids->addField('pmi', 'recipient');
$rids->condition('mid', $message->id());
foreach ($rids->execute()->fetchCol() as $rid) {
if ($delete > 1 && $thread_manager->countMessages($message->getThread(), $rid) < 1) {
$thread_manager->deleteIndex($message->getThread(), $delete, $rid);
}
}
}
$thread_manager->updateCounts($message->getThread(), $account ? $account->id() : NULL);
}
/**
* Delete indexed information of the message.
*
* @param int $mid
* The ID of the ptalk_message entity.
* @param int $account_id
* Account ID for which index must be deleted,
* if NULL then deletion will be done for all recipients.
*/
function ptalk_delete_message_index($mid = NULL, $account_id = NULL) {
if ($mid || $account_id) {
$delete = \Drupal::database()->delete('ptalk_message_index');
if ($mid) {
$delete->condition('mid', $mid);
}
if ($account_id) {
$delete->condition('recipient', $account_id);
}
$delete->execute();
}
// Allows modules to respond to the deletion message index.
$module_handler = Drupal::moduleHandler();
$module_handler->invokeAll('ptalk_delete_message_index', [$mid, $account_id]);
}
/**
* Delete indexed information of the thread.
*
* @param int $tid
* The ID of the ptalk_thread entity.
* @param int $account_id
* Account ID for which index must be deleted,
* if NULL then deletion will be done for all participants.
*/
function ptalk_delete_thread_index($tid = NULL, $account_id = NULL) {
if ($tid || $account_id) {
$delete = \Drupal::database()->delete('ptalk_thread_index');
if ($tid) {
$delete->condition('tid', $tid);
}
if ($account_id) {
$delete->condition('participant', $account_id);
}
$delete->execute();
}
// Allows modules to respond to the deletion thread index.
$module_handler = Drupal::moduleHandler();
$module_handler->invokeAll('ptalk_delete_thread_index', [$tid, $account_id]);
}
/**
* Marks thread messages as (un)read.
*
* @param \Drupal\ptalk\ThreadInterface $ptalk_thread
* The ptalk_thread entity.
* @param $status
* Either PTALK_READ or PTALK_UNREAD, sets the new status.
* @param \Drupal\Core\Session\AccountInterface $account
* The account object, defaults to the current user.
*/
function ptalk_thread_change_status($thread, $status, $account = NULL) {
if (is_null($account)) {
$current_user = \Drupal::currentUser();
$account = $current_user;
}
// Record which messages will change status.
$query = \Drupal::database()->select('ptalk_message_index', 'pmi');
$query->addField('pmi', 'mid');
$value = $status;
$operator = '<>';
// Actually if status is a timestamp (status 'read'), we avoid changing status
// in the database if it is a timestamp (status 'read').
if ($status > 1) {
$value = substr($status, 0, 2) . '%';
$operator = 'NOT LIKE';
}
$changed = $query
->condition('tid', $thread->id())
->condition('recipient', $account->id())
->condition('status', $value, $operator)
->condition('status', 0, '<>')
->execute()
->fetchCol();
if ($changed) {
// Update the status of the thread.
\Drupal::database()->update('ptalk_message_index')
->fields(['status' => $status])
->condition('tid', $thread->id())
->condition('recipient', $account->id())
->condition('status', $value, $operator)
->condition('status', 0, '<>')
->execute();
}
// Allow modules to respond to the status changes.
$module_handler = Drupal::moduleHandler();
$module_handler->invokeAll('ptalk_thread_status_changed', [$thread, $changed, $status, $account]);
}
/**
* Implements hook_ptalk_thread_status_changed().
*/
function ptalk_ptalk_thread_status_changed($thread, $changed, $status, $account) {
if (count($changed) > 0) {
\Drupal::service('ptalk_thread.manager')->updateNewCount($thread);
}
}
/**
* Delete or restore one thread.
*
* @param \Drupal\ptalk\ThreadInterface $ptalk_thread
* The ptalk_thread entity.
* @param $delete
* Indicates if the thread should be deleted or restored. PTALK_DELETED => delete, PTALK_UNDELETED => restore.
* @param \Drupal\Core\Session\AccountInterface $account
* The account object for which the delete action should be carried out.
*/
function ptalk_thread_change_delete($thread, $delete, $account = NULL) {
// Record which messages will change delete status.
$query = \Drupal::database()->select('ptalk_message_index', 'pmi');
$query->addField('pmi', 'mid');
$value = $delete;
$operator = '<>';
if ($delete != 0) {
$value = substr($delete, 0, 2) . '%';
$operator = 'NOT LIKE';
}
$query
->condition('tid', $thread->id())
->condition('deleted', $value, $operator);
if ($account) {
$query
->condition('recipient', $account->id());
}
$changed = $query
->groupBy('mid')
->execute()
->fetchCol();
if ($changed) {
// Delete or restore messages of the thread.
$update = \Drupal::database()->update('ptalk_message_index')
->fields(['deleted' => $delete])
->condition('tid', $thread->id())
// Do not delete already deleted messages or
// do not restore undeleted messages.
->condition('deleted', $value, $operator);
if ($account) {
$update
->condition('recipient', $account->id());
}
$update->execute();
}
// Allow modules to respond to the deletion thread.
$module_handler = Drupal::moduleHandler();
$module_handler->invokeAll('ptalk_thread_delete_changed', [$thread, $changed, $delete, $account]);
}
/**
* Implements hook_ptalk_thread_delete_changed().
*/
function ptalk_ptalk_thread_delete_changed($thread, $changed, $delete, $account) {
if (count($changed) > 0) {
\Drupal::service('ptalk_thread.manager')->deleteIndex($thread, $delete, $account ? $account->id() : NULL);
\Drupal::service('ptalk_thread.manager')->updateCounts($thread, $account ? $account->id() : NULL);
}
}
/**
* Returns a link to send message form for a specific users.
*
* Contains permission checks of author/recipient, blocking and
* if an anonymous user is involved.
*
* @param $recipients
* Recipients of the message
* @param $account
* Sender of the message, defaults to the current user
* @param $string
* Subject of the message, defaults NULL
*
* @return
* Either FALSE or a URL string
*
* @ingroup api
*/
function ptalk_get_link($recipients, $account = array(), $subject = NULL) {
$config = \Drupal::config('ptalk.settings');
$module_handler = \Drupal::moduleHandler();
if ($account == NULL) {
$current_user = \Drupal::currentUser();
$account = $current_user;
}
if (!is_array($recipients)) {
$recipients = array($recipients);
}
if (!$account->hasPermission('start private conversation') || $account->id() == 0) {
return FALSE;
}
$validated = array();
foreach ($recipients as $recipient) {
if (!$account->hasPermission('read private conversation')) {
continue;
}
if ($config->get('ptalk_display_link_self') == FALSE && $account->id() == $recipient->id()) {
continue;
}
$validated[] = $recipient->id();
}
if (empty($validated)) {
return FALSE;
}
$uri = Url::fromRoute('ptalk.start_conversation');
$uri->setRouteParameters(['participants' => implode(',', $validated)]);
if (!is_null($subject)) {
$uri->setRouteParameters(['subject' => $subject]);
}
return $uri;
}
/**
* Implements hook_ptalk_thread_links_alter().
*/
function ptalk_ptalk_thread_links_alter(array &$links, ThreadInterface $entity, array &$context) {
$config = \Drupal::config('ptalk.settings');
if ($config->get('ptalk_display_on_thread')) {
$uri = ptalk_get_link([$entity->getOwner()]);
if (!empty($uri)){
$links['ptalk_link'] = [
'#theme' => 'links__thread__ptalk',
'#attributes' => ['class' => ['links', 'inline']],
'#links' => [
'ptalk-link' => [
'title' => t('Send author a message'),
'url' => $uri,
],
],
];
}
}
}
/**
* Implements hook_ptalk_message_links_alter().
*/
function ptalk_ptalk_message_links_alter(array &$links, MessageInterface $entity, array &$context) {
$config = \Drupal::config('ptalk.settings');
if ($config->get('ptalk_display_on_messages')) {
$uri = ptalk_get_link([$entity->getOwner()]);
if (!empty($uri)) {
$links['ptalk_link'] = [
'#theme' => 'links__message__ptalk',
'#attributes' => ['class' => ['links', 'inline']],
'#links' => [
'ptalk-link' => [
'title' => t('Send author a message'),
'url' => $uri,
],
],
];
}
}
}
/**
* Implements hook_node_links_alter().
*/
function ptalk_node_links_alter(array &$links, NodeInterface $entity, array &$context) {
$config = \Drupal::config('ptalk.settings');
$types = array_filter($config->get('ptalk_link_node_types'));
if (in_array($entity->getType(), $types) && ($context['view_mode'] == 'full' || ($config->get('ptalk_display_on_teaser') && $context['view_mode'] == 'teaser'))) {
$uri = ptalk_get_link([$entity->getOwner()]);
if (!empty($uri)) {
$links['ptalk_link'] = [
'#theme' => 'links__node__ptalk',
'#attributes' => ['class' => ['links', 'inline']],
'#links' => [
'ptalk-link' => [
'title' => t('Send author a message'),
'url' => $uri,
],
],
];
}
}
}
/**
* Implements hook_comment_links_alter().
*/
function ptalk_comment_links_alter(array &$links, CommentInterface $entity, array &$context) {
$config = \Drupal::config('ptalk.settings');
$types = array_filter($config->get('ptalk_link_node_types'));
if (in_array($entity->getCommentedEntity()->getType(), $types) && $config->get('ptalk_display_on_comments')) {
$uri = ptalk_get_link([$entity->getOwner()]);
if (!empty($uri)) {
$links['ptalk_link'] = [
'#theme' => 'links__comment__ptalk',
'#attributes' => ['class' => ['links', 'inline']],
'#links' => [
'ptalk-link' => [
'title' => t('Send author a message'),
'url' => $uri,
],
],
];
}
}
}
/**
* Implements hook_user_view().
*/
function ptalk_user_view(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode) {
if ($view_mode == 'full') {
$config = \Drupal::config('ptalk.settings');
if (($uri = ptalk_get_link([$entity])) && $config->get('ptalk_display_profile_links')) {
$build['ptalk_link'] = [
'#theme' => 'links__user__ptalk',
'#attributes' => ['class' => ['links', 'inline']],
'#links' => [
'ptalk-link' => [
'title' => t('Send this user a private message'),
'url' => $uri,
],
],
];
}
}
}
/**
* Generates a URL to the particular message on the thread page.
*
* @param \Drupal\ptalk\MessageInterface $message
* The ptalk_message entity.
* @param \Drupal\Core\Session\AccountInterface $account
* The account object for which url must be generated.
* @param array $options
* (optional) A keyed array of url options.
*
* @return string
* A URL that will redirect to the particular message on the thread
* page.
*/
function ptalk_message_url($message, $account, $options = []) {
$config = \Drupal::config('ptalk.settings');
$per_page = $config->get('ptalk_messages_per_page');
$count_deleted = $account->hasPermission('read all private conversation') ? TRUE : FALSE;
$url = $message->getThread()->toUrl();
$query = [];
$page = \Drupal::entityTypeManager()->getStorage('ptalk_message')->getNumPage($message, $per_page, 'message', $count_deleted);
if ($page > 0) {
$query['page'] = $page;
}
// Redirect to the last message.
if (empty($options)) {
$options = ['query' => $query];
}
else {
$options = array_merge($options, ['query' => $query]);
}
$url->setOptions($options);
return $url;
}
/**
* Implements hook_user_login().
*/
function ptalk_user_login($account) {
$config = \Drupal::config('ptalk.settings');
if ($config->get('ptalk_display_loginmessage')) {
$count = \Drupal::entityTypeManager()->getStorage('ptalk_thread')->countUnread($account);
if ($count) {
\Drupal::messenger()->addMessage(\Drupal::translation()->formatPlural($count, 'You have <a href="@conversations">1 unread message</a>.', 'You have <a href="@conversations">@count unread messages</a>', ['@conversations' => Url::fromRoute('entity.ptalk_thread.collection')]));
}
}
}
/**
* Loads a single ptalk_message entity.
*
* @param $mid
* Message id.
*
* @return \Drupal\ptalk\Entity\Message|null
* The ptalk_message entity, if exists, NULL otherwise. Results are
* statically cached.
*/
function ptalk_message_load($mid = NULL) {
return Message::load($mid);
}
/**
* Loads multiple ptalk_message entities.
*
* @param array $mids
* An array of entity IDs.
*
* @return array
* An array of ptalk_message entities, indexed by mid. When no results are
* found, an empty array is returned.
*/
function ptalk_message_load_multiple(array $mids = NULL) {
return Message::loadMultiple($mids);
}
/**
* Loads multiple ptalk_message entities by property tid (id of the ptalk_thread entity).
*
* @param $tid
* Thread id of the message.
*
* @return
* An array of matching ptalk_message entities.
*/
function ptalk_message_load_multiple_by_tid($tid) {
$values = ['tid' => $tid];
return \Drupal::entityTypeManager()->getStorage('ptalk_message')->loadMultiple($values);
}
/**
* Loads a single ptalk_thread entity.
*
* @param $tid
* Thread id.
* @param $reset
* (optional) Whether to reset the internal ptalk_thread_load() cache. Defaults to
* FALSE.
*
* @return \Drupal\ptalk\Entity\Thread|null
* The ptalk_thread entity, if exists, NULL otherwise. Results are
* statically cached.
*/
function ptalk_thread_load($tid = NULL, $reset = FALSE) {
if ($reset) {
\Drupal::entityTypeManager()->getStorage('ptalk_thread')->resetCache($tid);
}
return Thread::load($tid);
}
/**
* Loads multiple ptalk_thread entities.
*
* @param array $tids
* An array of entity IDs.
* @param $reset
* (optional) Whether to reset the internal ptalk_thread_load_multiple() cache. Defaults to
* FALSE.
*
* @return array
* An array of ptalk_thread entities, indexed by tid. When no results are
* found, an empty array is returned.
*/
function ptalk_thread_load_multiple(array $tids = NULL, $reset = FALSE) {
if ($reset) {
\Drupal::entityTypeManager()->getStorage('ptalk_thread')->resetCache($tids);
}
return Thread::loadMultiple($tids);
}
/**
* Clear all static cache variables for threads.
*
* @param $ids
* An array of ids to reset in the entity cache.
*/
function ptalk_thread_static_reset(array $ids = NULL) {
\Drupal::entityTypeManager()->getStorage('ptalk_thread')->resetCache($ids);
}
/**
* Clear all static cache variables for messages.
*
* @param $ids
* An array of ids to reset in the entity cache.
*/
function ptalk_message_static_reset(array $ids = NULL) {
\Drupal::entityTypeManager()->getStorage('ptalk_message')->resetCache($ids);
}
/**
* Implements hook_cron().
*/
function ptalk_cron() {
$config = \Drupal::config('ptalk.settings');
$flush_days = $config->get('ptalk_flush_days');
$flush_max = $config->get('ptalk_flush_max');
if ($config->get('ptalk_flush_enabled')) {
$query = \Drupal::database()->select('ptalk_thread_index', 'pti');
$query->addField('pti', 'tid');
$tids = $query
->groupBy('pti.tid')
// Checks if thread is deleted for all users and last deleted thread is older then defined in option.
// Only in that case thread entities and all related data will be deleted from the database.
->having('MIN(pti.deleted) > 0 AND MAX(pti.deleted) < :old', array(':old' => REQUEST_TIME - $flush_days * 86400))
->range(0, $flush_max)
->execute()
->fetchCol();
if ($tids) {
$storage_handler = \Drupal::entityTypeManager()->getStorage('ptalk_thread');
$entities = $storage_handler->loadMultiple($tids);
$storage_handler->delete($entities);
}
// Select deleted message entities for flushing with same logic as for thread.
$query = \Drupal::database()->select('ptalk_message_index', 'pmi');
$query->addField('pmi', 'mid');
$mids = $query
->groupBy('pmi.mid')
->having('MIN(pmi.deleted) > 0 AND MAX(pmi.deleted) < :old', array(':old' => REQUEST_TIME - $flush_days * 86400))
->range(0, $flush_max)
->execute()
->fetchCol();
if ($mids) {
$storage_handler = \Drupal::entityTypeManager()->getStorage('ptalk_message');
$entities = $storage_handler->loadMultiple($mids);
$storage_handler->delete($entities);
}
}
}
/**
* Implements hook_views_query_alter()
*/
function ptalk_views_query_alter(ViewExecutable $view, $query) {
$current_user = \Drupal::currentUser();
if ($view->storage->get('base_table') == 'ptalk_thread') {
// If we do not add to the view at least one field from the table ptalk_thread_index
// then this table will be not added to the tables of the view, so add this table here.
if (!in_array('ptalk_thread_index', array_keys($query->tables[$view->storage->get('base_table')]))) {
$query->addTable('ptalk_thread_index');
}
// Filters outputed threads list for the particular participant,
// so the thread may see only participant of the thread.
$query->addWhere(0, 'ptalk_thread_index.participant', $current_user->id());
}
if ($view->storage->get('base_table') == 'ptalk_message') {
if (!in_array('ptalk_message_index', array_keys($query->tables[$view->storage->get('base_table')]))) {
$query->addTable('ptalk_message_index');
}
$query->addWhere(0, 'ptalk_message_index.recipient', $current_user->id());
}
}
/**
* Implements hook_ptalk_load_thread_messages()
*/
function ptalk_ptalk_load_thread_messages($thread, $messages) {
if (!empty($messages)) {
$new_messages = [];
foreach ($messages as $message) {
// If message is unread only for recipient not for author
// and message is readed by recipient of the message.
if ($message->isUnRead() && !$message->isCurrentUserOwner() && $thread->participantOf(\Drupal::currentUser())) {
array_push($new_messages, $message->id());
}
}
if ($new_messages) {
ptalk_message_change_status($new_messages, PTALK_READ);
\Drupal::service('ptalk_thread.manager')->updateNewCount($thread);
}
}
}
/**
* Generates a message preview.
*
* @param \Drupal\ptalk\MessageInterface $message
* The ptalk_message entity to preview.
* @param Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* An array as expected by drupal_render().
*/
function ptalk_message_preview(MessageInterface $message, FormStateInterface $form_state) {
$preview_build = [];
if (!$form_state->getErrors()) {
$message->in_preview = TRUE;
$message_build = \Drupal::entityTypeManager()->getViewBuilder('ptalk_message')->view($message);
$message_build['#weight'] = -100;
$preview_build['ptalk_message_preview'] = $message_build;
}
if ($message->thread_id) {
$thread = Thread::load($message->thread_id);
$thread->in_preview = TRUE;
$build = \Drupal::entityTypeManager()->getViewBuilder('ptalk_thread')->view($thread);
$preview_build['ptalk_message_output_below'] = $build;
$preview_build['ptalk_message_output_below']['#weight'] = 100;
}
return $preview_build;
}
/**
* Checks whether the current page is the full page view of the ptalk_thread entity.
*
* @param \Drupal\ptalk\ThreadInterface $ptalk_thread
* A ptalk_thread entity.
*
* @return true|false
* The bool TRUE if this is a full page view, otherwise FALSE.
*/
function ptalk_thread_is_page(ThreadInterface $ptalk_thread) {
$route_match = \Drupal::routeMatch();
if ($route_match->getRouteName() == 'entity.ptalk_thread.canonical') {
$page_thread = $route_match->getParameter('ptalk_thread');
}
$is_page = (!empty($page_thread) ? $page_thread->id() == $ptalk_thread->id() : FALSE);
return $is_page;
}
/**
* Implements hook_form_alter().
*/
function ptalk_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
if ($form_id == 'user_form') {
$user = $form_state->getFormObject()->getEntity();
if (($user->hasPermission('start private conversations') || $user->hasPermission('read private conversation')) && $user->hasPermission('allow disabling private conversation')) {
$form['ptalk'] = [
'#type' => 'details',
'#title' => t('Private conversations'),
'#open' => TRUE,
'#weight' => 10,
];
$form['ptalk']['ptalk_disable'] = [
'#type' => 'checkbox',
'#title' => t('Disable private conversations'),
'#default_value' => ptalk_is_disabled($user),
'#description' => t('Disabling private conversations prevents you from reading or starting private conversations between users.'),
'#weight' => -10,
];
$form['actions']['submit']['#submit'][] = 'ptalk_user_form_submit';
}
}
}
/**
* Submit handler to disabling starting or reading private conversations.
*/
function ptalk_user_form_submit(array &$form, FormStateInterface $form_state) {
$user = $form_state->getFormObject()->getEntity();
$disabled = ptalk_is_disabled($user);
$ptalk_disable = $form_state->getValue('ptalk_disable');
if ($disabled != $ptalk_disable) {
if ($ptalk_disable) {
\Drupal::database()->insert('ptalk_disable')
->fields(['uid' => $user->id()])
->execute();
drupal_static_reset('ptalk_is_disabled');
}
else {
\Drupal::database()->delete('ptalk_disable')
->condition('uid', $user->id())
->execute();
drupal_static_reset('ptalk_is_disabled');
}
}
}
/**
* Checks the status of private messaging for provided user.
*
* @param user
* User object to check.
*
* @return TRUE if user has disabled private messaging, FALSE otherwise
*/
function ptalk_is_disabled($account) {
$disabled = &drupal_static(__FUNCTION__, []);
if (!isset($disabled[$account->id()])) {
$query = \Drupal::database()->select('ptalk_disable', 'pd');
$query->addField('pd', 'uid');
$id = $query
->condition('uid', $account->id())
->execute()
->fetchField();
if ($id == FALSE) {
$id = 0;
}
$disabled[$account->id()] = $id;
}
return (bool) $disabled[$account->id()];
}
/**
* Wrapper function for user_load_multiple().
*
* The function adds the information about recipient to the account object,
* for exapmle - disabled ptalk module for the particular uid or not.
* this information also may be altered by the hook_query_ptalk_load_message_recipients_alter().
*
* @param array $uids
* Which uid to load. Can either be a single id or an array of uids.
*
* @return array
* An array keyed by users id with users object.
*
* @see hook_query_ptalk_load_message_recipients_alter()
*/
function ptalk_load_message_recipients($uids) {
$accounts = [];
$query = \Drupal::database()->select('users_field_data', 'u');
$query->leftJoin('ptalk_disable', 'pd', 'u.uid = pd.uid');
$query->addField('u', 'uid');
$query->addField('pd', 'uid', 'ptalk_disabled');
$query->condition('u.uid', $uids, 'IN');
$query->addTag('ptalk_load_message_recipients');
$recipient_info = $query
->execute()
->fetchAllAssoc('uid');
foreach (User::loadMultiple($uids) as $account) {
if ($recipient_info) {
$account->recipient_info = $recipient_info[$account->id()];
}
$accounts[$account->id()] = $account;
}
return $accounts;
}
/**
* Implements hook_ptalk_block_message().
*/
function ptalk_ptalk_block_message($author, $recipients, $context = []) {
$blocked = [];
if ($author->recipient_info->ptalk_disabled) {
$blocked['author']['ptalk_disabled'] = [
'message' => [
'type' => 'warning',
'singular' => t('You have disabled private message sending and receiving.'),
]
];
// If author has disabled ptalk module on his or her account settings,
// then do not need collecting other reasons because this is the highest reason to block message.
return $blocked;
}
foreach ($recipients as $recipient) {
if ($recipient->recipient_info->ptalk_disabled) {
if (!isset($blocked['recipients']['ptalk_disabled'])) {
$blocked['recipients']['ptalk_disabled'] = [
'ids' => [$recipient->id() => ptalk_participant_format($recipient)],
'message' => [
'type' => 'warning',
'plural' => t('The following users has disabled private conversation module:'),
'singular' => t('has disabled private message receiving.'),
]
];
}
else {
$blocked['recipients']['ptalk_disabled']['ids'][$recipient->id()] = ptalk_participant_format($recipient);
}
}
// Do not send private messages to blocked users.
else if (isset($recipient->status) && (!$recipient->status)) {
if (!isset($blocked['recipients']['account_disabled'])) {
$blocked['recipients']['account_disabled'] = [
'ids' => [$recipient->id() => ptalk_participant_format($recipient)],
'message' => [
'type' => 'warning',
'plural' => t('The following users has disabled their account:'),
'singular' => t('has disabled his or her account.'),
]
];
}
else {
$blocked['recipients']['account_disabled']['ids'][$recipient->id()] = ptalk_participant_format($recipient);
}
}
}
return $blocked;
}
/**
* Invoke implementations of the hook_ptalk_block_message().
*
* The function looping through implementations of the hook_ptalk_block_message()
* and collects types and reasons of the blocking, until finds enough reasons to block
* author sending message.
*
* @param object $author
* Author of the message for wich blocking must be search,
* must be gererated by the function ptalk_load_message_recipients().
* @param array $recipients
* An array with recipients objects of the message
* generated by the function ptalk_load_message_recipients().
* @param $context
* Additional information. Can contain the thread object.
*
* @return array
* An array with types and reasons of the blocking.
*/
function invoke_hooks_ptalk_block_message($author, $recipients, $context) {
$blocked = [];
$blocked_ids = [];
foreach (\Drupal::moduleHandler()->getImplementations('ptalk_block_message') as $module) {
$function = $module . '_ptalk_block_message';
$blocked_implementation = $function($author, $recipients, $context);
if ($blocked_implementation) {
foreach ($blocked_implementation as $type => $info) {
$reason = array_keys($info);
$reason = $reason[0];
// If author of the message for some reasons is blocked,
// then do not need to continue.
if ($type == 'author') {
$blocked[$type] = $info;
break 2;
}
// If we already have some reason to block author sending message to recipient
// then do not need add another one reason if such exists.
elseif ($type == 'recipients' && $ids = array_diff($info[$reason]['ids'], $blocked_ids)) {
$info[$reason]['ids'] = $ids;
$blocked[$type][$reason] = array_pop($info);
$blocked_ids += $ids;
}
// If all recipients of the message are blocked for the author
// then do not need looping through other implementations.
if (count($blocked_ids) == count($recipients)) {
break 2;
}
}
}
}
return [array_keys($blocked_ids), $blocked];
}
/**
* Implements hook_user_cancel().
*/
function ptalk_user_cancel($edit, $account, $method) {
switch ($method) {
case 'user_cancel_reassign':
\Drupal::database()->update('ptalk_message')
->condition('author', $account->id())
->fields(['author' => 0])
->execute();
break;
case 'user_cancel_block_unpublish':
ptalk_delete_account_data($account);
break;
}
\Drupal::database()->delete('ptalk_disable')
->condition('uid', $account->id())
->execute();
}
/**
* Implements hook_user_delete().
*/
function ptalk_user_delete($account) {
ptalk_delete_account_data($account);
}
/**
* Delete all messages and threads data from an account.
*/
function ptalk_delete_account_data($account) {
$tids = \Drupal::database()->select('ptalk_thread', 'pt')
->fields('pt', ['tid'])
->condition('uid', $account->id())
->execute()
->fetchCol();
// Delete all threads related to this account.
if (!empty($tids)) {
$storage_handler = \Drupal::entityTypeManager()->getStorage('ptalk_thread');
$entities = $storage_handler->loadMultiple($tids);
$storage_handler->delete($entities);
}
// If user is a participant of the conversations
// delete his from its.
ptalk_delete_thread_index(NULL, $account->id());
$mids = \Drupal::database()->select('ptalk_message', 'pm')
->fields('pm', ['mid'])
->condition('author', $account->id())
->execute()
->fetchCol();
// Delete all messages related to this account.
if (!empty($mids)) {
$storage_handler = \Drupal::entityTypeManager()->getStorage('ptalk_message');
$entities = $storage_handler->loadMultiple($mids);
$storage_handler->delete($entities);
}
\Drupal::database()->delete('ptalk_disable')
->condition('uid', $account->id())
->execute();
}
/**
* Implements hook_theme()
*/
function ptalk_theme() {
return [
'ptalk_thread' => [
'render element' => 'elements',
],
'ptalk_message' => [
'render element' => 'elements',
],
];
}
/**
* Prepares variables for ptalk_message templates.
*
* Default template: ptalk-message.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An array of elements to display in view mode.
* - ptalk_message: The ptalk_message object.
* - view_mode: View mode 'full'.
*/
function template_preprocess_ptalk_message(&$variables) {
$config = \Drupal::config('ptalk.settings');
$current_user = \Drupal::currentUser();
$message = $variables['elements']['#ptalk_message'];
$variables['datetime'] = \Drupal::service('date.formatter')->format($message->created->value);
$variables['status_new'] = $message->isUnread() && !$message->isCurrentUserOwner() ? 'new' : '';
if ($message->in_preview) {
$variables['message_status'] = t('Message in preview mode');
}
elseif ($config->get('ptalk_message_status')) {
$variables['message_status'] = _ptalk_define_message_status($message);
}
$variables['elements']['body']['#title'] = '';
$variables['message_body'] = $variables['elements']['body'];
$variables['message_links'] = $variables['elements']['links'];
if ($current_user->id() == $message->getOwnerId()) {
$variables['message_author'] = t('You');
}
else {
$message_author = $message->getOwner();
$variables['message_author'] = ptalk_participant_format($message_author);
}
$variables['message_id'] = $message->id();
foreach (Element::children($variables['elements']) as $key) {
if (in_array($key, ['body', 'subject', 'links'])) {
continue;
}
$variables['content'][$key] = $variables['elements'][$key];
}
}
/**
* Prepares variables for ptalk_thread templates.
*
* Default template: ptalk-thread.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An array of elements to display in view mode.
* - ptalk_thread: The ptalk_thread object.
* - view_mode: View mode 'full'.
*/
function template_preprocess_ptalk_thread(&$variables) {
$thread = $variables['elements']['#ptalk_thread'];
$account = $thread->getOwner();
$variables['author'] = ptalk_participant_format($account);
$variables['attributes'] = ['class' => ['permalink'], 'rel' => 'bookmark'];
$variables['date'] = \Drupal::service('date.formatter')->format($thread->getCreatedTime());
$variables['thread_participants'] = $variables['elements']['participants'];
unset($variables['elements']['participants']);
$variables['thread_links'] = $variables['elements']['links'];
unset($variables['elements']['links']);
$variables['#attached']['library'][] = 'ptalk/ptalk-thread';
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}
