date_recur-8.x-2.2/src/Rl/RlHelper.php

src/Rl/RlHelper.php
<?php

declare(strict_types=1);

namespace Drupal\date_recur\Rl;

use Drupal\date_recur\DateRange;
use Drupal\date_recur\DateRecurHelperInterface;
use Drupal\date_recur\Exception\DateRecurHelperArgumentException;
use RRule\RRule;
use RRule\RSet;
use RRule\RfcParser;

/**
 * Helper for recurring rules implemented with rlanvin/rrule.
 *
 * @ingroup RLanvinPhpRrule
 */
class RlHelper implements DateRecurHelperInterface {

  /**
   * The RRULE set.
   *
   * @var \RRule\RSet
   */
  protected RSet $set;

  /**
   * The time zone used to normalize other date objects.
   *
   * @var \DateTimeZone
   */
  protected \DateTimeZone $timeZone;

  /**
   * Difference between start date and start date end.
   *
   * Calculated value.
   *
   * @var \DateInterval
   */
  protected \DateInterval $recurDiff;

  /**
   * Constructor for DateRecurHelper.
   *
   * @param string $string
   *   The repeat rule.
   * @param \DateTimeInterface $dtStart
   *   The initial occurrence start date.
   * @param \DateTimeInterface|null $dtStartEnd
   *   The initial occurrence end date, or NULL to use start date.
   */
  public function __construct(
    string $string,
    \DateTimeInterface $dtStart,
    ?\DateTimeInterface $dtStartEnd = NULL,
  ) {
    $dtStartEnd ??= clone $dtStart;
    $this->recurDiff = $dtStart->diff($dtStartEnd);
    $this->timeZone = $dtStart->getTimezone();

    // Ensure the string is prefixed with RRULE if not multiline.
    if (!\str_contains($string, "\n") && !\str_starts_with($string, 'RRULE:')) {
      $string = "RRULE:$string";
    }

    $parts = [
      'RRULE' => [],
      'RDATE' => [],
      'EXRULE' => [],
      'EXDATE' => [],
    ];

    $lines = \explode("\n", $string);
    foreach ($lines as $n => $line) {
      $line = \trim($line);

      if (\str_contains($line, ':') === FALSE) {
        throw new DateRecurHelperArgumentException(\sprintf('Multiline RRULE must be prefixed with either: RRULE, EXDATE, EXRULE, or RDATE. Missing for line %s', $n + 1));
      }

      [$part, $partValue] = \explode(':', $line, 2);
      $parts[$part] ?? throw new DateRecurHelperArgumentException("Unsupported line: " . $part);
      $parts[$part][] = $partValue;
    }

    if (($count = \count($parts['RRULE'])) !== 1) {
      throw new DateRecurHelperArgumentException(\sprintf('One RRULE must be provided. %d provided.', $count));
    }

    $this->set = new RSet();

    foreach ($parts as $type => $values) {
      foreach ($values as $value) {
        switch ($type) {
          case 'RRULE':
            $this->set->addRRule(new RRule($value, $dtStart));
            break;

          case 'RDATE':
            $dates = RfcParser::parseRDate('RDATE:' . $value);
            \array_walk($dates, function (\DateTimeInterface $value): void {
              $this->set->addDate($value);
            });
            break;

          case 'EXDATE':
            $dates = RfcParser::parseExDate('EXDATE:' . $value);
            \array_walk($dates, function (\DateTimeInterface $value): void {
              $this->set->addExDate($value);
            });
            break;

          case 'EXRULE':
            $this->set->addExRule($value);
        }
      }
    }
  }

  public static function createInstance(string $string, \DateTimeInterface $dtStart, ?\DateTimeInterface $dtStartEnd = NULL): DateRecurHelperInterface {
    return new static($string, $dtStart, $dtStartEnd);
  }

  /**
   * {@inheritdoc}
   */
  public function getRules(): array {
    return \array_map(
      static function (RRule $rule): RlDateRecurRule {
        // RL returns all parts, even if no values originally provided. Filter
        // out the useless parts.
        $parts = \array_filter($rule->getRule());
        return new RlDateRecurRule($parts);
      },
      $this->set->getRRules(),
    );
  }

  public function isInfinite(): bool {
    return $this->set->isInfinite();
  }

  public function generateOccurrences(?\DateTimeInterface $rangeStart = NULL, ?\DateTimeInterface $rangeEnd = NULL): \Generator {
    foreach ($this->set as $occurrenceStart) {
      /** @var \DateTime $occurrence */
      $occurrenceEnd = clone $occurrenceStart;
      $occurrenceEnd->add($this->recurDiff);

      if ($rangeStart) {
        if ($occurrenceStart < $rangeStart && $occurrenceEnd < $rangeStart) {
          continue;
        }
      }

      if ($rangeEnd) {
        if ($occurrenceStart > $rangeEnd && $occurrenceEnd > $rangeEnd) {
          break;
        }
      }

      yield new DateRange($occurrenceStart, $occurrenceEnd);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getOccurrences(?\DateTimeInterface $rangeStart = NULL, ?\DateTimeInterface $rangeEnd = NULL, ?int $limit = NULL): array {
    if ($this->isInfinite() && !isset($rangeEnd) && !isset($limit)) {
      throw new \InvalidArgumentException('An infinite rule must have a date or count limit.');
    }

    $generator = $this->generateOccurrences($rangeStart, $rangeEnd);
    if (isset($limit)) {
      if (!\is_int($limit) || $limit < 0) {
        // Limit must be a number and more than zero.
        throw new \InvalidArgumentException('Invalid count limit.');
      }

      // Generate occurrences until the limit is reached.
      $occurrences = [];
      foreach ($generator as $value) {
        if (\count($occurrences) >= $limit) {
          break;
        }
        $occurrences[] = $value;
      }
      return $occurrences;
    }

    return \iterator_to_array($generator);
  }

  /**
   * {@inheritdoc}
   */
  public function getExcluded(): array {
    // Implementation normally returns the same time zone as the EXDATE from the
    // rule string, normalize it here.
    return \array_map(
      fn (\DateTime $date): \DateTime => $date->setTimezone($this->timeZone),
      $this->set->getExDates(),
    );
  }

  public function current(): DateRange {
    $occurrenceStart = $this->set->current();
    $occurrenceEnd = clone $occurrenceStart;
    $occurrenceEnd->add($this->recurDiff);
    return new DateRange($occurrenceStart, $occurrenceEnd);
  }

  public function next(): void {
    $this->set->next();
  }

  public function key(): ?int {
    return $this->set->key();
  }

  public function valid(): bool {
    return $this->set->valid();
  }

  public function rewind(): void {
    $this->set->rewind();
  }

  /**
   * Get the set.
   *
   * @return \RRule\RSet
   *   Returns the set.
   *
   * @internal this method is specific to rlanvin/rrule implementation only.
   */
  public function getRlRuleset(): RSet {
    return $this->set;
  }

}

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

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