fullcalendar-8.x-2.x-dev/src/Plugin/views/style/FullCalendar.php

src/Plugin/views/style/FullCalendar.php
<?php

namespace Drupal\fullcalendar\Plugin\views\style;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\fullcalendar\Plugin\FullcalendarPluginCollection;
use Drupal\fullcalendar\Plugin\fullcalendar\type\OptionsFormHelperTrait;
use Drupal\views\Plugin\views\style\StylePluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides the FullCalendar views style plugin.
 *
 * @ViewsStyle(
 *   id = "fullcalendar",
 *   title = @Translation("FullCalendar"),
 *   help = @Translation("Displays items on a calendar."),
 *   theme = "views_view--fullcalendar",
 *   display_types = {"normal"}
 * )
 */
class FullCalendar extends StylePluginBase {

  use OptionsFormHelperTrait;

  /**
   * {@inheritdoc}
   */
  protected $usesFields = TRUE;

  /**
   * {@inheritdoc}
   */
  protected $usesGrouping = FALSE;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected ModuleHandlerInterface $moduleHandler;

  /**
   * Entity Field Manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected EntityFieldManagerInterface $fieldManager;

  /**
   * Stores the FullCalendar plugins used by this style plugin.
   *
   * @var \Drupal\fullcalendar\Plugin\FullcalendarPluginCollection
   */
  protected FullcalendarPluginCollection $pluginBag;

  /**
   * The date formatter.
   *
   * @var \Drupal\Core\Datetime\DateFormatter
   *   The date formatter service.
   */
  protected DateFormatter $dateFormatter;

  /**
   * The date and time service.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected TimeInterface $dateTime;

  /**
   * The language manager.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected LanguageManagerInterface $languageManager;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected EntityFieldManagerInterface $entityFieldManager;

  /**
   * The access manager.
   *
   * @var \Drupal\Core\Access\AccessManagerInterface
   */
  protected AccessManagerInterface $accessManager;

  /**
   * The CSRF token generator.
   *
   * @var \Drupal\Core\Access\CsrfTokenGenerator
   */
  protected $tokenGenerator;

  /**
   * An array of color mappings.
   *
   * @var array
   */
  protected $eventColors = [];

  /**
   * Whether or not to convert timezones.
   *
   * @var bool
   */
  protected $convertTzs = TRUE;

  /**
   * {@inheritdoc}
   */
  public function evenEmpty(): bool {
    return TRUE;
  }

