Drupal 9: Auto Tweeting From A Drupal Site When Content Is Published

13 Jun 2022

Once upon a time, tweeting already published content from a Drupal site was a simple task that required no extra effort. The main deal was to install the Social Post Twitter module, and everything worked.
To date, this module, unfortunately, does not work appropriately on Drupal 9. But this does not mean that you need to stop integrating your site with Twitter. It is one of the main communication channels between businesses and potential customers. Integrating social media with your website increases your brand awareness, attracts users to interact, and so has a positive effect on SEO. In a word, there are a lot of pluses.
In this blog post, our Drupal 9 development team shows you how to create a Twitter integration and share tweets after the content has already been published on the Drupal 9 site. We invite you on this exciting journey.

Step-by-step guide on how to tweet from a Drupal site 9

Today, Golems will demonstrate how to set up programmatically tweets from a Drupal site if the Drupal Twitter module is not working or work improperly.

Step #1. Getting Know How the Social Post Twitter Module Works

To create something similar to the Social Post Twitter module, you need to understand the logic of its work. This module is based on the abraham/twitteroauth package.

Step #2. Get Authentication Data

Our journey begins with getting access tokens. Without this, you won't enable automatic tweeting from your Drupal site when the content is already published. Therefore, next, we go to the Developer Twitter Platform page. We should do this to apply for developer access.

There is a hidden menu in the top-right corner of the page; expand it, select Products, and click on Twitter API.

After that, we go to the top-left menu, select the "Use cases'' and then "Do research." Then we have a new window that needs to be filled in with the information to validate our Twitter Drupal @username and its email.

Then Twitter will ask you why you need the API. Select the options you need.
* If your site generates revenue or is from advertising, we advise you to choose the Professional variant.

After answering all questions:

  1. Check all completed fields before confirming.
  2. Accept the Twitter terms and conditions and verify your email address.
  3. Name your project sensibly because that name will be visible to everyone.

That's it, and now you can create your new project! A form like this will appear to you:

Step #3. Find the authentication details for your Twitter app

You can find more authentication details for your Drupal Twitter app by clicking on the key icon. As we said earlier, Twitter uses public authorization, so the secret API keys are critical. These consist of two keys: API Keys and API Secret Keys.
To create your apps, you must be given these two authentication keys. You can re-generate them by clicking the Regenerate button.

Step #4. Allow the app to Tweet on behalf of a user

It is necessary to go through the OAuth access grant workflow to set up your app to tweet on behalf of a user. There is a standard procedure for Twitter third-party integration.
Nevertheless, this procedure can be bypassed. The app you create is attached to a single user. As a result, you have all the access tokens you need to run your app. It is possible to gain access to the API without checking permissions. Doesn't that sound great?
Click the "Generate Link" button to generate access to the Twitter app. It is down at the bottom of the page.

All the previous steps will help you finally get an Access Token and an Access Token Secret keys. These keys you will use to authenticate users with the Twitter API. Save them somewhere, so you don't lose and re-generate them again.
These tokens should now be in your possession after you have followed all of those steps.

  • API Key
  • API Secret Key
  • User Access Token
  • User Access Token Secret

With these values in hand, we can now set about the code needed to post the Tweets from the Drupal site. After obtaining tokens, we can move forward with setting up the Drupal code that will allow us to post Tweets.

Step #5. Pull in the TwitterOAuth library

The TwitterOAuth library must be installed before proceeding with the actual code itself. In the future, we will use it to authenticate and work with the Twitter API. To activate it, run the following command.

composer require abraham/twitteroauth

The Twitter OAuth library is already in our repository, so we just have to create the TwitterOAuth objects.

use Abraham\TwitterOAuth\TwitterOAuth;
$consumerKey = 'api key';
$consumerSecret = 'api secret key';
$accessToken = 'access token';
$accessTokenSecret = 'access token secret';
$connection = new TwitterOAuth($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);

In this case, the connection objects enable us to check if the credentials are valid. So we run the "account/verify_credentials" method. Sometimes an "error" property occurs. This means that something went wrong, and you need to figure out precisely what happened.

$content = $connection->get("account/verify_credentials");
if (isset($content->errors)) {
  $message = $content->errors[0]->message;
  print 'An error occurred verifying credentials. Error: ' . $message;
}

To investigate what happened, we should send the payload to Twitter.

$tweet = [];
$tweet['status'] = 'The tweet';
$post = $connection->post('statuses/update', $tweet);

The "status" of this payload is the best part to make sure your tweets are getting sent.

Step #6.Integrate With Drupal

