contacts_events-8.x-1.x-dev/modules/teams/src/Controller/TeamApplicationController.php
modules/teams/src/Controller/TeamApplicationController.php
<?php
namespace Drupal\contacts_events_teams\Controller;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\contacts_events\Entity\TicketInterface;
use Drupal\contacts_events_teams\Entity\TeamApplication;
use Drupal\contacts_events_teams\Plugin\TeamApplicationStep\TeamApplicationStepManager;
use Drupal\contacts_events_teams\TeamQueries;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Link;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Controller for managing team application process.
*
* @package Drupal\contacts_events_teams\Controller
*/
class TeamApplicationController extends ControllerBase {
/**
* Team app step manager.
*
* @var \Drupal\contacts_events_teams\Plugin\TeamApplicationStep\TeamApplicationStepManager
*/
protected $stepManager;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The team queries service.
*
* @var \Drupal\contacts_events_teams\TeamQueries
*/
protected $queries;
/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* The block manager service.
*
* @var \Drupal\Core\Block\BlockManagerInterface
*/
protected $blockManager;
/**
* Plugin context handler.
*
* @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
*/
protected $contextHandler;
/**
* Reference field helper.
*
* @var \Drupal\contacts_references\ReferenceFieldHelper
*/
protected $referenceFieldHelper;
/**
* TeamApplicationController constructor.
*
* @param \Drupal\contacts_events_teams\Plugin\TeamApplicationStep\TeamApplicationStepManager $step_manager
* Application step plugin manager.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Drupal\contacts_events_teams\TeamQueries $queries
* The team queries service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\Core\Block\BlockManagerInterface $block_manager
* The block manager service.
* @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler
* The context handler service.
*/
public function __construct(TeamApplicationStepManager $step_manager, DateFormatterInterface $date_formatter, TeamQueries $queries, TimeInterface $time, BlockManagerInterface $block_manager, ContextHandlerInterface $context_handler) {
$this->stepManager = $step_manager;
$this->dateFormatter = $date_formatter;
$this->queries = $queries;
$this->time = $time;
$this->blockManager = $block_manager;
$this->contextHandler = $context_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.team_application_step'),
$container->get('date.formatter'),
$container->get('contacts_events_teams.queries'),
$container->get('datetime.time'),
$container->get('plugin.manager.block'),
$container->get('context.handler')
);
}
/**
* Determines whether the team application can be accessed (apply).
*
* @param \Drupal\contacts_events\Entity\TicketInterface $contacts_ticket
* The ticket we want to apply for.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* Access result.
*/
public function accessApplication(TicketInterface $contacts_ticket, AccountInterface $account) {
// Forbid access for any status other than currently in progress.
if ($contacts_ticket->getStatus() != 'team_app_in_progress') {
return AccessResult::forbidden('Team application is not in progress.')
->addCacheableDependency($contacts_ticket);
}
// Allow if the ticket owner matches the current user.
$result = AccessResult::allowedIf($contacts_ticket->getTicketHolderId() === (int) $account->id())
->addCacheableDependency($contacts_ticket)
->addCacheContexts(['user']);
// Also allow if user has the right permission.
return $result->orIf(AccessResult::allowedIfHasPermission($account, 'manage all contacts events team applications'));
}
/**
* The team application process.
*
* @param \Drupal\contacts_events\Entity\TicketInterface $contacts_ticket
* The ticket we want to apply for.
* @param string|null $step
* The step of the application form to show..
*
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
* Render array or redirect response.
*/
public function apply(TicketInterface $contacts_ticket, ?string $step) {
// If this is not a team ticket, redirect with a message.
if (!$contacts_ticket->get('is_team_ticket')->value) {
$this->messenger()->addWarning($this->t('Your booking manager has not indicated that you wish to apply for a team place. Please ask them to update the details on your booking to continue.'));
return $this->redirect('contacts_events_teams.my_teams', [
'user' => $contacts_ticket->getTicketHolderId(),
]);
}
$app = $this->queries->getTeamApplicationForTicket($contacts_ticket);
// Redirect back to the user dashboard if the application is already
// submitted.
if ($app && $app->isSubmitted()) {
// If the application is already submitted, just redirect back to the
// booking summary.
$this->messenger()->addMessage("Your application has already been submitted. You can track the status of your application from your 'My Account' page.");
// @todo Assume this should redirect somewhere different if applicant
// is not booking manager?
return $this->redirect('contacts_events_teams.my_teams', [
'user' => $contacts_ticket->getTicketHolderId(),
]);
}
$progress = $this->stepManager->getProgressIndicator($contacts_ticket, $step);
// Show our step form.
$step = $this->stepManager->getStepForm($contacts_ticket, $step);
if ($step == NULL) {
throw new NotFoundHttpException();
}
return [$progress, $step];
}
/**
* Lists team applications for a user.
*
* @param \Drupal\user\Entity\User $user
* The user.
*
* @return array
* Render array.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function myActiveTeams(User $user) {
/** @var \Drupal\contacts_events\Entity\TicketInterface[] $tickets */
$tickets = $this->loadTeamTickets($user);
$build['table'] = [
'#type' => 'table',
'#header' => [
'event' => $this->t('Event'),
'date' => $this->t('Date'),
'team' => $this->t('Team'),
'status' => $this->t('Status'),
],
'#rows' => [],
'#empty' => $this->t("You don't have any active team applications. To apply for team, please book onto an event and select to be on team when adding your ticket."),
];
$has_actions = FALSE;
$check_references = FALSE;
$has_references = FALSE;
if ($this->moduleHandler()->moduleExists('contacts_references')) {
$check_references = TRUE;
$this->referenceFieldHelper = \Drupal::service('contacts_references.reference_field_helper');
}
foreach ($tickets as $ticket) {
if ($ticket->getStatus() == 'pending') {
continue;
}
/** @var \Drupal\contacts_events\Entity\EventInterface $event */
$event = $ticket->get('event')->entity;
$app = $this->queries->getTeamApplicationForTicket($ticket);
$row = [
'event' => $event->label(),
'date' => $this->dateFormatter->format($event->get('date')->start_date->getTimestamp(), 'short'),
'team' => $ticket->team->entity->label(),
'status' => $this->getStatus($app),
];
if ($check_references) {
$reference_status = $this->getReferenceStatus($app);
if ($reference_status) {
$has_references = TRUE;
$row['references'] = $reference_status;
}
}
if (!$app || $app->get('state')->value === 'draft') {
$has_actions = TRUE;
$text = $app ? $this->t('Continue application') : $this->t('Start application');
$row['action'] = Link::createFromRoute($text,
'contacts_events_teams.application_flow',
['contacts_ticket' => $ticket->id()]);
}
$build['table']['#rows'][$ticket->id()] = $row;
}
if ($has_references) {
$build['table']['#header']['references'] = 'References';
}
if ($has_actions) {
$build['table']['#header']['action'] = '';
}
// Disable caching because user could be staff viewing different user pages.
$build['#cache'] = ['max-age' => 0];
return $build;
}
/**
* Lists team applications for a user for events that have passed.
*
* @param \Drupal\user\Entity\User $user
* The user.
*
* @return array
* Render array.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function myPastTeams(User $user) {
/** @var \Drupal\contacts_events\Entity\TicketInterface[] $tickets */
$tickets = $this->loadTeamTickets($user, TRUE);
$build['table'] = [
'#type' => 'table',
'#header' => [
'event' => $this->t('Event'),
'date' => $this->t('Date'),
'team' => $this->t('Team'),
'status' => $this->t('Status'),
],
'#rows' => [],
'#empty' => $this->t("You don't have any past team applications."),
];
foreach ($tickets as $ticket) {
if ($ticket->getStatus() == 'pending') {
continue;
}
/** @var \Drupal\contacts_events\Entity\EventInterface $event */
$event = $ticket->get('event')->entity;
$app = $this->queries->getTeamApplicationForTicket($ticket);
$row = [
'event' => $event->label(),
'date' => $this->dateFormatter->format($event->get('date')->start_date->getTimestamp(), 'short'),
'team' => $ticket->team->entity->label(),
'status' => $this->getStatus($app),
];
$build['table']['#rows'][$ticket->id()] = $row;
}
// Disable caching because user could be staff viewing different user pages.
$build['#cache'] = ['max-age' => 0];
return $build;
}
/**
* Load any team tickets (past or future) for the current user.
*/
private function loadTeamTickets($user, $past_events = FALSE) {
$storage = $this->entityTypeManager()
->getStorage('contacts_ticket');
$query = $storage->getQuery();
$query->accessCheck(TRUE);
$query->condition('contact', $user->id());
$query->condition('is_team_ticket', TRUE);
if ($past_events) {
$query->condition('event.entity.date.end_value', date(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $this->time->getRequestTime()), '<=');
}
else {
$query->condition('event.entity.date.end_value', date(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $this->time->getRequestTime()), '>');
}
$query->sort('event.entity.date.value', 'DESC');
return $storage->loadMultiple($query->execute());
}
/**
* Displays team applications for past and upcoming events.
*
* @param \Drupal\user\UserInterface $user
* The user being viewed.
*
* @return array
* Render array.
*/
public function myTeams(UserInterface $user): array {
$build = [];
// Convert the AccountInterface back into an underlying user entity.
$current_user = $this->entityTypeManager()->getStorage('user')
->load($this->currentUser()->id());
$contexts = [
'current_user' => EntityContext::fromEntity($current_user),
'user' => EntityContext::fromEntity($user),
];
$build['upcoming_title'] = [
'#markup' => $this->t('<h2>Active team applications</h2>'),
];
$build['upcoming'] = $this->myActiveTeams($user);
$build['past_title'] = [
'#markup' => $this->t('<h2>Past team applications</h2>'),
];
$build['past'] = $this->myPastTeams($user);
return $build;
}
/**
* Builds the block output.
*
* @param string $block_id
* Block ID to build.
* @param array $contexts
* Contexts for the block.
*
* @return array
* Render array.
*/
private function buildBlock($block_id, array $contexts) {
$block = $this->blockManager->createInstance($block_id);
if ($block instanceof ContextAwarePluginInterface) {
$this->contextHandler->applyContextMapping($block, $contexts);
}
return $block->build();
}
/**
* Gets the public status of a team application.
*
* This doesn't return the literal status values, as we don't necessarily
* want to show users that they've been rejected for a team straight away.
*
* @todo Don't hard-code these?
*
* @param \Drupal\contacts_events_teams\Entity\TeamApplication|null $app
* The team application.
*
* @return string
* The public status label.
*/
private function getStatus(?TeamApplication $app) : string {
if (!$app) {
return $this->t('Not started');
}
switch ($app->get('state')->value) {
case 'draft':
return $this->t('Not Submitted');
case 'submitted':
case 'references_received':
case 'references_reviewed':
case 'accepted':
case 'rejected':
$message = $this->t('Submitted');
// Checkout for dbs integration.
if ($this->moduleHandler()->moduleExists('contacts_dbs')) {
if ($app->getTeam() && $app->getTeam()->hasField('dbs_workforce') && $workforce = $app->getTeam()->get('dbs_workforce')->value) {
$message = $this->getDbsStatus($app, $workforce);
}
}
return $message;
case 'approved':
return $this->t('Accepted');
}
}
/**
* Check DBS record for specific status information.
*
* @param \Drupal\contacts_events_teams\Entity\TeamApplication $app
* The team application.
* @param string $workforce
* The workforce to check against.
*
* @return string
* The public status label for DBS.
*
* @see \Drupal\contacts_events_teams\Plugin\views\field\DbsStatus::getDbsStatus
*/
private function getDbsStatus(TeamApplication $app, $workforce) : string {
/** @var \Drupal\contacts_dbs\DBSManager $dbs_manager */
$dbs_manager = \Drupal::service('contacts_dbs.dbs_manager');
$dbs_record = $dbs_manager->getDbs($app->getOwnerId(), $workforce);
$dbs_status = $dbs_record ? $dbs_record->get('status')->value : NULL;
switch ($dbs_status) {
case 'letter_required':
case 'letter_sent':
case 'dbs_expired':
return $this->t('Submitted (DBS required)');
case 'disclosure_requested':
case 'update_service_check_required':
case 'disclosure_review':
return $this->t('Submitted (DBS in progress)');
case 'dbs_clear':
case 'update_service_checked':
case 'dbs_exception':
case 'disclosure_accepted':
case 'living_abroad':
return $this->t('Submitted (DBS clear)');
case 'dbs_not_clear':
return $this->t('Contact the Office');
case NULL:
return $this->t('DBS Error');
default:
return $this->t('Submitted');
}
}
/**
* Gets the state of submitted references.
*
* @param \Drupal\contacts_events_teams\Entity\TeamApplication|null $app
* The team application.
*
* @return string
* The public reference status label.
*/
private function getReferenceStatus(?TeamApplication $app) : string {
if (!$app) {
return '';
}
switch ($app->get('state')->value) {
case 'submitted':
$submitted_count = 0;
$total_count = 0;
foreach ($this->referenceFieldHelper->findReferences($app) as $reference) {
$total_count++;
if (!in_array($reference->get('state')->value, [
'pending',
'requested',
])) {
$submitted_count++;
}
}
if ($submitted_count == 0) {
return $this->t('Waiting for references');
}
if ($submitted_count == 1) {
return $this->t('One reference received');
}
if ($submitted_count < $total_count) {
return $this->t('@count references received', ['@count' => $submitted_count]);
}
return $this->t('References received');
case 'references_received':
return $this->t('References received');
default:
return '';
}
}
}