  /**
   * Get all available FullCalendar plugins.
   *
   * @return \Drupal\fullcalendar\Plugin\FullcalendarPluginCollection
   *   The available plugins.
   */
  public function getPlugins(): FullcalendarPluginCollection {
    return $this->pluginBag;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $instance->pluginBag = new FullcalendarPluginCollection($container->get('plugin.manager.fullcalendar'), $instance);
    $instance->moduleHandler = $container->get('module_handler');
    $instance->fieldManager = $container->get('entity_field.manager');
    $instance->dateFormatter = $container->get('date.formatter');
    $instance->messenger = $container->get('messenger');
    $instance->dateTime = $container->get('datetime.time');
    $instance->languageManager = $container->get('language_manager');
    $instance->entityFieldManager = $container->get('entity_field.manager');
    $instance->accessManager = $container->get('access_manager');
    $instance->tokenGenerator = $container->get('csrf_token');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  protected function defineOptions(): array {
    $options = parent::defineOptions();

    /** @var \Drupal\fullcalendar\Plugin\FullcalendarInterface $plugin */
    foreach ($this->getPlugins() as $plugin) {
      $options += $plugin->defineOptions();
    }

    return $options;
  }

  /**
   * Builds the options form.
   *
   * @todo remove this duplicate docblock. It was included to resolve a specific
   * parameterByRef.type phpstan error.
   *
   * @param array $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   */
  public function buildOptionsForm(&$form, FormStateInterface $form_state): void {
    parent::buildOptionsForm($form, $form_state);

    /** @var \Drupal\fullcalendar\Plugin\FullcalendarInterface $plugin */
    foreach ($this->getPlugins() as $plugin) {
      $plugin->buildOptionsForm($form, $form_state);
    }
  }

  /**
   * Submits the options form.
   *
   * @todo remove this duplicate docblock. It was included to resolve a specific
   * parameterByRef.type phpstan error.
   *
   * @param array $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   */
  public function submitOptionsForm(&$form, FormStateInterface $form_state): void {
    parent::submitOptionsForm($form, $form_state);

    /** @var \Drupal\fullcalendar\Plugin\FullcalendarInterface $plugin */
    foreach ($this->getPlugins() as $plugin) {
      $plugin->submitOptionsForm($form, $form_state);
    }
  }

  /**
   * Extracts date fields from the view.
   */
  public function parseFields(): array {
    $this->view->initHandlers();
    $labels = $this->displayHandler->getFieldLabels();

    $date_fields = [];

    /** @var \Drupal\views\Plugin\views\field\EntityField $field */
    foreach ($this->view->field as $id => $field) {
      if (fullcalendar_field_is_date($field)) {
        $date_fields[$id] = $labels[$id];
      }
    }

    return $date_fields;
  }

  /**
   * {@inheritdoc}
   */
  public function validate() {
    $settings = $this->prepareSettings();
    if ($this->displayHandler->display['display_plugin'] !== 'default' && empty($settings['google']['googleCalendarApiKey']) && !$this->parseFields()) {
      $this->messenger->deleteAll();
      $this->messenger->addWarning($this->t('Display "@display" requires at least one date field unless you are displaying data from a Google Calendar.', [
        '@display' => $this->displayHandler->display['display_title'],
      ]), TRUE);
    }

    return parent::validate();
  }

  /**
   * {@inheritdoc}
   */
  public function render(): array {
    return [
      '#theme' => $this->themeFunctions(),
      '#view' => $this->view,
      '#options' => $this->options,
      '#attached' => $this->prepareAttached(),
    ];
  }

  /**
   * Load libraries.
   *
   * @throws \Exception
   */
  protected function prepareAttached(): array {
    $attached['library'][] = 'fullcalendar/drupal.fullcalendar';

    $settings = $this->prepareSettings();

    if (!empty($settings['options']['themeSystem']) && ($settings['options']['themeSystem'] !== 'standard')) {
      $attached['library'][] = 'fullcalendar/fullcalendar.' . $settings['options']['themeSystem'];
    }

    if (!empty($settings['locale']) && $settings['locale'] !== 'en') {
      $attached['library'][] = 'fullcalendar/fullcalendar.locales';
    }

    $attached['drupalSettings']['fullcalendar'] = [
      'js-view-dom-id-' . $this->view->dom_id => $settings,
    ];

    return $attached;
  }

  /**
   * Prepare JavaScript settings.
   *
   * @throws \Exception
   */
  protected function prepareSettings(): array {
    $settings = &drupal_static(__METHOD__, []);

    if (empty($settings)) {
      /** @var \Drupal\fullcalendar\Plugin\fullcalendar\type\FullCalendar $plugin */
      foreach ($this->getPlugins() as $plugin) {
        $plugin->process($settings);
      }
    }

    // Google Calendar events.
    if (!empty($settings['options']['googleCalendarApiKey'])) {
      $ids = array_map('trim', explode(',', trim($settings['googleCalendarId'])));
      foreach ($ids as $id) {
        $settings['options']['eventSources'][] = [
          'googleCalendarId' => $id,
          'className' => 'fc-event-default',
        ];
      }
    }

    // Make any color settings available for event processing.
    if (!empty($settings['colors'])) {
      $this->eventColors = $settings['colors'];
    }

    if (isset($settings['convert_timezones'])) {
      $this->convertTzs = $settings['convert_timezones'];
    }

    // Drupal events.
    $events = $this->prepareEvents();
    if ($events) {
      $settings['options']['eventSources'][] = $events;
    }
    $settings['entityType'] = $this->view->getBaseEntityType()->id();
    if (!empty($settings['bundle_type'])) {
      // Can the user add a new event?
      if ($settings['entityType'] === 'node') {
        if ($this->accessManager->checkNamedRoute('node.add', ['node_type' => $settings['bundle_type']])) {
          $settings['addLink'] = 'node/add/' . $settings['bundle_type'];
        }
      }
      else {
        $entity_type = $this->view->getBaseEntityType();
        $entity_links = $entity_type->get('links');
        if (isset($entity_links['add-form'])) {
          $settings['addLink'] = str_replace('{' . $entity_type->id() . '}', $settings['bundle_type'], $entity_links['add-form']);
        }
        elseif (isset($entity_links['add-page'])) {
          $settings['addLink'] = str_replace('{' . $entity_type->id() . '}', $settings['bundle_type'], $entity_links['add-page']);
        }
      }
    }
    // Current user.
    $user = $this->view->getUser();
    // CSRF token.
    $token = '';
    if (!$user->isAnonymous()) {
      $token = $this->tokenGenerator->get();
    }
    $settings['token'] = $token;
    $settings['timeZone'] = date_default_timezone_get();

    $settings['locale'] = $this->languageManager->getCurrentLanguage()->getId();
    $settings['convertTzs'] = $this->convertTzs;

    return $settings;
  }

  /**
   * Prepare events for calendar.
   *
   * @return array
   *   Array of events ready for fullcalendar.
   *
   * @throws \Exception
   */
  protected function prepareEvents(): array {
    $events = [];

    $title_field = $this->options['title_field'] ?? '';

    foreach ($this->view->result as $delta => $row) {
      $this->view->row_index = $row->index;

      /** @var \Drupal\Core\Entity\EntityInterface $entity */
      $entity = $row->_entity;

      // Collect only date fields.
      $date_fields = [];
      // Collect prepared events.
      $event = [];

      // Prepare title.
      $title = '';
      if ($title_field && $entity instanceof FieldableEntityInterface) {
        // Retrieve the rewritten field value.
        $title = $this->view->style_plugin->getField($row->index, $title_field);
        if ($title instanceof MarkupInterface) {
          $title = strip_tags($title->__toString());
        }
      }
      // Default to the entity label if no other value found.
      if (!$title) {
        $title = $entity->label();
      }

      /** @var \Drupal\views\Plugin\views\field\EntityField $field */
      foreach ($this->view->field as $field_name => $field) {
        if (fullcalendar_field_is_date($field)) {
          $field_storage_definitions = $this->fieldManager->getFieldStorageDefinitions($field->definition['entity_type']);
          $field_definition = $field_storage_definitions[$field->definition['field_name']];

          $values = $field->getItems($row);
          if (!empty($values)) {
            $date_fields[$field_name] = [
              'value' => $values,
              'field_alias' => $field->field_alias,
              'field_name' => $field_definition->getName(),
              'field_info' => $field_definition,
              'timezone_override' => $field->options['settings']['timezone_override'],
            ];
          }
        }
      }

      // @todo custom date field?
      // If using a custom date field, filter the fields to process.
      if (!empty($this->options['fields']['date'])) {
        $date_fields = array_intersect_key($date_fields, $this->options['fields']['date_field']);
      }

      // If there are no date fields, return.
      if (empty($date_fields)) {
        return $events;
      }

      foreach ($date_fields as $field) {
        /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $field_definition */
        $field_definition = $field['field_info'];
        // Get 'min' and 'max' dates appear in the Calendar.
        $date_range = $this->getExposedDates($field['field_name']);

        // "date_recur" field (with recurring date).
        if ($field_definition->getType() === 'date_recur') {
          /** @var \Drupal\date_recur\Plugin\Field\FieldType\DateRecurFieldItemList $field_items */
          $field_items = $row->_entity->{$field['field_name']};

          $isRecurring = FALSE;

          /** @var \Drupal\date_recur\Plugin\Field\FieldType\DateRecurItem $item */
          foreach ($field_items as $index => $item) {
            // @todo The method getOccurrenceHandler does not exist.
            // Get DateRecur Occurrence Handler.
            // @phpstan-ignore-next-line
            $occurrenceHandler = $item->getOccurrenceHandler();

            // If this field is a DateRecur field.
            if ($occurrenceHandler->isRecurring()) {
              // Get a list of occurrences for display.
              $occurrences = $occurrenceHandler->getOccurrencesForDisplay($date_range['min'], $date_range['max']);

              foreach ($occurrences as $occurrence) {
                /** @var \DateTime $start */
                $start = $occurrence['value'];
                /** @var \DateTime $end */
                $end = $occurrence['end_value'];

                $event = $this->prepareEvent($entity, $title, $field, (int) $index);
              }

              $isRecurring = TRUE;
            }
          }

          if ($isRecurring === TRUE) {
            // At this point, all DateRecur occurrences are merged into $rows
            // so we can continue adding date items with the next field.
            continue;
          }
        }

        // "datetime" and "daterange" fields or "date_recur" field (without
        // recurring date).
        foreach ($field['value'] as $index => $item) {
          // Start time is required!
          if (empty($item['raw']->value)) {
            continue;
          }

          $event = $this->prepareEvent($entity, $title, $date_fields, (int) $index);
          if (!empty($event)) {
            // @todo more sophisticated key assignment needed?
            $events[] = $event;
          }
        }
      }

      if (empty($events) && !empty($event)) {
        $events[$delta] = $event;
      }
    }

    return $events;
  }

  /**
   * Helper method to prepare an event.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   Event entity.
   * @param string $title
   *   The event title.
   * @param array $fields
   *   The fields.
   * @param int $delta
   *   Field delta.
   *
   * @return array
   *   The prepared event.
   */
  private function prepareEvent(EntityInterface $entity, string $title, array $fields, int $delta): array {
    $classes = $this->moduleHandler->invokeAll('fullcalendar_classes', [$entity]);
    $this->moduleHandler->alter('fullcalendar_classes', $classes, $entity);

    $classes = array_map([
      Html::class,
      'getClass',
    ], $classes);
    $class = (count($classes)) ? implode(' ', array_unique($classes)) : '';

    // Start/end dates.
    $event_start_end = $this->getEventStartEndDates($fields, $delta);

    $event_start = $event_start_end['start'];
    $event_end = $event_start_end['end'];
    $context = [
      'entity' => $entity,
      'fields' => $fields,
    ];
    $this->moduleHandler->alter('fullcalendar_process_dates', $event_start, $event_end, $context);
    $event_start_end['start'] = $event_start;
    $event_start_end['end'] = $event_end;

    $all_day = $this->isAllDayEvent($event_start_end);

    // Truncate all day events to reduce time zone issues.
    if ($all_day) {
      $event_start = substr($event_start, 0, 10);
      $end = new \DateTime(substr(($event_end ?: $event_start), 0, 10));
      // Fullcalendar reads end time as exclusive, so add a day.
      $end->modify('+1 day');
      $event_end = $end->format('Y-m-d');
    }

    $request_time = $this->dateTime->getRequestTime();
    $current_time = new \DateTime();
    $current_time->setTimestamp($request_time)->format(\DateTime::ATOM);

    // Add a class if the event was in the past or is in the future, based
    // on the end time. We can't do this in hook_fullcalendar_classes()
    // because the date hasn't been processed yet.
    if (($all_day && $event_start < $current_time) || (!$all_day && $event_end < $current_time)) {
      $time_class = 'fc-event-past';
    }
    elseif ($event_start > $current_time) {
      $time_class = 'fc-event-future';
    }
    else {
      $time_class = 'fc-event-now';
    }

    $editable = $entity->access('update', NULL, TRUE)->isAllowed();

    $event = [
      'id' => $entity->id(),
      'eid' => $event_start_end['eid'],
      'startField' => $event_start_end['startField'],
      'endField' => $event_start_end['endField'],
      'allDay' => $all_day,
      'start' => $event_start,
      'end' => $event_end,
      'editable' => $editable,
      'type' => $event_start_end['type'],
      'className' => $class . ' ' . $time_class,
      'title' => strip_tags(htmlspecialchars_decode($title, ENT_QUOTES)),
      'url' => $entity->toUrl('canonical', [
        'language' => $this->languageManager->getCurrentLanguage(),
      ])->toString(),
    ];

    if (!empty($this->eventColors)) {
      $event_style = [];
      // Look for a bundle style override.
      $bundle = $entity->bundle();
      $event_style = $this->eventColors['color_bundle'][$bundle] ?? [];
      // Emulate new structure if old config passed.
      if (!is_array($event_style)) {
        $event_style = ['color' => $event_style];
      }
      // Look for a taxonomy style override.
      if (!empty($this->eventColors['tax_field']) && $entity->hasField($this->eventColors['tax_field'])) {
        $term = $entity->get($this->eventColors['tax_field'])?->first();
        $term_id = $term?->getValue()['target_id'] ?? NULL;
        if ($term_id && !empty($this->eventColors['color_taxonomies'][$term_id])) {
          $term_style = $this->eventColors['color_taxonomies'][$term_id];
          if (!is_array($term_style)) {
            $term_style = ['color' => $term_style];
          }
          // Merge the styles found, with the term styles taking precedence.
          $event_style = $term_style + $event_style;
        }
      }
      if ($event_style) {
        foreach ($event_style as $key => $value) {
          $event[$key] = $value;
        }
      }
    }

    return $event;
  }

  /**
   * Get 'min' and 'max' dates appear in the calendar.
   *
   * @param string $field_name
   *   Field machine name.
   *
   * @return array
   *   An array with min and max dates.
   */
  public function getExposedDates(string $field_name): array {
    $dates = &drupal_static(__METHOD__, []);

    if (empty($dates[$field_name])) {
      $entity_type = $this->view->getBaseEntityType();
      $entity_type_id = $entity_type->id();

      $settings = $this->view->style_plugin->options;

      /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_storages */
      $field_storages = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id);
      /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage */
      $field_storage = $field_storages[$field_name];
      $field_value = $field_storage->getName() . '_value';

      $exposed_input = $this->view->getExposedInput();

      // Min and Max dates for exposed filter.
      $dateMin = new \DateTime();
      $dateMax = new \DateTime();

      // First, we try to set initial Min and Max date values based on the
      // exposed form values.
      // @todo These offsets don't seem to be possible.
      if (isset($exposed_input[$field_value]['min'], $exposed_input[$field_value]['max'])) {
        $dateMin->setTimestamp(strtotime($exposed_input[$field_value]['min']));
        $dateMax->setTimestamp(strtotime($exposed_input[$field_value]['max']));
      }
      // If no exposed values set, use user-defined date values.
      elseif (!empty($settings['date']['month']) && !empty($settings['date']['year'])) {
        $ts = mktime(0, 0, 0, $settings['date']['month'] + 1, 1, $settings['date']['year']);

        $dateMin->setTimestamp($ts);
        $dateMax->setTimestamp($ts);

        $dateMin->modify('first day of this month');
        $dateMax->modify('first day of next month');
      }
      // Use default 1 month date-range.
      else {
        $dateMin->modify('first day of this month');
        $dateMax->modify('first day of next month');
      }

      $dates[$field_name] = [
        'min' => $dateMin,
        'max' => $dateMax,
      ];
    }

    return $dates[$field_name];
  }

