This module adds a block of recent titles that update in real time on new content via a WebSocket. This functionality comes from Rachetphp and it's implemented as a Drupal service, so it can be invoked on any updates you want shown to the block.

Available customization:

Filter which content types you want to see updates from
Limit the number of recent titles shown at a time in descending order
Enable more realtime events to the block with the supplied service

NB I haven't added any tests since this module works with external libraries that already come with their own tests and Drupal functionality introduced isn't that complex.

Project link
https://www.drupal.org/project/live_content

Usage
To get all dependencies Install with composer require drupal/live_content and use the module code from branch below.
Configure the Websocket server and feed output on Configuration » Web services » Live content.
Enable the module on Structure » Block layout » Place block button of your desired region.
Start the server with drush 9+ and enable the Live content block to your desired section page. See readme for more details.

Git instructions
git clone --branch 8.x-1.x https://git.drupalcode.org/project/live_content.git

PAReview checklist
https://pareview.sh/pareview/https-git.drupal.org-project-live_content.g...

My reviews
https://www.drupal.org/project/projectapplications/issues/3064265#commen...
https://www.drupal.org/project/projectapplications/issues/3063812#commen...
https://www.drupal.org/project/projectapplications/issues/3065331#commen...

CommentFileSizeAuthor
#7 security-issue.jpg4.22 KBvuil
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

seroton created an issue. See original summary.

seroton’s picture

Issue summary: View changes
vuil’s picture

Thank you for the contribution!

I have some small notices as recommendations which are not security issues:

  • Please, remove in your code all unused function parameters and variables.
  • It is always better and more "safely to use single quotes" for simple texts,
    example (to-be): $this->t('WebSocket server port')
  • And, always keep attention on your Drupal project's issues.
  • It is better to "handle the exception" when persist,
    like on this $this->entityTypeManager->getStorage('node_type')->loadMultiple() usage.

I didn't find any security issues.

Please, be patience through the whole reviewing process.
Good job!

apaderno’s picture

Status: Needs review » Needs work

I am changing status as per first point on previous comment.

seroton’s picture

Status: Needs work » Needs review

@ilchovuchkov Thanks alot for your recommendations!

Have fixed all the items you've suggested now.

Thanks again.

vuil’s picture

Status: Needs review » Needs work

Thanks for the contribution!

Great work of Ratchet & Websocket integration!

Just a little confusion but it needs to be fixed:
Could you add a Roave/SecurityAdvisories and use it as a firewall for vulnerable components? (see the attachment file)

composer require --dev roave/security-advisories:dev-master

vuil’s picture

FileSize
4.22 KB
seroton’s picture

Status: Needs work » Needs review

Hey @ilchovuchkov thanks for this tip it was new to me. I've added roave to composer now.

Please can you or anyone tell me how you get that warning (attachment) to show up on your local dev environment? Is it IDE/editor specific? I'm using phpstorm.

Thanks!

vuil’s picture

@seroton Thank you for the question! I will try to help.

Sometimes I am using external security testing software & programs.

Yes, I'm also using a dozen of Phpstorm plugins for drupal, symfony, composer, and other security related plugins into it.

And, the most important one is the manual security review, when I can touch and feel the code inside. ;)

vuil’s picture

vuil’s picture

Status: Needs review » Reviewed & tested by the community

I have not found security related issues into the code.

apaderno’s picture

Status: Reviewed & tested by the community » Needs work
  • What follows is a quick review of the project; it doesn't mean to be complete
  • For every point, I didn't make a complete list of where the code should be fixed, but an example of what is wrong in the code
  • Not all the points are application stoppers; some of them describe changes that would be preferable to make
function live_content_entity_insert(EntityInterface $entity) {
  // Get live content service.
  $live_content_service = \Drupal::service('live_content.feed');
  $live_content_content_types = $live_content_service->getContentTypes();
  if (
    !empty($live_content_content_types) &&
    in_array($entity->bundle(), $live_content_content_types)
  ) {
    if (Node::load($entity->id())->isPublished()) {
      // Push new content feed.
      $live_content_service->send($entity->toLink()->toString());
    }
  }
}

