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
}