  /**
   * Get start/end dates for an event.
   *
   * @param array $fields
   *   Array of date fields for the event.
   * @param int $delta
   *   Field delta.
   *
   * @return array
   *   The array of dates with 'start' and 'end' keys.
   */
  public function getEventStartEndDates(array $fields, $delta = 0): array {
    $event_start_end_date = [];
    $field = current($fields);
    $event_start_end_date['startField'] = key($fields);
    $event_start_end_date['endField'] = $event_start_end_date['startField'];
    $event_start_end_date['eid'] = $delta;
    /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $field_info */
    $field_info = $field['field_info'];

    $type = $field_info->getType();
    switch ($type) {
      case 'datetime':
        $field_names = array_keys($fields);

        if (count($field_names) === 1) {
          $event_start_end_date['start'] = $this->updateEventTimezone($field['value'][0]['raw']->value, $field['timezone_override']);
          $event_start_end_date['end'] = '';
        }
        else {
          $first = $this->updateEventTimezone($fields[$field_names[0]]['value'][0]['raw']->value, $fields[$field_names[0]]['timezone_override']);
          $second = $this->updateEventTimezone($fields[$field_names[1]]['value'][0]['raw']->value, $fields[$field_names[1]]['timezone_override']);

          if ($first > $second) {
            $event_start_end_date['start'] = $second;
            $event_start_end_date['end'] = $first;
          }
          else {
            $event_start_end_date['start'] = $first;
            $event_start_end_date['end'] = $second;
          }

          $event_start_end_date['endField'] = $field_names[1];
        }
        break;

      case 'daterange':
        $event_start_end_date['start'] = $this->updateEventTimezone($field['value'][0]['raw']->value, $field['timezone_override']);
        $end = $field['value'][0]['raw']->end_value;
        $event_start_end_date['end'] = !empty($end) ? $this->updateEventTimezone($end, $field['timezone_override']) : '';
        break;

      case 'date_recur':
        // @todo This needs to be implemented.
        break;

      case 'smartdate':
        $value = $field['value'][$delta]['raw']->getValue();
        // Append the id with necessary additional data.
        if (!empty($value['rrule'])) {
          $event_start_end_date['eid'] = 'R-' . $value['rrule'] . '-I-' . $value['rrule_index'];
        }

        $timezone = NULL;
        if (!$this->convertTzs && !empty($value['timezone'])) {
          $timezone = $value['timezone'];
        }
        $event_start_end_date['start'] = $this->dateFormatter->format($value['value'], 'custom', 'c', $timezone);
        $event_start_end_date['end'] = $this->dateFormatter->format($value['end_value'], 'custom', 'c', $timezone);
        break;
    }
    $event_start_end_date['type'] = $type;

    return $event_start_end_date;

  }

