Skip to content
Open
81 changes: 78 additions & 3 deletions application/controllers/ScheduleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
use Icinga\Module\Notifications\Forms\ScheduleForm;
use Icinga\Module\Notifications\Model\Schedule;
use Icinga\Module\Notifications\Widget\RecipientSuggestions;
use Icinga\Module\Notifications\Util\ScheduleDateTimeFactory;
use Icinga\Module\Notifications\Web\Control\TimezonePicker;
use Icinga\Module\Notifications\Widget\Detail\ScheduleDetail;
use Icinga\Module\Notifications\Widget\TimezoneWarning;
use ipl\Html\Form;
use ipl\Html\Html;
use ipl\Stdlib\Filter;
Expand Down Expand Up @@ -47,11 +50,24 @@ public function indexAction(): void

$this->controls->addAttributes(['class' => 'schedule-detail-controls']);

$timezonePicker = $this->createTimezonePicker($schedule->timezone);
$this->controls->addHtml($timezonePicker);

$scheduleControls = (new ScheduleDetail\Controls())
->setAction(Url::fromRequest()->getAbsoluteUrl())
->populate(['mode' => $this->params->get('mode')])
->on(Form::ON_SUCCESS, function (ScheduleDetail\Controls $controls) use ($id) {
$this->redirectNow(Links::schedule($id)->with(['mode' => $controls->getMode()]));
$redirectUrl = Links::schedule($id)->with(['mode' => $controls->getMode()]);
$requestUrl = Url::fromRequest();
if ($requestUrl->getParam('mode') !== $controls->getValue('mode')) {
$defaultTimezoneParam = TimezonePicker::DEFAULT_TIMEZONE_PARAM;
if ($requestUrl->hasParam($defaultTimezoneParam)) {
$redirectUrl->addParams(
[$defaultTimezoneParam => $requestUrl->getParam($defaultTimezoneParam)]
);
}
$this->redirectNow($redirectUrl);
}
})
->handleRequest($this->getServerRequest());

