muser-8.x-1.x-dev/modules/custom/muser_project/muser_project.module

modules/custom/muser_project/muser_project.module
<?php

/**
 * @file
 * Contains muser_project.module.
 */

use Drupal\Core\Field\FieldItemList;
use Drupal\field\Entity\FieldConfig;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Component\Utility\Html;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\flag\FlaggingInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\flag\FlagInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Component\Utility\Environment;
use Drupal\muser_project\GetApplicationCountText;
use Drupal\views\ViewExecutable;

define('MUSER_CONTRACT_ICON', '<i class="far fa-handshake"></i>');
define('MUSER_STAR_ICON', '<i title="Mentor star" class="far fa-star"></i>');

/**
 * Implements hook_entity_extra_field_info().
 */
function muser_project_entity_extra_field_info() {

  $extra = [];

  $project_availability = [
    'label' => t('Footer - Project availability'),
    'description' => t('View number of applicants and available spaces for a project'),
    'visible' => FALSE,
  ];

  $extra['node']['project']['display']['project_availability'] = $project_availability;
  $extra['node']['project']['display']['hours_per_week'] = [
    'label' => t('Footer - Hours per week'),
    'description' => t('View formatted "Hours per week" for a project'),
    'visible' => FALSE,
  ];
  $extra['node']['project']['display']['categories'] = [
    'label' => t('Footer - Project Categories'),
    'description' => t('View formatted "Project Categories" for a project'),
    'visible' => FALSE,
  ];
  $extra['node']['project']['display']['favorite_link'] = [
    'label' => t('"Favorite" link (Flag)'),
    'description' => t('View "Favorite" flagging link for a project'),
    'visible' => FALSE,
  ];
  $extra['node']['project']['display']['application_form'] = [
    'label' => t('Application form'),
    'description' => t('Application form for a project'),
    'visible' => FALSE,
  ];
  $extra['node']['project']['display']['application_status_and_submitted'] = [
    'label' => t('Application status and submitted'),
    'description' => t("Status of the user's Application for a project"),
    'visible' => FALSE,
  ];
  $extra['node']['project']['display']['application_contract_link'] = [
    'label' => t('Application contract link'),
    'description' => t("Link to accept contract for Application"),
    'visible' => FALSE,
  ];
  $extra['node']['project']['display']['mentor_info'] = [
    'label' => t('Mentor information'),
    'description' => t("Contact information for the Project's Mentor"),
    'visible' => FALSE,
  ];
  $extra['node']['project']['display']['activate_link'] = [
    'label' => t('"Activate" link (rounds)'),
    'description' => t('View "Activate"/"Inactivate" link for a project'),
    'visible' => FALSE,
  ];

  $extra['flagging']['favorites']['display']['project_availability'] = $project_availability;
  $extra['flagging']['favorites']['display']['project_info'] = [
    'label' => t('Project information'),
    'description' => t('Information about the project'),
    'visible' => FALSE,
  ];
  $extra['flagging']['favorites']['display']['applicant_info'] = [
    'label' => t('Applicant information'),
    'description' => t('Information about the applicant'),
    'visible' => FALSE,
  ];
  $extra['flagging']['favorites']['display']['update_status_link'] = [
    'label' => t('"Update status" link'),
    'description' => t('Link to update the status of applicant'),
    'visible' => FALSE,
  ];
  $extra['flagging']['favorites']['display']['application_contract_link'] = $extra['node']['project']['display']['application_contract_link'];

  return $extra;
}

/**
 * Implements hook_theme().
 */
function muser_project_theme() {
  return [
    'ajax_link_activate' => [
      'variables' => [
        'attributes' => [],
        'title' => NULL,
        'action' => 'activate',
        'project' => NULL,
      ],
    ],
  ];
}

/**
 * Implements hook_entity_view().
 */
