summaryrefslogtreecommitdiffstats
path: root/vendor/ipl/web/src/FormElement/ScheduleElement
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/ipl/web/src/FormElement/ScheduleElement')
-rw-r--r--vendor/ipl/web/src/FormElement/ScheduleElement/AnnuallyFields.php133
-rw-r--r--vendor/ipl/web/src/FormElement/ScheduleElement/Common/FieldsProtector.php41
-rw-r--r--vendor/ipl/web/src/FormElement/ScheduleElement/Common/FieldsUtils.php243
-rw-r--r--vendor/ipl/web/src/FormElement/ScheduleElement/FieldsRadio.php58
-rw-r--r--vendor/ipl/web/src/FormElement/ScheduleElement/MonthlyFields.php191
-rw-r--r--vendor/ipl/web/src/FormElement/ScheduleElement/Recurrence.php89
-rw-r--r--vendor/ipl/web/src/FormElement/ScheduleElement/WeeklyFields.php151
7 files changed, 906 insertions, 0 deletions
diff --git a/vendor/ipl/web/src/FormElement/ScheduleElement/AnnuallyFields.php b/vendor/ipl/web/src/FormElement/ScheduleElement/AnnuallyFields.php
new file mode 100644
index 0000000..857711a
--- /dev/null
+++ b/vendor/ipl/web/src/FormElement/ScheduleElement/AnnuallyFields.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace ipl\Web\FormElement\ScheduleElement;
+
+use InvalidArgumentException;
+use ipl\Html\Attributes;
+use ipl\Html\FormattedString;
+use ipl\Html\FormElement\FieldsetElement;
+use ipl\Html\HtmlElement;
+use ipl\Web\FormElement\ScheduleElement\Common\FieldsProtector;
+use ipl\Web\FormElement\ScheduleElement\Common\FieldsUtils;
+use ipl\Web\Widget\Icon;
+
+class AnnuallyFields extends FieldsetElement
+{
+ use FieldsUtils;
+ use FieldsProtector;
+
+ /** @var array A list of valid months */
+ protected $months = [];
+
+ /** @var string A month to preselect by default */
+ protected $default = 'JAN';
+
+ public function __construct($name, $attributes = null)
+ {
+ $this->months = [
+ 'JAN' => $this->translate('Jan'),
+ 'FEB' => $this->translate('Feb'),
+ 'MAR' => $this->translate('Mar'),
+ 'APR' => $this->translate('Apr'),
+ 'MAY' => $this->translate('May'),
+ 'JUN' => $this->translate('Jun'),
+ 'JUL' => $this->translate('Jul'),
+ 'AUG' => $this->translate('Aug'),
+ 'SEP' => $this->translate('Sep'),
+ 'OCT' => $this->translate('Oct'),
+ 'NOV' => $this->translate('Nov'),
+ 'DEC' => $this->translate('Dec')
+ ];
+
+ parent::__construct($name, $attributes);
+ }
+
+ protected function init(): void
+ {
+ parent::init();
+ $this->initUtils();
+ }
+
+ /**
+ * Set the default month to be activated
+ *
+ * @param string $default
+ *
+ * @return $this
+ */
+ public function setDefault(string $default): self
+ {
+ if (! isset($this->months[strtoupper($this->default)])) {
+ throw new InvalidArgumentException(sprintf('Invalid month provided: %s', $default));
+ }
+
+ $this->default = strtoupper($default);
+
+ return $this;
+ }
+
+ protected function assemble()
+ {
+ $this->getAttributes()->set('id', $this->protectId('annually-fields'));
+
+ $fieldsSelector = new FieldsRadio('month', [
+ 'class' => ['autosubmit', 'sr-only'],
+ 'value' => $this->default,
+ 'options' => $this->months,
+ 'protector' => function ($value) {
+ return $this->protectId($value);
+ }
+ ]);
+ $this->registerElement($fieldsSelector);
+
+ $runsOnThe = $this->getPopulatedValue('runsOnThe', 'n');
+ $this->addElement('checkbox', 'runsOnThe', [
+ 'class' => 'autosubmit',
+ 'value' => $runsOnThe
+ ]);
+
+ $checkboxControls = HtmlElement::create('div', ['class' => 'toggle-slider-controls']);
+ $checkbox = $this->getElement('runsOnThe');
+ $checkbox->prependWrapper($checkboxControls);
+ $checkboxControls->addHtml($checkbox, HtmlElement::create('span', null, $this->translate('On the')));
+
+ $annuallyWrapper = HtmlElement::create('div', ['class' => 'annually']);
+ $checkboxControls->prependWrapper($annuallyWrapper);
+ $annuallyWrapper->addHtml($fieldsSelector);
+
+ $notes = HtmlElement::create('div', ['class' => 'note']);
+ $notes->addHtml(
+ FormattedString::create(
+ $this->translate('Use %s / %s keys to choose a month by keyboard.'),
+ new Icon('arrow-left'),
+ new Icon('arrow-right')
+ )
+ );
+ $annuallyWrapper->addHtml($notes);
+
+ $enumerations = $this->createOrdinalElement();
+ $enumerations->getAttributes()->set('disabled', $runsOnThe === 'n');
+ $this->registerElement($enumerations);
+
+ $selectableDays = $this->createOrdinalSelectableDays();
+ $selectableDays->getAttributes()->set('disabled', $runsOnThe === 'n');
+ $this->registerElement($selectableDays);
+
+ $ordinalWrapper = HtmlElement::create('div', ['class' => ['ordinal', 'annually']]);
+ $this
+ ->decorate($enumerations)
+ ->addHtml($enumerations);
+
+ $enumerations->prependWrapper($ordinalWrapper);
+ $ordinalWrapper->addHtml($enumerations, $selectableDays);
+ }
+
+ protected function registerAttributeCallbacks(Attributes $attributes)
+ {
+ parent::registerAttributeCallbacks($attributes);
+
+ $attributes
+ ->registerAttributeCallback('default', null, [$this, 'setDefault'])
+ ->registerAttributeCallback('protector', null, [$this, 'setIdProtector']);
+ }
+}
diff --git a/vendor/ipl/web/src/FormElement/ScheduleElement/Common/FieldsProtector.php b/vendor/ipl/web/src/FormElement/ScheduleElement/Common/FieldsProtector.php
new file mode 100644
index 0000000..affd519
--- /dev/null
+++ b/vendor/ipl/web/src/FormElement/ScheduleElement/Common/FieldsProtector.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace ipl\Web\FormElement\ScheduleElement\Common;
+
+trait FieldsProtector
+{
+ /** @var callable */
+ protected $protector;
+
+ /**
+ * Set callback to protect ids with
+ *
+ * @param ?callable $protector
+ *
+ * @return $this
+ */
+ public function setIdProtector(?callable $protector): self
+ {
+ $this->protector = $protector;
+
+ return $this;
+ }
+
+ /**
+ * Protect the given html id
+ *
+ * The provided id is returned as is, if no protector is specified
+ *
+ * @param string $id
+ *
+ * @return string
+ */
+ public function protectId(string $id): string
+ {
+ if (is_callable($this->protector)) {
+ return call_user_func($this->protector, $id);
+ }
+
+ return $id;
+ }
+}
diff --git a/vendor/ipl/web/src/FormElement/ScheduleElement/Common/FieldsUtils.php b/vendor/ipl/web/src/FormElement/ScheduleElement/Common/FieldsUtils.php
new file mode 100644
index 0000000..bf28255
--- /dev/null
+++ b/vendor/ipl/web/src/FormElement/ScheduleElement/Common/FieldsUtils.php
@@ -0,0 +1,243 @@
+<?php
+
+namespace ipl\Web\FormElement\ScheduleElement\Common;
+
+use DateInterval;
+use DateTime;
+use Exception;
+use InvalidArgumentException;
+use ipl\Html\Contract\FormElement;
+use ipl\Scheduler\RRule;
+use ipl\Web\FormElement\ScheduleElement\MonthlyFields;
+
+trait FieldsUtils
+{
+ // Non-standard frequency options
+ public static $everyDay = 'day';
+ public static $everyWeekday = 'weekday';
+ public static $everyWeekend = 'weekend';
+
+ // Enumerators for the monthly and annually schedule of a custom frequency
+ public static $first = 'first';
+ public static $second = 'second';
+ public static $third = 'third';
+ public static $fourth = 'fourth';
+ public static $fifth = 'fifth';
+ public static $last = 'last';
+
+ private $regulars = [];
+
+ protected function initUtils(): void
+ {
+ $this->regulars = [
+ 'MO' => $this->translate('Monday'),
+ 'TU' => $this->translate('Tuesday'),
+ 'WE' => $this->translate('Wednesday'),
+ 'TH' => $this->translate('Thursday'),
+ 'FR' => $this->translate('Friday'),
+ 'SA' => $this->translate('Saturday'),
+ 'SU' => $this->translate('Sunday')
+ ];
+ }
+
+ protected function createOrdinalElement(): FormElement
+ {
+ return $this->createElement('select', 'ordinal', [
+ 'class' => 'autosubmit',
+ 'value' => $this->getPopulatedValue('ordinal', static::$first),
+ 'options' => [
+ static::$first => $this->translate('First'),
+ static::$second => $this->translate('Second'),
+ static::$third => $this->translate('Third'),
+ static::$fourth => $this->translate('Fourth'),
+ static::$fifth => $this->translate('Fifth'),
+ static::$last => $this->translate('Last')
+ ]
+ ]);
+ }
+
+ protected function createOrdinalSelectableDays(): FormElement
+ {
+ $select = $this->createElement('select', 'day', [
+ 'class' => 'autosubmit',
+ 'value' => $this->getPopulatedValue('day', static::$everyDay),
+ 'options' => $this->regulars + [
+ 'separator' => '──────────────────────────',
+ static::$everyDay => $this->translate('Day'),
+ static::$everyWeekday => $this->translate('Weekday (Mon - Fri)'),
+ static::$everyWeekend => $this->translate('WeekEnd (Sat or Sun)')
+ ]
+ ]);
+ $select->getOption('separator')->getAttributes()->set('disabled', true);
+
+ return $select;
+ }
+
+ /**
+ * Load the given RRule instance into a list of key=>value pairs
+ *
+ * @param RRule $rule
+ *
+ * @return array
+ */
+ public function loadRRule(RRule $rule): array
+ {
+ $values = [];
+ $isMonthly = $rule->getFrequency() === RRule::MONTHLY;
+ if ($isMonthly && (! empty($rule->getByMonthDay()) || empty($rule->getByDay()))) {
+ $monthDays = $rule->getByMonthDay() ?? [];
+ foreach (range(1, $this->availableFields) as $value) {
+ $values["day$value"] = in_array((string) $value, $monthDays, true) ? 'y' : 'n';
+ }
+
+ $values['runsOn'] = MonthlyFields::RUNS_EACH;
+ } else {
+ $position = $rule->getBySetPosition();
+ $byDay = $rule->getByDay() ?? [];
+
+ if ($isMonthly) {
+ $values['runsOn'] = MonthlyFields::RUNS_ONTHE;
+ } else {
+ $months = $rule->getByMonth();
+ if (empty($months) && $rule->getStart()) {
+ $months[] = $rule->getStart()->format('m');
+ } elseif (empty($months)) {
+ $months[] = date('m');
+ }
+
+ $values['month'] = strtoupper($this->getMonthByNumber((int)$months[0]));
+ $values['runsOnThe'] = ! empty($byDay) ? 'y' : 'n';
+ }
+
+ if (count($byDay) == 1 && preg_match('/^(-?\d)(\w.*)$/', $byDay[0], $matches)) {
+ $values['ordinal'] = $this->getOrdinalString($matches[1]);
+ $values['day'] = $this->getWeekdayName($matches[2]);
+ } elseif (! empty($byDay)) {
+ $values['ordinal'] = $this->getOrdinalString(current($position));
+ switch (count($byDay)) {
+ case MonthlyFields::WEEK_DAYS:
+ $values['day'] = static::$everyDay;
+
+ break;
+ case MonthlyFields::WEEK_DAYS - 2:
+ $values['day'] = static::$everyWeekday;
+
+ break;
+ case 1:
+ $values['day'] = current($byDay);
+
+ break;
+ case 2:
+ $byDay = array_flip($byDay);
+ if (isset($byDay['SA']) && isset($byDay['SU'])) {
+ $values['day'] = static::$everyWeekend;
+ }
+ }
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Transform the given expression part into a valid week day string representation
+ *
+ * @param string $day
+ *
+ * @return string
+ */
+ public function getWeekdayName(string $day): string
+ {
+ // Not transformation is needed when the given day is part of the valid weekdays
+ if (isset($this->regulars[strtoupper($day)])) {
+ return $day;
+ }
+
+ try {
+ // Try to figure it out using date time before raising an error
+ $datetime = new DateTime('Sunday');
+ $datetime->add(new DateInterval("P$day" . 'D'));
+
+ return $datetime->format('D');
+ } catch (Exception $_) {
+ throw new InvalidArgumentException(sprintf('Invalid weekday provided: %s', $day));
+ }
+ }
+
+ /**
+ * Transform the given integer enums into something like first,second...
+ *
+ * @param string $ordinal
+ *
+ * @return string
+ */
+ public function getOrdinalString(string $ordinal): string
+ {
+ switch ($ordinal) {
+ case '1':
+ return static::$first;
+ case '2':
+ return static::$second;
+ case '3':
+ return static::$third;
+ case '4':
+ return static::$fourth;
+ case '5':
+ return static::$fifth;
+ case '-1':
+ return static::$last;
+ default:
+ throw new InvalidArgumentException(
+ sprintf('Invalid ordinal string representation provided: %s', $ordinal)
+ );
+ }
+ }
+
+ /**
+ * Get the string representation of the given ordinal to an integer
+ *
+ * This transforms the given ordinal such as (first, second...) into its respective
+ * integral representation. At the moment only (1..5 + the non-standard "last") options
+ * are supported. So if this method returns the character "-1", is meant the last option.
+ *
+ * @param string $ordinal
+ *
+ * @return int
+ */
+ public function getOrdinalAsInteger(string $ordinal): int
+ {
+ switch ($ordinal) {
+ case static::$first:
+ return 1;
+ case static::$second:
+ return 2;
+ case static::$third:
+ return 3;
+ case static::$fourth:
+ return 4;
+ case static::$fifth:
+ return 5;
+ case static::$last:
+ return -1;
+ default:
+ throw new InvalidArgumentException(sprintf('Invalid enumerator provided: %s', $ordinal));
+ }
+ }
+
+ /**
+ * Get a short textual representation of the given month
+ *
+ * @param int $month
+ *
+ * @return string
+ */
+ public function getMonthByNumber(int $month): string
+ {
+ $time = DateTime::createFromFormat('!m', $month);
+ if ($time) {
+ return $time->format('M');
+ }
+
+ throw new InvalidArgumentException(sprintf('Invalid month number provided: %d', $month));
+ }
+}
diff --git a/vendor/ipl/web/src/FormElement/ScheduleElement/FieldsRadio.php b/vendor/ipl/web/src/FormElement/ScheduleElement/FieldsRadio.php
new file mode 100644
index 0000000..31b77c3
--- /dev/null
+++ b/vendor/ipl/web/src/FormElement/ScheduleElement/FieldsRadio.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace ipl\Web\FormElement\ScheduleElement;
+
+use ipl\Html\Attributes;
+use ipl\Html\FormElement\InputElement;
+use ipl\Html\FormElement\RadioElement;
+use ipl\Html\HtmlElement;
+use ipl\Web\FormElement\ScheduleElement\Common\FieldsProtector;
+
+class FieldsRadio extends RadioElement
+{
+ use FieldsProtector;
+
+ protected function assemble()
+ {
+ $listItems = HtmlElement::create('ul', ['class' => ['schedule-element-fields', 'single-fields']]);
+ foreach ($this->options as $option) {
+ $radio = (new InputElement($this->getValueOfNameAttribute()))
+ ->setValue($option->getValue())
+ ->setType($this->type);
+
+ $radio->setAttributes(clone $this->getAttributes());
+
+ $htmlId = $this->protectId($option->getValue());
+ $radio->getAttributes()
+ ->set('id', $htmlId)
+ ->registerAttributeCallback('checked', function () use ($option) {
+ return (string) $this->getValue() === (string) $option->getValue();
+ })
+ ->registerAttributeCallback('required', [$this, 'getRequiredAttribute'])
+ ->registerAttributeCallback('disabled', function () use ($option) {
+ return $this->getAttributes()->get('disabled')->getValue() || $option->isDisabled();
+ });
+
+ $listItem = HtmlElement::create('li');
+ $listItem->addHtml(
+ $radio,
+ HtmlElement::create('label', [
+ 'for' => $htmlId,
+ 'class' => $option->getLabelCssClass(),
+ 'tabindex' => -1
+ ], $option->getLabel())
+ );
+ $listItems->addHtml($listItem);
+ }
+
+ $this->addHtml($listItems);
+ }
+
+ protected function registerAttributeCallbacks(Attributes $attributes)
+ {
+ parent::registerAttributeCallbacks($attributes);
+
+ $attributes
+ ->registerAttributeCallback('protector', null, [$this, 'setIdProtector']);
+ }
+}
diff --git a/vendor/ipl/web/src/FormElement/ScheduleElement/MonthlyFields.php b/vendor/ipl/web/src/FormElement/ScheduleElement/MonthlyFields.php
new file mode 100644
index 0000000..26329fc
--- /dev/null
+++ b/vendor/ipl/web/src/FormElement/ScheduleElement/MonthlyFields.php
@@ -0,0 +1,191 @@
+<?php
+
+namespace ipl\Web\FormElement\ScheduleElement;
+
+use ipl\Html\Attributes;
+use ipl\Html\FormElement\FieldsetElement;
+use ipl\Html\HtmlElement;
+use ipl\Validator\CallbackValidator;
+use ipl\Validator\InArrayValidator;
+use ipl\Validator\ValidatorChain;
+use ipl\Web\FormElement\ScheduleElement\Common\FieldsProtector;
+use ipl\Web\FormElement\ScheduleElement\Common\FieldsUtils;
+
+class MonthlyFields extends FieldsetElement
+{
+ use FieldsUtils;
+ use FieldsProtector;
+
+ /** @var string Used as radio option to run each selected days/months */
+ public const RUNS_EACH = 'each';
+
+ /** @var string Used as radio option to build complex job schedules */
+ public const RUNS_ONTHE = 'onthe';
+
+ /** @var int Number of days in a week */
+ public const WEEK_DAYS = 7;
+
+ /** @var int Day of the month to preselect by default */
+ protected $default = 1;
+
+ /** @var int Number of fields to render */
+ protected $availableFields;
+
+ protected function init(): void
+ {
+ parent::init();
+ $this->initUtils();
+
+ $this->availableFields = (int) date('t');
+ }
+
+ /**
+ * Set the available fields/days of the month to be rendered
+ *
+ * @param int $fields
+ *
+ * @return $this
+ */
+ public function setAvailableFields(int $fields): self
+ {
+ $this->availableFields = $fields;
+
+ return $this;
+ }
+
+ /**
+ * Set the default field/day to be selected
+ *
+ * @param int $default
+ *
+ * @return $this
+ */
+ public function setDefault(int $default): self
+ {
+ $this->default = $default;
+
+ return $this;
+ }
+
+ /**
+ * Get all the selected weekdays
+ *
+ * @return array
+ */
+ public function getSelectedDays(): array
+ {
+ $selectedDays = [];
+ foreach (range(1, $this->availableFields) as $day) {
+ if ($this->getValue("day$day", 'n') === 'y') {
+ $selectedDays[] = $day;
+ }
+ }
+
+ if (empty($selectedDays)) {
+ $selectedDays[] = $this->default;
+ }
+
+ return $selectedDays;
+ }
+
+ protected function assemble()
+ {
+ $this->getAttributes()->set('id', $this->protectId('monthly-fields'));
+
+ $runsOn = $this->getPopulatedValue('runsOn', static::RUNS_EACH);
+ $this->addElement('radio', 'runsOn', [
+ 'required' => true,
+ 'class' => 'autosubmit',
+ 'value' => $runsOn,
+ 'options' => [static::RUNS_EACH => $this->translate('Each')],
+ ]);
+
+ $listItems = HtmlElement::create('ul', ['class' => ['schedule-element-fields', 'multiple-fields']]);
+ if ($runsOn === static::RUNS_ONTHE) {
+ $listItems->getAttributes()->add('class', 'disabled');
+ }
+
+ foreach (range(1, $this->availableFields) as $day) {
+ $checkbox = $this->createElement('checkbox', "day$day", [
+ 'class' => ['autosubmit', 'sr-only'],
+ 'value' => $day === $this->default && $runsOn === static::RUNS_EACH
+ ]);
+ $this->registerElement($checkbox);
+
+ $htmlId = $this->protectId("day$day");
+ $checkbox->getAttributes()->set('id', $htmlId);
+
+ $listItem = HtmlElement::create('li');
+ $listItem->addHtml($checkbox, HtmlElement::create('label', ['for' => $htmlId], $day));
+ $listItems->addHtml($listItem);
+ }
+
+ $monthlyWrapper = HtmlElement::create('div', ['class' => 'monthly']);
+ $runsEach = $this->getElement('runsOn');
+ $runsEach->prependWrapper($monthlyWrapper);
+ $monthlyWrapper->addHtml($runsEach, $listItems);
+
+ $this->addElement('radio', 'runsOn', [
+ 'required' => $runsOn !== static::RUNS_EACH,
+ 'class' => 'autosubmit',
+ 'options' => [static::RUNS_ONTHE => $this->translate('On the')],
+ 'validators' => [
+ new InArrayValidator([
+ 'strict' => true,
+ 'haystack' => [static::RUNS_EACH, static::RUNS_ONTHE]
+ ])
+ ]
+ ]);
+
+ $ordinalWrapper = HtmlElement::create('div', ['class' => 'ordinal']);
+ $runsOnThe = $this->getElement('runsOn');
+ $runsOnThe->prependWrapper($ordinalWrapper);
+ $ordinalWrapper->addHtml($runsOnThe);
+
+ $enumerations = $this->createOrdinalElement();
+ $enumerations->getAttributes()->set('disabled', $runsOn === static::RUNS_EACH);
+ $this->registerElement($enumerations);
+
+ $selectableDays = $this->createOrdinalSelectableDays();
+ $selectableDays->getAttributes()->set('disabled', $runsOn === static::RUNS_EACH);
+ $this->registerElement($selectableDays);
+
+ $ordinalWrapper->addHtml($enumerations, $selectableDays);
+ }
+
+ protected function registerAttributeCallbacks(Attributes $attributes)
+ {
+ parent::registerAttributeCallbacks($attributes);
+
+ $attributes
+ ->registerAttributeCallback('default', null, [$this, 'setDefault'])
+ ->registerAttributeCallback('availableFields', null, [$this, 'setAvailableFields'])
+ ->registerAttributeCallback('protector', null, [$this, 'setIdProtector']);
+ }
+
+ protected function addDefaultValidators(ValidatorChain $chain): void
+ {
+ $chain->add(
+ new CallbackValidator(function ($_, CallbackValidator $validator): bool {
+ if ($this->getValue('runsOn', static::RUNS_EACH) !== static::RUNS_EACH) {
+ return true;
+ }
+
+ $valid = false;
+ foreach (range(1, $this->availableFields) as $day) {
+ if ($this->getValue("day$day") === 'y') {
+ $valid = true;
+
+ break;
+ }
+ }
+
+ if (! $valid) {
+ $validator->addMessage($this->translate('You must select at least one of these days'));
+ }
+
+ return $valid;
+ })
+ );
+ }
+}
diff --git a/vendor/ipl/web/src/FormElement/ScheduleElement/Recurrence.php b/vendor/ipl/web/src/FormElement/ScheduleElement/Recurrence.php
new file mode 100644
index 0000000..8693b20
--- /dev/null
+++ b/vendor/ipl/web/src/FormElement/ScheduleElement/Recurrence.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace ipl\Web\FormElement\ScheduleElement;
+
+use DateTime;
+use ipl\Html\Attributes;
+use ipl\Html\FormElement\BaseFormElement;
+use ipl\Html\HtmlElement;
+use ipl\Html\Text;
+use ipl\I18n\Translation;
+use ipl\Scheduler\Contract\Frequency;
+use ipl\Scheduler\RRule;
+
+class Recurrence extends BaseFormElement
+{
+ use Translation;
+
+ protected $tag = 'div';
+
+ protected $defaultAttributes = ['class' => 'schedule-recurrences'];
+
+ /** @var callable A callable that generates a frequency instance */
+ protected $frequencyCallback;
+
+ /** @var callable A validation callback for the schedule element */
+ protected $validateCallback;
+
+ /**
+ * Set a validation callback that will be called when assembling this element
+ *
+ * @param callable $callback
+ *
+ * @return $this
+ */
+ public function setValid(callable $callback): self
+ {
+ $this->validateCallback = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Set a callback that generates an {@see Frequency} instance
+ *
+ * @param callable $callback
+ *
+ * @return $this
+ */
+ public function setFrequency(callable $callback): self
+ {
+ $this->frequencyCallback = $callback;
+
+ return $this;
+ }
+
+ protected function assemble()
+ {
+ list($isValid, $reason) = ($this->validateCallback)();
+ if (! $isValid) {
+ // Render why we can't generate the recurrences
+ $this->addHtml(Text::create($reason));
+
+ return;
+ }
+
+ /** @var RRule $frequency */
+ $frequency = ($this->frequencyCallback)();
+ $recurrences = $frequency->getNextRecurrences(new DateTime(), 3);
+ if (! $recurrences->valid()) {
+ // Such a situation can be caused by setting an invalid end time
+ $this->addHtml(HtmlElement::create('p', null, Text::create($this->translate('Never'))));
+
+ return;
+ }
+
+ foreach ($recurrences as $recurrence) {
+ $this->addHtml(HtmlElement::create('p', null, $recurrence->format($this->translate('D, Y/m/d, H:i:s'))));
+ }
+ }
+
+ protected function registerAttributeCallbacks(Attributes $attributes)
+ {
+ parent::registerAttributeCallbacks($attributes);
+
+ $attributes
+ ->registerAttributeCallback('frequency', null, [$this, 'setFrequency'])
+ ->registerAttributeCallback('validate', null, [$this, 'setValid']);
+ }
+}
diff --git a/vendor/ipl/web/src/FormElement/ScheduleElement/WeeklyFields.php b/vendor/ipl/web/src/FormElement/ScheduleElement/WeeklyFields.php
new file mode 100644
index 0000000..01933ca
--- /dev/null
+++ b/vendor/ipl/web/src/FormElement/ScheduleElement/WeeklyFields.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace ipl\Web\FormElement\ScheduleElement;
+
+use InvalidArgumentException;
+use ipl\Html\Attributes;
+use ipl\Html\FormElement\FieldsetElement;
+use ipl\Html\HtmlElement;
+use ipl\Validator\CallbackValidator;
+use ipl\Validator\ValidatorChain;
+use ipl\Web\FormElement\ScheduleElement\Common\FieldsProtector;
+
+class WeeklyFields extends FieldsetElement
+{
+ use FieldsProtector;
+
+ /** @var array A list of valid week days */
+ protected $weekdays = [];
+
+ /** @var string A valid weekday to be selected by default */
+ protected $default = 'MO';
+
+ public function __construct($name, $attributes = null)
+ {
+ $this->weekdays = [
+ 'MO' => $this->translate('Mon'),
+ 'TU' => $this->translate('Tue'),
+ 'WE' => $this->translate('Wed'),
+ 'TH' => $this->translate('Thu'),
+ 'FR' => $this->translate('Fri'),
+ 'SA' => $this->translate('Sat'),
+ 'SU' => $this->translate('Sun')
+ ];
+
+ parent::__construct($name, $attributes);
+ }
+
+ /**
+ * Set the default weekday to be preselected
+ *
+ * @param string $default
+ *
+ * @return $this
+ */
+ public function setDefault(string $default): self
+ {
+ $weekday = strlen($default) > 2 ? substr($default, 0, -1) : $default;
+ if (! isset($this->weekdays[strtoupper($weekday)])) {
+ throw new InvalidArgumentException(sprintf('Invalid weekday provided: %s', $default));
+ }
+
+ $this->default = strtoupper($weekday);
+
+ return $this;
+ }
+
+ /**
+ * Get all the selected weekdays
+ *
+ * @return array
+ */
+ public function getSelectedWeekDays(): array
+ {
+ $selectedDays = [];
+ foreach ($this->weekdays as $day => $_) {
+ if ($this->getValue($day, 'n') === 'y') {
+ $selectedDays[] = $day;
+ }
+ }
+
+ if (empty($selectedDays)) {
+ $selectedDays[] = $this->default;
+ }
+
+ return $selectedDays;
+ }
+
+ /**
+ * Transform the given weekdays into key=>value array that can be populated
+ *
+ * @param array $weekdays
+ *
+ * @return array
+ */
+ public function loadWeekDays(array $weekdays): array
+ {
+ $values = [];
+ foreach ($this->weekdays as $weekday => $_) {
+ $values[$weekday] = in_array($weekday, $weekdays, true) ? 'y' : 'n';
+ }
+
+ return $values;
+ }
+
+ protected function assemble()
+ {
+ $this->getAttributes()->set('id', $this->protectId('weekly-fields'));
+
+ $fieldsWrapper = HtmlElement::create('div', ['class' => 'weekly']);
+ $listItems = HtmlElement::create('ul', ['class' => ['schedule-element-fields', 'multiple-fields']]);
+
+ foreach ($this->weekdays as $day => $value) {
+ $checkbox = $this->createElement('checkbox', $day, [
+ 'class' => ['autosubmit', 'sr-only'],
+ 'value' => $day === $this->default
+ ]);
+ $this->registerElement($checkbox);
+
+ $htmlId = $this->protectId("weekday-$day");
+ $checkbox->getAttributes()->set('id', $htmlId);
+
+ $listItem = HtmlElement::create('li');
+ $listItem->addHtml($checkbox, HtmlElement::create('label', ['for' => $htmlId], $value));
+ $listItems->addHtml($listItem);
+ }
+
+ $fieldsWrapper->addHtml($listItems);
+ $this->addHtml($fieldsWrapper);
+ }
+
+ protected function registerAttributeCallbacks(Attributes $attributes)
+ {
+ parent::registerAttributeCallbacks($attributes);
+
+ $attributes
+ ->registerAttributeCallback('default', null, [$this, 'setDefault'])
+ ->registerAttributeCallback('protector', null, [$this, 'setIdProtector']);
+ }
+
+ protected function addDefaultValidators(ValidatorChain $chain): void
+ {
+ $chain->add(
+ new CallbackValidator(function ($_, CallbackValidator $validator): bool {
+ $valid = false;
+ foreach ($this->weekdays as $weekday => $_) {
+ if ($this->getValue($weekday) === 'y') {
+ $valid = true;
+
+ break;
+ }
+ }
+
+ if (! $valid) {
+ $validator->addMessage($this->translate('You must select at least one of these weekdays'));
+ }
+
+ return $valid;
+ })
+ );
+ }
+}