Skip to main content

 

Using the Drupal Poll module and passing via RESTful API -  How do you get it working?

 

The initial set-up

POST: {domain}/mhc_custom/poll?_format=json
Content-type: application/json
Accept: application/json

{
  "choice": "1"
}

 

If you run the above credentials, you will find the following response

{
    "message": "The 'restful post mhc_custom_poll_submit' permission is required."
}

 

Let's have a look at the backend

path: Admin > Configuration > Web services > REST

Methods POST
Accepted request formats json
Authentication providers cookie

The set-up does not pass a cookie.

When the user was logged in the response looked similar to

{
    "current_user": {
        "uid": "200",
        "name": "jacque"
    },
    "csrf_token": "kHVeh_ZWh5mwzZxrZGwc0smH3M5vfNX5C6H6oFyEVik",
    "logout_token": "VGvCWlByqwU4r5d_D45t4Kj1TShDaCdNrEZZ3qtuAUQ"
}

The csrf_token needs to be used.  In your headers add

"X-CSRF-Token": "kHVeh_ZWh5mwzZxrZGwc0smH3M5vfNX5C6H6oFyEVik"

Whereas you can see the csrf_token matches the X-CSRF-Token

Now finally you can add the poll ID and the user's selection:

{
  "choice": "{choice_id}",
  "poll_id": "{poll_id}"
}

Replace the following values:

{poll_id} - Poll being targeted

{choice_id} - Corresponding selection from the Poll

{
  "choice": "1",
  "poll_id": "1"
}

 

Response on success

{
    "success": true
}

 

Using REST API to get the poll results

First, in the rest directory of my custom module, I created code that leveraged the Poll module architecture. No major rewriting, more grabbing the key elements that I needed.

<?php

namespace Drupal\mhc_custom\Plugin\rest\resource;

use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\poll\Entity\Poll;

/**
 * Provides a resource to get view modes by entity and bundle.
 *
 * @RestResource(
 *   id = "mhc_custom_poll_results",
 *   label = @Translation("MHC:Poll results"),
 *   uri_paths = {
 *     "canonical" = "/mhc_custom/poll/results/{id}"
 *   }
 * )
 */
class PollResults extends ResourceBase
{

    /**
     * A current user instance.
     *
     * @var \Drupal\Core\Session\AccountProxyInterface
     */
    protected $currentUser;

    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
        $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
        $instance->logger = $container->get('logger.factory')->get('mhc_custom');
        $instance->currentUser = $container->get('current_user');

        return $instance;
    }

    /**
     * Responds to GET requests.
     *
     * @param array $data
     *
     * @return \Drupal\rest\ModifiedResourceResponse
     *   The HTTP response object.
     *
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     *   Throws exception expected.
     */
    public function get($id = 0)
    {
        $options = [];
        $options['uid'] = $this->currentUser->id();
        $options['pid'] = $id;
        $options['hostname'] = \Drupal::request()->getClientIp();
        $options['timestamp'] = \Drupal::time()->getRequestTime();

        $response = [];
        $entity = Poll::load($options['pid']);

        // Validate that a poll_id value (id) has been entered and the
        // poll exists.
        if ($id > 0 && is_object($entity)) {
            /** @var \Drupal\poll\PollVoteStorage $vote_storage */
            $vote_storage = \Drupal::service('poll_vote.storage');
            $response['totalVotes'] = $vote_storage->getTotalVotes($entity);
            $response['votes'] = $vote_storage->getVotes($entity);
            // $poll = \Drupal::service('poll');
            // $response['options'] = $poll->getOptions($poll);
        }

        return new ModifiedResourceResponse($response);
    }

}

Checking the REST response through Postman:

GET: {domain}/custom/poll/results/{poll_id}?_format=json
Content-type: application/json
Accept: application/json

Replace {poll_id} with the Poll you are targeting.

GET: {domain}/custom/poll/results/1?_format=json
Content-type: application/json
Accept: application/json

Response

{
    "totalVotes": "1",
    "votes": {
        "1": "1",
        "2": 0,
        "3": 0
    }
}

Done.

 

An issue with the current set-up

The code is being run through REST API from the backend Drupal 9 to the frontend React.  However, React isn't determining whether the user is logged in or not.  Therefore, having separate REST calls is not pragmatic.

As a solution, I altered my poll script to now to provide GET and POST functions.  Below the Poll ID is known and presented in the path.

GET: {domain}/mhc_custom/poll/status/1?_format=json
Content-type: application/json
Accept: application/json

If the current user has voted, then the response will be something similar to 

{
    "totalVotes": "2",
    "votes": {
        "1": "1",
        "2": "1",
        "3": 0
    }
}

Above you are seeing the poll JSON results screen.

 

What happens if the current user hasn't responded?