Expand Down Expand Up @@ -91,6 +107,7 @@ public function addAction(): void
{
$this->setTitle($this->translate('New Schedule'));
$form = (new ScheduleForm(Database::get()))
->setShowTimezoneDropdown()
->setAction($this->getRequest()->getUrl()->getAbsoluteUrl())
->on(Form::ON_SUCCESS, function (ScheduleForm $form) {
$scheduleId = $form->addSchedule();
Expand All @@ -107,9 +124,16 @@ public function addAction(): void
public function addRotationAction(): void
{
$scheduleId = (int) $this->params->getRequired('schedule');
$displayTimezone = $this->params->get('display_timezone');
$this->setTitle($this->translate('Add Rotation'));

$form = new RotationConfigForm($scheduleId, Database::get());
$scheduleTimezone = $this->getScheduleTimezone($scheduleId);

if ($displayTimezone !== $scheduleTimezone) {
$this->addContent(new TimezoneWarning($scheduleTimezone));
}

$form = new RotationConfigForm($scheduleId, Database::get(), $displayTimezone);
$form->setAction($this->getRequest()->getUrl()->setParam('showCompact')->getAbsoluteUrl());
$form->setSuggestionUrl(Url::fromPath('notifications/schedule/suggest-recipient'));
$form->on(RotationConfigForm::ON_SENT, function ($form) {
Expand Down Expand Up @@ -139,10 +163,17 @@ public function addRotationAction(): void
public function editRotationAction(): void
{
$id = (int) $this->params->getRequired('id');
$displayTimezone = $this->params->get('display_timezone');
$scheduleId = (int) $this->params->getRequired('schedule');
$this->setTitle($this->translate('Edit Rotation'));

$form = new RotationConfigForm($scheduleId, Database::get());
$scheduleTimezone = $this->getScheduleTimezone($scheduleId);

if ($displayTimezone !== $scheduleTimezone) {
$this->addContent(new TimezoneWarning($scheduleTimezone));
}

$form = new RotationConfigForm($scheduleId, Database::get(), $displayTimezone);
$form->disableModeSelection();
$form->setShowRemoveButton();
$form->loadRotation($id);
Expand Down Expand Up @@ -203,4 +234,48 @@ public function suggestRecipientAction(): void

$this->getDocument()->addHtml($suggestions);
}

/**
* Get the timezone of a schedule
*
* @param int $scheduleId The ID of the schedule
*
* @return string The timezone of the schedule
*/
protected function getScheduleTimezone(int $scheduleId): string
{
return Schedule::on(Database::get())
->filter(Filter::equal('schedule.id', $scheduleId))
->first()
->timezone;
}

/**
* Create a timezone picker control
*
* @param string $defaultTimezone The default timezone to use if none is set in the request
*
* @return TimezonePicker The timezone picker control
*/
protected function createTimezonePicker(string $defaultTimezone): TimezonePicker
{
$defaultTimezoneParam = TimezonePicker::DEFAULT_TIMEZONE_PARAM;
$timezoneParam = $this->params->shift($defaultTimezoneParam);

ScheduleDateTimeFactory::setDisplayTimezone($timezoneParam ?? $defaultTimezone);

return (new TimezonePicker())
->populate([$defaultTimezoneParam => $timezoneParam ?? $defaultTimezone])
->on(
TimezonePicker::ON_SUBMIT,
function (TimezonePicker $timezonePicker) use ($defaultTimezoneParam) {
$requestUrl = Url::fromRequest();
if ($requestUrl->getParam($defaultTimezoneParam) !== $timezonePicker->getValue($defaultTimezoneParam)) {
$this->redirectNow($requestUrl->with([
$defaultTimezoneParam => $timezonePicker->getValue($defaultTimezoneParam)
]));
}
}
)->handleRequest($this->getServerRequest());
}
}
10 changes: 0 additions & 10 deletions application/controllers/SchedulesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@

namespace Icinga\Module\Notifications\Controllers;

use DateTime;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Common\Links;
use Icinga\Module\Notifications\Model\Schedule;
use Icinga\Module\Notifications\View\ScheduleRenderer;
use Icinga\Module\Notifications\Web\Control\SearchBar\ObjectSuggestions;
use Icinga\Module\Notifications\Widget\ItemList\ObjectList;
use Icinga\Module\Notifications\Widget\TimeGrid\DaysHeader;
use ipl\Html\Attributes;
use ipl\Html\HtmlElement;
use ipl\Stdlib\Filter;
use ipl\Web\Compat\CompatController;
use ipl\Web\Compat\SearchControls;
Expand Down Expand Up @@ -78,12 +74,6 @@ public function indexAction(): void
))->openInModal()
);

$this->addContent(new HtmlElement(
'div',
Attributes::create(['class' => 'schedules-header']),
new DaysHeader((new DateTime())->setTime(0, 0), 7)
));

$this->addContent(new ObjectList($schedules, new ScheduleRenderer()));

if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) {
Expand Down
54 changes: 46 additions & 8 deletions application/forms/RotationConfigForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

use DateInterval;
use DateTime;
use DateTimeZone;
use Generator;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\Http\HttpNotFoundException;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Model\Contact;
use Icinga\Module\Notifications\Model\Contactgroup;
use Icinga\Module\Notifications\Model\Rotation;
use Icinga\Module\Notifications\Model\Schedule;
use Icinga\Module\Notifications\Model\TimeperiodEntry;
use Icinga\Util\Json;
use Icinga\Web\Session;
Expand Down Expand Up @@ -78,6 +80,9 @@ class RotationConfigForm extends CompatForm
/** @var int The rotation id */
protected $rotationId;

/** @var string The timezone to display the timeline in */
protected $displayTimezone;

/**
* Set the label for the submit button
*
Expand Down Expand Up @@ -185,11 +190,13 @@ public function hasBeenWiped(): bool
*
* @param int $scheduleId
* @param Connection $db
* @param string $displayTimezone
*/
public function __construct(int $scheduleId, Connection $db)
public function __construct(int $scheduleId, Connection $db, string $displayTimezone)
{
$this->db = $db;
$this->scheduleId = $scheduleId;
$this->displayTimezone = $displayTimezone;
}

/**
Expand Down Expand Up @@ -800,9 +807,10 @@ protected function assemblePartialDayOptions(FieldsetElement $options): DateTime
]);

$selectedFromTime = $from->getValue();
$nextDayTimeOptions = [];
foreach ($timeOptions as $key => $value) {
unset($timeOptions[$key]); // unset to re-add it at the end of array
$timeOptions[$key] = sprintf('%s (%s)', $value, $this->translate('Next Day'));
unset($timeOptions[$key]);
$nextDayTimeOptions[$key] = $value;

if ($selectedFromTime === $key) {
break;
Expand All @@ -811,7 +819,9 @@ protected function assemblePartialDayOptions(FieldsetElement $options): DateTime

$to = $options->createElement('select', 'to', [
'required' => true,
'options' => $timeOptions
'options' => empty($timeOptions)
? ['Next Day' => $nextDayTimeOptions]
: ['Today' => $timeOptions, 'Next Day' => $nextDayTimeOptions]
]);
$options->registerElement($to);

Expand Down Expand Up @@ -1145,7 +1155,8 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
(new \IntlDateFormatter(
\Locale::getDefault(),
\IntlDateFormatter::MEDIUM,
\IntlDateFormatter::SHORT
\IntlDateFormatter::SHORT,
$this->getScheduleTimezone()
))->format($actualFirstHandoff)
);
}
Expand Down Expand Up @@ -1270,7 +1281,8 @@ private function parseDateAndTime(?string $date = null, ?string $time = null): D
return (new DateTime())->setTime(0, 0);
}

$datetime = DateTime::createFromFormat($format, $expression);
$datetime = DateTime::createFromFormat($format, $expression, new DateTimeZone($this->getScheduleTimezone()));

if ($datetime === false) {
$datetime = (new DateTime())->setTime(0, 0);
} elseif ($time === null) {
Expand All @@ -1293,12 +1305,25 @@ private function getTimeOptions(): array
\IntlDateFormatter::SHORT
);

$dtzFormatter = new \IntlDateFormatter(
\Locale::getDefault(),
\IntlDateFormatter::NONE,
\IntlDateFormatter::SHORT,
$this->displayTimezone
);

$options = [];
$dt = new DateTime();
$dt = new DateTime('now', new DateTimeZone($this->getScheduleTimezone()));
for ($hour = 0; $hour < 24; $hour++) {
for ($minute = 0; $minute < 60; $minute += 30) {
$dt->setTime($hour, $minute);
$options[$dt->format('H:i')] = $formatter->format($dt);
$dtzDt = (clone $dt)->setTimezone(new DateTimeZone($this->displayTimezone));

$options[$dt->format('H:i')] = sprintf(
'%s (%s)',
$formatter->format($dt),
$dtzFormatter->format($dtzDt)
);
}
}

Expand Down Expand Up @@ -1665,4 +1690,17 @@ public function hasChanges(): bool

return ! empty(array_udiff_assoc($values, $dbValuesToCompare, $checker));
}

/**
* Get the timezone of the schedule
*
* @return string The timezone identifier
*/
protected function getScheduleTimezone(): string
{
return Schedule::on(Database::get())
->filter(Filter::equal('id', $this->scheduleId))
->first()
->timezone;
}
}
31 changes: 30 additions & 1 deletion application/forms/ScheduleForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Icinga\Module\Notifications\Forms;

use DateTime;
use DateTimeZone;
use Icinga\Exception\Http\HttpNotFoundException;
use Icinga\Module\Notifications\Model\Rotation;
use Icinga\Module\Notifications\Model\RuleEscalationRecipient;
Expand All @@ -29,6 +30,9 @@ class ScheduleForm extends CompatForm
/** @var bool */
protected $showRemoveButton = false;

/** @var bool */
protected $showTimezoneDropdown = false;

/** @var Connection */
private $db;

Expand Down Expand Up @@ -59,6 +63,20 @@ public function setShowRemoveButton(bool $state = true): self
return $this;
}

