You are here

CourseObjectAttendance.php in Course 3.x

File

modules/course_attendance/src/Plugin/course/CourseObject/CourseObjectAttendance.php
View source
<?php

namespace Drupal\course_attendance\Plugin\course\CourseObject;

use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResultForbidden;
use Drupal;
use Drupal\Core\Form\FormStateInterface;
use Drupal\course\Entity\CourseObject;

/**
 * @CourseObject(
 *   id = "attendance",
 *   label = "Attendance",
 * )
 */
class CourseObjectAttendance extends CourseObject {
  public function optionsDefinition() {
    $config = \Drupal::config('course_attendance.settings');
    $defaults = parent::optionsDefinition();
    $defaults['open'] = $config
      ->get('open');
    $defaults['close'] = $config
      ->get('closed');
    return $defaults;
  }

  /**
   * {@inheritdoc}
   */
  public function take() {
    $account = Drupal::currentUser();
    if ($this
      ->getFulfillment($account)
      ->isComplete()) {
      return [
        '#markup' => t("You have been marked attended by an administrator."),
      ];
    }
    else {
      return [
        '#markup' => t("You must be marked attended by an administrator."),
      ];
    }
  }

  /**
   * {@inheritdoc}
   */
  function optionsForm(&$form, FormStateInterface $form_state) {
    parent::optionsForm($form, $form_state);
    $config = $this
      ->getOptions();

    /* @var $duration \Drupal\duration_field\Service\DurationService */
    $duration = \Drupal::service('duration_field.service');
    $form['instance'] = [
      '#type' => 'textfield',
      '#title' => t('Attendance code'),
      '#description' => t('Randomly generated when this signup is created, but you can change it here.'),
      '#default_value' => $config['instance'],
    ];
    $form['open']['#type'] = 'container';
    $form['open']['#prefix'] = '<div class="container-inline">';
    $form['open']['#suffix'] = ' start date</div>';
    $form['open']['open'] = array(
      '#title' => t('Open attendance'),
      '#type' => 'duration',
      '#granularity' => 'd:h:i',
      '#default_value' => $duration
        ->getDurationStringFromDateInterval($this
        ->secondsToInterval(abs($config['open']))),
    );
    $form['open']['open_direction'] = array(
      '#title' => t(''),
      '#title_display' => 'invisible',
      '#type' => 'select',
      '#options' => array(
        1 => 'after',
        -1 => 'before',
      ),
      '#default_value' => $config['open'] > 1 ? 1 : -1,
    );
    $form['close']['#type'] = 'container';
    $form['close']['#prefix'] = '<div class="container-inline">';
    $form['close']['#suffix'] = ' start date</div>';
    $form['close']['close'] = array(
      '#title' => t('Close attendance'),
      '#type' => 'duration',
      '#granularity' => 'd:h:i',
      '#default_value' => $duration
        ->getDurationStringFromDateInterval($this
        ->secondsToInterval(abs($config['close']))),
    );
    $form['close']['close_direction'] = array(
      '#title' => t(''),
      '#title_display' => 'invisible',
      '#type' => 'select',
      '#options' => array(
        1 => 'after',
        -1 => 'before',
      ),
      '#default_value' => $config['close'] > 1 ? 1 : -1,
    );
    $form['reset'] = array(
      '#title' => 'Reset code',
      '#description' => t('Check this to randomly generate a new code.'),
      '#type' => 'checkbox',
    );
  }

  /**
   * {@inheritdoc}
   *
   * Check code uniqueness.
   */
  function optionsValidate(&$form, FormStateInterface $form_state) {
    parent::optionsValidate($form, $form_state);
    if ($courseObject = $this
      ->findObjectByCode($form_state
      ->getValue('instance'))) {
      if ($courseObject
        ->id() != $this
        ->id()) {
        $form_state
          ->setError($form['attendance']['instance'], t('Code is already in use.'));
      }
    }

    /* @var $duration \Drupal\duration_field\Service\DurationService */
    $duration = \Drupal::service('duration_field.service');
    $form_state
      ->setValue('open', $form_state
      ->getValue('open_direction') * $duration
      ->getSecondsFromDateInterval($form_state
      ->getValue('open')));
    $form_state
      ->setValue('close', $form_state
      ->getValue('close_direction') * $duration
      ->getSecondsFromDateInterval($form_state
      ->getValue('close')));
  }

  /**
   * {@inheritdoc}
   *
   * Generate an attendance code if not provided.
   */
  function preSave(EntityStorageInterface $storage) {
    if ($this
      ->get('instance')
      ->isEmpty()) {
      $this
        ->set('instance', self::generateWord());
    }
  }

  /**
   * Find an attendance course object by the attendance code.
   *
   * @param string $code
   *   The SMS code.
   *
   * @return CourseObject
   */
  public static function findObjectByCode(string $code) {
    $entities = Drupal::entityTypeManager()
      ->getStorage('course_object')
      ->loadByProperties([
      'object_type' => 'attendance',
      'instance' => $code,
    ]);
    return $entities ? reset($entities) : NULL;
  }

  /**
   * Generate an attendance code that isn't used.
   */
  static function generateWord() {

    // Distinguishable characters.
    $accepted_cons_start = "BCDFGHJKLMNPQRSTVWYZ";
    $accepted_cons_end = "BCDFGHKLMNPQRSTVWXYZ";
    $accepted_vowels = "AEUO";

    // Gen word, then check
    do {
      $word = '';
      for ($i = 1; $i <= 6; $i++) {
        switch ($i) {
          case 1:
          case 4:
            $word .= $accepted_cons_start[rand(0, strlen($accepted_cons_start) - 1)];
            break;
          case 3:
          case 6:
            $word .= $accepted_cons_end[rand(0, strlen($accepted_cons_end) - 1)];
            break;
          case 2:
          case 5:
            $word .= $accepted_vowels[rand(0, strlen($accepted_vowels) - 1)];
            break;
        }
      }
      \Drupal::moduleHandler()
        ->alter('course_attendance_word', $word);
    } while (self::findObjectByCode($word));
    return $word;
  }

  /**
   * Convert seconds to full ISO8601 string.
   *
   * Yes, we can use DateInterval but Duration expects a full string
   * (not just PT100S).
   *
   * Why isn't this a thing?
   */
  function secondsToInterval($seconds) {
    $date1 = new \DateTime('1904-01-01');
    $date2 = new \DateTime('1904-01-01');
    $date2
      ->add(new \DateInterval("PT{$seconds}S"));
    return $date2
      ->diff($date1);
  }
  function getOptionsSummary() {
    $summary = parent::getOptionsSummary();
    $summary['instance'] = t('Code: @code', [
      '@code' => $this
        ->getOption('instance'),
    ]);
    return $summary;
  }

  /**
   * {@inheritdoc}
   *
   * Response to "attendance" operations to check attendance window access.
   */
  function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
    if ($operation == 'attendance') {
      $start_date = strtotime($this
        ->getCourse()
        ->get('field_course_event_date')->value . ' UTC');
      $open = $start_date + $this
        ->getOption('open');
      $close = $start_date + $this
        ->getOption('close');
      if (\Drupal::time()
        ->getRequestTime() < $open) {
        return $return_as_object ? AccessResultForbidden::forbidden('not_open') : FALSE;
      }
      if (\Drupal::time()
        ->getRequestTime() > $close) {
        return $return_as_object ? AccessResultForbidden::forbidden('closed') : FALSE;
      }
    }
    return parent::access($operation, $account, $return_as_object);
  }

}

Classes

Namesort descending Description
CourseObjectAttendance Plugin annotation @CourseObject( id = "attendance", label = "Attendance", )