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 ''; } } }