function muser_project_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {

  if ($entity->getEntityTypeId() == 'flagging' && $entity->bundle() == 'favorites') {
    $build['#attached']['library'][] = 'muser_project/applications';
  }

  if ($entity->getEntityTypeId() == 'node' && $entity->bundle() == 'project') {

    if ($view_mode == 'full') {

      if (_muser_system_contracts_enabled() && $entity->field_use_contract->value) {
        $build['field_use_contract'][0]['#markup'] = MUSER_CONTRACT_ICON . ' ' . t('This project will use a Mentor-Student contract.');
      }
      else {
        $build['field_use_contract']['#access'] = FALSE;
      }
    }
    else {
      // Add this to make sure modal links work.
      $build['#attached']['library'][] = 'core/drupal.dialog.ajax';
    }
    if ($view_mode == 'application') {
      $options = [
        'attributes' => [
          'class' => ['view-more', 'use-ajax'],
          'data-dialog-options' => '{"width":800}',
          'data-dialog-type' => 'modal',
        ],
      ];
      $build['body'][0]['#suffix'] = Link::createFromRoute(t('View more...'), 'entity.node.canonical', ['node' => $entity->id()], $options)
        ->toString();
    } // Viewing a project application?
  } // Viewing a project?

  if ($component = $display->getComponent('project_availability')) {

    if ($entity->getEntityTypeId() == 'flagging') {
      $project = muser_project_get_project_for_flagging($entity);
      $project_round_nid = muser_project_get_project_round_for_project($project->id());
      $count = $project->field_num_students->value;
    }
    else {
      $project_round_nid = muser_project_get_project_round_for_project($entity->id());
      $count = $entity->field_num_students->value;
    }

    $build['project_availability'] = [
      '#weight' => $component['weight'],
    ];
    $build['project_availability'][]['#markup'] = '<div class="project-detail">'
      . '<div class="project-detail__icon">'
      . '<div><i class="far fa-user"></i></div>'
      . '</div>'
      . '<div class="project-detail__value">'
      . '<div class="availability">'
      . '<span class="spaces">'
      . t('@spaces sp.', ['@spaces' => intval($count)])
      . '</span>';
    $build['project_availability'][] = [
      '#lazy_builder' => [GetApplicationCountText::class . ':lazyBuilder', [$project_round_nid, $entity->id()]],
      '#create_placeholder' => TRUE,
    ];
    $build['project_availability'][]['#markup'] = '</div></div></div>';

  } // Show available spaces info?

  $extra_fields = [
    'hours_per_week' => 'first',
    'categories' => 'random',
  ];
  foreach ($extra_fields as $field => $item_to_show) {
    if ($component = $display->getComponent($field)) {
      $display_options = [
        'type' => 'single_term_plus',
        'settings' => [
          'item_to_show' => $item_to_show,
        ]
      ];
      $build[$field] = $entity->{'field_' . $field}->view($display_options);
      $build[$field]['#weight'] = $component['weight'];
    } // Show extra field?
  } // Loop thru similar extra fields.

  if ($component = $display->getComponent('favorite_link')) {
    $build['#cache']['contexts'][] = 'user.permissions';
    if (\Drupal::currentUser()->isAuthenticated()) {
      if ($project_round_nid = muser_project_get_project_round_for_project($entity->id())) {
        $project_round = \Drupal\node\Entity\Node::load($project_round_nid);
        // Need to make this dependent on the project-round.
        $build['#cache']['tags'][] = 'node:' . $project_round_nid;
        // Code taken from flag_entity_view().
        $flag_service = \Drupal::service('flag');
        /** @var \Drupal\flag\Entity\Flag $flag */
        $flag = $flag_service->getFlagById('favorites');
        $build['#cache']['tags'] = Cache::mergeTags($build['#cache']['tags'], $flag->getCacheTags());
        $build['favorite_link'] = [
          '#lazy_builder' => ['flag.link_builder:build', [
            $project_round->getEntityTypeId(),
            $project_round->id(),
            $flag->id(),
          ]],
          '#create_placeholder' => TRUE,
          '#weight' => $component['weight'],
        ];
        $text_flag = $flag->getShortText('flag');
        $text_unflag = $flag->getShortText('unflag');
        $placeholder_text = (strlen($text_flag) > strlen($text_unflag)) ? $text_flag : $text_unflag;
        $build['favorite_link_placeholder_text'] = [
          '#markup' => Html::escape($placeholder_text),
        ];
        $build['#cache']['max-age'] = muser_project_round_period_change_time('application');
      } // Got a project round ID?
    } // User is logged in?
  } // Show "Favorite" flag link?

  if ($component = $display->getComponent('mentor_info')) {
    /** @var \Drupal\Core\Session\AccountInterface $mentor */
    $mentor = $entity->getOwner();
    $markup = [];
    $markup[] = muser_project_get_mentor_info_markup($mentor);
    if ($entity->field_additional_mentors->count()) {
      foreach ($entity->field_additional_mentors as $item) {
        $markup[] .= muser_project_get_mentor_info_markup($item->entity);
      } // Loop thru additional mentors.
    } // Got additional mentors?
    if ($markup = array_filter($markup)) {
      $build['mentor_info'] = [
        '#type' => 'markup',
        '#weight' => $component['weight'],
        '#markup' => '<div class="field field--name-field-mentor-info field--label-above">
  <div class="field__label">' . \Drupal::translation()->formatPlural(count($markup), 'Mentor', 'Mentors') . '</div>
  <div class="field__items">
    <div class="field__item">' . implode('</div><div class="field__item">', $markup) . '</div>
  </div>
</div>',
      ];
    }
  } // Show Mentor info?

  if ($component = $display->getComponent('application_form')) {
    if ($project_round_nid = muser_project_get_project_round_for_project($entity->id())) {
      $project_round = \Drupal\node\Entity\Node::load($project_round_nid);
      $form_builder = \Drupal::formBuilder();
      $form_object = new \Drupal\muser_project\Form\UserApplicationForm();
      $form_object->setProjectRound($project_round);
      $build['application_form'] = $form_builder->getForm($form_object);
      $build['application_form']['#weight'] = $component['weight'];
    }
  } // Show Application form?

  if ($component = $display->getComponent('application_status_and_submitted')) {
    if ($project_round_nid = muser_project_get_project_round_for_project($entity->id())) {
      if (\Drupal::currentUser()->isAuthenticated()) {
        $project_round = \Drupal\node\Entity\Node::load($project_round_nid);
        $flagging = muser_project_get_flagging($project_round);
        if ($flagging && $flagging->field_is_submitted->value) {
          $build['application_status_and_submitted']['submitted'] = [
            '#type' => 'markup',
            '#markup' => muser_project_get_application_submitted_display(),
          ];
          $build['application_status_and_submitted']['status'] = [
            '#type' => 'markup',
            '#markup' => muser_project_get_application_status_display($flagging->field_status->value, MUSER_STUDENT, $project_round->field_round->entity),
          ];
          $build['application_status_and_submitted']['#weight'] = $component['weight'];
          $build['application_status_and_submitted']['#cache'] = [
            'contexts' => ['user'],
            'tags' => [
              'flagging:' . $flagging->id(),
              'user:' . \Drupal::currentUser()->id(),
            ],
          ];
        } // Application submitted?
      } // User logged in?
    }
  } // Show Application status and submitted?

  if (_muser_system_contracts_enabled() && $component = $display->getComponent('application_contract_link')) {
    $build['application_contract_link'] = muser_project_get_application_contract_link($entity);
    $build['application_contract_link']['#weight'] = $component['weight'];
  } // Show contract link?

  if ($component = $display->getComponent('project_info')) {
    $project = muser_project_get_project_for_flagging($entity);
    $build['project_info'] = [
      '#weight' => $component['weight'],
    ];
    $contract_icon = (_muser_system_contracts_enabled() && $project->field_use_contract->value) ? MUSER_CONTRACT_ICON . ' ' : '';
    $starred_mentor_icon = ($project->getOwner()->field_has_star->value) ? MUSER_STAR_ICON . ' ' : '';
    $build['project_info']['#markup'] = '<h3 class="application__project-title">'
      . $contract_icon
      . $starred_mentor_icon
      . Html::escape($project->label())
      . '</h3>';
  } // Show Project info?

  if ($component = $display->getComponent('applicant_info')) {
    /** @var \Drupal\user\Entity\User $applicant */
    $applicant = $entity->getOwner();
    $view_builder = \Drupal::entityTypeManager()->getViewBuilder('user');
    $build['applicant_info'] = $view_builder->view($applicant, 'applicant_info');
    $build['applicant_info']['#weight'] = $component['weight'];
    $build['applicant_info']['#prefix'] = '<div class="applicant-info"><div class="field__label">'
      . t('Applicant information')
      . '</div>';
    $build['applicant_info']['#suffix'] = '</div>';
  } // Show Applicant info?

  if ($component = $display->getComponent('update_status_link')) {
    $round = muser_project_get_round_for_flagging($entity);
    $access = ($round && muser_project_round_in_period($round->id(), 'acceptance'));

    $url = Url::fromRoute('muser_project.update_flagging_status', ['flagging' => $entity->id()]);
    $url->setOption('query', \Drupal::destination()->getAsArray());

    if (empty($build['#cache']['tags'])) {
      $build['#cache']['tags'] = [];
    }
    $build['#cache']['tags'] = Cache::mergeTags($build['#cache']['tags'], ["muser_project_update_status:{$entity->id()}","muser_project_update_status"]);
    $build['#cache']['max-age'] = muser_project_round_period_change_time('acceptance');

    $build['update_status_link'] = [
      '#type' => 'link',
      '#url' => $url,
      '#title' => t('Change status'),
      '#attributes' => [
        'class' => ['use-ajax'],
        'data-dialog-options' => '{"width":"auto"}',
        'data-dialog-type' => 'modal',
      ],
      '#weight' => $component['weight'],
      '#attached' => [
        'library' => ['core/drupal.dialog.ajax'],
      ],
      '#access' => $access,
    ];

  } // Show Update status link?

  if ($component = $display->getComponent('activate_link')) {

    if (!muser_project_get_project_round_for_project($entity->id())) {
      $url = Url::fromRoute('muser_project.action_activate_project', ['entity_id' => $entity->id()]);
      $action = 'activate';
      $label = "Activate";
      $status_class = 'project--inactive';
    } else {
      $url = Url::fromRoute('muser_project.action_inactivate_project', ['entity_id' => $entity->id()]);
      $action = 'inactivate';
      $label = "Inactivate";
      $status_class = 'project--active';
    }

    $url->setOption('query', \Drupal::destination()->getAsArray());

    $build['#cache']['contexts'][] = 'user';
    if (empty($build['#cache']['tags'])) {
      $build['#cache']['tags'] = [];
    }
    $build['#cache']['tags'] = Cache::mergeTags($build['#cache']['tags'], ["muser_project_activate:{$entity->id()}","muser_project_activate"]);
    $build['#cache']['max-age'] = muser_project_round_period_change_time('posting');

    $build['activate_link'] = [
      '#theme' => 'ajax_link_activate',
      '#title' => t($label),
      '#action' => $action,
      '#attributes' => [
        'class' => ['use-ajax'],
      ],
      '#project' => $entity,
      '#weight' => $component['weight'],
      '#access' => $url->access(),
      '#status_class' => $status_class,
      '#attached' => [
        'library' => ['core/drupal.dialog.ajax'],
      ],
    ];

    // Bubble to CSRF
    $rendered_url = $url->toString(TRUE);
    $rendered_url->applyTo($build['activate_link']);

    $build['activate_link']['#attributes']['href'] = $rendered_url->getGeneratedUrl();

  } // Show "Activate" link?

}

