1

I'm putting together a private message module. Conversations threads are a custom entity within the module. A thread can potentially appear twice on a page - once in the 'inbox' (a block), and once as the main content of the page. I'm wrapping the rendered threads with a div that I'm attaching in an override of EntityViewBUilder::view(). If the user has not viewed the thread, I'm adding a class of 'unread-thread' to the div wrapper.

Where I'm running into a problem, is finding a place to mark the thread as 'read'. If I do it at the end of EntityViewBuilder::view(), then the class is only applied to the first instance of the thread on the page, since it's already been marked read by the time it comes to the next instance of the thread on the page. I want the class to be part of the wrapper on all instances of the thread on the page.

So I need to find somewhere to mark a thread as read, but only after all instances on the page have passed through EntityViewBuilder::view(). I'm thinking a post-render hook may be good, but I'm not finding one. Does anyone have any suggestions?

1

You can use an event for this, a RESPONSE event with a low priority or a TERMINATE event. See for example UserRequestSubscriber::onKernelTerminate:

/**
   * Updates the current user's last access time.
   *
   * @param \Symfony\Component\HttpKernel\Event\PostResponseEvent $event
   *   The event to process.
   */
  public function onKernelTerminate(PostResponseEvent $event) {
    if ($this->account->isAuthenticated() && REQUEST_TIME - $this->account->getLastAccessedTime() > Settings::get('session_write_interval', 180)) {
      // Do that no more than once per 180 seconds.
      /** @var \Drupal\user\UserStorageInterface $storage */
      $storage = $this->entityManager->getStorage('user');
      $storage->updateLastAccessTimestamp($this->account, REQUEST_TIME);
    }
  }
  • Thanks! I've actually been working on that method right now! Glad to see I'm on the right track. – Jaypan Apr 8 '17 at 15:29
  • I'm having troubles figuring out how to pass an argument that can be used in the event subscriber. In the above example, $this->entityManager and $this->account are both services, so they are declared in the services.yml file. However, I need to pass an argument that is not a service, and is dynamic. I tried extending the FinishRequestEvent and dispatching my extension, but that throws an error. Any ideas how to do this? – Jaypan Apr 8 '17 at 16:17
  • First I would try to use the request $event->getrequest() and response $event->getresponse() for dynamic content. Or you can define a custom service to set and get dynamic arguments and inject this in the event subscriber class. – 4k4 Apr 8 '17 at 16:58
  • 1
    Thanks for your help - I got it working. Posting my solution below. – Jaypan Apr 8 '17 at 17:21
1

Here is my final solution. I've commented the code to track what is happening. Basically the steps are:

1) Register a handler to be executed on page request

2) In that handler, retrieve the data necessary to perform the action that needs to happen

3) Execute the action

namespace Drupal\private_message\Event;

use Drupal\Core\Session\AccountProxyInterface;
use Drupal\private_message\Entity\PrivateMessageThread;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;

class PrivateMessageEventSubscriber implements EventSubscriberInterface {

  /**
   * The current user
   *
   * @var \Drupal\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * Construct a PrivateMessageEventSubscriber object
   *
   * @param \Drupal\Session\AccountProxyInterface $currentUser
   *   The current user
   */
  public function __construct(AccountProxyInterface $currentUser) {
    $this->currentUser = $currentUser;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    // Register an event listener for when the request finishes
    $events[KernelEvents::FINISH_REQUEST][] = ['onKernelTerminate'];

    return $events;
  }

  /**
   * Register the thread access for the user. This is called on the end
   * of the page request
   *
   * @param \Symfony\Component\HttpKernel\Event\FinishRequestEvent $event
   */
  public function onKernelTerminate(FinishRequestEvent $event) {
    // Retrieve the thread ID for the current page
    $private_message_thread_id = $event->getRequest()->get('private_message_thread_id');
    if ($private_message_thread_id) {
      // Load the thread
      $private_message_thread = PrivateMessageThread::load($private_message_thread_id);
      if ($private_message_thread) {
        // Perform the action at the end of the page load
        $private_message_thread->updateLastAccessTime();
      }
    }
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.