Once the library is in place, we need to create a Drupal module that contains the code. We called it "drupal_twitter_module." You may call it whatever you prefer.
Then we need to create two hooks to achieve our needed action, namely posting to Twitter when a page is published. First one is hook_ENTITY_TYPE_create(). We will use it to create a node. And the second one is hook_ENTITY_TYPE_update(). It helps us to update a node. Also, we check the node type to ensure that it is an 'article' content type. Keep your eye on the content type to avoid posting to Twitter other content types.

<?php
 
use Drupal\Core\Entity\EntityInterface;
 
/**
 * Implements hook_ENTITY_TYPE_create().
 */
function drupal_twitter_module_node_insert(EntityInterface $entity) {
  if ($entity->getType() == 'article') {
    drupal_twitter_module_publish($entity);
  }
}
 
/**
 * Implements hook_ENTITY_TYPE_update().
 */
function drupal_twitter_module_node_update(EntityInterface $entity) {
  if ($entity->getType() == 'article') {
    drupal_twitter_module_publish($entity);
  }
}

The drupal_twitter_module_publish() function extracts the necessary information from the node before posting it to Twitter. Next, we'll look at each function and what it does. The is_first_published() function remembers the time the post was first published.

<?php
 
use Drupal\node\NodeInterface;
use Abraham\TwitterOAuth\TwitterOAuth;
 
/**
 * @param NodeInterface $node
 */
function drupal_twitter_module_publish(NodeInterface $entity) {
  if ($entity->isPublished() && is_first_published($entity)) {
    // Build an array of term labels.
    $tagLabels = [];
    $tags = $entity->get('field_tags')->referencedEntities();
    foreach ($tags as $tag) {
      $tagLabels[] = '#' . $tag->label();
    }
 
    // Grab the URL for the current page.
    $urlAlias = $entity->toUrl('canonical', ['absolute' => TRUE])->toString();
 
    // Combine into a twitter status.
    $tweetText = $entity->getTitle() . ' ' . $urlAlias . ' ' . implode(' ', $tagLabels);
 
    // Set up the connection to TwitterOAuth.
    $consumerKey = 'api key';
    $consumerSecret = 'api secret key';
    $accessToken = 'access token';
    $accessTokenSecret = 'access token secret';
    $connection = new TwitterOAuth($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);
 
    // Create Tweet payload.
    $tweet = [];
    $tweet['status'] = $tweetText;
 
    // Post Tweet.
    $post = $connection->post('statuses/update', $tweet);
 
    // Log the response from the API.
    $logger = \Drupal::logger('mymodule_social_push');
 
    if (isset($post->errors)) {
      $message = $post->errors[0]->message;
      $logger->error('An error occurred when sending the tweet. Error: %error', ['%error' => $message]);
    }
    else {
      $logger->info('Post sent. Contents: %contents, Response: %response', ['%contents' => $tweet['status'], '%response' => print_r($post, true)]);
    }
  }
}

Make sure your module has a similar structure.

mymodule.info.yml
mymodule.module

Weaknesses of the above code

A barrel of honey is always tainted with a fly. There is something wrong with this Drupal Twitter module code. Let's take a closer look at the two vulnerabilities that should be fixed.

  1. There is a possibility of a codebase leak
    Use hard coding to hide the authentication details and other private information securely. In any other case, hackers will have access to all data and curation on the Twitter account.
  2. Try to avoid services to encapsulate the post
    Don't use services to encapsulate the Twitter posting process. You should separate the concerns to make testing those services more convenient.

How to fix it?

Our first step should be to create a service that encapsulates the TwitterOAuth integration.

services:
  drupal_twitter_module.twitter_social_push:
   class: Drupal\drupal_twitter_module\TwitterSocialPush
   arguments: ['@config.factory', '@state', '@logger.factory']

Using the config function in the example above, we can get some authentication details as configuration outside the code. Plus, so we can pass the state service. The app keys should be stored in the configuration, but the user authentication tokens should be stored in the API.
Drupal's logging service is the final component added to the service. Use this service to write to the Drupal logs when posting or failing to publish content to Twitter. Below is a full-service class.

<?php
namespace Drupal\ drupal_twitter_module;
 
use Drupal\Core\Config\ConfigFactoryInterface;
use Abraham\TwitterOAuth\TwitterOAuth;
use Psr\Log\LoggerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\State\StateInterface;
 
/**
 * Class TwitterSocialPush.
 *
 * @package Drupal\ drupal_twitter_module
 */
class TwitterSocialPush {
 
  /**
   * The config name.
   *
   * @var string
   */
  protected $configName = ' drupal_twitter_module.settings';
 