/**
 * Get markup for a mentor to show with Project.
 *
 * @param \Drupal\Core\Session\AccountInterface $mentor
 *
 * @return string
 */
function muser_project_get_mentor_info_markup(AccountInterface $mentor) {
  if (!$mentor || !$mentor->isAuthenticated()) {
    return '';
  }
  $name = Html::escape($mentor->getDisplayName())
    . ' '
    . muser_system_get_mentor_star($mentor)
    . muser_system_get_mentor_star($mentor, TRUE);
  $markup = muser_system_get_display_only_field('field_mentor_name', '', $name)
    . muser_system_get_display_only_field('field_mentor_email', '', Html::escape($mentor->getEmail()));
  if ($mentor->field_lab_affiliation->value) {
    $markup .= muser_system_get_display_only_field('field_mentor_position', '', Html::escape($mentor->field_lab_affiliation->value));
  }
  return $markup;
}

function muser_project_get_current_round($load_node = FALSE) {
  $round = &drupal_static(__FUNCTION__, NULL);
  if (!isset($round)) {
    $nids = \Drupal::entityQuery('node')
      ->condition('status', \Drupal\node\NodeInterface::PUBLISHED)
      ->condition('type', 'round')
      ->condition('field_is_current', 1)
      ->execute();
    $round = ($nids) ? reset($nids) : NULL;
  }
  return ($load_node && $round) ? Node::load($round) : $round;
}

function muser_project_get_project_round_for_project($nid, $round = NULL) {
  $project_rounds = &drupal_static(__FUNCTION__, []);
  if (!$round) {
    $round = muser_project_get_current_round();
  }
  if (!isset($project_rounds[$round][$nid])) {
    $nids = \Drupal::entityQuery('node')
      ->condition('status', \Drupal\node\NodeInterface::PUBLISHED)
      ->condition('type', 'project_round')
      ->condition('field_round', $round)
      ->condition('field_project', $nid)
      ->execute();
    $project_rounds[$round][$nid] = ($nids) ? reset($nids) : NULL;
  }
  return $project_rounds[$round][$nid];
}

function muser_project_get_application_count_cid($nid) {
  return 'muser_project_application_count:' . $nid;
}

/**
 * @param $nid
 *   Project-Round nid.
 *
 * @return mixed
 */
function muser_project_get_application_count($nid) {
  $counts = &drupal_static(__FUNCTION__, []);
  $cid = muser_project_get_application_count_cid($nid);
  if (!isset($counts[$cid])) {
    if ($cache = \Drupal::cache()->get($cid)) {
      $counts[$cid] = $cache->data;
    }
    else {
      $counts[$cid] = \Drupal::entityQuery('flagging')
        ->condition('flag_id', 'favorites')
        ->condition('entity_type', 'node')
        ->condition('entity_id', $nid)
        ->condition('field_is_submitted', 1)
        ->count()
        ->execute();
      \Drupal::cache()->set($cid, $counts[$cid]);
    }
  }
  return $counts[$cid];
}

/**
 * @param $nid
 *   Project-Round nid.
 *
 * @return array
 */
function muser_project_get_application_count_text($nid, $project_nid) {
  return [
    '#cache' => [
      'tags' => ['project_application_count:' . $project_nid],
    ],
    '#markup' => ' | <span class="applicants">'
      . t('@applicants appl.', ['@applicants' => muser_project_get_application_count($nid)])
      . '</span>',
  ];
}

function muser_project_get_user_application_count_cid($uid, $round_nid = NULL) {
  if (!$round_nid) {
    $round_nid = muser_project_get_current_round();
  }
  return 'muser_project_user_application_count:' . $round_nid . ':' . $uid;
}

/**
 * @param $uid
 *   User uid.
 *
 * @return mixed
 */
function muser_project_get_user_application_count($uid) {
  $counts = &drupal_static(__FUNCTION__, []);
  $round_nid = muser_project_get_current_round();
  $cid = muser_project_get_user_application_count_cid($uid, $round_nid);
  if (!isset($counts[$cid])) {
    if ($cache = \Drupal::cache()->get($cid)) {
      $counts[$cid] = $cache->data;
    }
    else {
      $query = \Drupal::database()->select('muser_applications', 'ma');
      $query->addField('ma', 'fid');
      $query->condition('ma.application_uid', $uid)
        ->condition('ma.is_submitted', 1)
        ->condition('ma.round_nid', $round_nid);
      $counts[$cid] = $query->countQuery()->execute()->fetchField();
      \Drupal::cache()->set($cid, $counts[$cid]);
    }
  }
  return $counts[$cid];
}

function muser_project_add_project_to_round($nid, $round) {

  if (empty($nid) || empty($round)) {
    return FALSE;
  }

  $node = Node::create([
    'type' => 'project_round',
    'title' => t('round_@round_project_@project', ['@round' => $round, '@project' => $nid]),
    'field_round' => [['target_id' => $round]],
    'field_project' => [['target_id' => $nid]],
  ]);

  Cache::invalidateTags(["muser_project_activate:{$nid}"]);

  return $node->save();
}

function muser_project_remove_project_from_round($project_round_nid) {

  if (empty($project_round_nid)) {
    return FALSE;
  }

  $project_round = Node::load($project_round_nid);

  if (!$project_round) {
    return FALSE;
  }
  $project_etid = $project_round->field_project->entity->id();

  if ($project_etid) {
    Cache::invalidateTags(["muser_project_activate:{$project_etid}"]);
  }

  $project_round->delete();

  return TRUE;
}

/**
 * @param $round_nid
 * @param string $period posting|application|acceptance
 * @param bool $allow_ignore
 *
 * @return bool
 */