/**
* Set whether to show the timezone dropdown or not
*
* @param bool $state If true, the timezone dropdown will be shown (defaults to true)
*
* @return $this
*/
public function setShowTimezoneDropdown(bool $state = true): self
{
$this->showTimezoneDropdown = $state;

return $this;
}

public function hasBeenRemoved(): bool
{
$btn = $this->getPressedSubmitElement();
Expand All @@ -78,7 +96,8 @@ public function addSchedule(): int
return $this->db->transaction(function (Connection $db) {
$db->insert('schedule', [
'name' => $this->getValue('name'),
'changed_at' => (int) (new DateTime())->format("Uv")
'changed_at' => (int) (new DateTime())->format("Uv"),
'timezone' => $this->getValue('timezone')
]);

return $db->lastInsertId();
Expand Down Expand Up @@ -175,6 +194,16 @@ protected function assemble()
'placeholder' => $this->translate('e.g. working hours, on call, etc ...')
]);

if ($this->showTimezoneDropdown) {
$this->addElement('select', 'timezone', [
'required' => true,
'label' => $this->translate('Schedule Timezone'),
'description' => $this->translate('Select the time zone in which this schedule operates.'),
'multiOptions' => array_combine(DateTimeZone::listIdentifiers(), DateTimeZone::listIdentifiers()),
'value' => date_default_timezone_get(),
]);
}

$this->addElement('submit', 'submit', [
'label' => $this->getSubmitLabel()
]);
Expand Down
12 changes: 10 additions & 2 deletions library/Notifications/Common/Links.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Icinga\Module\Notifications\Common;

use Icinga\Module\Notifications\Util\ScheduleDateTimeFactory;
use ipl\Web\Url;

/**
Expand Down Expand Up @@ -118,12 +119,19 @@ public static function contactGroupEdit(int $id): Url

public static function rotationAdd(int $scheduleId): Url
{
return Url::fromPath('notifications/schedule/add-rotation', ['schedule' => $scheduleId]);
return Url::fromPath('notifications/schedule/add-rotation', [
'schedule' => $scheduleId,
'display_timezone' => ScheduleDateTimeFactory::getDisplayTimezone()->getName()
]);
}

public static function rotationSettings(int $id, int $scheduleId): Url
{
return Url::fromPath('notifications/schedule/edit-rotation', ['id' => $id, 'schedule' => $scheduleId]);
return Url::fromPath('notifications/schedule/edit-rotation', [
'id' => $id,
'schedule' => $scheduleId,
'display_timezone' => ScheduleDateTimeFactory::getDisplayTimezone()->getName()
]);
}

public static function moveRotation(): Url
Expand Down
Loading