{
    "id": [
        {
            "value": 1
        }
    ],
    "uid": [
        {
            "target_id": 1,
            "target_type": "user",
            "target_uuid": "72fac3b3-5f28-2f3c-81b2-457c79e364cb",
            "url": "/user/39"
        }
    ],
    "uuid": [
        {
            "value": "19e62209-5b5b-40d7-b8a5-b200da5ad461"
        }
    ],
    "question": [
        {
            "value": "Poll"
        }
    ],
    "langcode": [
        {
            "value": "en"
        }
    ],
    "choice": [
        {
            "target_id": 1,
            "target_type": "poll_choice",
            "target_uuid": "fc62f72c-8a44-4f87-8fd6-2286149ec3b9"
        },
        {
            "target_id": 2,
            "target_type": "poll_choice",
            "target_uuid": "af80c35a-e944-4d8e-b272-670d03cf87e2"
        },
        {
            "target_id": 3,
            "target_type": "poll_choice",
            "target_uuid": "617ca24d-f1fd-4342-a0cc-5e0df7ccb7c3"
        }
    ],
    "runtime": [
        {
            "value": 0
        }
    ],
    "anonymous_vote_allow": [
        {
            "value": false
        }
    ],
    "cancel_vote_allow": [
        {
            "value": true
        }
    ],
    "result_vote_allow": [
        {
            "value": false
        }
    ],
    "status": [
        {
            "value": true
        }
    ],
    "created": [
        {
            "value": "2022-04-28T00:29:11+00:00",
            "format": "Y-m-d\\TH:i:sP"
        }
    ],
    "default_langcode": [
        {
            "value": true
        }
    ],
    "metatag": {
        "value": {
            "canonical_url": "http://your-site.com/mhc_custom/poll/status/1",
            "robots": "noindex",
            "title": "| My site title"
        }
    }
}

 

What about if the Poll ID is unknown

When the Poll ID is unknown, we are working from the premise that there is at least a Poll that has an active state.  In this instance use zero (0)

GET: {domain}/mhc_custom/poll/status/0?_format=json
Content-type: application/json
Accept: application/json

The response will be same as above pending whether the user has previously voted or not.

 

Cancel a vote

Need to cancel a vote using REST API?  Whilst you can using the Poll module, I wanted the ability for a future flexibility and hence a new file was created PollCancelVote.php

<?php

namespace Drupal\mhc_custom\Plugin\rest\resource;

use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
// use Drupal\rest\ResourceResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
// use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Drupal\poll\Entity\Poll;
// use PhpParser\Builder\Interface_;

/**
 * Provides a resource to get view modes by entity and bundle.
 *
 * @RestResource(
 *   id = "mhc_custom_poll_cancel",
 *   label = @Translation("MHC:Poll cancel"),
 *   uri_paths = {
 *     "create" = "/mhc_custom/poll/cancel/{id}"
 *   }
 * )
 */
class PollCancelVote extends ResourceBase
{
    /**
     * A current user instance.
     *
     * @var \Drupal\Core\Session\AccountProxyInterface
     */
    protected $currentUser;

    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition)
    {
        $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
        $instance->logger = $container->get('logger.factory')->get('mhc_custom');
        $instance->currentUser = $container->get('current_user');

        return $instance;
    }

    /**
     * Responds to POST requests.
     *
     * @param array $data
     *
     * @return \Drupal\rest\ModifiedResourceResponse
     *   The HTTP response object.
     *
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     *   Throws exception expected.
     */
    public function post($data)
    {
        $pid = $data['poll_id'];

        $entity = Poll::load($pid);

        $response = [];
        $response['statusCode'] = 403;
        $response['success'] = false;

        if ($pid > 0 && is_object($entity)) {
            // @var \Drupal\poll\PollVoteStorage $vote_storage
            $vote_storage = $this->getStorageVote();
            $vote_storage->cancelVote($entity, $this->currentUser);
            $response['statusCode'] = 200;
            $response['success'] = true;
        }

        return new ModifiedResourceResponse($response);
    }

    /**
     * Undocumented function
     *
     * @return \Drupal\poll\PollVoteStorage $vote_storage
     */
    public function getStorageVote()
    {
        return \Drupal::service('poll_vote.storage');
    }

}

 

The REST call headers:

POST: {domain}/mhc_custom/poll/cancel/{poll_id}?_format=json
Content-type: application/json
Accept: application/json
X-CSRF-Token: {token_value}

The path contains a numeric value of the poll id {poll_id}.  Replace the {poll_id} with the poll you are targeting.

REST body:

{
    "poll_id": "{poll_id}"
}

 

Cancel response

On success, the response will be

{
    "statusCode": 200,
    "success": true
}

Whereas, on failure the response is

{
    "statusCode": 403,
    "success": false
}