function muser_project_round_in_period($round_nid, $period = 'posting', $allow_ignore = TRUE) {

  // Allow global override of date checking.
  if ($allow_ignore && \Drupal::service('settings')::get('ignore_date_checking')) {
    return TRUE;
  }

  $period_field = [
    'before' => 'field_start_date',
    'posting' => 'field_post_projects',
    'application' => 'field_apply',
    'acceptance' => 'field_accept_applications',
    'after' => 'field_accept_applications',
  ];

  if (_muser_system_contracts_enabled()) {
    $period_field['contract'] = 'field_sign_contracts';
    $period_field['after'] = 'field_sign_contracts';
  }

  if (empty($period_field[$period])) {
    return FALSE;
  }

  $field = $period_field[$period];

  $round = Node::load($round_nid);
  if (empty($round)) {
    return FALSE;
  }

  $date_field = $round->get($field)->get(0);
  if (empty($date_field) || empty($date_field->getValue())) {
    // This can be the case when the contract fields are not filled out after
    // switching from contracts disabled to enabled.
    // If we want to see if the round is in a field that has no dates, it isn't.
    if ($period === 'after') {
      $date_field = $round->get('field_accept_applications')->get(0);
    }
    else {
      return FALSE;
    }
  }
  $field_data = $date_field->getValue();

  if ($period == 'before') {
    // Looking at "before" period (between round start and project posting).
    // Use start and end from separate fields.
    $begining = new DrupalDateTime($field_data['value'], DateTimeItemInterface::STORAGE_TIMEZONE);
    $field_data_end = $round->get('field_post_projects')->get(0)->getValue();
    $end = new DrupalDateTime($field_data_end['value'], DateTimeItemInterface::STORAGE_TIMEZONE);
  }
  elseif ($period == 'after') {
    // Looking at "after" period (after application acceptance).
    // Use start from acceptance field and current time+.
    $begining = new DrupalDateTime($field_data['end_value'], DateTimeItemInterface::STORAGE_TIMEZONE);
    $end = new DrupalDateTime('+1 day');
  }
  else {
    $begining = new DrupalDateTime($field_data['value'], DateTimeItemInterface::STORAGE_TIMEZONE);
    $end = new DrupalDateTime($field_data['end_value'], DateTimeItemInterface::STORAGE_TIMEZONE);
  }

  $now = new DrupalDateTime();

  $b = $begining->diff($now);
  $e = $end->diff($now);

  // Invert is 1 if the date was in the future and 0 if it was in the past.
  return !$b->invert && $e->invert;
}

/**
 * Returns the time till the next event entering or exiting a round period.
 *
 * When the period is in the past the passed default_expire will be returned, the default is PERMANENT
 *
 * @param string $period posting|application|acceptance
 * @param $round_nid
 * @param int $default_expire
 *
 * @return bool
 */
function muser_project_round_period_change_time($period = 'posting', $round_nid = FALSE, $default_expire = CACHE::PERMANENT) {

  if (!$round_nid) {
    $round_nid = muser_project_get_current_round();
  }

  if (!$round_nid) {
    return $default_expire;
  }

  $period_field = [
    'posting' => 'field_post_projects',
    'application' => 'field_apply',
    'acceptance' => 'field_accept_applications',
  ];

  if (_muser_system_contracts_enabled()) {
    $period_field['contract'] = 'field_sign_contracts';
  }

  if (empty($period_field[$period])) {
    return 0;
  }

  $field = $period_field[$period];

  $round = Node::load($round_nid);
  if (empty($round)) {
    return 0;
  }

  $field_data = $round->get($field)->get(0)->getValue();

  $now = new DrupalDateTime();
  $begining = new DrupalDateTime($field_data['value'], DateTimeItemInterface::STORAGE_TIMEZONE);
  $end = new DrupalDateTime($field_data['end_value'], DateTimeItemInterface::STORAGE_TIMEZONE);

  $b = $begining->diff($now);
  $e = $end->diff($now);

  // Invert is 1 if the date was in the future and 0 if it was in the past.
  if ($b->invert) {
    // Seconds till change.
    return $begining->getTimestamp() - $now->getTimestamp();
  }

  if ($e->invert) {
    // Seconds till change.
    return $end->getTimestamp() - $now->getTimestamp();
  }

  return $default_expire;
}

function muser_project_get_current_period($round_nid) {
  $periods = ['before', 'posting', 'application', 'acceptance'];

  if (_muser_system_contracts_enabled()) {
    $periods[] = 'contract';
  }

  $periods[] = 'after';

  if ($round_nid) {
    foreach ($periods as $period) {
      if (muser_project_round_in_period($round_nid, $period, FALSE)) {
        return $period;
      }
    } // Loop thru periods.
  } // Got a round?
  return FALSE;
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function muser_project_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if (strpos($form_id, 'node_project_') !== 0) {
    return;
  }
  // No revision log for projects.
  $form['revision_information']['#access'] = FALSE;
  $config = \Drupal::config('muser_system.settings');

  if (($form_id == 'node_project_edit_form' || $form_id == 'node_project_form') && _muser_system_contracts_enabled()) {

    if ($new_contract_label = $config->get('contract_label')) {
      $form['field_use_contract']['widget']['value']['#title'] = check_markup($new_contract_label, 'basic_html');
    }
    if ($new_contract_description = $config->get('contract_description')) {
      $form['field_use_contract']['widget']['value']['#description'] = check_markup($new_contract_description, 'basic_html');
    }

    $form['field_use_contract']['#attributes']['data-enable-contract-modal'] = ($config->get('enable_contract_modal')) ? "1" : "0";

    $form['field_use_contract']['#attributes']['data-require-confirmation-text'] = ($config->get('contract_require_short_confirmation_text')) ? "1" : "0";
    $form['field_use_contract']['#attributes']['data-contract-short-confirmation-text'] = $config->get('contract_short_confirmation_text') ?? t("I understand");

    $form['field_use_contract']['#attributes']['data-contract-modal-text'] = $config->get('contract_modal_text') ? check_markup($config->get('contract_modal_text')['value'], $config->get('contract_modal_text')['format']) : "";
    $form['field_use_contract']['#attributes']['data-contract-modal-text-confirm'] = t('Enter the text "@enable_text" to accept.', ['@enable_text' => $form['field_use_contract']['#attributes']['data-contract-short-confirmation-text']]);
    $form['#attached']['library'][] = 'muser_project/contract-confirm';
  }
  else {
    $form['field_use_contract']['#access'] = FALSE;
  }

  if ($form_id == 'node_project_form') {
    // Add a "Cancel" button to the "Add node" form.
    $account = \Drupal::currentUser();
    $my_projects_url = Url::fromRoute('view.my_projects.page', ['user' => $account->id()]);
    $form['actions']['cancel'] = [
      '#type' => 'link',
      '#title' => t('Cancel'),
      '#weight' => 100,
      '#attributes' => [
        'class' => [
          'button',
          'button--secondary',
        ],
      ],
      '#url' => $my_projects_url,
    ];
    if (muser_project_get_current_round()) {
      $form['actions']['activate'] = [
        '#type' => 'checkbox',
        '#title' => t('Activate project'),
        '#description' => t('If checked, this new project will be made active in the current Round.'),
        '#weight' => -100,
        '#default_value' => 1,
      ];
    } // Got a current round?
  } // Create form?

}

/**
 * Implements hook_views_pre_view().
 */
function muser_project_views_pre_view(ViewExecutable $view, $display_id, array &$args) {
  if ($view->id() != 'administer_applications' && $view->id() != 'application_counts') {
    return;
  }
  // Set the default for the Round to the current Round.
  $filters = $view->display_handler->getOption('filters');
  $filters['nid']['value']['value'] = muser_project_get_current_round();
  $view->display_handler->setOption('filters', $filters);
}

