competition-8.x-1.x-dev/modules/competition_voting/competition_voting.module
modules/competition_voting/competition_voting.module
<?php
/**
* @file
* Contains competition_voting.module.
*/
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_theme().
*/
function competition_voting_theme() {
return [
'competition_voting' => [
'render element' => 'elements',
'template' => 'competition_voting',
],
];
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Add voting fields.
*/
function competition_voting_form_competition_edit_form_alter(&$form, FormStateInterface $form_state) {
$num_rounds = $form_state->get('num_rounds');
$competition = $form_state->getFormObject()->getEntity();
$judging = $competition->getJudging();
for ($i = 1; $i <= $num_rounds; $i++) {
$round = (!empty($judging->rounds[$i]) ? $judging->rounds[$i] : []);
$form['judging']['rounds'][$i]['round_type']['#options']['voting'] = t('Voting');
$states_voting = [
'visible' => [
'input[name="judging[rounds][' . $i . '][round_type]"]' => ['value' => 'voting'],
],
];
$form['judging']['rounds'][$i]['votes_allowed'] = [
'#type' => 'number',
'#title' => t('Number of votes allowed per user'),
'#default_value' => (!empty($round['votes_allowed']) ? $round['votes_allowed'] : 1),
'#description' => t('Set this to 0 to allow unlimited voting.'),
'#states' => $states_voting,
];
$form['judging']['rounds'][$i]['votes_allowed_interval_anonymous'] = [
'#type' => 'select',
'#title' => t('Voting interval for anonymous users'),
'#options' => [
0 => t('Immediately'),
300 => t('5 min'),
900 => t('15 min'),
1800 => t('30 min'),
3600 => t('1 hour'),
10800 => t('3 hours'),
21600 => t('6 hours'),
32400 => t('9 hours'),
43200 => t('12 hours'),
86400 => t('1 day'),
172800 => t('2 days'),
345600 => t('4 days'),
604800 => t('1 week'),
-1 => t('Never'),
],
'#default_value' => (!empty($round['votes_allowed_interval_anonymous']) ? $round['votes_allowed_interval_anonymous'] : 0),
'#description' => t('The amount of time that must pass before two anonymous votes from the same computer are considered unique.'),
'#states' => $states_voting,
];
$form['judging']['rounds'][$i]['votes_allowed_interval_authenticated'] = [
'#type' => 'select',
'#title' => t('Voting interval for authenticated users'),
'#options' => [
0 => t('Immediately'),
300 => t('5 min'),
900 => t('15 min'),
1800 => t('30 min'),
3600 => t('1 hour'),
10800 => t('3 hours'),
21600 => t('6 hours'),
32400 => t('9 hours'),
43200 => t('12 hours'),
86400 => t('1 day'),
172800 => t('2 days'),
345600 => t('4 days'),
604800 => t('1 week'),
-1 => t('Never'),
],
'#default_value' => (!empty($round['votes_allowed_interval_authenticated']) ? $round['votes_allowed_interval_authenticated'] : 0),
'#description' => t('The amount of time that must pass before two registered user votes from the same computer are considered unique.'),
'#states' => $states_voting,
];
$form['judging']['rounds'][$i]['voting_thanks'] = [
'#type' => 'textarea',
'#title' => t('Thank you message'),
'#default_value' => (!empty($round['voting_thanks']) ? $round['voting_thanks'] : ''),
'#states' => $states_voting,
'#required' => FALSE,
];
$form['judging']['rounds'][$i]['voting_legal_message'] = [
'#type' => 'textarea',
'#title' => t('Legal message'),
'#default_value' => (!empty($round['voting_legal_message']) ? $round['voting_legal_message'] : ''),
'#states' => $states_voting,
];
$form['judging']['rounds'][$i]['voting_inactive_redirect_path'] = [
'#type' => 'textfield',
'#title' => t('Redirect path when voting is inactive'),
'#default_value' => (!empty($round['voting_inactive_redirect_path']) ? $round['voting_inactive_redirect_path'] : ''),
'#states' => $states_voting,
];
$form['judging']['rounds'][$i]['remove']['#weight'] = 999;
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* This form is the "Assign Judges to Rounds" fieldset on the judging Setup tab.
* Remove any voting round checkbox columns entirely - since no judges are
* assigned in a voting round.
*
* @see Drupal\competition\Form\CompetitionJudgesRoundsSetupForm
*/
function competition_voting_form_competition_judges_rounds_setup_alter(&$form, FormStateInterface $form_state, $form_id) {
if (!empty($form['wrap']['assignments'])) {
$competition = $form_state->get('competition');
$voting_rounds = \Drupal::service('competition.voting')->getVotingRounds($competition);
if (!empty($voting_rounds)) {
foreach (Element::children($form['wrap']['assignments']) as $key) {
$row = &$form['wrap']['assignments'][$key];
// 'rounds' should be checkboxes.
if (!empty($row['rounds']['#options'])) {
foreach ($row['rounds']['#options'] as $round_id => $label) {
if (!empty($voting_rounds[$round_id])) {
unset($row['rounds']['#options'][$round_id]);
}
}
}
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* This is the "Manage Judging Round" fieldset on the judging Setup tab.
*
* @see Drupal\competition\Form\CompetitionJudgingRoundWorkflowForm
*/
function competition_voting_form_competition_judging_round_workflow_alter(&$form, FormStateInterface $form_state, $form_id) {
/** @var \Drupal\Core\StringTranslation\TranslationManager $translation */
$translation = \Drupal::translation();
$active_round = $form_state->get('active_round');
$active_round_type = $form_state->get('active_round_type');
$count_round_entries = $form_state->get('count_round_entries');
$count_avail_entries = $form_state->get('count_avail_entries');
if ($active_round_type == 'voting') {
// Adjust round count message language.
$status_count = '';
if ($count_round_entries > 0) {
$status_count = $translation->formatPlural($count_round_entries,
"There is 1 entry in this voting round.",
"There are @count entries in this voting round."
);
}
else {
if ($active_round > 1) {
$status_count = $translation->formatPlural($count_avail_entries,
"There is 1 entry from the previous round available for promotion to this voting round.",
"There are @count entries from the previous round available for promotion to this voting round."
);
}
else {
$status_count = $translation->formatPlural($count_avail_entries,
"There is 1 finalized entry available for promotion to this voting round.",
"There are @count finalized entries available for promotion to this voting round."
);
}
}
if (!empty($status_count)) {
$form['wrap']['status_count'] = [
'#markup' => '<p>' . $status_count . '</p>',
];
}
// Re-label "Assign entries" button to "Promote entries".
if (!empty($form['wrap']['pre_submit_assign'])) {
// This is an inline_template element.
$form['wrap']['pre_submit_assign']['#context']['text'] = t('Promote entries');
}
// Alter assignment confirmation submit to use our submit handler instead.
if (!empty($form['wrap']['sub_assign']['action_confirm']['submit_assign'])) {
$form['wrap']['sub_assign']['action_confirm']['submit_assign'] = array_merge(
$form['wrap']['sub_assign']['action_confirm']['submit_assign'],
[
'#name' => 'submit_promote',
'#submit' => [
'competition_voting_form_competition_judging_round_workflow_submit_promote',
],
]
);
}
// Re-label "Delete scores" button to "Remove entries" and tweak
// confirmation description.
if (!empty($form['wrap']['pre_submit_unassign_round'])) {
// This is an inline_template element.
$form['wrap']['pre_submit_unassign_round']['#context']['text'] = t("Remove entries");
}
if (!empty($form['wrap']['sub_unassign_round']['confirm_note'])) {
$form['wrap']['sub_unassign_round']['confirm_note']['#markup'] = '<p class="confirm-description">' . t("This will remove all entries from this round and delete their votes.") . '</p>';
}
// Remove "Generate test scores" stuff.
if (!empty($form['wrap']['pre_submit_generate_scores'])) {
unset($form['wrap']['pre_submit_generate_scores']);
}
if (!empty($form['wrap']['sub_generate_scores'])) {
unset($form['wrap']['sub_generate_scores']);
}
}
}
/**
* Submit handler for judging round workflow form.
*
* Attached to "Promote entries" confirm button.
*
* Unfortunately this duplicates much of the original form's handler,
* submitAssignEntries(), in order to run a different batch process.
*/
function competition_voting_form_competition_judging_round_workflow_submit_promote(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\competition\CompetitionJudgingSetup $judging_setup */
$judging_setup = \Drupal::service('competition.judging_setup');
/** @var \Drupal\competition_voting\CompetitionVoting $voting */
$voting = \Drupal::service('competition.voting');
$competition_id = $form_state->get('competition')->id();
$active_round = (int) $form_state->get('active_round');
$entry_ids = $form_state->get('avail_entry_ids');
// If user entered specific entry IDs, this takes precedence.
// @see CompetitionJudgingRoundWorkflowForm::validateForm()
$specific_ids = $form_state->get('specific_ids_validated');
if (!empty($specific_ids)) {
// If any specified IDs were NOT in previous round - warn user, but for
// flexibility, don't prevent moving those entries into the round.
$not_in_previous = array_diff($specific_ids, $entry_ids);
if (!empty($not_in_previous)) {
drupal_set_message(t("Warning: the following specified entry IDs have been moved into this round, but were NOT in the previous round: %ids<br/>
If this was a mistake, please remove all entries from the round and then re-add the corrected list of entry IDs.", [
'%ids' => implode(", ", $not_in_previous),
]), 'warning');
}
$entry_ids = $specific_ids;
drupal_set_message(t("Entries for this round have been limited to the listed IDs."));
}
// If user did not provide specific IDs, filter by entries that passed or
// by a min average score if provided.
if (empty($specific_ids) && !empty($entry_ids)) {
$min_score = NULL;
// Previous round was pass/fail - check the passed-only checkbox.
$passed_only = $form_state->getValue('passed_only');
if (!empty($passed_only)) {
// Currently, pass == all judges marked as pass. Since a pass ==
// score of 100, the min average for all-pass is 100.
// TODO: pass == all pass, or at least one pass?
$min_score = 100;
}
// Previous round was criteria scores - check for a min score.
$min_score_submitted = $form_state->getValue('min_score');
if (isset($min_score_submitted)) {
$min_score = $min_score_submitted;
}
// isset() ensures not NULL, but allows 0.
if (isset($min_score)) {
// We can safely subtract 1 from active round because the min-score
// field is only presented if there is a previous round.
$entry_ids = $judging_setup->filterJudgingEntries($competition_id, [
'ceid' => $entry_ids,
'round_id' => ($active_round - 1),
'min_score' => (float) $min_score,
]);
if (!empty($passed_only)) {
drupal_set_message(t("Entries for this round have been limited to those which passed in the previous round."));
}
else {
drupal_set_message(t("Entries for this round have been limited to those with a minimum average score of <strong>@min_score</strong> in the previous round.", [
'@min_score' => $min_score,
]));
}
}
}
if ($entry_ids === NULL) {
drupal_set_message(t("Entries have already been promoted to Round @round_id for voting.", [
'@round_id' => $active_round,
]), 'warning');
}
else {
$voting->addEntriesToRound($competition_id, $active_round, $entry_ids);
}
}
/**
* Implements hook_ENTITY_TYPE_view_alter().
*/
function competition_voting_competition_entry_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
if ($build['#view_mode'] == 'voting') {
$competitionVoting = \Drupal::service('competition.voting');
$judging = $entity->getCompetition()->getJudging();
$round = $judging->rounds[$judging->active_round];
// If we're in a voting round, add the voting form if user is allowed to
// in current interval.
if ($judging->active_round && $round['round_type'] === 'voting' && $competitionVoting->access($entity)) {
$build = [
'base' => $build,
'#view_mode' => $build['#view_mode'],
];
// Enforce the number of votes allowed on all contest entries per
// user per the votingapi interval.
$allow = $competitionVoting->isVoteAllowed($entity);
if ($allow) {
// All good, show the voting form.
$build['judging_voting_forms']["round_{$judging->active_round}"] = \Drupal::formBuilder()->getForm('Drupal\competition_voting\Form\CompetitionEntryVoteForm', $entity);
}
$build['judging_voting_forms']['#weight'] = 999;
// Since actual voting form and the voting-disallowed message are in the
// same place in render array, add a convenience boolean for templates to
// check.
$build['#voting_allowed_on_this_entry'] = $allow;
// Allow custom markup on an entry that user has voted for (within
// configured interval).
// Note this is a different check than whether voting is allowed on entry,
// since there may be a limit on total user votes across all entries.
$user_votes_this = $competitionVoting->getVoteCount([
'ceid' => $entity->id(),
'source_id' => $competitionVoting->getUserSourceId(),
]);
$build['#user_voted_for_this_entry'] = (bool) $user_votes_this;
}
}
}
/**
* Prepare variables for competition entry voting template.
*
* @param array $variables
* Associative array of template variables.
*
* @see CompetitionVotingController::vote()
* @see competition_entry-voting.html.twig
*/
function template_preprocess_competition_voting(array &$variables) {
$custom_vars = [
'voting_allowed_on_this_entry',
'user_voted_for_this_entry',
];
foreach ($custom_vars as $custom) {
// These are only set when voting round is active, so we set defaults.
$variables[$custom] = isset($variables['elements']['#' . $custom]) ? $variables['elements']['#' . $custom] : FALSE;
}
// Voting messages.
$variables['round_description'] = $variables['elements']['#voting_criteria_description'];
$variables['voting_thanks'] = $variables['elements']['#voting_thanks'];
$variables['legal_message'] = $variables['elements']['#voting_legal_message'];
// Determine overall "status" of page by combining status of each entry.
// @see competition_voting_competition_entry_view_alter()
$variables['can_vote_currently'] = TRUE;
$variables['already_voted'] = FALSE;
// Entries.
foreach (Element::children($variables['elements']) as $key) {
$variables['entries'][$key] = $variables['elements'][$key];
if (isset($variables['entries'][$key]['#voting_allowed_on_this_entry']) && !$variables['entries'][$key]['#voting_allowed_on_this_entry']) {
$variables['can_vote_currently'] = FALSE;
}
if (isset($variables['entries'][$key]['#user_voted_for_this_entry']) && $variables['entries'][$key]['#user_voted_for_this_entry']) {
$variables['already_voted'] = TRUE;
}
}
if (!$variables['can_vote_currently']) {
foreach ($variables['entries'] as &$entry) {
unset($entry['judging_voting_forms']);
}
}
}
