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)]),
];
}