function muser_project_form_views_exposed_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  switch ($form['#id']) {
    case (strpos($form['#id'], 'views-exposed-form-applications-page-') === 0):
      $form['project']['#options'] = _muser_project_restrict_project_options($form['project']['#options'], $form_state->getStorage()['view']->args);
      if (count($form['project']['#options']) < 2) {
        $form['#access'] = FALSE;
        return;
      }
      // Fall thru.
    case 'views-exposed-form-projects-page':
      _muser_project_add_views_filters_code($form);
      break;
    case 'views-exposed-form-administer-applications-page-1':
      $form['mentor']['#description'] = NULL;
      $form['mentor']['#after_build'][] = '_muser_system_user_autocomplete_after_build';
      $form['student']['#description'] = NULL;
      $form['student']['#after_build'][] = '_muser_system_user_autocomplete_after_build';
      _muser_project_form_views_exposed_form_create_round_select($form);
      break;
    case 'views-exposed-form-application-counts-page-1':
      $form['uid']['#after_build'][] = '_muser_system_user_autocomplete_after_build';
      _muser_project_form_views_exposed_form_create_round_select($form);
      break;
    default:
      return;
  }
} // End muser_project_form_views_exposed_form_alter()

function _muser_project_form_views_exposed_form_create_round_select(&$form) {

  $rounds = &drupal_static(__FUNCTION__, []);

  if (!$rounds) {
    $query = \Drupal::database()->select('node_field_data', 'nfd');
    $query->addField('nfd', 'nid');
    $query->addField('nfd', 'title');
    $query->addField('ic', 'field_is_current_value', ' is_current');
    $query->join('node__field_start_date', 'sd', 'sd.entity_id = nfd.nid');
    $query->join('node__field_is_current', 'ic', 'ic.entity_id = nfd.nid');
    $query->condition('nfd.status', 1);
    $query->orderBy('sd.field_start_date_value', 'DESC');
    $results = $query->execute();
    foreach ($results as $row) {
      $title = $row->title;
      if ($row->is_current) {
        $title .= ' (' . t('Current') . ')';
      }
      $rounds[$row->nid] = $title;
    }
  } // Got rounds alrady?

  $form['round']['#type'] = 'select';
  $form['round']['#options'] = $rounds;
  $form['round']['#size'] = 1;

}

function _muser_project_add_views_filters_code(&$form) {
  foreach ($form['#info'] as $item) {
    if (empty($form[$item['value']]['#theme_wrappers']['details'])) {
      continue;
    }
    $form[$item['value']]['#theme_wrappers']['details']['#attributes']['class'][] = Html::getClass('details--' . $item['value']);
  } // Loop thru form items.
  $form['#attributes']['class'][] = 'muser-view-filters';
  $form['#attached']['library'][] = 'muser_project/view-filters';
  $form['actions']['submit']['#attributes']['class'] = ['button-submit'];
  $form['actions']['reset']['#attributes']['class'] = ['button-reset'];
  $form['actions']['reset']['#access'] = TRUE;
} // End _muser_project_add_views_filters_code()

function _muser_project_restrict_project_options($options, $args) {

  $projects = &drupal_static(__FUNCTION__, []);

  if (empty($args[0])) {
    return [];
  }

  $uid = $args[0];
  if (isset($projects[$uid])) {
    return $projects[$uid];
  }

  $projects[$uid] = [];
  $round_nid = muser_project_get_current_round();

  $query = \Drupal::database()->select('node_field_data', 'nfd');
  $query->addField('nfd', 'nid');
  $query->join('node__field_project', 'fp', "fp.field_project_target_id = nfd.nid");
  $query->join('node__field_round', 'fr', "fr.entity_id = fp.entity_id AND fr.bundle = 'project_round'");
  $query->condition('nfd.type', 'project')
    ->condition('nfd.uid', $uid)
    ->condition('nfd.status', 1)
    ->condition('fr.field_round_target_id', $round_nid);
  if ($nids = $query->execute()) {
    foreach ($nids as $row) {
      if (empty($options[$row->nid])) {
        continue;
      }
      $projects[$uid][$row->nid] = $options[$row->nid];
    }
  }

  return $projects[$uid];

} // End _muser_project_restrict_project_options()

/**
 * Implements hook_preprocess_HOOK().
 */
function muser_project_preprocess_taxonomy_term(&$variables) {

  /** @var \Drupal\taxonomy\Entity\Term $term */
  $term = $variables['term'];
  if ($term->bundle() != 'categories') {
    return;
  }

  if (empty($variables['content']['field_icon']['#items'])) {

    $variables['content']['field_icon'] = [
      '#theme' => 'field',
      '#field_type' => 'string',
      '#field_name' => 'field_icon',
      '#entity_type' => 'taxonomy_term',
      '#bundle' => 'categories',
      '#label_display' => 'hidden',
      '#title' => '',
      '#formatter' => 'fontawesome_iconpicker_formatter_type',
      '#is_multiple' => FALSE,
      '#object ' => $term,
    ];

    $definition = FieldConfig::loadByName('taxonomy_term', 'categories', 'field_icon');
    /** @var \Drupal\Core\Field\FieldItemList $items */
    $items = new FieldItemList($definition);
    $item = [
      '#theme' => 'fontawesome_iconpicker_formatter',
      '#icon' => 'fas fa-tag',
      '#size' => 'fa-1x',
    ];
    $items->set(0, $item);
    $item['#attached']['library'] = 'fontawesome_iconpicker/fontawesome';
    $variables['content']['field_icon'][0] = $item;
    $variables['content']['field_icon']['#items'] = $items;

  }

} // End muser_project_preprocess_taxonomy_term()

function muser_project_preprocess_page_title(&$variables) {
  $variables['contract_icon'] = '';
  $variables['mentor_star_icon'] = '';
  /** @var \Drupal\Core\Routing\CurrentRouteMatch $route_match */
  $route_match = \Drupal::service('current_route_match');
  if ($node = $route_match->getParameter('node')) {
    if (method_exists($node, 'bundle') && $node->bundle() == 'project') {
      if (_muser_system_contracts_enabled() && $node->field_use_contract->value) {
        $variables['contract_icon'] = MUSER_CONTRACT_ICON . ' ';
      }
      if ($node->getOwner()->field_has_star->value) {
        $variables['mentor_star_icon'] = MUSER_STAR_ICON . ' ';
      }
    }
  }
} // End muser_project_preprocess_page_title()

function muser_project_preprocess_node(&$variables) {
  if ($variables['node']->bundle() == 'project') {
    $variables['contract_icon'] = (_muser_system_contracts_enabled() && $variables['node']->field_use_contract->value) ? MUSER_CONTRACT_ICON . ' ' : '';
    $variables['mentor_star_icon'] = ($variables['node']->getOwner()->field_has_star->value) ? MUSER_STAR_ICON . ' ' : '';
    if ($variables['view_mode'] == 'application') {
      $variables['attributes']['id'] = 'open__' . $variables['node']->id();
      $variables['attributes']['class'][] = 'application-collapsible';
    }
  }
}

