You are here

FullcalendarViewPreprocess.php in Fullcalendar View 8.3

File

src/FullcalendarViewPreprocess.php
View source
<?php

namespace Drupal\fullcalendar_view;

use Drupal\Component\Utility\Xss;
use Drupal\Core\Datetime\DrupalDateTime;
class FullcalendarViewPreprocess {

  /**
   * Process the view variable array.
   *
   * @param array $variables
   *   Template variables.
   */
  public function process(array &$variables) {
    global $base_path;
    $view = $variables['view'];
    $style = $view->style_plugin;
    $options = $style->options;
    $fields = $view->field;

    // Get current language.
    $language = \Drupal::languageManager()
      ->getCurrentLanguage();
    if ($language
      ->isDefault()) {
      $variables['language'] = $base_path;
    }
    else {
      $variables['language'] = $base_path . $language
        ->getId() . '/';
    }

    // Current user.
    $user = $variables['user'];

    // CSRF token.
    $token = '';
    if (!$user
      ->isAnonymous()) {
      $token = \Drupal::csrfToken()
        ->get($user
        ->id());
    }

    // New event bundle type.
    $event_bundle_type = $options['bundle_type'];
    $entity_type = $view
      ->getBaseEntityType();
    if ($entity_type
      ->id() === 'node') {
      $add_form = 'node/add/' . $event_bundle_type;
    }
    else {
      $entity_links = $entity_type
        ->get('links');
      if (isset($entity_links['add-form'])) {
        $add_form = str_replace('{' . $entity_type
          ->id() . '}', $event_bundle_type, $entity_links['add-form']);
      }
      elseif (isset($entity_links['add-page'])) {
        $add_form = str_replace('{' . $entity_type
          ->id() . '}', $event_bundle_type, $entity_links['add-page']);
      }
    }

    // Can the user add a new event?
    $entity_manager = \Drupal::entityTypeManager();
    $access_handler = $entity_manager
      ->getAccessControlHandler($entity_type
      ->id());
    $dbl_click_to_create = FALSE;
    if ($access_handler
      ->createAccess($event_bundle_type)) {
      $dbl_click_to_create = TRUE;
    }

    // Pass entity type to twig template.
    $variables['entity_id'] = $entity_type
      ->id();

    // Update options for twig.
    $variables['options'] = $options;

    // Hide the create event link from user who doesn't have the permission
    // or if this feature is turn off.
    $variables['showAddEvent'] = $dbl_click_to_create && $options['createEventLink'];

    // Time format
    $timeFormat = $options['timeFormat'];

    // Field machine name of start date.
    $start_field = $options['start'];

    // Start date field is critical.
    if (empty($start_field)) {
      return;
    }

    // Field machine name of end date.
    $end_field = $options['end'];

    // Field machine name for event description.
    $des_field = $options['des'];

    // Field machine name of taxonomy field.
    $tax_field = $options['tax_field'];

    // Field machine name of excluding dates field.
    $exc_field = isset($options['excluding_dates']) ? $options['excluding_dates'] : NULL;

    // Default date of the calendar.
    switch ($options['default_date_source']) {
      case 'now':
        $default_date = date('Y-m-d');
        break;
      case 'fixed':
        $default_date = $options['defaultDate'];
        break;
      case 'first':

        // Don't do anything, we'll set it below.
        break;
    }

    // Default Language.
    $default_lang = $options['defaultLanguage'];

    // Color for bundle types.
    $color_content = $options['color_bundle'];

    // Color for taxonomies.
    $color_tax = $options['color_taxonomies'];

    // Date fields.
    $start_field_option = $fields[$start_field]->options;
    $end_field_option = empty($end_field) ? NULL : $fields[$end_field]->options;
    $exc_field_option = empty($exc_field) ? NULL : $fields[$exc_field]->options;

    // Custom timezone or user timezone.
    $timezone = !empty($start_field_option['settings']['timezone_override']) ? $start_field_option['settings']['timezone_override'] : date_default_timezone_get();

    // Open a new window for details of an event.
    $title_field = empty($options['title']) || $options['title'] == 'title' ? 'title' : $options['title'];

    // Calendar entries linked to entity.
    $link_to_entity = FALSE;
    if (isset($fields[$title_field]->options['settings']['link_to_entity'])) {
      $link_to_entity = $fields[$title_field]->options['settings']['link_to_entity'];
    }
    elseif (isset($fields[$title_field]->options['settings']['link'])) {
      $link_to_entity = $fields[$title_field]->options['settings']['link'];
    }

    // Set the first day setting.
    $first_day = isset($options['firstDay']) ? $options['firstDay'] : 0;

    // Left side buttons.
    $left_buttons = 'prev,next today';

    // Right side buttons.
    $right_buttons = 'month';
    foreach ($options['right_buttons'] as $key => $value) {
      if ($value !== 0) {
        $right_buttons .= ',' . $key;
      }
    }
    $entries = [];
    if (!empty($start_field)) {

      // Timezone conversion service.
      $timezone_service = \Drupal::service('fullcalendar_view.timezone_conversion_service');

      // Save view results into entries array.
      foreach ($view->result as $row) {

        // Set the row_index property used by advancedRender function.
        $view->row_index = $row->index;

        // Days of a week for a recurring event.
        $dow = NULL;

        // Days of a month for a recurring event.
        $dom = NULL;
        $monthly = NULL;
        $weekly = NULL;

        // Recurring range.
        $range = NULL;

        // Result entity of current row.
        $current_entity = $row->_entity;

        // Start field is vital, if it doesn't exist then ignore this entity.
        if (!$current_entity
          ->hasField($start_field)) {
          continue;
        }

        // Monthly recurring.
        if ($current_entity
          ->hasField('field_monthly_event')) {
          $monthly = $current_entity
            ->get('field_monthly_event')
            ->getValue();
        }

        // If the monthly recurring is set, the weekly recurring will be ignored.
        if (!empty($monthly)) {
          $dom = [];
          foreach ($monthly as $day) {
            if (!empty($day['value'])) {
              $dom[] = $day['value'];
            }
          }
        }
        elseif ($current_entity
          ->hasField('field_weekly_event')) {
          $weekly = $current_entity
            ->get('field_weekly_event')
            ->getValue();
        }
        if (!empty($weekly)) {
          $dow = [];
          foreach ($weekly as $day) {
            if (isset($day['value'])) {

              // Sunday is 0.
              $dow[] = $day['value'] % 7;
            }
          }
          if (!empty($dow)) {
            $dow = '[' . implode(',', $dow) . ']';
          }
        }

        // Entity id.
        $entity_id = $current_entity
          ->id();

        // Entity bundle type.
        $entity_bundle = $current_entity
          ->bundle();

        // Background color based on taxonomy field.
        if (!empty($tax_field) && $current_entity
          ->hasField($tax_field)) {

          // Event type.
          $event_type = $current_entity
            ->get($tax_field)->target_id;
        }

        // Calendar event start date.
        $start_date = $current_entity
          ->get($start_field)
          ->getValue();

        // Calendar event end date.
        $end_date = empty($end_field) || !$current_entity
          ->hasField($end_field) ? '' : $current_entity
          ->get($end_field)
          ->getValue();

        // Apart from start date and end date field,
        // other fields might be rewritten by
        // the field setting of the view.
        if ($options['use_entity_fields']) {

          // Description for events. For multiple bundle types,
          // there might be more than one field specified.
          if (!empty($des_field) && is_array($des_field)) {
            foreach ($des_field as $des_field_name) {
              if ($current_entity
                ->hasField($des_field_name)) {
                $des = $current_entity
                  ->get($des_field_name)
                  ->getValue();

                // We just need only one description text.
                // Once we got it, quit the loop.
                break;
              }
            }
          }
          if (!isset($des)) {
            $des = '';
          }
          if (is_array($des)) {
            $des = reset($des);
            if (isset($des['value'])) {
              $des = $des['value'];
            }
          }

          // Event title.
          if (empty($options['title']) || $options['title'] == 'title') {
            $title = $current_entity
              ->label();
          }
          elseif ($current_entity
            ->hasField($options['title'])) {
            $title = $current_entity
              ->get($options['title'])->value;
          }
          else {
            $title = 'Invalid event title';
          }
        }
        else {

          // Description for events. For multiple bundle types,
          // there might be more than one field specified.
          if (!empty($des_field) && is_array($des_field)) {

            // Render all other fields to so they can be used in rewrite.
            foreach ($fields as $field) {
              if (method_exists($field, 'advancedRender')) {
                $field
                  ->advancedRender($row);
              }
            }

            // We need to render the description field again,
            // in case a replacement pattern for other field.
            // For example, {{field name}}.
            foreach ($des_field as $des_field_name) {
              if (isset($fields[$des_field_name]) && method_exists($fields[$des_field_name], 'advancedRender')) {
                $des_raw = $fields[$des_field_name]
                  ->advancedRender($row);
                $des = empty($des_raw) ? '' : $des_raw;

                // We just need only one description text.
                // Once we got it, quit the loop.
                break;
              }
            }
          }
          if (!isset($des)) {
            $des = '';
          }
          if (is_array($des)) {
            $des = reset($des);
            if (isset($des['value'])) {
              $des = $des['value'];
            }
          }

          // Event title.
          if (empty($options['title']) || $options['title'] == 'title') {
            $title = $fields['title']
              ->advancedRender($row);
          }
          elseif (!empty($fields[$options['title']])) {
            $title = $fields[$options['title']]
              ->advancedRender($row);
          }
          else {
            $title = 'Invalid event title';
          }
        }
        $entry = [
          'title' => Xss::filter($title),
          'description' => $des,
          'id' => $entity_id,
          'url' => $current_entity
            ->toUrl()
            ->toString(),
        ];
        if (!empty($start_date)) {

          // There might be more than one value for a field,
          // but we only need the first one and ignore others.
          $start_date = reset($start_date)['value'];

          // Examine the field type.
          if ($start_field_option['type'] === 'timestamp') {
            $start_date = intval($start_date);
            $start_date = date(DATE_ATOM, $start_date);
          }
          elseif (strpos($start_field_option['type'], 'datetime') === FALSE && strpos($start_field_option['type'], 'daterange') === FALSE) {
            $valid = FALSE;

            // checking supported field types form plugin defintions
            foreach ($variables['fullcalendar_fieldtypes'] as $fieldtype) {
              if (strpos($start_field_option['type'], $fieldtype) === 0) {
                $valid = TRUE;
              }
            }
            if (!$valid) {

              // This field is not a valid date time field.
              continue;
            }
          }

          // A user who doesn't have the permission can't edit an event.
          if (!$current_entity
            ->access('update')) {
            $entry['editable'] = FALSE;
          }

          // If we don't yet know the default_date (we're configured to use the
          // date from the first row, and we haven't set it yet), do so now.
          if (!isset($default_date)) {

            // Only use the first 10 digits since we only care about the date.
            $default_date = substr($start_date, 0, 10);
          }
          $all_day = strlen($start_date) < 11 ? TRUE : FALSE;
          if ($all_day) {

            // Recurring event.
            if (!empty($dow) || !empty($dom)) {
              if (empty($options['business_start'])) {
                $business_start = new DrupalDateTime('2018-02-24T08:00:00');
              }
              else {
                $business_start = new DrupalDateTime($options['business_start']);
              }
              $entry['start'] = $business_start
                ->format('H:i');
              $range['start'] = $start_date;
            }
            else {
              $entry['start'] = $start_date;
            }
          }
          else {

            // Recurring event.
            if (!empty($dow) || !empty($dom)) {
              $format = 'H:i';
              $range['start'] = $timezone_service
                ->utcToLocal($start_date, $timezone, 'Y-m-d');
            }
            else {
              $format = DATE_ATOM;
            }

            // By default, Drupal store date time in UTC timezone.
            // So we need to convert it into user timezone.
            $entry['start'] = $timezone_service
              ->utcToLocal($start_date, $timezone, $format);
          }
        }
        else {
          continue;
        }

        // Cope with end date in the same way as start date above.
        if (!empty($end_date)) {
          if ($end_field_option['type'] === 'timestamp') {
            $end_date = reset($end_date)['value'];
            $end_date = intval($end_date);
            $end_date = date(DATE_ATOM, $end_date);
          }
          elseif (strpos($end_field_option['type'], 'daterange') !== FALSE) {
            $end_date = $end_date[0]['end_value'];
          }
          elseif (strpos($end_field_option['type'], 'datetime') === FALSE) {

            // This field is not a valid date time field.
            $end_date = '';
          }
          else {
            $end_date = reset($end_date)['value'];
          }
          if (!empty($end_date)) {
            $all_day = strlen($end_date) < 11 ? TRUE : FALSE;
            if ($all_day) {
              $end = new DrupalDateTime($end_date);

              // The end date is inclusive for a all day event,
              // which is not what we want. So we need one day offset.
              $end
                ->modify('+1 day');

              // Recurring event.
              if (!empty($dow) || !empty($dom)) {
                if (!empty($options['business_end'])) {
                  $business_end = new DrupalDateTime($options['business_end']);
                }
                else {
                  $business_end = new DrupalDateTime('2018-02-24T18:00:00');
                }
                $entry['end'] = $business_end
                  ->format('H:i');
                $range['end'] = $end
                  ->format('Y-m-d');
              }
              else {
                $entry['end'] = $end
                  ->format('Y-m-d');
              }
            }
            else {

              // Recurring event.
              if (!empty($dow) || !empty($dom)) {
                $format = 'H:i';
                $range['end'] = $timezone_service
                  ->utcToLocal($end_date, $timezone, 'Y-m-d', '+1 day');
              }
              else {
                $format = DATE_ATOM;
              }
              $entry['end'] = $timezone_service
                ->utcToLocal($end_date, $timezone, $format);
            }
          }
        }
        else {

          // Without end date field, this event can't be resized.
          $entry['durationEditable'] = FALSE;
        }

        // Set the color for this event.
        if (isset($event_type) && isset($color_tax[$event_type])) {
          $entry['backgroundColor'] = $color_tax[$event_type];
        }
        elseif (isset($color_content[$entity_bundle])) {
          $entry['backgroundColor'] = $color_content[$entity_bundle];
        }

        // Collect excluding dates values for recurring event only.
        if ((!empty($dom) || !empty($dow)) && !empty($exc_field) && !empty($exc_field_option)) {
          $exc_dates = $current_entity
            ->hasField($exc_field) ? $current_entity
            ->get($exc_field)
            ->getValue() : '';
          $exc_dates_array = [];
          foreach ($exc_dates as $exc_date) {
            if (!empty($exc_date['value'])) {
              if ($exc_field_option['type'] === 'timestamp') {
                $date_item = date(DATE_ATOM, intval($exc_date['value']));
              }
              elseif (strpos($exc_field_option['type'], 'datetime') !== FALSE) {
                $date_item = $exc_date['value'];

                // Drupal store date and time date in UTC timezone.
                // We need to convert it into local time zone.
                if (strlen($date_item) > 10) {
                  $date_item = $timezone_service
                    ->utcToLocal($date_item, $timezone, 'Y-m-d');
                }
              }

              // Only use the first 10 digits since we only care about the date.
              if (!empty($date_item)) {
                $exc_dates_array[] = substr($date_item, 0, 10);
              }
            }
          }
          if (!empty($exc_dates_array)) {
            $range['excluding_dates'] = $exc_dates_array;
          }
        }
        if (!empty($dom)) {
          $entry['dom'] = $dom;
          $entry['ranges'] = [
            $range,
          ];

          // Recurring event is not editable.
          $entry['editable'] = FALSE;
        }
        elseif (!empty($dow)) {
          $entry['dow'] = $dow;
          $entry['ranges'] = [
            $range,
          ];

          // Recurring event is not editable.
          $entry['editable'] = FALSE;
        }
        $entries[] = $entry;
      }

      // Remove the row_index property as we don't it anymore.
      unset($view->row_index);

      // Title format.
      // @see https://fullcalendar.io/docs/v3/titleFormat
      // @see https://fullcalendar.io/docs/v3/date-formatting-string
      // @see https://momentjs.com/docs/#/displaying/format/
      $title_format = NULL;

      // Control the column header, as used in the week/agenda display.
      $column_header_format = NULL;

      // Load tooltip plugin.
      if (!empty($des_field)) {
        $variables['#attached']['library'][] = 'fullcalendar_view/tooltip';
      }

      // Load the fullcalendar js library.
      $variables['#attached']['library'][] = 'fullcalendar_view/fullcalendar';

      // Pass data to js file.
      $variables['#attached']['drupalSettings'] = [
        'firstDay' => $first_day,
        'fullCalendarView' => $entries,
        'defaultDate' => empty($default_date) ? date('Y-m-d') : $default_date,
        'defaultLang' => $default_lang,
        'languageSelector' => $options['languageSelector'],
        'allowEventOverlap' => $options['allowEventOverlap'],
        'updateAllowed' => $options['updateAllowed'],
        'updateConfirm' => $options['updateConfirm'],
        'dialogWindow' => $options['dialogWindow'],
        'linkToEntity' => $link_to_entity,
        'columnHeaderFormat' => $column_header_format,
        'eventBundleType' => $event_bundle_type,
        'startField' => $start_field,
        'endField' => $end_field,
        'leftButtons' => $left_buttons,
        'rightButtons' => $right_buttons,
        'defaultView' => isset($options['default_view']) ? $options['default_view'] : 'month',
        'dblClickToCreate' => $dbl_click_to_create,
        'navLinks' => $options['nav_links'],
        'entityType' => $entity_type
          ->id(),
        'addForm' => isset($add_form) ? $add_form : '',
        'token' => $token,
        'openEntityInNewTab' => $options['openEntityInNewTab'],
        'timeFormat' => $timeFormat,
        'titleFormat' => $title_format,
      ];
    }
  }

}

Classes