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
}

 

Related articles

Andrew Fletcher04 Apr 2025
Managing .gitignore changes
When working with Git, the .gitignore file plays a critical role in controlling which files and folders are tracked by version control. Yet, many developers are unsure when changes to .gitignore take effect and how to manage files that are already being tracked. This uncertainty can lead to...
Andrew Fletcher26 Mar 2025
How to fix the ‘Undefined function t’ error in Drupal 10 or 11 code
Upgrading to Drupal 10.4+ you might have noticed a warning in their code editor stating “Undefined function ‘t’”. While Drupal’s `t()` function remains valid in procedural code, some language analysis tools — such as Intelephense — do not automatically recognise Drupal’s global functions. This...
Andrew Fletcher17 Mar 2025
Upgrading to PHP 8.4 challenges with Drupal contrib modules
The upgrade from PHP 8.3.14 to PHP 8.4.4 presents challenges for Drupal 10.4 websites, particularly when dealing with contributed modules. While Drupal core operates seamlessly, various contrib modules have not yet been updated to accommodate changes introduced in PHP 8.4.x. This has resulted in...