function muser_project_preprocess_flagging(&$variables) {
  if ($variables['elements']['#flagging']->getFlagID() == 'favorites') {
    $variables['attributes']['class'][] = 'application-collapsible';
    $variables['attributes']['id'] = 'open__' . $variables['elements']['#flagging']->id();
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function muser_project_form_unflag_confirm_form_alter(&$form, FormStateInterface $form_state, $form_id) {

  $build_info = $form_state->getBuildInfo();

  /** @var \Drupal\flag\Entity\Flag $flag */
  $flag = $build_info['args'][0];
  if ($flag->id() != 'favorites') {
    // Not the flag we want to change.
    return;
  }

  $form['description']['#markup'] = '<p>'
    . t('Are you sure you want to remove this Project from your Favorites?')
    . '</p>';

  $project_round = Node::load($build_info['args'][1]);
  if ($flagging = muser_project_get_flagging($project_round, $flag)) {
    if ($flagging->field_is_submitted->value) {
      $form['description']['#markup'] .= '<p>'
        . t('You have <strong>submitted this as an application</strong>. This application <em>will be withdrawn and the essay deleted</em> if you remove this Project from your Favorites.')
        . '</p>';
      $form['actions']['submit']['#value'] = t('Delete application & Remove from Favorites');
    }
    elseif ($flagging->field_essay->value) {
      $form['description']['#markup'] .= '<p>'
        . t('You have created an essay that will be lost if you remove this Project from your Favorites.')
        . '</p>';
    }
  }

}

function muser_project_application_review_complete(Node $round = NULL) {
  if (!$round) {
    if ($round_nid = muser_project_get_current_round()) {
      $round = Node::load($round_nid);
    }
  }
  if (empty($round)) {
    return FALSE;
  }
  $field_data = $round->get('field_accept_applications')->get(0)->getValue();
  $now = new DrupalDateTime();
  $end = new DrupalDateTime($field_data['end_value'], DateTimeItemInterface::STORAGE_TIMEZONE);
  $e = $end->diff($now);
  // Invert is 1 if the date was in the future and 0 if it was in the past.
  return !$e->invert;
}

function muser_project_get_application_submitted_display() {
  return '<span class="application-status application-status--submitted">'
  . '<span class="application-status__icon">'
  . '<i class="fa-2x fas fa-check" data-fa-transform="shrink-10" data-fa-mask="fas fa-certificate"></i>'
  . '</span><span class="application-status__label">'
  . t('Submitted')
  . '</span>'
  . '</span>';
}

function muser_project_get_application_status_display($status, $role, $round = NULL) {

  if ($role == MUSER_STUDENT) {
    // For Students...
    if (!muser_project_application_review_complete($round)) {
      // ... always show "Pending" until the Round is over.
      $status = 'pending';
    }
    else {
      // ... if Round is complete, show "Reviewed" unless they're "Accepted".
      switch ($status) {
        case 'accepted':
          // Do nothing.
          break;
        case 'rejected':
        case 'in_review':
        case 'pending':
        default:
          $status = 'reviewed';
          break;
      }
    } // Is Round complete?
  } // Are they a Student?

  switch ($status) {
    case 'rejected':
      $icon = '<i class="fa-2x fas fa-thumbs-down" data-fa-transform="shrink-8" data-fa-mask="fas fa-circle"></i>';
      $text = t('Rejected');
      break;
    case 'accepted':
      $icon = '<i class="fa-2x fas fa-thumbs-up" data-fa-transform="shrink-8" data-fa-mask="fas fa-circle"></i>';
      $text = t('Accepted');
      break;
    case 'in_review':
      $icon = '<i class="fa-2x fas fa-search" data-fa-transform="shrink-8" data-fa-mask="fas fa-circle"></i>';
      $text = t('In review');
      break;
    case 'reviewed':
      $icon = '<i class="fa-2x fas fa-clipboard-check" data-fa-transform="shrink-8" data-fa-mask="fas fa-circle"></i>';
      $text = t('Reviewed');
      break;
    case 'pending':
    default:
      $icon = '<i class="fa-2x fas fa-ellipsis-h" data-fa-transform="shrink-7" data-fa-mask="fas fa-circle"></i>';
      $text = t('Pending');
  }

  return '<span class="application-status application-status--' . $status . '">'
    .'<span class="application-status__icon">'
    . $icon
    . '</span><span class="application-status__label">'
    . $text
    . '</span>'
    . '</span>';

}

/**
 * @param $project_round
 * @param null $flag
 * @param null $account
 *
 * @return \Drupal\flag\FlaggingInterface|null
 */
function muser_project_get_flagging($project_round, $flag = NULL, $account = NULL) {
  static $flag_service = NULL;
  if (!$flag_service) {
    /** @var \Drupal\flag\FlagService $service */
    $flag_service = \Drupal::service('flag');
  }
  if (!$flag) {
    $flag = $flag_service->getFlagById('favorites');
  }
  if (!$account) {
    $account = \Drupal::currentUser();
  }
  if ($account->isAnonymous()) {
    return NULL;
  }
  /** @var \Drupal\flag\FlaggingInterface $flagging */
  $flagging = $flag_service->getFlagging($flag, $project_round, $account);
  return $flagging;
}

/**
 * Return the User ID of the mentor that owns the flagged project.
 *
 * @param \Drupal\flag\FlaggingInterface $flagging
 *   Flagging entity.
 *
 * @return int
 *   User ID.
 */
function muser_project_get_flagged_project_mentor_id(FlaggingInterface $flagging) {
  if (!$project = muser_project_get_project_for_flagging($flagging)) {
    return NULL;
  }
  return $project->getOwnerId();
}

/**
 * Get project Node for a specified Flagging entity.
 *
 * @param \Drupal\flag\FlaggingInterface $flagging
 *
 * @return \Drupal\node\Entity\Node
 */
function muser_project_get_project_for_flagging(FlaggingInterface $flagging) {
  /** @var  \Drupal\node\Entity\Node $project_round */
  $project_round = $flagging->getFlaggable();
  /** @var  \Drupal\node\Entity\Node $project */
  $project = $project_round->field_project->entity;
  return $project;
}

/**
 * Get round Node for a specified Flagging entity.
 *
 * @param \Drupal\flag\FlaggingInterface $flagging
 *
 * @return \Drupal\node\Entity\Node
 */
function muser_project_get_round_for_flagging(FlaggingInterface $flagging) {
  /** @var  \Drupal\node\Entity\Node $project_round */
  $project_round = $flagging->getFlaggable();
  /** @var  \Drupal\node\Entity\Node $project */
  $round = $project_round->field_round->entity;
  return $round;
}

/**
 * Implements hook_ENTITY_TYPE_create_access().
 */
function muser_project_node_create_access(AccountInterface $account, array $context, $entity_bundle) {
  if ($entity_bundle == 'project') {
    return muser_project_project_access_check($account);
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_node_access().
 */
function muser_project_node_access(NodeInterface $node, $op, AccountInterface $account) {
  if ($node->bundle() == 'project') {
    if ($op == 'view') {
      // Viewing it.
      if (!$account->hasPermission('administer project rounds')
        // Project isn't part of this round.
        && !muser_project_get_project_round_for_project($node->id())
        // They're not the author-- don't allow viewing.
        && $node->getRevisionUserId() != $account->id()) {
        return AccessResult::forbidden();
      }
      // Project is part of the round or they're the author or admin.
      return AccessResult::neutral();
    }
    else {
      // Not viewing it.
      return muser_project_project_access_check($account);
    } // Are they viewing it?
  } // Is it a project?
  if ($op == 'view' && $node->bundle() == 'round') {
    return AccessResult::forbiddenIf(!$account->hasPermission('create round content'), "The 'create round content' is required.");
  }
  return AccessResult::neutral();
}

/**
 * Checks dates to see if the user can manage projects.
 *
 * @param \Drupal\Core\Session\AccountInterface $account
 *
 * @return \Drupal\Core\Access\AccessResultForbidden|\Drupal\Core\Access\AccessResultNeutral
 */
function muser_project_project_access_check(AccountInterface $account) {

  if ($account->hasPermission('administer project rounds')) {
    // They can manage projects outside of dates-- don't bother.
    return AccessResult::neutral();
  }
  if (!$round = muser_project_get_current_round()) {
    // No round-- just return.
    // @todo - Should we return forbidden here instead?
    return AccessResult::neutral();
  }
  if (!muser_project_round_in_period($round, 'posting')) {
    // Outside of posting period-- don't allow it.
    return AccessResult::forbidden();
  }
  return AccessResult::neutral();
}

/**
 * Implements hook_entity_access().
 */
function muser_project_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {

  if ($entity->getEntityTypeId() != 'flagging' || $entity->bundle() != 'favorites') {
    return AccessResult::neutral();
  }

  if ($operation == 'view'
    && muser_project_allow_flagging_access($operation, $entity, $account)) {
      return AccessResult::allowed();
  }

  return AccessResult::neutral();

}

function muser_project_allow_flagging_access($operation, FlaggingInterface $flagging, AccountInterface $account) {

  switch ($operation) {
    case 'view':
    case 'update_status':
      /** @var  \Drupal\node\Entity\Node $project */
      $project = muser_project_get_project_for_flagging($flagging);
      $additional_mentors = [];
      if ($project->field_additional_mentors->count()) {
        foreach ($project->field_additional_mentors as $item) {
          $additional_mentors[$item->target_id] = TRUE;
        } // Loop thru additional mentors.
      } // Got additional mentors?
      if ($flagging->field_is_submitted->value
        && ($project->getOwnerId() == $account->id() || !empty($additional_mentors[$account->id()]))) {
        // Application is submitted and they own the project or are and additional mentor.
        return TRUE;
      }
      break;
    case 'accept_contract':
      /** @var  \Drupal\node\Entity\Node $project */
      $project = muser_project_get_project_for_flagging($flagging);
      // Need to check round?
//      $access = ($round && muser_project_round_in_period($round->id(), 'contract');
      if (_muser_system_contracts_enabled()
        && $project && $project->field_use_contract->value
        && $flagging && $flagging->field_is_submitted->value
        && $flagging->field_status->value == 'accepted'
        && ($project->getOwnerId() == $account->id() || $flagging->getOwnerId() == $account->id())) {
        // Application is submitted, accepted, is using a contract, and they own the project or the application.
        return TRUE;
      }
      break;
  }

  return FALSE;

}

/**
 * Implements hook_flag_action_access().
 */
function muser_project_flag_action_access($action, FlagInterface $flag, AccountInterface $account, EntityInterface $flaggable = NULL) {

  if ($flag->id() != 'favorites') {
    return AccessResult::neutral();
  }

  if (!$round = muser_project_get_current_round()) {
    // No round-- return FALSE.
    return AccessResult::forbidden();
  }

  if (!muser_project_round_in_period($round, 'application')) {
    // Outside of application dates-- return FALSE.
    return AccessResult::forbidden();
  }

  return AccessResult::neutral();

}

/**
 * Implements hook_flag_conditional_confirm_confirmation_required().
 */
function muser_project_flag_conditional_confirm_confirmation_required($action, FlagInterface $flag, EntityInterface $entity, $flagging, AccountInterface $account) {
  if ($action == 'flag' || !$account->isAuthenticated()) {
    return FALSE;
  }
  $route_name = \Drupal::routeMatch()->getRouteName();
  if ($route_name == 'view.my_favorites.page') {
    // Always require confirmation on the "My Favorites" page because we
    // need to force a reload afterwards to remove it from the list.
    return TRUE;
  }
  /** @var \Drupal\flag\FlaggingInterface $flagging */
  if ($flagging && $flagging->field_essay->value) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_module_implements_alter().
 */
function muser_project_module_implements_alter(&$implementations, $hook) {
  switch ($hook) {
    // Move our hook_entity_insert() implementation to the end of the list so
    // that the path alias is set.
    case 'entity_insert':
      $group = $implementations['muser_project'];
      unset($implementations['muser_project']);
      $implementations['muser_project'] = $group;
      break;
  }
}

/**
 * Implements hook_entity_insert().
 */
function muser_project_entity_insert(EntityInterface $entity) {
  if ($entity->getEntityTypeId() != 'node' || $entity->bundle() != 'project') {
    return;
  }
  muser_project_send_project_creation_email($entity, \Drupal::currentUser());

  // This seems pretty hacky to use $_POST here, but I don't know how else to
  // get this value from the node form since it's not a "real" field.
  if (!empty($_POST['activate']) && $round = muser_project_get_current_round()) {
    // Add newly-created project to the current round.
    muser_project_add_project_to_round($entity->id(), $round);
  } // Activate project?
}

/*
 * Implements hook_entity_predelete().
 */
function muser_project_entity_predelete(EntityInterface $entity) {
  // Only act on project nodes.
  if ($entity->getEntityTypeId() == 'node' && $entity->bundle() == 'project') {

    // Get all project rounds, including unpublished, that reference this project.
    $nids = \Drupal::entityQuery('node')
      ->condition('type', 'project_round')
      ->condition('field_project', $entity->id())
      ->execute();

    // Delete the project rounds, this will also delete flaggings.
    if ($nids) {
      $storage_handler = \Drupal::entityTypeManager()->getStorage('node');
      $entities = $storage_handler->loadMultiple($nids);
      $storage_handler->delete($entities);
    }
  }

  // Act on Rounds
  if ($entity->getEntityTypeId() == 'node' && $entity->bundle() == 'round') {

    // Get all project rounds, including unpublished, that reference this round.
    $nids = \Drupal::entityQuery('node')
      ->condition('type', 'project_round')
      ->condition('field_round', $entity->id())
      ->execute();

    // Delete the project rounds, this will also delete flaggings.
    if ($nids) {
      // Try to allocate enough time to run all the deletions.
      // @todo this should be a batch / queue
      Environment::setTimeLimit(0);

      foreach($nids as $nid) {
        if ($node = Node::load($nid)) {
          $node->delete();
        }
      }
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_update().
 */
function muser_project_node_update(EntityInterface $entity) {
  if ($entity->getEntityTypeId() != 'node' || $entity->bundle() != 'round') {
    return;
  }
  // Clear current_round, project activate link caches.
  Cache::invalidateTags(['current_round', 'muser_project_activate', 'muser_project_update_status']);
}

function muser_project_flagging_delete(EntityInterface $entity) {
  if ($entity->bundle() != 'favorites') {
    return;
  }
  if (!$entity->field_is_submitted->value) {
    return;
  }
  // Clear the count cache.
  $cache = \Drupal::cache();
  $cache->delete(muser_project_get_application_count_cid($entity->getFlaggable()->id()));
  $cache->delete(muser_project_get_user_application_count_cid($entity->getOwnerId()));
  // Clear the block's build array cache, too.
  Cache::invalidateTags(['application_count:' . $entity->getOwnerId()]);
  // Clear the review info block's build array cache.
  Cache::invalidateTags(['application_review_count:' . muser_project_get_flagged_project_mentor_id($entity)]);
  // Clear the project's application count build array cache.
  // This may not be needed because the flag handles it.
  $project_nid = $entity->getFlaggable()->field_project->entity->id();
  Cache::invalidateTags(['project_application_count:' . $project_nid]);
}

/**
 * Sends the "New project" email.
 *
 * @param \Drupal\node\NodeInterface $node
 * @param \Drupal\Core\Session\AccountInterface $account
 */
function muser_project_send_project_creation_email(NodeInterface $node, AccountInterface $account) {

  $recipients = [];
  if ($node->field_pi_email->value) {
    $recipients[$node->field_pi_email->value] = muser_system_format_email($node->field_pi_name->value, $node->field_pi_email->value);
  }
  $recipients[$account->getEmail()] = muser_system_format_email($account->getDisplayName(), $account->getEmail());

  $to = implode(',', $recipients);

  $muser_config = \Drupal::config('muser_system.settings');
  $site_config = \Drupal::config('system.site');

  $language = \Drupal::languageManager()->getCurrentLanguage()->getId();

  /** @var \Drupal\Core\Utility\Token $token_service */
  $token_service = \Drupal::token();
  $muser_data['project'] = $node;
  $muser_data['round'] = muser_project_get_current_round(TRUE);
  $params['from'] = muser_system_format_email($site_config->get('name'), $site_config->get('mail'));
  $params['subject'] = $token_service->replace($muser_config->get('new_project_email_subject'), ['user' => $account, 'node' => $node, 'muser' => $muser_data]);
  $params['message'] = $token_service->replace($muser_config->get('new_project_email_body'), ['user' => $account, 'node' => $node, 'muser' => $muser_data]);

  /** @var \Drupal\Core\Mail\MailManager $mailer */
  $mailer = \Drupal::service('plugin.manager.mail');
  $module = 'muser_project';
  $key = 'new_project';

  $send = TRUE;
  $result = $mailer->mail($module, $key, $to, $language, $params, NULL, $send);
  if ($result['result'] !== TRUE) {
    \Drupal::logger('muser_project')->error('Error sending new project email for project @title.', ['@title' => $node->label()]);
  }
  else {
    \Drupal::logger('muser_project')->info('New project email sent for project @title.', ['@title' => $node->label()]);
  }

}

/**
 * Implements hook_mail().
 */
function muser_project_mail($key, &$message, $params) {
  $message['from'] = $params['from'];
  $message['subject'] = $params['subject'];
  $message['body'][] = $params['message'];
}

/**
 * Returns name of field for the user type.
 *
 * @param $flagging
 * @param string $field
 * @param null $account
 *
 * @return string
 */
function muser_project_get_contract_field($flagging, $field, $account = NULL) {
  if (!$account) {
    $account = \Drupal::currentUser();
  }
  $accepted_field = 'field_contract_' . $field . '_student';
  if ($flagging->getOwnerId() != $account->id()) {
    $accepted_field = 'field_contract_' . $field . '_mentor';
  }
  return $accepted_field;
}

function muser_project_get_contract_accepted_field($flagging, $account = NULL) {
  return muser_project_get_contract_field($flagging, 'signed', $account);
}

function muser_project_get_contract_date_field($flagging, $account = NULL) {
  return muser_project_get_contract_field($flagging, 'date', $account);
}

function muser_project_get_application_contract_link(EntityInterface $entity) {

  $flagging = NULL;
  $round = NULL;

  if ($entity->getEntityTypeId() == 'flagging') {
    // It's a flagging.
    $flagging = $entity;
    $round = muser_project_get_round_for_flagging($flagging);
    $project = muser_project_get_project_for_flagging($flagging);
  }
  else {
    // It's a project.
    $project = $entity;
    if ($project_round_nid = muser_project_get_project_round_for_project($entity->id())) {
      $project_round = \Drupal\node\Entity\Node::load($project_round_nid);
      $flagging = muser_project_get_flagging($project_round);
      $round = $project_round->field_round->entity;
    }
  }

  $contract_accepted_field = muser_project_get_contract_accepted_field($flagging);
  $contract_date_field = muser_project_get_contract_date_field($flagging);

  $access = ($project && $project->field_use_contract->value
    && $flagging && $flagging->field_is_submitted->value
    && $flagging->field_status->value == 'accepted');

  if ($access && $flagging->{$contract_accepted_field}->value) {
    $accepted_text = '';
    if ($flagging->{$contract_date_field}->value) {
      $date = \Drupal::service('date.formatter')->format($flagging->{$contract_date_field}->date->getTimestamp(), 'long');
      $accepted_text = t('Accepted on @date', ['@date' => $date]);
    }
    $build['accept'] = [
      '#type' => 'markup',
      '#markup' => '<div title="' . $accepted_text . '" class="contract-status contract-status--accepted"><span class="contract-status__icon">'
        . MUSER_CONTRACT_ICON . '</span> <span class="contract-status__label">'  . t('Contract accepted')
        . '</span></div>',
      '#access' => TRUE,
    ];
    $build['#cache'] = [
      'contexts' => ['user'],
      'tags' => [
        'flagging:' . $flagging->id(),
        'user:' . \Drupal::currentUser()->id(),
      ],
    ];
    return $build;
  } // Already accepted?

  $access = $access && ($round && muser_project_round_in_period($round->id(), 'contract'));

  $url = Url::fromRoute('muser_project.accept_contract', ['flagging' => $flagging->id()]);
  $url->setOption('query', \Drupal::destination()->getAsArray());

  $build['accept'] = [
    '#type' => 'link',
    '#url' => $url,
    '#title' => t('Accept contract'),
    "#prefix" => "<div class='application__contract-change'>",
    "#suffix" => "</div>",
    '#attributes' => [
      'class' => ['use-ajax'],
      'data-dialog-options' => '{"width":"auto"}',
      'data-dialog-type' => 'modal',
    ],
    '#attached' => [
      'library' => ['core/drupal.dialog.ajax'],
    ],
    '#access' => $access,
  ];
  $build['#cache'] = [
    'contexts' => ['user'],
    'tags' => [
      'flagging:' . $flagging->id(),
      'user:' . \Drupal::currentUser()->id(),
    ],
  ];

  return $build;

}

/**
 * Implements hook_views_data_alter().
 */
function muser_project_views_data_alter(array &$data) {
  $data['node_field_data']['allowed_mentor'] = [
    'title' => t('Allowed mentor'),
    'argument' => [
      'title' => t('Allowed mentor'),
      'help' => t('Provides a custom argument for project nodes by their Author or Additional allowed mentors.'),
      'table' => 'node__field_additional_mentors',
      'field' => 'field_additional_mentor_target_id',
      'id' => 'allowed_mentor',
    ],
  ];
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc