Newer
Older
<?php
/**
* @file
* Contains election.module.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\election\Entity\Election;
use Drupal\election\Entity\ElectionInterface;
use Drupal\election\Entity\ElectionPost;
use Drupal\election\Entity\ElectionPostInterface;
use Drupal\election\Entity\ElectionPostType;
* Provides a theme definition for custom content entity.
*/
function election_theme($existing, $type, $theme, $path) {
$theme = [
'election' => [
'render element' => 'elements',
'file' => 'election.page.inc',
],
'election_post' => [
'render element' => 'elements',
'file' => 'election_post.page.inc',
],
'election_ballot' => [
'render element' => 'elements',
'file' => 'election_ballot.page.inc',
],
'election_candidate' => [
'render element' => 'elements',
'file' => 'election_candidate.page.inc',
],
'election_status_summary' => [
'variables' => [
],
],
'election_actions' => [
'variables' => [
],
],
'election_post_actions' => [
'variables' => [
],
],
];
return $theme;
}
/**
* Implements hook_help().
*/
function election_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the election module.
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
case 'help.page.election':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Run democratic elections through nomination, voting and results counting.') . '</p>';
return $output;
default:
}
}
/**
* Helper function to extract the entity for the supplied route.
*
* @return null|ContentEntityInterface
*/
function election_get_route_entity() {
$route_match = \Drupal::routeMatch();
// Entity will be found in the route parameters.
if (($route = $route_match->getRouteObject()) && ($parameters = $route->getOption('parameters'))) {
// Determine if the current route represents an entity.
foreach ($parameters as $name => $options) {
if (isset($options['type']) && strpos($options['type'], 'entity:') === 0) {
$entity = $route_match->getParameter($name);
if ($entity instanceof ContentEntityInterface && $entity->hasLinkTemplate('canonical')) {
return $entity;
}
// Since entity was found, no need to iterate further.
return NULL;
}
}
}
}
/**
* Sort posts on election page by preferred eligibility order.
*
* @param array $variables
*/
function election_preprocess_views_view(&$variables) {
$view = $variables['view'];
if ($view->id() == 'election_posts_for_election_page' && $variables['display_id'] == 'embed') {
// Sort posts on election page by preferred eligibility order.
$rows = $variables['rows'];
$order = Election::getEligibilityOrder();
usort($rows, function ($a, $b) use ($order) {
$aTitle = trim(preg_replace('/<!--(.|\s)*?-->/', '', $a['#title']->__toString()));
$bTitle = trim(preg_replace('/<!--(.|\s)*?-->/', '', $b['#title']->__toString()));
// https://stackoverflow.com/questions/11145393/sorting-a-php-array-of-arrays-by-custom-order
$a = array_search($aTitle, $order);
$b = array_search($bTitle, $order);
// Both items are dont cares.
if ($a === FALSE && $b === FALSE) {
// A == b.
return 0;
}
// $a is a dont care
elseif ($a === FALSE) {
// $a > $b
return 1;
}
// $b is a dont care
elseif ($b === FALSE) {
// $a < $b
return -1;
}
else {
// Sort $a and $b ascending.
return $a - $b;
}
});
$variables['rows'] = $rows;
}
}
/**
* Implements hook_entity_extra_field_info().
*/
function election_entity_extra_field_info() {
foreach (ElectionPostType::loadMultiple() as $bundle) {
$extra['election_post'][$bundle->id()]['display']['field_post_actions'] = [
'label' => t('Action links for post'),
'description' => t('e.g. Vote, Nominate, check eligibility, depending on user, phases open, and eligibility'),
'weight' => -1,
'visible' => TRUE,
];
$extra['election_post'][$bundle->id()]['display']['field_status_summary'] = [
'label' => t('Status and eligibility summary'),
'description' => t('Shows the open/close status for each phase, and the current user\'s eligibility.'),
'weight' => 0,
'visible' => TRUE,
}
return $extra;
}
/**
* Implements hook_ENTITY_TYPE_view().
*/
function election_election_post_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
if ($display->getComponent('field_status_summary')) {
election_election_post_view_field_status_summary($build, $entity, $display, $view_mode);
}
if ($display->getComponent('field_post_actions')) {
election_election_post_view_field_post_actions($build, $entity, $display, $view_mode);
}
}
function election_election_post_view_field_status_summary(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
$summary = [
'#theme' => 'election_status_summary',
'#phases' => $entity->getUserEligibilityInformation(\Drupal::currentUser(), $entity->getEnabledPhases()),
];
$build['field_status_summary'] = [
'#type' => 'markup',
'#markup' => \Drupal::service('renderer')->render($summary),
'#cache' => [
// 'max-age' => 0,
],
];
}
function election_election_post_view_field_post_actions(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
if (method_exists($entity, 'getActionLinks')) {
$summary = [
'#theme' => 'election_post_actions',
'#actions' => $entity->getActionLinks(\Drupal::currentUser()),
];
$build['field_post_actions'] = [
'#type' => 'markup',
'#markup' => \Drupal::service('renderer')->render($summary),
'#cache' => [
// 'max-age' => 0,
],
];
}
}
function election_election_update(ElectionInterface $election) {
$phases = election_check_phase_status_changed($election);
foreach ($phases as $phase) {
$election->onPhaseOpenOrClose($phase);
}
// @todo check if conditions have changed
}
function election_election_post_update(ElectionPostInterface $election_post) {
$phases = election_check_phase_status_changed($election_post);
foreach ($phases as $phase) {
$election_post->onPhaseOpenOrClose($phase);
}
// @todo check if conditions have changed
}
function election_check_phase_status_changed($entity) {
$phases = [];
foreach (Election::getPhases() as $phase_id => $phase) {
if ($entity->original->getPhaseStatus($phase_id) != $entity->getPhaseStatus($phase_id)) {
// Phases will always have unique key as we are using $phase_id as key.
$phases[$phase_id] = $phase;
}
// $fieldsToTriggerChange = [
// 'status_' . $phase_id,
// 'status_' . $phase_id . '_open',
// 'status_' . $phase_id . '_close',
// ];
// foreach ($fieldsToTriggerChange as $field) {
// if ($entity->original->$field->value != $entity->$field->value) {
// $phases[$phase_id] = $phase;
// }
// }
}
function election_get_election_from_context() {
$context_provider = \Drupal::service('election.election_route_context');
$contexts = $context_provider->getRuntimeContexts(['election']);
$election = $contexts['election']->getContextValue();
if (!$election) {
return NULL;
}
if (is_string($election)) {
$election = ElectionPost::load($election);
}
return $election;
}
function election_get_election_post_from_context() {
$context_provider = \Drupal::service('election.election_route_context');
$contexts = $context_provider->getRuntimeContexts(['election_post']);
$election_post = $contexts['election_post']->getContextValue();
if (!$election_post) {
return NULL;
}
if (is_string($election_post)) {
$election_post = ElectionPost::load($election_post);
}
return $election_post;
}
/**
* @param mixed $form_display
* @param mixed $context
*
* @return [type]
*/
function election_entity_form_display_alter(&$form_display, $context) {
// Show nomination form for candidates if not an editor.
if ($context['entity_type'] == 'election_candidate') {
$user = \Drupal::currentUser();
$canEditFull = $user->hasPermission('add election candidate entities without eligibility');
if (!$canEditFull) {
$storage = \Drupal::service('entity_type.manager')->getStorage('entity_form_display');
$nomination_display = $storage->load($context['entity_type'] . '.' . $context['bundle'] . '.nomination');
if ($nomination_display) {
$form_display = $nomination_display;
}
}
}
}
/**
* Adds template possibility for view modes
* Implements hook_provider_theme_suggestions_hook_alter
*/
function election_theme_suggestions_election_candidate_alter(array &$suggestions, array $vars, $hook) {
if ($election_candidate = $vars['elements']['#election_candidate']) {
if (isset($vars['elements']['#view_mode'])) {
$suggestions[] = 'election_candidate__' . $vars['elements']['#view_mode'];
}
}
}
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/**
* Override ballot form error to be a bit friendlier.
*
* Implements hook_preprocess_HOOK().
*
* @param $variables
*/
function election_preprocess_status_messages(&$variables) {
if (isset($variables['message_list']['error'])) {
$error_messages = $variables['message_list']['error'];
foreach ($error_messages as $delta => $message) {
if (is_array($message)) {
continue;
}
if (strpos((string) $message, '#edit-rankings') !== FALSE) {
$variables['message_list']['error'][$delta] = t("You have not completed the ballot correctly, see highlighted areas below.");
}
}
}
}
/**
* Override format for HTML results field.
*
* @todo not sure why can't do this on entity BaseFieldDefinition?
*
* @param array $variables
* Preprocess variables.
*/
function election_preprocess_field__count_results_html(&$variables) {
if (isset($variables['items'][0]['content'])) {
$variables['items'][0]['content']['#format'] = 'full_html';
}
}
/**
* Implements hook_mail().
*/
function election_mail($key, &$message, $params) {
'langcode' => $message['langcode'],
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
switch ($key) {
case 'election.notify_user':
$message['format'] = 'text/html';
$message['headers']['Content-Type'] = 'text/html';
$message['from'] = \Drupal::config('system.site')->get('mail');
$message['subject'] = $params['subject'];
$message['body'][] = t($params['body']);
break;
}
}
/**
* Close based on access to status fields.
*
* Implements hook_entity_field_access.
*/
function election_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
if ($items && $operation == 'view') {
$entity = $items->getEntity();
if ($entity->getEntityTypeId() == 'election_candidate') {
// If results have been set, don't allow viewing candidate status until they are published.
if ($field_definition->getName() == 'candidate_status') {
if (in_array($entity->candidate_status->value, ['defeated', 'elected'])) {
if (!$account->hasPermission('view published count results')) {
return AccessResult::forbidden();
}
if (!$account->hasPermission('view unpublished count results')) {
return AccessResult::forbiddenIf(!$entity->getElectionPost()->count_results_published->value);
}
}
}
return AccessResult::neutral();
}
if ($entity->getEntityTypeId() != 'election' && $entity->getEntityTypeId() != 'election_post') {
return AccessResult::neutral();
}
if ($entity->getEntityTypeId() == 'election_post' && stristr($field_definition->getName(), 'count_')) {
if (!$account->hasPermission('view published count results')) {
return AccessResult::forbidden();
}
if (!$entity->count_results_published->value && !$account->hasPermission('view unpublished count results')) {
return AccessResult::forbidden();
}
}
$phases = Election::getPhases();
foreach ($phases as $phase_id => $phase) {
if (in_array($field_definition->getName(), ['status_' . $phase_id])) {
// If it's a scheduled election or post, make sure we run a function ONCE.
if ($entity->get('status_' . $phase_id)->getValue() == 'scheduled') {
$started = $entity->get('status_' . $phase_id . '_open')->value < strtotime('now');
$ended = $entity->get('status_' . $phase_id . '_close')->value < strtotime('now');
// Store the fact we've run it in state so we don't do it again.
if ($started) {
$state_key = 'opened_' . $entity->getEntityTypeId() . '_' . $entity->id();
$state_key = 'opened_' . $entity->getEntityTypeId() . '_' . $entity->id();
}
if (!\Drupal::state()->get($state_key)) {
$entity->onPhaseOpenOrClose($phase);
}
\Drupal::state()->set($state_key, TRUE);
}
}
}
}
// Limit editing count-related fields to a specific permission.
if ($items && $operation == 'edit') {
$entity = $items->getEntity();
if ($entity->getEntityTypeId() == 'election_post' && stristr($field_definition->getName(), 'count_')) {
return AccessResult::allowedIfHasPermission($account, 'edit count results');
}
}
return AccessResult::neutral();
}