openquestions-1.0.x-dev/src/Form/AddVoteForm.php
src/Form/AddVoteForm.php
<?php
namespace Drupal\openquestions\Form;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\openquestions\Service\VoteDataFactoryInterface;
/**
* Implements the AddVoteForm form controller.
*
* @see \Drupal\Core\Form\FormBase
*/
class AddVoteForm extends FormBase {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* The vote data factory.
*
* @var \Drupal\openquestions\Service\VoteDataFactoryInterface
*/
protected $voteDataFactory;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
// Create a new form object and inject its services.
$form = new static();
$form->setRequestStack($container->get('request_stack'));
$form->setStringTranslation($container->get('string_translation'));
$form->setMessenger($container->get('messenger'));
$form->setEntityTypeManager($container->get('entity_type.manager'));
$form->setVoteDataFactory($container->get('openquestions.oq_vote_data_factory'));
return $form;
}
/**
* Sets the entity type manager.
*/
public function setEntityTypeManager(EntityTypeManager $entityTypeManager) {
$this->entityTypeManager = $entityTypeManager;
}
/**
* Sets the vote data factory.
*/
public function setVoteDataFactory(VoteDataFactoryInterface $voteDataFactory) {
$this->voteDataFactory = $voteDataFactory;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'add_oq_vote_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $oq_item_id = NULL, $oq_question_id = NULL, $uid = NULL, $nojs = NULL) {
$form_state->set('already_voted', FALSE);
$item = $this->entityTypeManager->getStorage('oq_item')->load($oq_item_id);
$question = $this->entityTypeManager->getStorage('oq_question')->load($oq_question_id);
$account = $this->entityTypeManager->getStorage('user')->load($uid);
if (!$item || !$question || !$account ||
$account->id() != $this->currentUser()->id() || !$account->hasPermission('vote on oq item')) {
$form['error'] = [
'#type' => 'item',
'#markup' => $this->getGenericErrorMessage(),
];
return $form;
}
$form_state->set('oq_item_id', $oq_item_id);
$form_state->set('oq_question_id', $oq_question_id);
$form_state->set('uid', $uid);
// Add the core AJAX library.
$form['#attached']['library'][] = 'core/drupal.ajax';
$form['description'] = [
'#type' => 'item',
'#markup' => $this->t('Enter your vote for the question <span class="oq-question-quote">@question</span> on the item <a class="oq-item-quote" href=":item_url">"@item"</a>.', [
'@item' => $item->label(),
'@question' => $question->label(),
':item_url' => $item->toUrl()->toString(),
]),
];
$form['vote_value'] = [
'#type' => 'radios',
'#title' => 'Vote value',
'#required' => TRUE,
'#options' => $question->getOptions(),
];
// This element is responsible for displaying form errors in the AJAX
// dialog.
if ($nojs == 'ajax') {
$form['status_messages'] = [
'#type' => 'status_messages',
'#weight' => -999,
];
}
$form['rationale'] = [
'#type' => 'textarea',
'#title' => $this->t('Enter the reason for your vote'),
'#description' => $this->t('Enter at least 10 words'),
'#required' => TRUE,
'#attributes' => [
'class' => ['js-text-counter'],
],
];
// Group submit handlers in an actions element with a key of "actions" so
// that it gets styled correctly, and so that other modules may add actions
// to the form.
$form['actions'] = [
'#type' => 'actions',
];
// Add a submit button that handles the submission of the form.
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save vote'),
'#ajax' => [
'callback' => '::ajaxSubmitForm',
'event' => 'click',
],
];
$form['#attached']['library'][] = 'openquestions/jquery_textcounter';
$form['#attached']['library'][] = 'openquestions/openquestions_voting';
// Set the form to not use AJAX if we're on a nojs path. When this form is
// within the modal dialog, Drupal will make sure we're using an AJAX path
// instead of a nojs one.
if ($nojs == 'nojs') {
unset($form['actions']['submit']['#ajax']);
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($form_state->get('already_voted')) {
return;
}
$voteValue = $form_state->getValue('vote_value');
$rationale = $form_state->getValue('rationale');
$oq_item_id = $form_state->get('oq_item_id');
$oq_question_id = $form_state->get('oq_question_id');
$uid = $form_state->get('uid');
$message = $this->createVote($oq_item_id, $oq_question_id, $uid, $voteValue, $rationale);
$form_state->set('already_voted', TRUE);
$message = $message ? $message : $this->t('Your vote has been saved.');
$this->messenger()->addMessage($message);
}
/**
* Implements the submit handler for the modal dialog AJAX call.
*
* @param array $form
* Render array representing from.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Current form state.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* Array of AJAX commands to execute on submit of the modal form.
*/
public function ajaxSubmitForm(array &$form, FormStateInterface $form_state) {
$title = $this->t('Success');
$content = [
'#type' => 'item',
'#markup' => $this->t('Your vote has been saved.'),
];
// We begin building a new ajax reponse.
$response = new AjaxResponse();
if ($form_state->get('already_voted')) {
$oq_item_id = $form_state->get('oq_item_id');
$oq_question_id = $form_state->get('oq_question_id');
$uid = $form_state->get('uid');
$voteData = $this->voteDataFactory->createVoteDataForItem($oq_item_id);
$votes = $voteData->getVotesForQuestionIdAndAccountId($oq_question_id, $uid);
if ($votes && count($votes) > 0) {
$vote = array_shift($votes);
if ($vote && $vote->get('status')->value) {
$question = $this->entityTypeManager->getStorage('oq_question')->load($oq_question_id);
$selector = '#votelink-item' . intval($oq_item_id) . '-question' . intval($oq_question_id) . '-uid' . intval($uid);
$replacement = [
'#type' => 'link',
'#title' => $question->getOptionName($vote->get('vote_value')->value),
'#attributes' => ['class' => ['oq_vote']],
'#url' => $vote->toUrl(),
];
$response->addCommand(new ReplaceCommand($selector, $replacement));
}
}
$response->addCommand(new OpenModalDialogCommand($title, $content, $this->getDataDialogOptions()));
return $response;
}
// If the user submitted the form and there are errors, show them the
// input dialog again with error messages. Since the title element is
// required, the empty string wont't validate and there will be an error.
if ($form_state->getErrors()) {
// If there are errors, we can show the form again with the errors in
// the status_messages section.
$form['status_messages'] = [
'#type' => 'status_messages',
'#weight' => -10,
];
$response->addCommand(new OpenModalDialogCommand($this->t('Errors'), $form, $this->getDataDialogOptions()));
}
// If there are no errors, show the output dialog.
else {
// We don't want any messages that were added by submitForm().
$this->messenger()->deleteAll();
$oq_item_id = $form_state->get('oq_item_id');
$oq_question_id = $form_state->get('oq_question_id');
$uid = $form_state->get('uid');
$voteValue = $form_state->getValue('vote_value');
$rationale = $form_state->getValue('rationale');
$message = $this->createVote($oq_item_id, $oq_question_id, $uid, $voteValue, $rationale);
$form_state->set('already_voted', TRUE);
if ($message) {
$title = $this->t('An error occured');
$content = [
'#type' => 'item',
'#markup' => $message,
];
}
// Add the OpenModalDialogCommand to the response. This will cause Drupal
// AJAX to show the modal dialog. The user can click the little X to close
// the dialog.
$response->addCommand(new OpenModalDialogCommand($title, $content, $this->getDataDialogOptions()));
}
// Finally return our response.
return $response;
}
/**
* Create a vote from the given parameters.
*/
protected function createVote($oq_item_id, $oq_question_id, $uid, $voteValue, $rationale) {
$item = $this->entityTypeManager->getStorage('oq_item')->load($oq_item_id);
$question = $this->entityTypeManager->getStorage('oq_question')->load($oq_question_id);
$account = $this->entityTypeManager->getStorage('user')->load($uid);
if (!$item || !$question || !$account ||
$account->id() != $this->currentUser()->id() || !$account->hasPermission('vote on oq item')) {
return $this->getGenericErrorMessage();
}
$options = $question->getOptions();
if (empty($options[$voteValue])) {
return $this->getGenericErrorMessage();
}
$voteData = $this->voteDataFactory->createVoteDataForItem($oq_item_id);
$votes = $voteData->getVotesForQuestionIdAndAccountId($oq_question_id, $uid);
if ($votes && count($votes) > 0 && $votes[0]->get('status')->value) {
return $this->getAlreadyVotedMessage();
}
$label = $this->t('Vote on item @oq_item_id, question @oq_question_id by uid @uid', [
'@oq_item_id' => $oq_item_id,
'@oq_question_id' => $oq_question_id,
'@uid' => $uid,
]);
$data = [
'label' => $label,
'status' => TRUE,
'description' => $rationale,
'uid' => $account,
'vote_value' => $voteValue,
'item' => $item,
'question' => $question,
];
$vote = $this->entityTypeManager->getStorage('oq_vote')->create($data);
$vote->save();
return NULL;
}
/**
* Returns a message.
*/
protected function getGenericErrorMessage() {
return $this->t('An error occured. Please reload the page and try again. If the problem persists, contact an administrator');
}
/**
* Returns a message.
*/
protected function getAlreadyVotedMessage() {
return $this->t('You appear to have already voted on this item & question. If that is in error, please reload the page and try again. If the problem persists, contact an administrator');
}
/**
* Helper method so we can have consistent dialog options.
*
* @return string[]
* An array of jQuery UI elements to pass on to our dialog form.
*/
protected function getDataDialogOptions() {
return [
'width' => '50%',
];
}
}
