fullcalendar-8.x-2.x-dev/src/Controller/UpdateController.php

src/Controller/UpdateController.php
<?php

namespace Drupal\fullcalendar\Controller;

use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Link;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Render\Renderer;
use Drupal\Core\Url;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\smart_date_recur\Controller\Instances;
use Drupal\smart_date_recur\Entity\SmartDateOverride;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

/**
 * Controller to drop an entity from the calendar.
 */
class UpdateController extends ControllerBase {

  /**
   * The class resolver service.
   *
   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
   */
  protected ClassResolverInterface $classResolver;

  /**
   * CSRF Token.
   *
   * @var \Drupal\Core\Access\CsrfTokenGenerator
   */
  protected CsrfTokenGenerator $csrfToken;

  // Fullcalendar is using defaultTimedEventDuration parameter
  // for event objects without a specified end value:
  // see https://fullcalendar.io/docs/v4/defaultTimedEventDuration -
  // so taking the value of 1 hour in seconds here,
  // not sure how to get this from the JS here.
  // @todo Get this from the configuration of Fullcalendar somehow.
  /**
   * The default duration for a new event.
   *
   * @var int
   */
  protected $defaultTimedEventDuration = 60 * 60;

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

  /**
   * The renderer.
   *
   * @var \Drupal\Core\Render\Renderer
   */
  protected Renderer $renderer;

