View source
<?php
namespace Drupal\fullcalendar\Plugin\views\style;
use DateTime;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\fullcalendar\Plugin\fullcalendar\type\OptionsFormHelperTrait;
use Drupal\fullcalendar\Plugin\FullcalendarBase;
use Drupal\fullcalendar\Plugin\FullcalendarPluginCollection;
use Drupal\views\Plugin\views\style\StylePluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
class FullCalendar extends StylePluginBase {
use OptionsFormHelperTrait;
protected $usesFields = TRUE;
protected $usesGrouping = FALSE;
protected $moduleHandler;
protected $fieldManager;
protected $pluginBag;
protected $dateFormatter;
protected $messenger;
public function evenEmpty() {
return TRUE;
}
public function getPlugins() {
return $this->pluginBag;
}
public function __construct(array $configuration, $plugin_id, $plugin_definition, PluginManagerInterface $fullcalendar_manager, ModuleHandlerInterface $module_handler, $field_manager, DateFormatter $date_formatter, MessengerInterface $messenger) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->pluginBag = new FullcalendarPluginCollection($fullcalendar_manager, $this);
$this->moduleHandler = $module_handler;
$this->fieldManager = $field_manager;
$this->dateFormatter = $date_formatter;
$this->messenger = $messenger;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('plugin.manager.fullcalendar'), $container
->get('module_handler'), $container
->get('entity_field.manager'), $container
->get('date.formatter'), $container
->get('messenger'));
}
protected function defineOptions() {
$options = parent::defineOptions();
foreach ($this
->getPlugins() as $plugin) {
if ($plugin instanceof FullcalendarBase) {
$options += $plugin
->defineOptions();
}
}
return $options;
}
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
foreach ($this
->getPlugins() as $plugin) {
if ($plugin instanceof FullcalendarBase) {
$plugin
->buildOptionsForm($form, $form_state);
}
}
}
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
parent::validateOptionsForm($form, $form_state);
}
public function submitOptionsForm(&$form, FormStateInterface $form_state) {
parent::submitOptionsForm($form, $form_state);
foreach ($this
->getPlugins() as $plugin) {
if ($plugin instanceof FullcalendarBase) {
$plugin
->submitOptionsForm($form, $form_state);
}
}
}
public function parseFields() {
$this->view
->initHandlers();
$labels = $this->displayHandler
->getFieldLabels();
$date_fields = [];
foreach ($this->view->field as $id => $field) {
if (fullcalendar_field_is_date($field)) {
$date_fields[$id] = $labels[$id];
}
}
return $date_fields;
}
public function validate() {
$settings = $this
->prepareSettings();
if ($this->displayHandler->display['display_plugin'] != 'default' && !$this
->parseFields() && empty($settings['google']['googleCalendarApiKey'])) {
$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'],
]), 'error');
}
return parent::validate();
}
public function render() {
return [
'#theme' => $this
->themeFunctions(),
'#view' => $this->view,
'#options' => $this->options,
'#attached' => $this
->prepareAttached(),
];
}
protected function prepareAttached() {
$attached['library'][] = 'fullcalendar/drupal.fullcalendar';
$settings = $this
->prepareSettings();
$fcPlugins = !empty($settings['options']['plugins']) ? $settings['options']['plugins'] : [
'dayGrid',
];
foreach ($fcPlugins as $fcPlugin) {
$attached['library'][] = 'fullcalendar/fullcalendar.' . strtolower($fcPlugin);
}
if (!empty($settings['options']['themeSystem']) && $settings['options']['themeSystem'] === 'bootstrap') {
$attached['library'][] = 'fullcalendar/fullcalendar.bootstrap';
}
$attached['drupalSettings']['fullcalendar'] = [
'js-view-dom-id-' . $this->view->dom_id => $settings,
];
return $attached;
}
protected function prepareSettings() {
$settings =& drupal_static(__METHOD__, []);
if (empty($settings)) {
foreach ($this
->getPlugins() as $plugin_id => $plugin) {
$plugin
->process($settings);
}
}
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',
];
}
}
$events = $this
->prepareEvents();
if ($events) {
$settings['options']['eventSources'][] = $events;
}
return $settings;
}
protected function prepareEvents() {
$events = [];
foreach ($this->view->result as $delta => $row) {
$entity = $row->_entity;
$fields = [];
$date_fields = [];
$event = [];
foreach ($this->view->field as $field_name => $field) {
$fields[$field_name] = $this
->getField($delta, $field_name);
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'],
];
}
}
}
if (!empty($this->options['fields']['date'])) {
$date_fields = array_intersect_key($date_fields, $this->options['fields']['date_field']);
}
if (empty($date_fields)) {
return $events;
}
foreach ($date_fields as $field) {
if (empty($field['value'])) {
continue;
}
$field_definition = $field['field_info'];
$date_range = $this
->getExposedDates($field['field_name']);
if ($field_definition
->getType() == 'date_recur') {
$field_items = $row->_entity->{$field['field_name']};
$isRecurring = FALSE;
foreach ($field_items as $index => $item) {
$occurrenceHandler = $item
->getOccurrenceHandler();
if ($occurrenceHandler
->isRecurring()) {
$occurrences = $occurrenceHandler
->getOccurrencesForDisplay($date_range['min'], $date_range['max']);
foreach ($occurrences as $occurrence) {
$start = $occurrence['value'];
$end = $occurrence['end_value'];
$event = $this
->prepareEvent($entity, $field, $index);
}
$isRecurring = TRUE;
}
}
if ($isRecurring === TRUE) {
continue;
}
}
foreach ($field['value'] as $index => $item) {
if (empty($item['raw']->value)) {
continue;
}
$event = $this
->prepareEvent($entity, $date_fields, $index);
}
}
if (!empty($event)) {
$events[$delta] = $event;
}
}
return $events;
}
private function prepareEvent($entity, $fields, $delta) {
$classes = $this->moduleHandler
->invokeAll('fullcalendar_classes', [
$entity,
]);
$this->moduleHandler
->alter('fullcalendar_classes', $classes, $entity);
$classes = array_map([
'\\Drupal\\Component\\Utility\\Html',
'getClass',
], $classes);
$class = count($classes) ? implode(' ', array_unique($classes)) : '';
$palette = $this->moduleHandler
->invokeAll('fullcalendar_palette', [
$entity,
]);
$this->moduleHandler
->alter('fullcalendar_palette', $palette, $entity);
$event_start_end = $this
->getEventStartEndDates($fields);
$event_start = $event_start_end['start'];
$event_end = $event_start_end['end'];
$all_day = $this
->isAllDayEvent($event_start_end);
$request_time = \Drupal::time()
->getRequestTime();
$current_time = new DateTime();
$current_time
->setTimestamp($request_time)
->format(DateTime::ATOM);
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 = !empty($settings['fullcalendar']['editable']) ? $entity
->access('update', NULL, TRUE)
->isAllowed() : FALSE;
return [
'id' => $entity
->id(),
'groupId' => $entity
->getEntityTypeId(),
'allDay' => $all_day,
'start' => $event_start,
'end' => $event_end,
'editable' => $editable,
'className' => $class . ' ' . $time_class,
'title' => strip_tags(htmlspecialchars_decode($entity
->label(), ENT_QUOTES)),
'url' => $entity
->toUrl('canonical', [
'language' => \Drupal::languageManager()
->getCurrentLanguage(),
])
->toString(),
'backgroundColor' => !empty($palette['backgroundColor']) ? $palette['backgroundColor'] : '',
'borderColor' => !empty($palette['borderColor']) ? $palette['borderColor'] : '',
'textColor' => !empty($palette['textColor']) ? $palette['textColor'] : '',
];
}
public function getExposedDates($field_name) {
$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;
$field_manager = \Drupal::getContainer()
->get('entity_field.manager');
$field_storages = $field_manager
->getFieldStorageDefinitions($entity_type_id);
$field_storage = $field_storages[$field_name];
$field_value = $field_storage
->getName() . '_value';
$exposed_input = $this->view
->getExposedInput();
$dateMin = new DateTime();
$dateMax = new DateTime();
if (isset($exposed_input[$field_value])) {
$dateMin
->setTimestamp(strtotime($exposed_input[$field_value]['min']));
$dateMax
->setTimestamp(strtotime($exposed_input[$field_value]['max']));
}
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');
}
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];
}
public function getEventStartEndDates(array $fields) {
$event_start_end_date = [];
$field = current($fields);
$field_info = $field['field_info'];
switch ($field_info
->getType()) {
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;
}
}
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':
break;
}
return $event_start_end_date;
}
public function updateEventTimezone($datetime, $tz_override) {
$tz = !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);
}
public function isAllDayEvent(array $start_end_date) {
if (empty($start_end_date['end'])) {
$allDay = TRUE;
}
else {
$end = new DateTime($start_end_date['end']);
$start = new DateTime($start_end_date['start']);
$start_ymd = $start
->format('Y-m-d');
$start_day_endtime = new DateTime($start_ymd . 'T23:59:59');
$allDay = $end
->getTimestamp() > $start_day_endtime
->getTimestamp();
}
return $allDay;
}
}