  /**
   * Update a date with timezone.
   *
   * @param string $datetime
   *   A datetime string.
   * @param string|null $tz_override
   *   Any timezone override for the date.
   *
   * @return string
   *   Formatted datetime with timezone applied.
   */
  public function updateEventTimezone(string $datetime, ?string $tz_override): string {
    $tz = (!$this->convertTzs && !empty($tz_override)) ? $tz_override : date_default_timezone_get();
    $timezone = new \DateTimeZone($tz);

    $dateTimezone = new \DateTime($datetime, new \DateTimeZone('UTC'));
    $dateTimezone->setTimezone($timezone);

    return $dateTimezone->format(\DateTime::ATOM);
  }

  /**
   * Check whether this is an all-day event.
   *
   * @param array $start_end_date
   *   Array of the start/end dates for the event.
   *
   * @return bool
   *   TRUE, if all day, otherwise FALSE.
   */
  public function isAllDayEvent(array $start_end_date): bool {
    if (empty($start_end_date['end'])) {
      $allDay = TRUE;
    }
    else {
      $allDay = FALSE;
      switch ($start_end_date['type']) {
        case 'smartdate':
          $start_time = substr($start_end_date['start'], 11, 5);
          $end_time = substr($start_end_date['end'], 11, 5);
          if ($start_time === '00:00' && $end_time === '23:59') {
            $allDay = TRUE;
          }
          break;

        default:
          $start_time = substr($start_end_date['start'], 11, 8);
          $end_time = substr($start_end_date['end'], 11, 8);
          if ($start_time === '00:00:00' && $end_time === '23:59:59') {
            $allDay = TRUE;
          }
      }
    }

    return $allDay;
  }

}

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

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