  /**
   * The menu link module config object.
   *
   * @var \Drupal\Core\Config\Config
   */
  protected $config;
 
  /**
   * The config factory object.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;
 
  /**
   * The state API.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;
 
  /**
   * A logger instance.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;
 
  /**
   * Constructs a StaticMenuLinkOverrides object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   A configuration factory instance.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state API.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger
   *   A logger instance.
   */
  public function __construct(ConfigFactoryInterface $config_factory, StateInterface $state, LoggerChannelFactoryInterface $logger) {
    $this->configFactory = $config_factory;
    $this->state = $state;
    $this->logger = $logger->get(' drupal_twitter_module');
  }
 
  /**
   * {@inheritdoc}
   */
  public function post(string $tweetPayload) {
 
    $consumerKey = $config->get('twitter_auth_consumer_key');
    $consumerSecret = $config->get('twitter_auth_consumer_secret');
 
    $accessToken = $this->state->get('drupal_twitter_module.twitter_auth_access_token');
    $accessTokenSecret = $this->state->get('drupal_twitter_module.twitter_auth_access_token_secret');
 
    $connection = new TwitterOAuth($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);
 
    $content = $connection->get("account/verify_credentials");
    if (isset($content->errors)) {
      $message = $content->errors[0]->message;
      $this->logger->error('An error occurred verifying credentials. Error: %error', ['%error' => $message]);
      return;
    }
 
    $tweet = [];
 
    $tweet['status'] = $tweetPayload;
    $post = $connection->post('statuses/update', $tweet);
 
    if (isset($post->errors)) {
      $message = $post->errors[0]->message;
      $this->logger->error('An error occurred when sending the tweet. Error: %error', ['%error' => $message]);
    }
    else {
      $this->logger->info('Post sent. Contents: %contents, Response: %response', ['%contents' => $tweet['status'], '%response' => print_r($post, true)]);
    }
  }
 
}

To address the first weakness of the above code, you can separate a TwitterOAuth object. Next, we use a Drupal form class to create the configuration and state items. It is a required step to extend the ConfigFormBase class and access the configuration functions. The final touch is injecting the state service into the form class.
Here is how the form submit handler should look.

public function submitForm(array &$form, FormStateInterface $form_state) {
  $config = $this->config('drupal_twitter_module.settings');
 
  $config
    ->set('twitter_auth_consumer_key', $form_state->getValue('twitter_auth_consumer_key'))
    ->set('twitter_auth_consumer_secret', $form_state->getValue('twitter_auth_consumer_secret'));
 
  $config->save();
 
  $state = [
    'drupal_twitter_module.twitter_auth_access_token' => $form_state->getValue('twitter_auth_access_token'),
    'drupal_twitter_module.twitter_auth_access_token_secret' => $form_state->getValue('twitter_auth_access_token_secret'),
  ];
  $this->state->setMultiple($state);
 
  parent::submitForm($form, $form_state);
}

The next goal is to transform the outgoing function so that the service can post the Tweet.

/**
 * @param NodeInterface $node
 */
function drupal_twitter_module_publish(NodeInterface $entity) {
  if ($entity->isPublished() && is_first_published($entity)) {
    // Build an array of term labels.
    $tagLabels = [];
    $tags = $entity->get('field_tags')->referencedEntities();
    foreach ($tags as $tag) {
      $tagLabels[] = '#' . $tag->label();
    }
 
    // Grab the URL for the current page.
    $urlAlias = $entity->toUrl('canonical', ['absolute' => TRUE])->toString();
 
    // Combine into a twitter status.
    $tweetText = $entity->getTitle() . ' ' . $urlAlias . ' ' . implode(' ', $tagLabels);
    $socialPushService = \Drupal::service('drupal_twitter_module.twitter_social_push');
    $socialPushService->post($tweetText);
  }
}

After, we need to ensure that we can unit test all the particles. Make sure all involved files are of the following structure:

mymodule.info.yml
mymodule.module
mymodule.routing.yml
/src
- TwitterSocialPush.php
- /Form
-  -  SocialPushSettingsForm.php

Wrapping up

Today we got quite an extensive blog on how to create your own Drupal Twitter module. If you can't do it yourself, click on the drupal web services page for help.

Comments

An
Anonymous
An
Anonymous
14 Tue 2022

Why not share this as a contributed module rather than encourage sites to roll their own custom ones?

An
Anonymous
14 Tue 2022

Instead of encouraging people to write the same code 100 times, why not spend the time to fix the module? Then everyone can benefit from having a working turnkey solution.