From 4ce65d59ca91871cfd126497158200a818720bce Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 13:30:08 +0200 Subject: Adding upstream version 0.13.1. Signed-off-by: Daniel Baumann --- vendor/ipl/web/src/FormElement/ScheduleElement.php | 636 +++++++++++++++++++++ 1 file changed, 636 insertions(+) create mode 100644 vendor/ipl/web/src/FormElement/ScheduleElement.php (limited to 'vendor/ipl/web/src/FormElement/ScheduleElement.php') diff --git a/vendor/ipl/web/src/FormElement/ScheduleElement.php b/vendor/ipl/web/src/FormElement/ScheduleElement.php new file mode 100644 index 0000000..f872f49 --- /dev/null +++ b/vendor/ipl/web/src/FormElement/ScheduleElement.php @@ -0,0 +1,636 @@ + 'schedule-element']; + + /** @var array A list of allowed frequencies used to configure custom expressions */ + protected $customFrequencies = []; + + /** @var array */ + protected $advanced = []; + + /** @var array */ + protected $regulars = []; + + /** @var string Schedule frequency of this element */ + protected $frequency = self::NO_REPEAT; + + /** @var string */ + protected $customFrequency; + + /** @var DateTime */ + protected $start; + + /** @var WeeklyFields Weekly parts of this schedule element */ + protected $weeklyField; + + /** @var MonthlyFields Monthly parts of this schedule element */ + protected $monthlyFields; + + /** @var AnnuallyFields Annually parts of this schedule element */ + protected $annuallyFields; + + protected function init(): void + { + $this->start = new DateTime(); + $this->weeklyField = new WeeklyFields('weekly-fields', [ + 'default' => $this->start->format('D'), + 'protector' => function (string $day) { + return $this->protectId($day); + }, + ]); + + $this->monthlyFields = new MonthlyFields('monthly-fields', [ + 'default' => $this->start->format('j'), + 'availableFields' => (int) $this->start->format('t'), + 'protector' => function ($day) { + return $this->protectId($day); + } + ]); + + $this->annuallyFields = new AnnuallyFields('annually-fields', [ + 'default' => $this->start->format('M'), + 'protector' => function ($month) { + return $this->protectId($month); + } + ]); + + + $this->regulars = [ + RRule::MINUTELY => $this->translate('Minutely'), + RRule::HOURLY => $this->translate('Hourly'), + RRule::DAILY => $this->translate('Daily'), + RRule::WEEKLY => $this->translate('Weekly'), + RRule::MONTHLY => $this->translate('Monthly'), + RRule::QUARTERLY => $this->translate('Quarterly'), + RRule::YEARLY => $this->translate('Annually'), + ]; + + $this->customFrequencies = array_slice($this->regulars, 2); + unset($this->customFrequencies[RRule::QUARTERLY]); + + $this->advanced = [ + static::CUSTOM_EXPR => $this->translate('Custom…'), + static::CRON_EXPR => $this->translate('Cron Expression…') + ]; + } + + /** + * Get whether this element is rendering a cron expression + * + * @return bool + */ + public function hasCronExpression(): bool + { + return $this->getFrequency() === static::CRON_EXPR; + } + + /** + * Get the frequency of this element + * + * @return string + */ + public function getFrequency(): string + { + return $this->getPopulatedValue('frequency', $this->frequency); + } + + /** + * Set the custom frequency of this schedule element + * + * @param string $frequency + * + * @return $this + */ + public function setFrequency(string $frequency): self + { + if ( + $frequency !== static::NO_REPEAT + && ! isset($this->regulars[$frequency]) + && ! isset($this->advanced[$frequency]) + ) { + throw new InvalidArgumentException(sprintf('Invalid frequency provided: %s', $frequency)); + } + + $this->frequency = $frequency; + + return $this; + } + + /** + * Get custom frequency of this element + * + * @return ?string + */ + public function getCustomFrequency(): ?string + { + return $this->getValue('custom-frequency', $this->customFrequency); + } + + /** + * Set custom frequency of this element + * + * @param string $frequency + * + * @return $this + */ + public function setCustomFrequency(string $frequency): self + { + if (! isset($this->customFrequencies[$frequency])) { + throw new InvalidArgumentException(sprintf('Invalid custom frequency provided: %s', $frequency)); + } + + $this->customFrequency = $frequency; + + return $this; + } + + /** + * Set start time of the parsed expressions + * + * @param DateTime $start + * + * @return $this + */ + public function setStart(DateTime $start): self + { + $this->start = $start; + + // Forward the start time update to the sub elements as well! + $this->weeklyField->setDefault($start->format('D')); + $this->annuallyFields->setDefault($start->format('M')); + $this->monthlyFields + ->setDefault((int) $start->format('j')) + ->setAvailableFields((int) $start->format('t')); + + return $this; + } + + public function getValue($name = null, $default = null) + { + if ($name !== null || ! $this->hasBeenValidated()) { + return parent::getValue($name, $default); + } + + $frequency = $this->getFrequency(); + $start = parent::getValue('start'); + switch ($frequency) { + case static::NO_REPEAT: + return new OneOff($start); + case static::CRON_EXPR: + $rule = new Cron(parent::getValue('cron_expression')); + + break; + case RRule::MINUTELY: + case RRule::HOURLY: + case RRule::DAILY: + case RRule::WEEKLY: + case RRule::MONTHLY: + case RRule::QUARTERLY: + case RRule::YEARLY: + $rule = RRule::fromFrequency($frequency); + + break; + default: // static::CUSTOM_EXPR + $interval = parent::getValue('interval', 1); + $customFrequency = parent::getValue('custom-frequency', RRule::DAILY); + switch ($customFrequency) { + case RRule::DAILY: + if ($interval === '*') { + $interval = 1; + } + + $rule = new RRule("FREQ=DAILY;INTERVAL=$interval"); + + break; + case RRule::WEEKLY: + $byDay = implode(',', $this->weeklyField->getSelectedWeekDays()); + + $rule = new RRule("FREQ=WEEKLY;INTERVAL=$interval;BYDAY=$byDay"); + + break; + /** @noinspection PhpMissingBreakStatementInspection */ + case RRule::MONTHLY: + $runsOn = $this->monthlyFields->getValue('runsOn', MonthlyFields::RUNS_EACH); + if ($runsOn === MonthlyFields::RUNS_EACH) { + $byMonth = implode(',', $this->monthlyFields->getSelectedDays()); + + $rule = new RRule("FREQ=MONTHLY;INTERVAL=$interval;BYMONTHDAY=$byMonth"); + + break; + } + // Fall-through to the next switch case + case RRule::YEARLY: + $rule = "FREQ=MONTHLY;INTERVAL=$interval;"; + if ($customFrequency === RRule::YEARLY) { + $runsOn = $this->annuallyFields->getValue('runsOnThe', 'n'); + $month = $this->annuallyFields->getValue('month', (int) $this->start->format('m')); + if (is_string($month)) { + $datetime = DateTime::createFromFormat('!M', $month); + if (! $datetime) { + throw new InvalidArgumentException(sprintf('Invalid month provided: %s', $month)); + } + + $month = (int) $datetime->format('m'); + } + + $rule = "FREQ=YEARLY;INTERVAL=1;BYMONTH=$month;"; + if ($runsOn === 'n') { + $rule = new RRule($rule); + + break; + } + } + + $element = $this->monthlyFields; + if ($customFrequency === RRule::YEARLY) { + $element = $this->annuallyFields; + } + + $runDay = $element->getValue('day', $element::$everyDay); + $ordinal = $element->getValue('ordinal', $element::$first); + $position = $element->getOrdinalAsInteger($ordinal); + + if ($runDay === $element::$everyDay) { + $rule .= "BYDAY=MO,TU,WE,TH,FR,SA,SU;BYSETPOS=$position"; + } elseif ($runDay === $element::$everyWeekday) { + $rule .= "BYDAY=MO,TU,WE,TH,FR;BYSETPOS=$position"; + } elseif ($runDay === $element::$everyWeekend) { + $rule .= "BYDAY=SA,SU;BYSETPOS=$position"; + } else { + $rule .= sprintf('BYDAY=%d%s', $position, $runDay); + } + + $rule = new RRule($rule); + + break; + default: + throw new LogicException(sprintf('Custom frequency %s is not supported!', $customFrequency)); + } + } + + $rule->startAt($start); + if (parent::getValue('use-end-time', 'n') === 'y') { + $rule->endAt(parent::getValue('end')); + } + + // Sync the start time and first recurrence of the rule + if (! $this->hasCronExpression() && $this->getFrequency() !== static::NO_REPEAT) { + $nextDue = $rule->getNextRecurrences($start)->current() ?? $start; + $rule->startAt($nextDue); + } + + return $rule; + } + + public function setValue($value) + { + $values = $value; + $rule = $value; + if ($rule instanceof Frequency) { + if ($rule->getStart()) { + $this->setStart($rule->getStart()); + } + + $values = []; + if ($rule->getEnd() && ! $rule instanceof OneOff) { + $values['use-end-time'] = 'y'; + $values['end'] = $rule->getEnd(); + } + + if ($rule instanceof OneOff) { + $values['frequency'] = static::NO_REPEAT; + } elseif ($rule instanceof Cron) { + $values['cron_expression'] = $rule->getExpression(); + $values['frequency'] = static::CRON_EXPR; + + $this->setFrequency(static::CRON_EXPR); + } elseif ($rule instanceof RRule) { + $values['interval'] = $rule->getInterval(); + switch ($rule->getFrequency()) { + case RRule::DAILY: + if ($rule->getInterval() <= 1 && strpos($rule->getString(), 'INTERVAL=') === false) { + $this->setFrequency(RRule::DAILY); + } else { + $this + ->setFrequency(static::CUSTOM_EXPR) + ->setCustomFrequency(RRule::DAILY); + } + + break; + case RRule::WEEKLY: + if (! $rule->getByDay() || empty($rule->getByDay())) { + $this->setFrequency(RRule::WEEKLY); + } else { + $values['weekly-fields'] = $this->weeklyField->loadWeekDays($rule->getByDay()); + $this + ->setFrequency(static::CUSTOM_EXPR) + ->setCustomFrequency(RRule::WEEKLY); + } + + break; + case RRule::MONTHLY: + case RRule::YEARLY: + $isMonthly = $rule->getFrequency() === RRule::MONTHLY; + if ($rule->getByDay() || $rule->getByMonthDay() || $rule->getByMonth()) { + $this->setFrequency(static::CUSTOM_EXPR); + + if ($isMonthly) { + $values['monthly-fields'] = $this->monthlyFields->loadRRule($rule); + $this->setCustomFrequency(RRule::MONTHLY); + } else { + $values['annually-fields'] = $this->annuallyFields->loadRRule($rule); + $this->setCustomFrequency(RRule::YEARLY); + } + } elseif ($isMonthly && $rule->getInterval() === 3) { + $this->setFrequency(RRule::QUARTERLY); + } else { + $this->setFrequency($rule->getFrequency()); + } + + break; + default: + $this->setFrequency($rule->getFrequency()); + } + + $values['frequency'] = $this->getFrequency(); + $values['custom-frequency'] = $this->getCustomFrequency(); + } + } + + return parent::setValue($values); + } + + protected function assemble() + { + $start = $this->getPopulatedValue('start') ?: $this->start; + if (! $start instanceof DateTime) { + $start = new DateTime($start); + } + $this->setStart($start); + + $autosubmit = ! $this->hasCronExpression() && $this->getFrequency() !== static::NO_REPEAT; + $this->addElement('localDateTime', 'start', [ + 'class' => $autosubmit ? 'autosubmit' : null, + 'required' => true, + 'label' => $this->translate('Start'), + 'value' => $start, + 'description' => $this->translate('Start time of this schedule') + ]); + + $this->addElement('checkbox', 'use-end-time', [ + 'required' => false, + 'class' => 'autosubmit', + 'disabled' => $this->getPopulatedValue('frequency', static::NO_REPEAT) === static::NO_REPEAT ?: null, + 'value' => $this->getPopulatedValue('use-end-time', 'n'), + 'label' => $this->translate('Use End Time') + ]); + + if ($this->getPopulatedValue('use-end-time', 'n') === 'y') { + $end = $this->getPopulatedValue('end', new DateTime()); + if (! $end instanceof DateTime) { + $end = new DateTime($end); + } + + $this->addElement('localDateTime', 'end', [ + 'class' => ! $this->hasCronExpression() ? 'autosubmit' : null, + 'required' => true, + 'value' => $end, + 'label' => $this->translate('End'), + 'description' => $this->translate('End time of this schedule') + ]); + } + + $this->addElement('select', 'frequency', [ + 'required' => false, + 'class' => 'autosubmit', + 'label' => $this->translate('Frequency'), + 'description' => $this->translate('Specifies how often this job run should be recurring'), + 'options' => [ + static::NO_REPEAT => $this->translate('None'), + $this->translate('Regular') => $this->regulars, + $this->translate('Advanced') => $this->advanced + ], + ]); + + if ($this->getFrequency() === static::CUSTOM_EXPR) { + $this->addElement('select', 'custom-frequency', [ + 'required' => false, + 'class' => 'autosubmit', + 'value' => parent::getValue('custom-frequency'), + 'options' => $this->customFrequencies, + 'label' => $this->translate('Custom Frequency'), + 'description' => $this->translate('Specifies how often this job run should be recurring') + ]); + + switch (parent::getValue('custom-frequency', RRule::DAILY)) { + case RRule::DAILY: + $this->assembleCommonElements(); + + break; + case RRule::WEEKLY: + $this->assembleCommonElements(); + $this->addElement($this->weeklyField); + + break; + case RRule::MONTHLY: + $this->assembleCommonElements(); + $this->addElement($this->monthlyFields); + + break; + case RRule::YEARLY: + $this->addElement($this->annuallyFields); + } + } elseif ($this->hasCronExpression()) { + $this->addElement('text', 'cron_expression', [ + 'required' => true, + 'label' => $this->translate('Cron Expression'), + 'description' => $this->translate('Job cron Schedule'), + 'validators' => [ + new CallbackValidator(function ($value, CallbackValidator $validator) { + if ($value && ! Cron::isValid($value)) { + $validator->addMessage($this->translate('Invalid CRON expression')); + + return false; + } + + return true; + }) + ] + ]); + } + + if ($this->getFrequency() !== static::NO_REPEAT && ! $this->hasCronExpression()) { + $this->addElement( + new Recurrence('schedule-recurrences', [ + 'id' => $this->protectId('schedule-recurrences'), + 'label' => $this->translate('Next occurrences'), + 'validate' => function (): array { + $isValid = $this->isValid(); + $reason = null; + if (! $isValid && $this->getFrequency() === static::CUSTOM_EXPR) { + if ( + $this->getCustomFrequency() !== RRule::YEARLY + && ! $this->getElement('interval')->isValid() + ) { + $reason = current($this->getElement('interval')->getMessages()); + } else { + $frequency = $this->getCustomFrequency(); + switch ($frequency) { + case RRule::WEEKLY: + $reason = current($this->weeklyField->getMessages()); + + break; + case RRule::MONTHLY: + $reason = current($this->monthlyFields->getMessages()); + + break; + default: // annually + $reason = current($this->annuallyFields->getMessages()); + + break; + } + } + } + + return [$isValid, $reason]; + }, + 'frequency' => function (): Frequency { + if ($this->getFrequency() === static::CUSTOM_EXPR) { + $rule = $this->getValue(); + } else { + $rule = RRule::fromFrequency($this->getFrequency()); + } + + $now = new DateTime(); + $start = $this->getValue('start'); + if ($start < $now) { + $now->setTime($start->format('H'), $start->format('i'), $start->format('s')); + $start = $now; + } + + $rule->startAt($start); + if ($this->getPopulatedValue('use-end-time') === 'y') { + $rule->endAt($this->getValue('end')); + } + + return $rule; + } + ]) + ); + } + } + + /** + * Assemble common parts for all the frequencies + */ + private function assembleCommonElements(): void + { + $repeat = $this->getCustomFrequency(); + if ($repeat === RRule::WEEKLY) { + $text = $this->translate('week(s) on'); + $max = 53; + } elseif ($repeat === RRule::MONTHLY) { + $text = $this->translate('month(s)'); + $max = 12; + } else { + $text = $this->translate('day(s)'); + $max = 31; + } + + $options = ['min' => 1, 'max' => $max]; + $this->addElement('number', 'interval', [ + 'class' => 'autosubmit', + 'value' => 1, + 'min' => 1, + 'max' => $max, + 'validators' => [new BetweenValidator($options)] + ]); + + $numberSpecifier = HtmlElement::create('div', ['class' => 'number-specifier']); + $element = $this->getElement('interval'); + $element->prependWrapper($numberSpecifier); + + $numberSpecifier->prependHtml(HtmlElement::create('span', null, $this->translate('Every'))); + $numberSpecifier->addHtml($element); + $numberSpecifier->addHtml(HtmlElement::create('span', null, $text)); + } + + /** + * Get prepared multipart updates + * + * @param RequestInterface $request + * + * @return array + */ + public function prepareMultipartUpdate(RequestInterface $request): array + { + $autoSubmittedBy = $request->getHeader('X-Icinga-AutoSubmittedBy'); + $pattern = '/\[(weekly-fields|monthly-fields|annually-fields)]\[(ordinal|month|day(\d+)?|[A-Z]{2})]$/'; + + $partUpdates = []; + if ( + $autoSubmittedBy + && ( + preg_match('/\[(start|end)]$/', $autoSubmittedBy[0], $matches) + || preg_match($pattern, $autoSubmittedBy[0]) + || preg_match('/\[interval]/', $autoSubmittedBy[0]) + ) + ) { + $this->ensureAssembled(); + + $partUpdates[] = $this->getElement('schedule-recurrences'); + if ( + $this->getFrequency() === static::CUSTOM_EXPR + && $this->getCustomFrequency() === RRule::MONTHLY + && isset($matches[1]) + && $matches[1] === 'start' + ) { + // To update the available fields/days based on the provided start time + $partUpdates[] = $this->monthlyFields; + } + } + + return $partUpdates; + } + + protected function registerAttributeCallbacks(Attributes $attributes) + { + parent::registerAttributeCallbacks($attributes); + + $attributes->registerAttributeCallback('protector', null, [$this, 'setIdProtector']); + } +} -- cgit v1.2.3