Why is the hook loading a node with the same ID of the inserted entity?
If the purpose is verifying the inserted entity is published, it's enough to check the value returned from $entity->isPublished(), once verified the entity object implements that method. If the hook must be invoked only for nodes, it should verify the entity is effectively a node, or the module should implement hook_ENTITY_TYPE_insert() instead of hook_entity_insert().
Otherwise, the code is assuming that two entities with the same ID are also two entities of the same type, which is incorrect. The ID is merely an integer that can be used from entities of different types.

    foreach ($nodes as $node) {
      $nodeTitles[$node->nid->value] = $node->title->value;
    }

Is necessary to use ->value to get the node ID and the title?

/**
 * Sends a server feed to the client based on admin configuration settings.
 */
class Feed {
}

The class used to implement a service should implement an interface, to allow other modules to change the service implementation.

seroton’s picture

Status: Needs work » Needs review

@kiamlaluno Thanks for very valuable feedback and apologies for taking so long to look at this with summer breaks etc.

Why is the hook loading a node with the same ID of the inserted entity?
If the purpose is verifying the inserted entity is published, it's enough to check the value returned from $entity->isPublished(), once verified the entity object implements that method. If the hook must be invoked only for nodes, it should verify the entity is effectively a node, or the module should implement hook_ENTITY_TYPE_insert() instead of hook_entity_insert().

You're right I've changed it, my check should only validate that entity is node and is published but it should't implement hook_ENTITY_TYPE_insert as it should be for any node type selected.

     foreach ($nodes as $node) {
      $nodeTitles[$node->nid->value] = $node->title->value;
    }
 


Is necessary to use ->value to get the node ID and the title?

Not sure what you mean here? How else would you suggest to get these 2 values out of the node?

The class used to implement a service should implement an interface, to allow other modules to change the service implementation.

Agreed, I've added this now for the send function.

apaderno’s picture

If the code implements hook_node_insert(), that hook is invoked for every node, independently from its content type. If then the hook should do something only when the content type is a specific one, the code should verify the node content type is that specific one.

if ($node->bundle() == 'the content type I want') {
  // ...
}

The code to get the node ID and the title I would use is similar to the following one.

foreach ($nodes as $node) {
  $node_titles[$node->id()] = $node->getTitle();
}
apaderno’s picture

Assigned: Unassigned » apaderno
Status: Needs review » Reviewed & tested by the community
  public function send($feed) {
    $this->feed = Xss::filterAdmin($feed);
    // Connect to WebSocket and attempt to push message.
    Client\connect($this->protocol . '://' . $this->serverIP . ':' . $this->port)->then(function ($conn) {
      $conn->on('message', function ($msg) use ($conn) {
        echo $this->t('Received: @msg', ['@msg' => $msg]) . PHP_EOL;
        $conn->close();
      });

      $conn->send($this->feed);
    }, function ($e) {
      echo $this->t('Could not connect: @error', ['@error' => $e->getMessage()]) . PHP_EOL;
      \Drupal::logger('live_content')
        ->error('Connection error: @error ', ['@error' => $e->getMessage()]);
    });
  }

The sanitation functions should be used to sanitize data obtained from users when it's output on the browser, not when it's passed to another server.

I didn't find relevant issues that need to be fixed.

apaderno’s picture

Status: Reviewed & tested by the community » Fixed

Thank you for your contribution! I am going to update your account.

These are some recommended readings to help with excellent maintainership:

You can find more contributors chatting on the IRC #drupal-contribute channel. So, come hang out and stay involved.
Thank you, also, for your patience with the review process.
Anyone is welcome to participate in the review process. Please consider reviewing other projects that are pending review. I encourage you to learn more about that process and join the group of reviewers.

I thank all the dedicated reviewers as well.

seroton’s picture

@kiamlaluno and @ilchovuchkov thank you very much for valuable feedback on this, it was much better than I could hope for!

I've updated the code to be inline with last comments too.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

vuil’s picture