  /**
   * Construct a FullCalendar controller.
   *
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
   *   Logger factory object.
   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrfToken
   *   CSRF token factory object.
   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $classResolver
   *   The class resolver service.
   */
  final public function __construct(
    LoggerChannelFactoryInterface $loggerFactory,
    CsrfTokenGenerator $csrfToken,
    ClassResolverInterface $classResolver,
  ) {
    $this->loggerFactory = $loggerFactory;
    $this->csrfToken = $csrfToken;
    $this->classResolver = $classResolver;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('logger.factory'),
      $container->get('csrf_token'),
      $container->get('class_resolver'),
    );
  }

  /**
   * Drops an entity from the calendar.
   *
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
   *   The entity.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   The json response.
   */
  public function drop(FieldableEntityInterface $entity, Request $request): JsonResponse {
    // Extract data from JSON request.
    $json = $request->getContent();
    $new_data = json_decode($json, TRUE);
    $domId = $new_data['token'] ?? '';

    // Validate the CSRF token.
    $user = $this->currentUser();
    if (!$user->isAnonymous()) {
      $csrf_token = $new_data['token'];
      if (!$this->csrfToken->validate($csrf_token)) {
        return $this->createError($this->t('Access denied!'), $domId);
      }
    }

    // Validate user access to make the change.
    if (!$entity->access('update')) {
      return $this->createError($this->t('Access denied!'), $domId);
    }

    if (empty($new_data)) {
      return $this->createError($this->t('Invalid data'), $domId, 400);
    }

    if (!isset($new_data['eid']) || empty($new_data['start']) || empty($new_data['startField'])) {
      return $this->createError($this->t('Necessary data is missing'), $domId, 400);
    }
    if (!$entity->hasField($new_data['startField'])) {
      return $this->createError($this->t('Invalid date field'), $domId, 400);
    }

    $this->convertTzs = $new_data['convertTzs'] ?? TRUE;

    switch ($new_data['type']) {
      // @todo write functions for other field types.
      case 'smartdate':
        $this->updateSmartDate($entity, $new_data);
        break;

      default:
        $this->updateDatetime($entity, $new_data);
    }

    $url = Url::fromUserInput('/');
    $link = Link::fromTextAndUrl($this->t('Close'), $url);
    $link = $link->toRenderable();
    $link['#attributes']['class'][] = 'fullcalendar-status-close';

    $message = $this->t('The new event time has been saved.');

    return new JsonResponse([
      'msg'    => $message,
      'dom_id' => $request->request->get('dom_id'),
    ]);
  }

  /**
   * Helper function to return a JSON error.
   *
   * @param string $message
   *   The message to display to the user.
   * @param string $domId
   *   The target element.
   * @param int $status
   *   HTTP status code to return.
   */
  protected function createError(string $message, string $domId, int $status = 403): JsonResponse {
    return new JsonResponse([
      'error'    => $message,
      'dom_id' => $domId,
    ],
    $status,
    );
  }

  /**
   * Helper function to update Datetime fields.
   *
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
   *   The entity to be updated.
   * @param array $new_data
   *   The data to use for the update.
   */
  protected function updateDatetime(FieldableEntityInterface $entity, array $new_data): void {
    $delta = $new_data['eid'];
    $start_field = $new_data['startField'];
    $end_field = $new_data['endField'];
    $start_date = $new_data['start'];
    $end_date = $new_data['end'];

    // Ignore time zones for all day events.
    if ($new_data['allDay']) {
      $start_date = substr($start_date, 0, 10);
      $end_date = ($end_date) ? substr($end_date, 0, 10) : $start_date;
    }
    $start_date = strtotime($start_date);
    $end_date = strtotime($end_date);
    if ($new_data['allDay']) {
      // Fullcalendar sets the time of all day events to 12am of the last day.
      $end_date += 1440 * 60 - 1;
    }

    // If necessary, convert the time values for storage.
    if ($new_data['type'] !== 'timestamp') {
      $start_date = $this->prepareDatetime($start_date);
      $end_date = $this->prepareDatetime($end_date);
    }

    $entity->{$start_field}[$delta]->value = $start_date;
    if ($start_field === $end_field) {
      $entity->{$start_field}[$delta]->end_value = $end_date;
    }
    else {
      $entity->{$end_field}[$delta]->value = $end_date;
    }
    $entity->save();
  }

  /**
   * Helper function to update Smart Date fields.
   *
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
   *   The entity to be updated.
   * @param array $new_data
   *   The data to use for the update.
   */
  protected function updateSmartDate(FieldableEntityInterface $entity, array $new_data): void {
    $eid = $new_data['eid'];
    $start_field = $new_data['startField'];
    $end_field = $new_data['endField'];
    $start_date = $new_data['start'];
    $end_date = $new_data['end'];

    $empty_end = FALSE;
    if (empty($end_date)) {
      $end_date = $start_date;
      $empty_end = TRUE;
    }
    // Ignore time zones for all day events.
    if ($new_data['allDay']) {
      $start_date = substr($start_date, 0, 10);
      $end_date = substr($end_date, 0, 10);
    }
    $start_date = strtotime($start_date);
    $end_date = strtotime($end_date);
    if ($new_data['allDay']) {
      if ($empty_end) {
        // Cloned from start, so set to end of day.
        $end_date += 1439 * 60;
      }
      else {
        // Fullcalendar sets the time of all day events to 12am of the last day.
        $end_date -= 60;
      }
    }
    else {
      if ($empty_end) {
        // Add the default duration from the field configuration.
        $end_date += $this->getDefaultDuration($end_field, $entity);
      }
    }
    $duration = round(($start_date - $end_date) / 60);

    // Recurring event eid values start with "R-".
    if (strpos($eid, 'R-') === 0) {
      $id = explode('-', $eid);
      /** @var \Drupal\smart_date_recur\Entity\SmartDateRule $rule */
      $rule = $this->entityTypeManager()
        ->getStorage('smart_date_rule')
        ->load($id[1]);

      $timezone = $rule->getTimeZone() ?? '';
      if ($new_data['allDay'] || ($empty_end && $timezone && !$this->convertTzs)) {
        $start_date = $this->remapToTimezone($start_date, $timezone);
        $end_date = $this->remapToTimezone($end_date, $timezone);
      }

      // Load overridden instances from rule object.
      $instances = $rule->getRuleInstances();
      $rrule_index = $id[3];
      $instance = $instances[$rrule_index];

      if (isset($instance['oid'])) {
        $override = SmartDateOverride::load($instance['oid']);
        $override->set('value', $start_date);
        $override->set('end_value', $end_date);
        $override->set('duration', $duration);
      }
      else {
        $values = [
          'rrule'       => $rule->id(),
          'rrule_index' => $rrule_index,
          'value'       => $start_date,
          'end_value'   => $end_date,
          'duration'    => $duration,
        ];
        $override = SmartDateOverride::create($values);
      }
      $override->save();
      /** @var \Drupal\smart_date_recur\Controller\Instances $instancesController */
      $instancesController = $this->classResolver->getInstanceFromDefinition(Instances::class);
      $instancesController->applyChanges($rule);
    }
    else {
      $delta = $eid;

      $timezone = $entity->{$start_field}[$delta]->timezone ?? '';
      if ($new_data['allDay'] || ($empty_end && $timezone && !$this->convertTzs)) {
        $start_date = $this->remapToTimezone($start_date, $timezone);
        $end_date = $this->remapToTimezone($end_date, $timezone);
      }

      $entity->{$start_field}[$delta]->value = $start_date;
      $entity->{$end_field}[$delta]->end_value = $end_date;
      $entity->{$start_field}[$delta]->duration = $duration;
      $entity->save();
    }
  }

  /**
   * Conditionally convert a DrupalDateTime object to a timestamp.
   *
   * @param int $time
   *   The time to be converted.
   */
  public static function prepareDatetime(int $time): string {
    $storage_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE);
    $datetime = DrupalDateTime::createFromTimestamp($time);
    return $datetime->setTimezone($storage_timezone)->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
  }

  /**
   * Get the configured default duration.
   *
   * @param string $field_name
   *   The field from which to retrieve the default.
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
   *   The entity to be updated.
   *
   * @return int|false
   *   The configured duration, or FALSE if it could not be retrieved.
   */
  protected function getDefaultDuration(string $field_name, FieldableEntityInterface $entity): int|false {
    $definitions = $entity->getFieldDefinitions();
    if (isset($definitions[$field_name])) {
      $defaults_set = $definitions[$field_name]->getDefaultValueLiteral();
      $defaults = array_shift($defaults_set);
      return $defaults['default_duration'] * 60;
    }
    return FALSE;
  }

  /**
   * Remap a timestamp to the same time of day in a different timezone.
   *
   * @param int $time
   *   The timestamp to remap.
   * @param string $timezone
   *   The timezone into which the time should be changed.
   *
   * @return int
   *   A new timestamp representing the same time in the new timezone.
   */
  protected function remapToTimezone(int $time, string $timezone): int {
    $date = DrupalDateTime::createFromTimestamp($time);
    $date_array = [
      'year' => $date->format('Y'),
      'month' => $date->format('n'),
      'day' => $date->format('j'),
      'hour' => $date->format('H'),
      'minute' => $date->format('i'),
      'second' => $date->format('s'),
    ];
    $remapped_date = DrupalDateTime::createFromArray($date_array, $timezone);
    return (int) $remapped_date->format('U');
  }

}

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

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