contacts_events-8.x-1.x-dev/modules/teams/contacts_events_teams.module
modules/teams/contacts_events_teams.module
<?php /** * @file * Contains contacts_events_teams.module. */ use Drupal\contacts_dbs\Entity\DBSWorkforce; use Drupal\contacts_events\Controller\TicketsController; use Drupal\contacts_events\Entity\EventInterface; use Drupal\contacts_events\Entity\EventType; use Drupal\contacts_events\Entity\Ticket; use Drupal\contacts_events_teams\Controller\TeamApplicationController; use Drupal\contacts_events_teams\Entity\Team; use Drupal\contacts_events_teams\Entity\TeamApplication; use Drupal\contacts_events_teams\Entity\TeamInterface; use Drupal\Core\Access\AccessResult; use Drupal\Core\Database\Query\AlterableInterface; use Drupal\Core\Database\Query\Select; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\PluralTranslatableMarkup; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\entity\BundleFieldDefinition; use Drupal\user\UserInterface; /** * Implements hook_theme(). */ function contacts_events_teams_theme($existing, $type, $theme, $path) { return [ 'c_events_team_app' => [ 'render element' => 'elements', 'file' => 'c_events_team_app.page.inc', ], ]; } /** * Implements hook_entity_base_field_info(). */ function contacts_events_teams_entity_base_field_info(EntityTypeInterface $entity_type) { $fields = []; if ($entity_type->id() == 'contacts_event') { // @todo Remove the has_teams after data has been migrated to settings. $fields['has_teams'] = BaseFieldDefinition::create('boolean') ->setName('has_teams') ->setTargetEntityTypeId($entity_type->id()) ->setLabel(new TranslatableMarkup('This event has teams')) ->setDefaultValue(FALSE) ->setDisplayOptions('form', ['region' => 'hidden']) ->setDisplayOptions('view', ['region' => 'hidden']) ->setDisplayConfigurable('form', FALSE) ->setDisplayConfigurable('form', FALSE); } if ($entity_type->id() === 'c_events_team'&& \Drupal::moduleHandler()->moduleExists('contacts_dbs')) { $fields['dbs_workforce'] = BaseFieldDefinition::create('list_string') ->setName('dbs_workforce') ->setTargetEntityTypeId($entity_type->id()) ->setLabel(new TranslatableMarkup('DBS Workforce')) ->setDefaultValue(NULL) // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration ->setSetting('allowed_values_function', [DBSWorkforce::class, 'getOptions']) ->setDisplayConfigurable('form', TRUE) ->setDisplayOptions('form', [ 'type' => 'options_select', 'weight' => 9, ]) ->setDisplayConfigurable('view', TRUE) ->setDisplayOptions('view', [ 'region' => 'hidden', ]); } return $fields; } /** * Implements hook_entity_field_storage_info(). */ function contacts_events_teams_entity_field_storage_info(EntityTypeInterface $entity_type) { $fields = []; if ($entity_type->id() === 'contacts_ticket') { $fields['is_team_ticket'] = BundleFieldDefinition::create('boolean') ->setName('is_team_ticket') ->setTargetEntityTypeId('contacts_ticket') ->setLabel(new TranslatableMarkup('Is this a team ticket?')); $fields['team'] = BundleFieldDefinition::create('entity_reference') ->setName('team') ->setTargetEntityTypeId('contacts_ticket') ->setSetting('target_type', 'c_events_team') ->setLabel(new TranslatableMarkup('Team')); } elseif ($entity_type->id() === 'c_events_team') { $fields['application_request_team_enabled'] = BundleFieldDefinition::create('boolean') ->setName('application_request_team_enabled') ->setTargetEntityTypeId('c_events_team') ->setLabel(new TranslatableMarkup('Enable application request (with team)?')); $fields['application_request_team_b_man'] = BundleFieldDefinition::create('boolean') ->setName('application_request_team_b_man') ->setTargetEntityTypeId('c_events_team') ->setLabel(new TranslatableMarkup('Do not send to the booking manager')); $fields['application_request_team_subject'] = BundleFieldDefinition::create('string') ->setName('application_request_team_subject') ->setTargetEntityTypeId('c_events_team') ->setLabel(new TranslatableMarkup('Application request subject (with team)')); $fields['application_request_team_body'] = BundleFieldDefinition::create('text_long') ->setName('application_request_team_body') ->setTargetEntityTypeId('c_events_team') ->setLabel(new TranslatableMarkup('Application request email (with team)')); $fields['application_accepted_enabled'] = BundleFieldDefinition::create('boolean') ->setName('application_accepted_enabled') ->setTargetEntityTypeId('c_events_team') ->setLabel(new TranslatableMarkup('Enable application accepted?')); $fields['application_accepted_subject'] = BundleFieldDefinition::create('string') ->setName('application_accepted_subject') ->setTargetEntityTypeId('c_events_team') ->setLabel(new TranslatableMarkup('Application accepted subject')); $fields['application_accepted_body'] = BundleFieldDefinition::create('text_long') ->setName('application_accepted_body') ->setTargetEntityTypeId('c_events_team') ->setLabel(new TranslatableMarkup('Application accepted email')); $fields['application_submitted_enabled'] = BundleFieldDefinition::create('boolean') ->setName('application_submitted_enabled') ->setTargetEntityTypeId('c_events_team') ->setLabel(new TranslatableMarkup('Enable application submitted?')); $fields['application_submitted_subject'] = BundleFieldDefinition::create('string') ->setName('application_submitted_subject') ->setTargetEntityTypeId('c_events_team') ->setLabel(new TranslatableMarkup('Application submitted subject')); $fields['application_submitted_body'] = BundleFieldDefinition::create('text_long') ->setName('application_submitted_body') ->setTargetEntityTypeId('c_events_team') ->setLabel(new TranslatableMarkup('Application submitted email')); } return $fields; } /** * Implements hook_entity_bundle_field_info(). */ function contacts_events_teams_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) { $fields = []; if ($entity_type->id() === 'contacts_ticket') { $fields['is_team_ticket'] = BundleFieldDefinition::create('boolean') ->setName('is_team_ticket') ->setTargetEntityTypeId('contacts_ticket') ->setLabel(new TranslatableMarkup('Is this a team ticket?')) ->setDisplayOptions('form', [ 'type' => 'boolean_checkbox', 'weight' => 9, ]); $fields['team'] = BundleFieldDefinition::create('entity_reference') ->setName('team') ->setTargetEntityTypeId('contacts_ticket') ->setLabel(new TranslatableMarkup('Team')) ->setDescription(new TranslatableMarkup('The team, if any, that this ticket holder is applying for. (Please enter the applicants own unique email address in the email address field above).')) ->setSetting('target_type', 'c_events_team') ->setSetting('handler', 'active_teams:c_events_team') ->setDisplayOptions('form', [ 'type' => 'options_select', 'weight' => 10, ]) ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE); } return $fields; } /** * Implements hook_entity_field_access(). */ function contacts_events_teams_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { // Don't allow modifying team status for cancelled tickets. if ($field_definition->getTargetEntityTypeId() == 'contacts_ticket') { if (in_array($field_definition->getName(), ['is_team_ticket', 'team'])) { /** @var \Drupal\contacts_events\Entity\TicketInterface|null $ticket */ $ticket = $items ? $items->getEntity() : NULL; if ($ticket) { if ($ticket->getStatus() == 'cancelled') { return AccessResult::forbidden(); } if ($event = $ticket->getEvent()) { $team_enabled_state = $event->getSetting('teams.enabled', TeamInterface::STATUS_CLOSED); // Check event has teams enabled. if ($team_enabled_state == TeamInterface::STATUS_CLOSED) { return AccessResult::forbidden(); } // Don't display teams if they're private and user isn't staff. elseif ($team_enabled_state == TeamInterface::STATUS_PRIVATE && !$account->hasPermission('can manage bookings for contacts_events')) { return AccessResult::forbidden(); } } } } } return AccessResult::neutral(); } /** * Implements hook_entity_access(). */ function contacts_events_teams_entity_access(EntityInterface $entity, $operation, AccountInterface $account) { // Don't allow team tickets to be transferred between orders. if ($entity->getEntityTypeId() == 'contacts_ticket' && $operation == 'transfer') { return AccessResult::forbiddenIf($entity->get('is_team_ticket')->value, 'Ticket transfer does not support team tickets'); } return AccessResult::neutral(); } /** * Implements hook_workflows_alter(). */ function contacts_events_teams_workflows_alter(array &$workflows) { // Remove the reference steps if the module is not installed. if (isset($workflows['contacts_events_teams_application_process']) && !\Drupal::moduleHandler()->moduleExists('contacts_references')) { $states = ['references_received', 'references_reviewed']; $workflow = &$workflows['contacts_events_teams_application_process']; // Remove the reference states. foreach ($states as $state) { unset($workflow['states'][$state]); } // Update the transitions. foreach ($workflow['transitions'] as $transition_id => &$transition) { // Remove any transition to a removed state. if (in_array($transition_id, $states)) { unset($workflow['transitions'][$transition_id]); continue; } // Remove our state from the from states. $transition['from'] = array_diff($transition['from'], $states); // Remove any states with no from. if (empty($transition['from'])) { unset($workflow['transitions'][$transition_id]); } } } // Add additional team statuses to the order item workflow. if (isset($workflows['contacts_events_order_item_process'])) { $workflow = &$workflows['contacts_events_order_item_process']; $workflow['states'] += [ 'team_app_in_progress' => ['label' => 'Team Application In Progress'], ]; $workflow['transitions'] += [ // Ticket moves from confirmed to team app in progress. 'team_app_in_progress' => [ 'label' => 'Team Application In Progress', 'from' => ['pending'], 'to' => 'team_app_in_progress', ], 'team_app_back_to_pending' => [ 'label' => 'Team Application back to Pending', 'from' => ['team_app_in_progress', 'paid_in_full'], 'to' => 'pending', ], 'team_payment_undone' => [ 'label' => 'Team payment undone', 'from' => ['paid_in_full'], 'to' => 'team_app_in_progress', ], ]; // Allow moving from team app in progress to paid in full. $workflow['transitions']['paid_in_full']['from'][] = 'team_app_in_progress'; $workflow['transitions']['cancel']['from'][] = 'team_app_in_progress'; } } /** * Implements hook_contacts_events_ticket_form_alter(). */ function contacts_events_teams_contacts_events_ticket_form_alter(array &$form, FormStateInterface $form_state, Ticket $ticket, string $display_mode) { \Drupal::service('contacts_events_teams.form_alter.ticket')->alter($form, $form_state, $ticket, $display_mode); } /** * Implements hook_form_FORM_ID_alter() for state_machine_transition_form. */ function contacts_events_teams_form_state_machine_transition_form_alter(&$form, FormStateInterface $form_state, $form_id) { // For team application state transitions, some are only available for staff // others for staff and team leaders, some are automated and not available for // any users to invoke. /** @var \Drupal\state_machine\Form\StateTransitionForm $form_object */ $form_object = $form_state->getFormObject(); $entity = $form_object->getEntity(); if ($entity instanceof TeamApplication) { $allowed_transitions = $entity->getManuallyAllowedTransitions(\Drupal::currentUser()); foreach (Element::children($form['actions']) as $delta) { if (!in_array($delta, $allowed_transitions)) { unset($form['actions'][$delta]); } } } } /** * Implements hook_mail(). */ function contacts_events_teams_mail($key, &$message, $params) { $message['subject'] = $params['subject']; $build = [ '#type' => 'processed_text', '#text' => $params['body'], '#format' => $params['body_format'], '#filter_types_to_skip' => [], '#langcode' => $message['langcode'], ]; $message['body'][] = \Drupal::service('renderer')->render($build); if ($params['from']) { $message['headers']['From'] = $params['from']; } } /** * Implements hook_query_TAG_alter(). * * For contacts_events_teams_tickets_without_apps. */ function contacts_events_teams_query_contacts_events_teams_tickets_without_apps_alter(AlterableInterface $query) { if (!$query instanceof Select) { throw new \Exception('Tag contacts_events_teams_tickets_without_apps only supported for Select queries.'); } // Check the metadata for an explicit table, otherwise assume the base table. if ($ticket_alias = $query->getMetaData('contacts_events_teams_tickets_without_apps_ticket_table')) { // Just in-case anyone puts user input into the query metadata, let's escape // the table. There isn't an escapeTable method, but escapeField should do. $ticket_alias = $query->escapeField($ticket_alias); } else { $ticket_alias = array_keys($query->getTables())[0]; } $app_alias = $query->leftJoin('c_events_team_app', 'app', "%alias.ticket = {$ticket_alias}.id AND %alias.state != :archived", [ ':archived' => 'archived', ]); $query->isNull("{$app_alias}.id"); } /** * Implements hook_contacts_user_dashboard_user_summary_blocks_alter(). */ function contacts_events_teams_contacts_user_dashboard_user_summary_blocks_alter(&$content, UserInterface $user) { $team_app_controller = \Drupal::service('class_resolver')->getInstanceFromDefinition(TeamApplicationController::class); $team_data = $team_app_controller->myActiveTeams($user); if (!empty($team_data['table']['#rows'])) { $content['team_applications'] = [ '#type' => 'user_dashboard_summary', '#buttons' => [], '#title' => 'Your active team applications', '#content' => $team_data, ]; } } /** * Implements hook_entity_extra_field_info(). */ function contacts_events_teams_entity_extra_field_info() { $extra = []; foreach (EventType::loadMultiple() as $bundle) { $extra['contacts_event'][$bundle->id()]['form']['team_settings'] = [ 'label' => new TranslatableMarkup('Team settings'), 'visible' => FALSE, ]; } return $extra; } /** * Implements hook_form_BASE_FORM_ID_alter(). */ function contacts_events_teams_form_contacts_event_form_alter(&$form, FormStateInterface $form_state, $form_id) { $event = $form_state->getFormObject()->getEntity(); $settings = $event->getSetting('teams', []); $form['team_settings'] = [ '#type' => 'fieldset', '#title' => new TranslatableMarkup('Team settings'), '#parents' => array_merge($form['#parents'] ?? [], ['settings', 'teams']), '#tree' => TRUE, ]; $form['team_settings']['enabled'] = [ '#type' => 'radios', '#title' => new TranslatableMarkup('Does this event have volunteer teams?'), '#description' => new TranslatableMarkup('Public and invite only teams can be created on the Teams tab once the event is saved.'), '#options' => [ TeamInterface::STATUS_OPEN => new TranslatableMarkup('Published'), TeamInterface::STATUS_PRIVATE => new TranslatableMarkup('Private'), TeamInterface::STATUS_CLOSED => new TranslatableMarkup('No teams'), ], '#default_value' => $settings['enabled'] ?? TeamInterface::STATUS_CLOSED, ]; $form['team_settings']['min_age'] = [ '#type' => 'number', '#title' => new TranslatableMarkup('Minimum age'), '#description' => new TranslatableMarkup('The minimum age allowed for team applications. Individual teams may set higher minimum ages.'), '#default_value' => $settings['min_age'] ?? NULL, '#min' => 0, ]; // If the teams checkbox is shown, hide the settings if unchecked. if (isset($form['team_settings']['enabled']) && ($form['team_settings']['enabled']['#access'] ?? TRUE)) { $form['team_settings']['min_age']['#states']['invisible'][] = [ ':input[name="settings[teams][enabled]"]' => ['value' => TeamInterface::STATUS_CLOSED], ]; } } /** * Implements hook_inline_entity_form_table_fields_alter(). */ function contacts_events_teams_inline_entity_form_table_fields_alter(&$fields, $context) { if ($context['entity_type'] == 'commerce_order_item' && $context['allowed_bundles'] == ['contacts_ticket']) { if (\Drupal::currentUser()->hasPermission('can manage bookings for contacts_events')) { $fields['team'] = [ 'type' => 'callback', 'label' => new TranslatableMarkup('Team'), 'weight' => 5.1, 'callback' => [Team::class, 'getTeamNameFromOrderItem'], ]; } } } /** * Implements hook_field_group_build_build_alter(). */ function contacts_events_teams_field_group_form_process_build_alter(&$element) { // Hide the email configuration if override is disabled. if (isset($element['group_emails_wrapper'])) { $element['group_emails_wrapper']['#states'] = [ 'invisible' => [ ':input[name="team_email_override[value]"]' => ['checked' => FALSE], ], ]; // Also hide the fieldset label. The fieldset is only added because #states // do not work with the tabs formatter. $element['group_emails_wrapper']['#title'] = ''; } } /** * Implements hook_contacts_events_ticket_summary_alter(). */ function contacts_events_teams_contacts_events_ticket_summary_alter(array &$build, EventInterface $event) { $build['paid_in_full']['#description'] = new TranslatableMarkup('Including approved team tickets.'); $build['paid_in_full']['#description_display'] = 'after'; $build['teams']['#cache']['tags'] = ['contacts_ticket_list']; $build['teams']['divider'] = ['#markup' => '<hr/>']; // Build our query, grouped by is team ticket. $query = TicketsController::getBaseStatsQuery($event); $query->condition('state', ['pending', 'cancelled'], 'NOT IN'); $query->groupBy('purchased_entity.entity:contacts_ticket.is_team_ticket'); // Build our result array. $team_counts = [ 'delegates' => 0, 'team' => 0, ]; foreach ($query->execute() as $row) { $key = $row['is_team_ticket_value'] ? 'team' : 'delegates'; $team_counts[$key] += $row['order_item_id_count']; } $build['teams']['delegates'] = [ '#type' => 'item', '#title' => new TranslatableMarkup('Confirmed delegates'), '#markup' => new PluralTranslatableMarkup($team_counts['delegates'], '@number ticket', '@number tickets', ['@number' => number_format($team_counts['delegates'], 0)] ), ]; $build['teams']['team'] = [ '#type' => 'item', '#title' => new TranslatableMarkup('Confirmed team'), '#markup' => new PluralTranslatableMarkup($team_counts['team'], '@number ticket', '@number tickets', ['@number' => number_format($team_counts['team'], 0)]), ]; }