From ee882e97455a57a48ffef9f6ab88d1c82f127af1 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 30 Apr 2024 15:30:32 +0200 Subject: [PATCH 01/34] Models: Add `changed_at` and `deleted` columns and their `Behaviors` --- library/Notifications/Model/Channel.php | 18 +++++- library/Notifications/Model/Contact.php | 15 +++-- .../Notifications/Model/ContactAddress.php | 13 +++- library/Notifications/Model/Contactgroup.php | 16 ++++- .../Model/ContactgroupMember.php | 59 +++++++++++++++++++ library/Notifications/Model/Rotation.php | 24 +++++--- .../Notifications/Model/RotationMember.php | 16 ++++- library/Notifications/Model/Rule.php | 16 ++++- .../Notifications/Model/RuleEscalation.php | 16 ++++- .../Model/RuleEscalationRecipient.php | 17 +++++- library/Notifications/Model/Schedule.php | 18 +++++- library/Notifications/Model/Source.php | 20 ++++++- library/Notifications/Model/Timeperiod.php | 13 +++- .../Notifications/Model/TimeperiodEntry.php | 9 ++- 14 files changed, 237 insertions(+), 33 deletions(-) create mode 100644 library/Notifications/Model/ContactgroupMember.php diff --git a/library/Notifications/Model/Channel.php b/library/Notifications/Model/Channel.php index e857e277..6f9d3ffe 100644 --- a/library/Notifications/Model/Channel.php +++ b/library/Notifications/Model/Channel.php @@ -4,6 +4,9 @@ namespace Icinga\Module\Notifications\Model; +use ipl\Orm\Behavior\BoolCast; +use ipl\Orm\Behavior\MillisecondTimestamp; +use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Relations; use ipl\Sql\Connection; @@ -26,15 +29,18 @@ public function getColumns(): array return [ 'name', 'type', - 'config' + 'config', + 'changed_at', + 'deleted' ]; } public function getColumnDefinitions() { return [ - 'name' => t('Name'), - 'type' => t('Type'), + 'name' => t('Name'), + 'type' => t('Type'), + 'changed_at' => t('Changed At') ]; } @@ -49,6 +55,12 @@ public function getDefaultSort() return ['name']; } + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + public function createRelations(Relations $relations) { $relations->hasMany('incident_history', IncidentHistory::class)->setJoinType('LEFT'); diff --git a/library/Notifications/Model/Contact.php b/library/Notifications/Model/Contact.php index 539e1728..7bbbd525 100644 --- a/library/Notifications/Model/Contact.php +++ b/library/Notifications/Model/Contact.php @@ -4,7 +4,9 @@ namespace Icinga\Module\Notifications\Model; +use ipl\Orm\Behavior\BoolCast; use Icinga\Module\Notifications\Model\Behavior\HasAddress; +use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Relations; @@ -29,15 +31,18 @@ public function getColumns(): array return [ 'full_name', 'username', - 'default_channel_id' + 'default_channel_id', + 'changed_at', + 'deleted' ]; } public function getColumnDefinitions() { return [ - 'full_name' => t('Full Name'), - 'username' => t('Username') + 'full_name' => t('Full Name'), + 'username' => t('Username'), + 'changed_at' => t('Changed At') ]; } @@ -46,9 +51,11 @@ public function getSearchColumns() return ['full_name']; } - public function createBehaviors(Behaviors $behaviors) + public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new HasAddress()); + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); } public function getDefaultSort() diff --git a/library/Notifications/Model/ContactAddress.php b/library/Notifications/Model/ContactAddress.php index 14ebd705..cc05ffd7 100644 --- a/library/Notifications/Model/ContactAddress.php +++ b/library/Notifications/Model/ContactAddress.php @@ -4,6 +4,9 @@ namespace Icinga\Module\Notifications\Model; +use ipl\Orm\Behavior\BoolCast; +use ipl\Orm\Behavior\MillisecondTimestamp; +use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Relations; @@ -24,10 +27,18 @@ public function getColumns(): array return [ 'contact_id', 'type', - 'address' + 'address', + 'changed_at', + 'deleted' ]; } + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + public function createRelations(Relations $relations) { $relations->belongsTo('contact', Contact::class); diff --git a/library/Notifications/Model/Contactgroup.php b/library/Notifications/Model/Contactgroup.php index aac6ab6d..5c18087c 100644 --- a/library/Notifications/Model/Contactgroup.php +++ b/library/Notifications/Model/Contactgroup.php @@ -4,6 +4,9 @@ namespace Icinga\Module\Notifications\Model; +use ipl\Orm\Behavior\BoolCast; +use ipl\Orm\Behavior\MillisecondTimestamp; +use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Query; use ipl\Orm\Relations; @@ -33,7 +36,9 @@ public function getKeyName(): string public function getColumns(): array { return [ - 'name' + 'name', + 'changed_at', + 'deleted' ]; } @@ -47,11 +52,18 @@ public function getSearchColumns(): array return ['name']; } - public function createRelations(Relations $relations): void + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + + public function createRelations(Relations $relations) { $relations->hasMany('rule_escalation_recipient', RuleEscalationRecipient::class) ->setJoinType('LEFT'); $relations->hasMany('incident_history', IncidentHistory::class); + $relations->hasMany('contactgroup_member', ContactgroupMember::class); $relations ->belongsToMany('contact', Contact::class) ->through('contactgroup_member') diff --git a/library/Notifications/Model/ContactgroupMember.php b/library/Notifications/Model/ContactgroupMember.php new file mode 100644 index 00000000..46fbfe78 --- /dev/null +++ b/library/Notifications/Model/ContactgroupMember.php @@ -0,0 +1,59 @@ +add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + + public function createRelations(Relations $relations): void + { + $relations->belongsTo('contactgroup', Contactgroup::class); + $relations->belongsTo('contact', Contact::class); + } +} diff --git a/library/Notifications/Model/Rotation.php b/library/Notifications/Model/Rotation.php index 9c0c4d82..0ed19e88 100644 --- a/library/Notifications/Model/Rotation.php +++ b/library/Notifications/Model/Rotation.php @@ -6,6 +6,7 @@ use DateTime; use Icinga\Util\Json; +use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Contract\RetrieveBehavior; @@ -24,6 +25,8 @@ * @property string|array $options * @property string $first_handoff * @property DateTime $actual_handoff + * @property DateTime $changed_at + * @property bool $deleted * * @property Query|Schedule $schedule * @property Query|RotationMember $member @@ -50,19 +53,22 @@ public function getColumns() 'mode', 'options', 'first_handoff', - 'actual_handoff' + 'actual_handoff', + 'changed_at', + 'deleted' ]; } public function getColumnDefinitions() { return [ - 'schedule_id' => t('Schedule'), - 'priority' => t('Priority'), - 'name' => t('Name'), - 'mode' => t('Mode'), - 'first_handoff' => t('First Handoff'), - 'actual_handoff' => t('Actual Handoff') + 'schedule_id' => t('Schedule'), + 'priority' => t('Priority'), + 'name' => t('Name'), + 'mode' => t('Mode'), + 'first_handoff' => t('First Handoff'), + 'actual_handoff' => t('Actual Handoff'), + 'changed_at' => t('Changed At') ]; } @@ -79,8 +85,10 @@ public function createRelations(Relations $relations) public function createBehaviors(Behaviors $behaviors) { $behaviors->add(new MillisecondTimestamp([ - 'actual_handoff' + 'actual_handoff', + 'changed_at' ])); + $behaviors->add(new BoolCast(['deleted'])); $behaviors->add(new class implements RetrieveBehavior { public function retrieve(Model $model): void { diff --git a/library/Notifications/Model/RotationMember.php b/library/Notifications/Model/RotationMember.php index 5f5212a5..54ce22f3 100644 --- a/library/Notifications/Model/RotationMember.php +++ b/library/Notifications/Model/RotationMember.php @@ -4,6 +4,10 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; +use ipl\Orm\Behavior\BoolCast; +use ipl\Orm\Behavior\MillisecondTimestamp; +use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Query; use ipl\Orm\Relations; @@ -16,6 +20,8 @@ * @property ?int $contact_id * @property ?int $contactgroup_id * @property int $position + * @property DateTime $changed_at + * @property bool $deleted * * @property Query|Rotation $rotation * @property Query|Contact $contact @@ -40,10 +46,18 @@ public function getColumns() 'rotation_id', 'contact_id', 'contactgroup_id', - 'position' + 'position', + 'changed_at', + 'deleted' ]; } + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + public function createRelations(Relations $relations) { $relations->belongsTo('rotation', Rotation::class); diff --git a/library/Notifications/Model/Rule.php b/library/Notifications/Model/Rule.php index 158b2228..b326fd5a 100644 --- a/library/Notifications/Model/Rule.php +++ b/library/Notifications/Model/Rule.php @@ -4,6 +4,9 @@ namespace Icinga\Module\Notifications\Model; +use ipl\Orm\Behavior\BoolCast; +use ipl\Orm\Behavior\MillisecondTimestamp; +use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Relations; @@ -25,7 +28,9 @@ public function getColumns() 'name', 'timeperiod_id', 'object_filter', - 'is_active' + 'is_active', + 'changed_at', + 'deleted' ]; } @@ -35,7 +40,8 @@ public function getColumnDefinitions() 'name' => t('Name'), 'timeperiod_id' => t('Timeperiod ID'), 'object_filter' => t('Object Filter'), - 'is_active' => t('Is Active') + 'is_active' => t('Is Active'), + 'changed_at' => t('Changed At') ]; } @@ -49,6 +55,12 @@ public function getDefaultSort() return ['name']; } + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + public function createRelations(Relations $relations) { $relations->hasMany('rule_escalation', RuleEscalation::class); diff --git a/library/Notifications/Model/RuleEscalation.php b/library/Notifications/Model/RuleEscalation.php index 78064eb6..f2120d38 100644 --- a/library/Notifications/Model/RuleEscalation.php +++ b/library/Notifications/Model/RuleEscalation.php @@ -4,6 +4,9 @@ namespace Icinga\Module\Notifications\Model; +use ipl\Orm\Behavior\BoolCast; +use ipl\Orm\Behavior\MillisecondTimestamp; +use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Relations; @@ -26,7 +29,9 @@ public function getColumns() 'position', 'condition', 'name', - 'fallback_for' + 'fallback_for', + 'changed_at', + 'deleted' ]; } @@ -37,7 +42,8 @@ public function getColumnDefinitions() 'position' => t('Position'), 'condition' => t('Condition'), 'name' => t('Name'), - 'fallback_for' => t('Fallback For') + 'fallback_for' => t('Fallback For'), + 'changed_at' => t('Changed At') ]; } @@ -51,6 +57,12 @@ public function getDefaultSort() return ['position']; } + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + public function createRelations(Relations $relations) { $relations->belongsTo('rule', Rule::class); diff --git a/library/Notifications/Model/RuleEscalationRecipient.php b/library/Notifications/Model/RuleEscalationRecipient.php index a1f4bdcb..4ff0557e 100644 --- a/library/Notifications/Model/RuleEscalationRecipient.php +++ b/library/Notifications/Model/RuleEscalationRecipient.php @@ -4,8 +4,12 @@ namespace Icinga\Module\Notifications\Model; +use ipl\Orm\Behavior\BoolCast; +use ipl\Orm\Behavior\MillisecondTimestamp; +use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Relations; +use ipl\Stdlib\Filter; class RuleEscalationRecipient extends Model { @@ -26,7 +30,9 @@ public function getColumns() 'contact_id', 'contactgroup_id', 'schedule_id', - 'channel_id' + 'channel_id', + 'changed_at', + 'deleted' ]; } @@ -37,7 +43,8 @@ public function getColumnDefinitions() 'contact_id' => t('Contact ID'), 'contactgroup_id' => t('Contactgroup ID'), 'schedule_id' => t('Schedule ID'), - 'channel_id' => t('Channel ID') + 'channel_id' => t('Channel ID'), + 'changed_at' => t('Changed At') ]; } @@ -46,6 +53,12 @@ public function getDefaultSort() return ['rule_escalation_id']; } + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + public function createRelations(Relations $relations) { $relations->belongsTo('rule_escalation', RuleEscalation::class); diff --git a/library/Notifications/Model/Schedule.php b/library/Notifications/Model/Schedule.php index ebad9c84..56921dbe 100644 --- a/library/Notifications/Model/Schedule.php +++ b/library/Notifications/Model/Schedule.php @@ -4,6 +4,9 @@ namespace Icinga\Module\Notifications\Model; +use ipl\Orm\Behavior\BoolCast; +use ipl\Orm\Behavior\MillisecondTimestamp; +use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Query; use ipl\Orm\Relations; @@ -31,13 +34,18 @@ public function getKeyName(): string public function getColumns(): array { return [ - 'name' + 'name', + 'changed_at', + 'deleted' ]; } public function getColumnDefinitions(): array { - return ['name' => t('Name')]; + return [ + 'name' => t('Name'), + 'changed_at' => t('Changed At') + ]; } public function getSearchColumns(): array @@ -50,6 +58,12 @@ public function getDefaultSort(): string return 'name'; } + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + public function createRelations(Relations $relations): void { $relations->hasMany('rotation', Rotation::class); diff --git a/library/Notifications/Model/Source.php b/library/Notifications/Model/Source.php index 823c6715..d3c46a7e 100644 --- a/library/Notifications/Model/Source.php +++ b/library/Notifications/Model/Source.php @@ -4,6 +4,9 @@ namespace Icinga\Module\Notifications\Model; +use ipl\Orm\Behavior\BoolCast; +use ipl\Orm\Behavior\MillisecondTimestamp; +use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Relations; use ipl\Web\Widget\IcingaIcon; @@ -20,6 +23,8 @@ * @property ?string $icinga2_ca_pem * @property ?string $icinga2_common_name * @property string $icinga2_insecure_tls + * @property int $changed_at + * @property bool $deleted */ class Source extends Model { @@ -47,15 +52,18 @@ public function getColumns() 'icinga2_auth_pass', 'icinga2_ca_pem', 'icinga2_common_name', - 'icinga2_insecure_tls' + 'icinga2_insecure_tls', + 'changed_at', + 'deleted' ]; } public function getColumnDefinitions() { return [ - 'type' => t('Type'), - 'name' => t('Name') + 'type' => t('Type'), + 'name' => t('Name'), + 'changed_at' => t('Changed At') ]; } @@ -69,6 +77,12 @@ public function getDefaultSort() return 'source.name'; } + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + public function createRelations(Relations $relations) { $relations->hasMany('object', Objects::class); diff --git a/library/Notifications/Model/Timeperiod.php b/library/Notifications/Model/Timeperiod.php index 28be50ca..445e2856 100644 --- a/library/Notifications/Model/Timeperiod.php +++ b/library/Notifications/Model/Timeperiod.php @@ -4,6 +4,9 @@ namespace Icinga\Module\Notifications\Model; +use ipl\Orm\Behavior\BoolCast; +use ipl\Orm\Behavior\MillisecondTimestamp; +use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Query; use ipl\Orm\Relations; @@ -32,10 +35,18 @@ public function getKeyName() public function getColumns() { return [ - 'owned_by_rotation_id' + 'owned_by_schedule_id', + 'changed_at', + 'deleted' ]; } + public function createBehaviors(Behaviors $behaviors): void + { + $behaviors->add(new MillisecondTimestamp(['changed_at'])); + $behaviors->add(new BoolCast(['deleted'])); + } + public function createRelations(Relations $relations) { $relations->belongsTo('rotation', Rotation::class) diff --git a/library/Notifications/Model/TimeperiodEntry.php b/library/Notifications/Model/TimeperiodEntry.php index 36c262d8..3a3f46ae 100644 --- a/library/Notifications/Model/TimeperiodEntry.php +++ b/library/Notifications/Model/TimeperiodEntry.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Notifications\Model; use DateTime; +use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; @@ -48,7 +49,9 @@ public function getColumns() 'end_time', 'until_time', 'timezone', - 'rrule' + 'rrule', + 'changed_at', + 'deleted' ]; } @@ -62,8 +65,10 @@ public function createBehaviors(Behaviors $behaviors) $behaviors->add(new MillisecondTimestamp([ 'start_time', 'end_time', - 'until_time' + 'until_time', + 'changed_at' ])); + $behaviors->add(new BoolCast(['deleted'])); } public function createRelations(Relations $relations) From 7dce156e229ffb06e69a7835fd230e3ba3f4bb8b Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 30 Apr 2024 16:22:09 +0200 Subject: [PATCH 02/34] Models: Add phpDoc for properties and return type to methods --- .../Model/AvailableChannelType.php | 12 ++++++- library/Notifications/Model/Channel.php | 23 +++++++++--- library/Notifications/Model/Contact.php | 24 ++++++++++--- .../Notifications/Model/ContactAddress.php | 14 +++++++- library/Notifications/Model/Contactgroup.php | 9 +++-- library/Notifications/Model/Event.php | 18 +++++----- library/Notifications/Model/Incident.php | 18 +++++----- .../Notifications/Model/IncidentContact.php | 19 +++++++--- .../Notifications/Model/IncidentHistory.php | 16 ++++----- .../Notifications/Model/ObjectExtraTag.php | 15 ++++---- library/Notifications/Model/ObjectIdTag.php | 4 +-- library/Notifications/Model/Objects.php | 14 ++++---- library/Notifications/Model/Rotation.php | 12 +++---- .../Notifications/Model/RotationMember.php | 8 ++--- library/Notifications/Model/Rule.php | 29 +++++++++++---- .../Notifications/Model/RuleEscalation.php | 32 +++++++++++++---- .../Model/RuleEscalationRecipient.php | 36 ++++++++++++++----- library/Notifications/Model/Schedule.php | 3 ++ library/Notifications/Model/Source.php | 20 ++++++----- library/Notifications/Model/Timeperiod.php | 11 +++--- .../Notifications/Model/TimeperiodEntry.php | 17 ++++----- 21 files changed, 242 insertions(+), 112 deletions(-) diff --git a/library/Notifications/Model/AvailableChannelType.php b/library/Notifications/Model/AvailableChannelType.php index 84fa77df..a2d5cae4 100644 --- a/library/Notifications/Model/AvailableChannelType.php +++ b/library/Notifications/Model/AvailableChannelType.php @@ -5,8 +5,18 @@ namespace Icinga\Module\Notifications\Model; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; +/** + * @property string $type + * @property string $name + * @property string $version + * @property string $author_name + * @property string $config_attrs + * + * @property Query|Channel $channel + */ class AvailableChannelType extends Model { public function getTableName(): string @@ -14,7 +24,7 @@ public function getTableName(): string return 'available_channel_type'; } - public function getKeyName() + public function getKeyName(): string { return 'type'; } diff --git a/library/Notifications/Model/Channel.php b/library/Notifications/Model/Channel.php index 6f9d3ffe..4c1108d8 100644 --- a/library/Notifications/Model/Channel.php +++ b/library/Notifications/Model/Channel.php @@ -4,14 +4,29 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; use ipl\Sql\Connection; use ipl\Web\Widget\Icon; +/** + * @property int $id + * @property string $name + * @property string $type + * @property string $config + * @property DateTime $changed_at + * @property bool $deleted + * + * @property Query|IncidentHistory $incident_history + * @property Query|RuleEscalationRecipient $rule_escalation_recipient + * @property Query|Contact $contact + * @property Query|AvailableChannelType $available_channel_type + */ class Channel extends Model { public function getTableName(): string @@ -35,7 +50,7 @@ public function getColumns(): array ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'name' => t('Name'), @@ -44,13 +59,13 @@ public function getColumnDefinitions() ]; } - public function getSearchColumns() + public function getSearchColumns(): array { return ['name']; } - public function getDefaultSort() + public function getDefaultSort(): array { return ['name']; } @@ -61,7 +76,7 @@ public function createBehaviors(Behaviors $behaviors): void $behaviors->add(new BoolCast(['deleted'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->hasMany('incident_history', IncidentHistory::class)->setJoinType('LEFT'); $relations->hasMany('rule_escalation_recipient', RuleEscalationRecipient::class)->setJoinType('LEFT'); diff --git a/library/Notifications/Model/Contact.php b/library/Notifications/Model/Contact.php index 7bbbd525..17f19e3e 100644 --- a/library/Notifications/Model/Contact.php +++ b/library/Notifications/Model/Contact.php @@ -4,15 +4,31 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; use ipl\Orm\Behavior\BoolCast; use Icinga\Module\Notifications\Model\Behavior\HasAddress; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; /** * @property int $id + * @property string $full_name + * @property ?string $username + * @property int $default_channel_id + * @property string $color + * @property DateTime $changed_at + * @property bool $deleted + * + * @property Query|Channel $channel + * @property Query|Incident $incident + * @property Query|IncidentContact $incident_contact + * @property Query|IncidentHistory $incident_history + * @property Query|RotationMember $rotation_member + * @property Query|ContactAddress $contact_address + * @property Query|RuleEscalationRecipient $rule_escalation_recipient */ class Contact extends Model { @@ -37,7 +53,7 @@ public function getColumns(): array ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'full_name' => t('Full Name'), @@ -46,7 +62,7 @@ public function getColumnDefinitions() ]; } - public function getSearchColumns() + public function getSearchColumns(): array { return ['full_name']; } @@ -58,12 +74,12 @@ public function createBehaviors(Behaviors $behaviors): void $behaviors->add(new BoolCast(['deleted'])); } - public function getDefaultSort() + public function getDefaultSort(): array { return ['full_name']; } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('channel', Channel::class) ->setCandidateKey('default_channel_id'); diff --git a/library/Notifications/Model/ContactAddress.php b/library/Notifications/Model/ContactAddress.php index cc05ffd7..712a2afd 100644 --- a/library/Notifications/Model/ContactAddress.php +++ b/library/Notifications/Model/ContactAddress.php @@ -4,12 +4,24 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; +/** + * @property int $id + * @property int $contact_id + * @property string $type + * @property string $address + * @property DateTime $changed_at + * @property bool $deleted + * + * @property Query|Contact $contact + */ class ContactAddress extends Model { public function getTableName(): string @@ -39,7 +51,7 @@ public function createBehaviors(Behaviors $behaviors): void $behaviors->add(new BoolCast(['deleted'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('contact', Contact::class); } diff --git a/library/Notifications/Model/Contactgroup.php b/library/Notifications/Model/Contactgroup.php index 5c18087c..4eba6e3b 100644 --- a/library/Notifications/Model/Contactgroup.php +++ b/library/Notifications/Model/Contactgroup.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; @@ -14,8 +15,10 @@ /** * Contact group * - * @param int $id - * @param string $name + * @property string $id + * @property string $name + * @property DateTime $changed_at + * @property bool $deleted * * @property Query | Contact $contact * @property Query | RuleEscalationRecipient $rule_escalation_recipient @@ -58,7 +61,7 @@ public function createBehaviors(Behaviors $behaviors): void $behaviors->add(new BoolCast(['deleted'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->hasMany('rule_escalation_recipient', RuleEscalationRecipient::class) ->setJoinType('LEFT'); diff --git a/library/Notifications/Model/Event.php b/library/Notifications/Model/Event.php index 56aeacb4..a59bfef6 100644 --- a/library/Notifications/Model/Event.php +++ b/library/Notifications/Model/Event.php @@ -37,17 +37,17 @@ */ class Event extends Model { - public function getTableName() + public function getTableName(): string { return 'event'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'time', @@ -61,7 +61,7 @@ public function getColumns() ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'time' => t('Received On'), @@ -75,17 +75,17 @@ public function getColumnDefinitions() ]; } - public function getSearchColumns() + public function getSearchColumns(): array { return ['object.name']; } - public function getDefaultSort() + public function getDefaultSort(): string { return 'event.time'; } - public static function on(Connection $db) + public static function on(Connection $db): Query { $query = parent::on($db); @@ -98,14 +98,14 @@ public static function on(Connection $db) return $query; } - public function createBehaviors(Behaviors $behaviors) + public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new MillisecondTimestamp(['time'])); $behaviors->add(new Binary(['object_id'])); $behaviors->add(new BoolCast(['mute'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('object', Objects::class)->setJoinType('LEFT'); diff --git a/library/Notifications/Model/Incident.php b/library/Notifications/Model/Incident.php index 408786b2..ddac10cb 100644 --- a/library/Notifications/Model/Incident.php +++ b/library/Notifications/Model/Incident.php @@ -34,17 +34,17 @@ */ class Incident extends Model { - public function getTableName() + public function getTableName(): string { return 'incident'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'object_id', @@ -54,7 +54,7 @@ public function getColumns() ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'object_id' => t('Object Id'), @@ -64,17 +64,17 @@ public function getColumnDefinitions() ]; } - public function getSearchColumns() + public function getSearchColumns(): array { return ['object.name']; } - public function getDefaultSort() + public function getDefaultSort(): array { return ['incident.severity desc, incident.started_at']; } - public static function on(Connection $db) + public static function on(Connection $db): Query { $query = parent::on($db); @@ -87,7 +87,7 @@ public static function on(Connection $db) return $query; } - public function createBehaviors(Behaviors $behaviors) + public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new Binary(['object_id'])); $behaviors->add(new MillisecondTimestamp([ @@ -96,7 +96,7 @@ public function createBehaviors(Behaviors $behaviors) ])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('object', Objects::class); diff --git a/library/Notifications/Model/IncidentContact.php b/library/Notifications/Model/IncidentContact.php index 22b63ca4..317528d1 100644 --- a/library/Notifications/Model/IncidentContact.php +++ b/library/Notifications/Model/IncidentContact.php @@ -5,21 +5,30 @@ namespace Icinga\Module\Notifications\Model; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; +/** + * @property int $incident_id + * @property int $contact_id + * @property string $role + * + * @property Query|Incident $incident + * @property Query|Contact $contact + */ class IncidentContact extends Model { - public function getTableName() + public function getTableName(): string { return 'incident_contact'; } - public function getKeyName() + public function getKeyName(): array { return ['incident_id', 'contact_id']; } - public function getColumns() + public function getColumns(): array { return [ 'incident_id', @@ -28,7 +37,7 @@ public function getColumns() ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'incident_id' => t('Incident Id'), @@ -37,7 +46,7 @@ public function getColumnDefinitions() ]; } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('incident', Incident::class); $relations->belongsTo('contact', Contact::class); diff --git a/library/Notifications/Model/IncidentHistory.php b/library/Notifications/Model/IncidentHistory.php index 320eb7cd..b76ebbba 100644 --- a/library/Notifications/Model/IncidentHistory.php +++ b/library/Notifications/Model/IncidentHistory.php @@ -45,17 +45,17 @@ */ class IncidentHistory extends Model { - public function getTableName() + public function getTableName(): string { return 'incident_history'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'incident_id', @@ -78,7 +78,7 @@ public function getColumns() ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'incident_id' => t('Incident Id'), @@ -98,17 +98,17 @@ public function getColumnDefinitions() ]; } - public function createBehaviors(Behaviors $behaviors) + public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new MillisecondTimestamp(['time', 'sent_at'])); } - public function getDefaultSort() + public function getDefaultSort(): array { return ['incident_history.time desc, incident_history.type desc']; } - public static function on(Connection $db) + public static function on(Connection $db): Query { $query = parent::on($db); @@ -121,7 +121,7 @@ public static function on(Connection $db) return $query; } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('incident', Incident::class); diff --git a/library/Notifications/Model/ObjectExtraTag.php b/library/Notifications/Model/ObjectExtraTag.php index fc172ab6..df7beb73 100644 --- a/library/Notifications/Model/ObjectExtraTag.php +++ b/library/Notifications/Model/ObjectExtraTag.php @@ -7,28 +7,31 @@ use ipl\Orm\Behavior\Binary; use ipl\Orm\Behaviors; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; /** * ObjectExtraTag database model * - * @property int $object_id + * @property string $object_id * @property string $tag * @property string $value + * + * @property Query|Objects $object */ class ObjectExtraTag extends Model { - public function getTableName() + public function getTableName(): string { return 'object_extra_tag'; } - public function getKeyName() + public function getKeyName(): array { return ['object_id', 'tag']; } - public function getColumns() + public function getColumns(): array { return [ 'object_id', @@ -37,12 +40,12 @@ public function getColumns() ]; } - public function createBehaviors(Behaviors $behaviors) + public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new Binary(['object_id'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('object', Objects::class); } diff --git a/library/Notifications/Model/ObjectIdTag.php b/library/Notifications/Model/ObjectIdTag.php index b0d33621..8a26ea79 100644 --- a/library/Notifications/Model/ObjectIdTag.php +++ b/library/Notifications/Model/ObjectIdTag.php @@ -12,7 +12,7 @@ /** * ObjectIdTag database model * - * @property int $object_id + * @property string $object_id * @property string $tag * @property string $value */ @@ -23,7 +23,7 @@ public function getTableName(): string return 'object_id_tag'; } - public function getKeyName() + public function getKeyName(): array { return ['object_id', 'tag']; } diff --git a/library/Notifications/Model/Objects.php b/library/Notifications/Model/Objects.php index 68d43e3b..f06f5529 100644 --- a/library/Notifications/Model/Objects.php +++ b/library/Notifications/Model/Objects.php @@ -34,17 +34,17 @@ */ class Objects extends Model { - public function getTableName() + public function getTableName(): string { return 'object'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'source_id', @@ -57,7 +57,7 @@ public function getColumns() /** * @return string[] */ - public function getSearchColumns() + public function getSearchColumns(): array { return ['object_id_tag.tag', 'object_id_tag.value']; } @@ -65,18 +65,18 @@ public function getSearchColumns() /** * @return string */ - public function getDefaultSort() + public function getDefaultSort(): string { return 'object.name'; } - public function createBehaviors(Behaviors $behaviors) + public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new Binary(['id'])); $behaviors->add(new IdTagAggregator()); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->hasMany('event', Event::class); $relations->hasMany('incident', Incident::class); diff --git a/library/Notifications/Model/Rotation.php b/library/Notifications/Model/Rotation.php index 0ed19e88..4a4f51ce 100644 --- a/library/Notifications/Model/Rotation.php +++ b/library/Notifications/Model/Rotation.php @@ -34,17 +34,17 @@ */ class Rotation extends Model { - public function getTableName() + public function getTableName(): string { return 'rotation'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'schedule_id', @@ -59,7 +59,7 @@ public function getColumns() ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'schedule_id' => t('Schedule'), @@ -72,7 +72,7 @@ public function getColumnDefinitions() ]; } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('schedule', Schedule::class); @@ -82,7 +82,7 @@ public function createRelations(Relations $relations) ->setForeignKey('owned_by_rotation_id'); } - public function createBehaviors(Behaviors $behaviors) + public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new MillisecondTimestamp([ 'actual_handoff', diff --git a/library/Notifications/Model/RotationMember.php b/library/Notifications/Model/RotationMember.php index 54ce22f3..c1ac0b74 100644 --- a/library/Notifications/Model/RotationMember.php +++ b/library/Notifications/Model/RotationMember.php @@ -30,17 +30,17 @@ */ class RotationMember extends Model { - public function getTableName() + public function getTableName(): string { return 'rotation_member'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'rotation_id', @@ -58,7 +58,7 @@ public function createBehaviors(Behaviors $behaviors): void $behaviors->add(new BoolCast(['deleted'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('rotation', Rotation::class); $relations->belongsTo('contact', Contact::class) diff --git a/library/Notifications/Model/Rule.php b/library/Notifications/Model/Rule.php index b326fd5a..dff07f5e 100644 --- a/library/Notifications/Model/Rule.php +++ b/library/Notifications/Model/Rule.php @@ -4,25 +4,40 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; +/** + * @property int $id + * @property string $name + * @property int $timeperiod_id + * @property ?string $object_filter + * @property string $is_active + * @property DateTime $changed_at + * @property bool $deleted + * + * @property Query|RuleEscalation $rule_escalation + * @property Query|Incident $incident + * @property Query|IncidentHistory $incident_history + */ class Rule extends Model { - public function getTableName() + public function getTableName(): string { return 'rule'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'name', @@ -34,7 +49,7 @@ public function getColumns() ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'name' => t('Name'), @@ -45,12 +60,12 @@ public function getColumnDefinitions() ]; } - public function getSearchColumns() + public function getSearchColumns(): array { return ['name']; } - public function getDefaultSort() + public function getDefaultSort(): array { return ['name']; } @@ -61,7 +76,7 @@ public function createBehaviors(Behaviors $behaviors): void $behaviors->add(new BoolCast(['deleted'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->hasMany('rule_escalation', RuleEscalation::class); diff --git a/library/Notifications/Model/RuleEscalation.php b/library/Notifications/Model/RuleEscalation.php index f2120d38..54b74bbb 100644 --- a/library/Notifications/Model/RuleEscalation.php +++ b/library/Notifications/Model/RuleEscalation.php @@ -4,25 +4,42 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; use ipl\Orm\Relations; +/** + * @property int $id + * @property int $rule_id + * @property int $position + * @property ?string $condition + * @property ?string $name + * @property ?string $fallback_for + * @property DateTime $changed_at + * @property bool $deleted + * + * @property Rule $rule + * @property Incident $incident + * @property Contact $contact + * @property RuleEscalationRecipient $rule_escalation_recipient + * @property IncidentHistory $incident_history + */ class RuleEscalation extends Model { - public function getTableName() + public function getTableName(): string { return 'rule_escalation'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'rule_id', @@ -35,7 +52,7 @@ public function getColumns() ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'rule_id' => t('Rule ID'), @@ -47,23 +64,24 @@ public function getColumnDefinitions() ]; } - public function getSearchColumns() + public function getSearchColumns(): array { return ['name']; } - public function getDefaultSort() + public function getDefaultSort(): array { return ['position']; } + public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new MillisecondTimestamp(['changed_at'])); $behaviors->add(new BoolCast(['deleted'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('rule', Rule::class); diff --git a/library/Notifications/Model/RuleEscalationRecipient.php b/library/Notifications/Model/RuleEscalationRecipient.php index 4ff0557e..71cfb90f 100644 --- a/library/Notifications/Model/RuleEscalationRecipient.php +++ b/library/Notifications/Model/RuleEscalationRecipient.php @@ -4,26 +4,44 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; -use ipl\Stdlib\Filter; + +/** + * @property int $id + * @property int $rule_escalation_id + * @property int $contact_id + * @property int $contactgroup_id + * @property int $schedule_id + * @property int $channel_id + * @property DateTime $changed_at + * @property bool $deleted + * + * @property Query|RuleEscalation $rule_escalation + * @property Query|Contact $contact + * @property Query|Schedule $schedule + * @property Query|Contactgroup $contactgroup + * @property Query|Channel $channel + */ class RuleEscalationRecipient extends Model { - public function getTableName() + public function getTableName(): string { return 'rule_escalation_recipient'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'rule_escalation_id', @@ -36,7 +54,7 @@ public function getColumns() ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'rule_escalation_id' => t('Rule Escalation ID'), @@ -48,7 +66,7 @@ public function getColumnDefinitions() ]; } - public function getDefaultSort() + public function getDefaultSort(): array { return ['rule_escalation_id']; } @@ -59,7 +77,7 @@ public function createBehaviors(Behaviors $behaviors): void $behaviors->add(new BoolCast(['deleted'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('rule_escalation', RuleEscalation::class); $relations->belongsTo('contact', Contact::class); @@ -71,9 +89,9 @@ public function createRelations(Relations $relations) /** * Get the recipient model * - * @return ?Model + * @return Contact|Contactgroup|Schedule|null */ - public function getRecipient() + public function getRecipient(): ?Model { $recipientModel = null; if ($this->contact_id) { diff --git a/library/Notifications/Model/Schedule.php b/library/Notifications/Model/Schedule.php index 56921dbe..b3e948d7 100644 --- a/library/Notifications/Model/Schedule.php +++ b/library/Notifications/Model/Schedule.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; @@ -14,6 +15,8 @@ /** * @property int $id * @property string $name + * @property DateTime $changed_at + * @property bool $deleted * * @property Rotation|Query $rotation * @property RuleEscalationRecipient|Query $rule_escalation_recipient diff --git a/library/Notifications/Model/Source.php b/library/Notifications/Model/Source.php index d3c46a7e..973bbb76 100644 --- a/library/Notifications/Model/Source.php +++ b/library/Notifications/Model/Source.php @@ -4,10 +4,12 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; use ipl\Web\Widget\IcingaIcon; use ipl\Web\Widget\Icon; @@ -23,25 +25,27 @@ * @property ?string $icinga2_ca_pem * @property ?string $icinga2_common_name * @property string $icinga2_insecure_tls - * @property int $changed_at + * @property DateTime $changed_at * @property bool $deleted + * + * @property Query|Objects $object */ class Source extends Model { /** @var string The type name used by Icinga sources */ public const ICINGA_TYPE_NAME = 'icinga2'; - public function getTableName() + public function getTableName(): string { return 'source'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'type', @@ -58,7 +62,7 @@ public function getColumns() ]; } - public function getColumnDefinitions() + public function getColumnDefinitions(): array { return [ 'type' => t('Type'), @@ -67,12 +71,12 @@ public function getColumnDefinitions() ]; } - public function getSearchColumns() + public function getSearchColumns(): array { return ['type']; } - public function getDefaultSort() + public function getDefaultSort(): string { return 'source.name'; } @@ -83,7 +87,7 @@ public function createBehaviors(Behaviors $behaviors): void $behaviors->add(new BoolCast(['deleted'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->hasMany('object', Objects::class); } diff --git a/library/Notifications/Model/Timeperiod.php b/library/Notifications/Model/Timeperiod.php index 445e2856..9cbddf6f 100644 --- a/library/Notifications/Model/Timeperiod.php +++ b/library/Notifications/Model/Timeperiod.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Notifications\Model; +use DateTime; use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; @@ -16,23 +17,25 @@ * * @property int $id * @property ?int $owned_by_rotation_id + * @property DateTime $changed_at + * @property bool $deleted * * @property Query|Rotation $rotation * @property Query|TimeperiodEntry $timeperiod_entry */ class Timeperiod extends Model { - public function getTableName() + public function getTableName(): string { return 'timeperiod'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'owned_by_schedule_id', @@ -47,7 +50,7 @@ public function createBehaviors(Behaviors $behaviors): void $behaviors->add(new BoolCast(['deleted'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('rotation', Rotation::class) ->setCandidateKey('owned_by_rotation_id') diff --git a/library/Notifications/Model/TimeperiodEntry.php b/library/Notifications/Model/TimeperiodEntry.php index 3a3f46ae..ee8d037e 100644 --- a/library/Notifications/Model/TimeperiodEntry.php +++ b/library/Notifications/Model/TimeperiodEntry.php @@ -9,6 +9,7 @@ use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; use Recurr\Frequency; use Recurr\Rule; @@ -25,22 +26,22 @@ * @property string $timezone * @property ?string $rrule * - * @property Timeperiod $timeperiod - * @property RotationMember $member + * @property Query|Timeperiod $timeperiod + * @property Query|RotationMember $member */ class TimeperiodEntry extends Model { - public function getTableName() + public function getTableName(): string { return 'timeperiod_entry'; } - public function getKeyName() + public function getKeyName(): string { return 'id'; } - public function getColumns() + public function getColumns(): array { return [ 'timeperiod_id', @@ -55,12 +56,12 @@ public function getColumns() ]; } - public function getDefaultSort() + public function getDefaultSort(): array { return ['start_time asc', 'end_time asc']; } - public function createBehaviors(Behaviors $behaviors) + public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new MillisecondTimestamp([ 'start_time', @@ -71,7 +72,7 @@ public function createBehaviors(Behaviors $behaviors) $behaviors->add(new BoolCast(['deleted'])); } - public function createRelations(Relations $relations) + public function createRelations(Relations $relations): void { $relations->belongsTo('timeperiod', Timeperiod::class); $relations->belongsTo('member', RotationMember::class); From 1059476383beddbb93abd76f1a51501b1ce6e3ec Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 2 May 2024 09:05:07 +0200 Subject: [PATCH 03/34] Controller: Add missing handeling for undefined `id` --- application/controllers/ChannelController.php | 13 ++++++++----- application/controllers/ContactController.php | 11 +++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/application/controllers/ChannelController.php b/application/controllers/ChannelController.php index 163fcc26..7ff61d82 100644 --- a/application/controllers/ChannelController.php +++ b/application/controllers/ChannelController.php @@ -26,12 +26,15 @@ public function init() public function indexAction() { - $channel = Channel::on($this->db); $channelId = $this->params->getRequired('id'); - - $channel->filter(Filter::equal('id', $channelId)); - - $channel = $channel->first(); + $query = Channel::on($this->db) + ->filter(Filter::equal('id', $channelId)); + + /** @var Channel $channel */ + $channel = $query->first(); + if ($channel === null) { + $this->httpNotFound(t('Channel not found')); + } $this->addTitleTab(sprintf(t('Channel: %s'), $channel->name)); diff --git a/application/controllers/ContactController.php b/application/controllers/ContactController.php index 13f511d7..ca754cb2 100644 --- a/application/controllers/ContactController.php +++ b/application/controllers/ContactController.php @@ -27,12 +27,15 @@ public function init() public function indexAction() { - $contact = Contact::on($this->db); $contactId = $this->params->getRequired('id'); + $query = Contact::on($this->db) + ->filter(Filter::equal('id', $contactId)); - $contact->filter(Filter::equal('id', $contactId)); - - $contact = $contact->first(); + /** @var Contact $contact */ + $contact = $query->first(); + if ($contact === null) { + $this->httpNotFound(t('Contact not found')); + } $this->addTitleTab(sprintf(t('Contact: %s'), $contact->full_name)); From 807f0255cf458d6a87883e0783df75187793b8d4 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 2 May 2024 11:18:17 +0200 Subject: [PATCH 04/34] Consider new `deleted` and `changed_at` column --- application/controllers/ChannelController.php | 5 +- .../controllers/ChannelsController.php | 3 +- application/controllers/ContactController.php | 5 +- .../controllers/ContactGroupController.php | 29 +++-- .../controllers/ContactGroupsController.php | 3 +- .../controllers/ContactsController.php | 3 +- .../controllers/EventRuleController.php | 5 +- .../controllers/EventRulesController.php | 3 +- .../controllers/IncidentController.php | 5 +- .../controllers/ScheduleController.php | 33 ++++- .../controllers/SchedulesController.php | 3 +- application/controllers/SourceController.php | 5 +- application/controllers/SourcesController.php | 4 +- application/forms/ChannelForm.php | 7 +- application/forms/ContactGroupForm.php | 77 ++++++++---- application/forms/EscalationRecipientForm.php | 7 +- application/forms/MoveRotationForm.php | 19 ++- application/forms/RotationConfigForm.php | 117 +++++++++++++----- application/forms/ScheduleForm.php | 15 ++- application/forms/SourceForm.php | 7 +- .../Model/Behavior/HasAddress.php | 10 +- library/Notifications/Model/Channel.php | 6 +- library/Notifications/Model/Contact.php | 2 + .../Model/RuleEscalationRecipient.php | 7 +- .../Notifications/Web/Form/ContactForm.php | 25 ++-- .../Widget/MemberSuggestions.php | 5 +- .../Widget/RecipientSuggestions.php | 16 ++- library/Notifications/Widget/Schedule.php | 12 +- .../Widget/Timeline/Rotation.php | 5 + 29 files changed, 337 insertions(+), 106 deletions(-) diff --git a/application/controllers/ChannelController.php b/application/controllers/ChannelController.php index 7ff61d82..daecea8e 100644 --- a/application/controllers/ChannelController.php +++ b/application/controllers/ChannelController.php @@ -28,7 +28,10 @@ public function indexAction() { $channelId = $this->params->getRequired('id'); $query = Channel::on($this->db) - ->filter(Filter::equal('id', $channelId)); + ->filter(Filter::all( + Filter::equal('id', $channelId), + Filter::equal('deleted', 'n') + )); /** @var Channel $channel */ $channel = $query->first(); diff --git a/application/controllers/ChannelsController.php b/application/controllers/ChannelsController.php index b55d1eda..4ace1d89 100644 --- a/application/controllers/ChannelsController.php +++ b/application/controllers/ChannelsController.php @@ -43,7 +43,8 @@ public function init() public function indexAction() { - $channels = Channel::on($this->db); + $channels = Channel::on($this->db) + ->filter(Filter::equal('deleted', 'n')); $this->mergeTabs($this->Module()->getConfigTabs()); $this->getTabs()->activate('channels'); diff --git a/application/controllers/ContactController.php b/application/controllers/ContactController.php index ca754cb2..c275bab4 100644 --- a/application/controllers/ContactController.php +++ b/application/controllers/ContactController.php @@ -29,7 +29,10 @@ public function indexAction() { $contactId = $this->params->getRequired('id'); $query = Contact::on($this->db) - ->filter(Filter::equal('id', $contactId)); + ->filter(Filter::all( + Filter::equal('id', $contactId), + Filter::equal('deleted', 'n') + )); /** @var Contact $contact */ $contact = $query->first(); diff --git a/application/controllers/ContactGroupController.php b/application/controllers/ContactGroupController.php index 8b760ab1..c52672c7 100644 --- a/application/controllers/ContactGroupController.php +++ b/application/controllers/ContactGroupController.php @@ -7,7 +7,9 @@ use Icinga\Module\Notifications\Common\Database; use Icinga\Module\Notifications\Common\Links; use Icinga\Module\Notifications\Forms\ContactGroupForm; +use Icinga\Module\Notifications\Model\Contact; use Icinga\Module\Notifications\Model\Contactgroup; +use Icinga\Module\Notifications\Model\ContactgroupMember; use Icinga\Module\Notifications\Widget\ItemList\ContactList; use Icinga\Web\Notification; use ipl\Html\Attributes; @@ -33,7 +35,10 @@ public function indexAction(): void $query = Contactgroup::on(Database::get()) ->columns(['id', 'name']) - ->filter(Filter::equal('id', $groupId)); + ->filter(Filter::all( + Filter::equal('id', $groupId), + Filter::equal('deleted', 'n') + )); $group = $query->first(); if ($group === null) { @@ -44,7 +49,14 @@ public function indexAction(): void $this->addControl(new HtmlElement('div', new Attributes(['class' => 'header']), Text::create($group->name))); - $this->addControl($this->createPaginationControl($group->contact)); + $contacts = $group + ->contact + ->filter(Filter::all( + Filter::equal('contactgroup_member.deleted', 'n'), + Filter::equal('deleted', 'n') + )); + + $this->addControl($this->createPaginationControl($contacts)); $this->addControl($this->createLimitControl()); $this->addContent( @@ -56,7 +68,7 @@ public function indexAction(): void ))->openInModal() ); - $this->addContent(new ContactList($group->contact)); + $this->addContent(new ContactList($contacts)); $this->addTitleTab(t('Contact Group')); $this->setTitle(sprintf(t('Contact Group: %s'), $group->name)); @@ -90,12 +102,11 @@ public function editAction(): void } }) ->on(Form::ON_SUCCESS, function (ContactGroupForm $form) use ($groupId) { - if ($form->editGroup()) { - Notification::success(sprintf( - t('Successfully updated contact group %s'), - $form->getValue('group_name') - )); - } + $form->editGroup(); + Notification::success(sprintf( + t('Successfully updated contact group %s'), + $form->getValue('group_name') + )); $this->closeModalAndRefreshRemainingViews(Links::contactGroup($groupId)); }) diff --git a/application/controllers/ContactGroupsController.php b/application/controllers/ContactGroupsController.php index d37e7e29..396a85b1 100644 --- a/application/controllers/ContactGroupsController.php +++ b/application/controllers/ContactGroupsController.php @@ -39,7 +39,8 @@ public function init(): void public function indexAction(): void { - $groups = Contactgroup::on(Database::get()); + $groups = Contactgroup::on(Database::get()) + ->filter(Filter::equal('deleted', 'n')); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($groups); diff --git a/application/controllers/ContactsController.php b/application/controllers/ContactsController.php index 7a65f7be..a2454c3e 100644 --- a/application/controllers/ContactsController.php +++ b/application/controllers/ContactsController.php @@ -43,7 +43,8 @@ public function init() public function indexAction() { $contacts = Contact::on($this->db) - ->withColumns('has_email'); + ->withColumns('has_email') + ->filter(Filter::equal('deleted', 'n')); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($contacts); diff --git a/application/controllers/EventRuleController.php b/application/controllers/EventRuleController.php index f52b59ad..e4d655e2 100644 --- a/application/controllers/EventRuleController.php +++ b/application/controllers/EventRuleController.php @@ -152,7 +152,10 @@ public function fromDb(int $ruleId): array { $query = Rule::on(Database::get()) ->withoutColumns('timeperiod_id') - ->filter(Filter::equal('id', $ruleId)); + ->filter(Filter::all( + Filter::equal('id', $ruleId), + Filter::equal('deleted', 'n') + )); $rule = $query->first(); if ($rule === null) { diff --git a/application/controllers/EventRulesController.php b/application/controllers/EventRulesController.php index a04cbc55..f4b766c1 100644 --- a/application/controllers/EventRulesController.php +++ b/application/controllers/EventRulesController.php @@ -44,7 +44,8 @@ public function init() public function indexAction(): void { - $eventRules = Rule::on(Database::get()); + $eventRules = Rule::on(Database::get()) + ->filter(Filter::equal('deleted', 'n')); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($eventRules); diff --git a/application/controllers/IncidentController.php b/application/controllers/IncidentController.php index b6695aa2..186232f6 100644 --- a/application/controllers/IncidentController.php +++ b/application/controllers/IncidentController.php @@ -47,7 +47,10 @@ public function indexAction(): void $contact = Contact::on(Database::get()) ->columns('id') - ->filter(Filter::equal('username', $this->Auth()->getUser()->getUsername())) + ->filter(Filter::all( + Filter::equal('username', $this->Auth()->getUser()->getUsername()), + Filter::equal('deleted', 'n') + )) ->first(); if ($contact !== null) { diff --git a/application/controllers/ScheduleController.php b/application/controllers/ScheduleController.php index 79fc1969..5932e808 100644 --- a/application/controllers/ScheduleController.php +++ b/application/controllers/ScheduleController.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Notifications\Controllers; +use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Notifications\Common\Database; use Icinga\Module\Notifications\Common\Links; use Icinga\Module\Notifications\Forms\MoveRotationForm; @@ -26,7 +27,10 @@ public function indexAction(): void $id = (int) $this->params->getRequired('id'); $query = Schedule::on(Database::get()) - ->filter(Filter::equal('schedule.id', $id)); + ->filter(Filter::all( + Filter::equal('schedule.id', $id), + Filter::equal('schedule.deleted', 'n') + )); /** @var ?Schedule $schedule */ $schedule = $query->first(); @@ -112,6 +116,7 @@ public function addAction(): void public function addRotationAction(): void { $scheduleId = (int) $this->params->getRequired('schedule'); + $this->assertScheduleExists($scheduleId); $form = new RotationConfigForm($scheduleId, Database::get()); $form->setAction($this->getRequest()->getUrl()->setParam('showCompact')->getAbsoluteUrl()); @@ -145,6 +150,7 @@ public function editRotationAction(): void { $id = (int) $this->params->getRequired('id'); $scheduleId = (int) $this->params->getRequired('schedule'); + $this->assertScheduleExists($scheduleId); $form = new RotationConfigForm($scheduleId, Database::get()); $form->disableModeSelection(); @@ -208,4 +214,29 @@ public function suggestRecipientAction(): void $this->getDocument()->addHtml($suggestions); } + + /** + * Assert that a schedule with the given ID exists + * + * @param int $id + * + * @return void + * + * @throws HttpNotFoundException If the schedule with the given ID does not exist + */ + private function assertScheduleExists(int $id): void + { + $query = Schedule::on(Database::get()) + ->columns('1') + ->filter(Filter::all( + Filter::equal('schedule.id', $id), + Filter::equal('schedule.deleted', 'n') + )); + + /** @var ?Schedule $schedule */ + $schedule = $query->first(); + if ($schedule === null) { + $this->httpNotFound(t('Schedule not found')); + } + } } diff --git a/application/controllers/SchedulesController.php b/application/controllers/SchedulesController.php index a9af10dc..00182525 100644 --- a/application/controllers/SchedulesController.php +++ b/application/controllers/SchedulesController.php @@ -27,7 +27,8 @@ class SchedulesController extends CompatController public function indexAction(): void { - $schedules = Schedule::on(Database::get()); + $schedules = Schedule::on(Database::get()) + ->filter(Filter::equal('deleted', 'n')); $limitControl = $this->createLimitControl(); $sortControl = $this->createSortControl( diff --git a/application/controllers/SourceController.php b/application/controllers/SourceController.php index a84e9298..01b4950c 100644 --- a/application/controllers/SourceController.php +++ b/application/controllers/SourceController.php @@ -25,7 +25,10 @@ public function indexAction(): void /** @var ?Source $source */ $source = Source::on(Database::get()) - ->filter(Filter::equal('id', $sourceId)) + ->filter(Filter::all( + Filter::equal('id', $sourceId), + Filter::equal('deleted', 'n') + )) ->first(); if ($source === null) { $this->httpNotFound($this->translate('Source not found')); diff --git a/application/controllers/SourcesController.php b/application/controllers/SourcesController.php index c7fdac02..410a2a67 100644 --- a/application/controllers/SourcesController.php +++ b/application/controllers/SourcesController.php @@ -12,6 +12,7 @@ use Icinga\Web\Notification; use Icinga\Web\Widget\Tabs; use ipl\Html\ValidHtml; +use ipl\Stdlib\Filter; use ipl\Web\Common\BaseItemList; use ipl\Web\Compat\CompatController; use ipl\Web\Compat\SearchControls; @@ -33,7 +34,8 @@ public function init() public function indexAction(): void { $sources = Source::on(Database::get()) - ->columns(['id', 'type', 'name']); + ->columns(['id', 'type', 'name']) + ->filter(Filter::equal('deleted', 'n')); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($sources); diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index d1ab6f0d..83eb8966 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -170,7 +170,11 @@ public function populate($values) protected function onSuccess() { if ($this->getPressedSubmitElement()->getName() === 'delete') { - $this->db->delete('channel', ['id = ?' => $this->channelId]); + $this->db->update( + 'channel', + ['changed_at' => time() * 1000, 'deleted' => 'y'], + ['id = ?' => $this->channelId] + ); return; } @@ -192,6 +196,7 @@ function ($configItem, $key) { if ($this->channelId === null) { $this->db->insert('channel', $channel); } else { + $channel['changed_at'] = time() * 1000; $this->db->update('channel', $channel, ['id = ?' => $this->channelId]); } } diff --git a/application/forms/ContactGroupForm.php b/application/forms/ContactGroupForm.php index 575dfba3..de5a9063 100644 --- a/application/forms/ContactGroupForm.php +++ b/application/forms/ContactGroupForm.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Notifications\Forms; use Icinga\Exception\Http\HttpNotFoundException; +use Icinga\Module\Notifications\Common\Database; use Icinga\Module\Notifications\Common\Links; use Icinga\Module\Notifications\Model\Contact; use Icinga\Module\Notifications\Model\Contactgroup; @@ -12,6 +13,7 @@ use ipl\Html\FormElement\SubmitElement; use ipl\Html\HtmlDocument; use ipl\Sql\Connection; +use ipl\Sql\Select; use ipl\Stdlib\Filter; use ipl\Web\Common\CsrfCounterMeasure; use ipl\Web\Compat\CompatForm; @@ -200,25 +202,23 @@ public function addGroup(): int /** * Edit the contact group * - * @return bool False if no changes found, true otherwise + * @return void */ - public function editGroup(): bool + public function editGroup(): void { - $isUpdated = false; $values = $this->getValues(); $this->db->beginTransaction(); $storedValues = $this->fetchDbValues(); + $changedAt = time() * 1000; if ($values['group_name'] !== $storedValues['group_name']) { $this->db->update( 'contactgroup', - ['name' => $values['group_name']], + ['name' => $values['group_name'], 'changed_at' => $changedAt], ['id = ?' => $this->contactgroupId] ); - - $isUpdated = true; } $storedContacts = []; @@ -235,34 +235,52 @@ public function editGroup(): bool $toAdd = array_diff($newContacts, $storedContacts); if (! empty($toDelete)) { - $this->db->delete( - 'contactgroup_member', + $this->db->update('contactgroup_member', + ['changed_at' => $changedAt, 'deleted' => 'y'], [ - 'contactgroup_id = ?' => $this->contactgroupId, - 'contact_id IN (?)' => $toDelete + 'contactgroup_id = ?' => $this->contactgroupId, + 'contact_id IN (?)' => $toDelete ] ); - - $isUpdated = true; } if (! empty($toAdd)) { + $contactsMarkedAsDeleted = $this->db->fetchCol( + (new Select()) + ->from('contactgroup_member') + ->columns(['contact_id']) + ->where([ + 'contactgroup_id = ?' => $this->contactgroupId, + 'contact_id IN (?)' => $toAdd + ]) + ); + + $removeDeletedFlagFromIds = []; foreach ($toAdd as $contactId) { - $this->db->insert( - 'contactgroup_member', + if (in_array($contactId, $contactsMarkedAsDeleted)) { + $removeDeletedFlagFromIds[] = $contactId; + } else { + $this->db->insert( + 'contactgroup_member', + [ + 'contactgroup_id' => $this->contactgroupId, + 'contact_id' => $contactId + ] + ); + } + } + if (! empty($removeDeletedFlagFromIds)) { + $this->db->update('contactgroup_member', + ['changed_at' => $changedAt, 'deleted' => 'n'], [ - 'contactgroup_id' => $this->contactgroupId, - 'contact_id' => $contactId + 'contactgroup_id = ?' => $this->contactgroupId, + 'contact_id IN (?)' => $removeDeletedFlagFromIds ] ); } - - $isUpdated = true; } $this->db->commitTransaction(); - - return $isUpdated; } /** @@ -272,8 +290,9 @@ public function removeContactgroup(): void { $this->db->beginTransaction(); - $this->db->delete('contactgroup_member', ['contactgroup_id = ?' => $this->contactgroupId]); - $this->db->delete('contactgroup', ['id = ?' => $this->contactgroupId]); + $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; + $this->db->update('contactgroup_member', $markAsDeleted, ['contactgroup_id = ?' => $this->contactgroupId]); + $this->db->update('contactgroup', $markAsDeleted, ['id = ?' => $this->contactgroupId]); $this->db->commitTransaction(); } @@ -289,15 +308,25 @@ private function fetchDbValues(): array { $query = Contactgroup::on($this->db) ->columns(['id', 'name']) - ->filter(Filter::equal('id', $this->contactgroupId)); + ->filter(Filter::all( + Filter::equal('id', $this->contactgroupId), + Filter::equal('deleted', 'n') + )); $group = $query->first(); if ($group === null) { throw new HttpNotFoundException($this->translate('Contact group not found')); } + $contacts = Contact::on(Database::get()) + ->filter(Filter::all( + Filter::equal('contactgroup_member.contactgroup_id', $group->id), + Filter::equal('contactgroup_member.deleted', 'n'), + Filter::equal('deleted', 'n') + )); + $groupMembers = []; - foreach ($group->contact->columns('id') as $contact) { + foreach ($contacts as $contact) { $groupMembers[] = $contact->id; } diff --git a/application/forms/EscalationRecipientForm.php b/application/forms/EscalationRecipientForm.php index 006d71a8..95a54710 100644 --- a/application/forms/EscalationRecipientForm.php +++ b/application/forms/EscalationRecipientForm.php @@ -11,6 +11,7 @@ use Icinga\Module\Notifications\Model\Schedule; use ipl\Html\Contract\FormElement; use ipl\Html\Html; +use ipl\Stdlib\Filter; use ipl\Web\Widget\Icon; class EscalationRecipientForm extends BaseEscalationForm @@ -25,15 +26,15 @@ public function __construct(?int $count) protected function fetchOptions(): array { $options = []; - foreach (Contact::on(Database::get()) as $contact) { + foreach (Contact::on(Database::get())->filter(Filter::equal('deleted', 'n')) as $contact) { $options['Contacts']['contact_' . $contact->id] = $contact->full_name; } - foreach (Contactgroup::on(Database::get()) as $contactgroup) { + foreach (Contactgroup::on(Database::get())->filter(Filter::equal('deleted', 'n')) as $contactgroup) { $options['Contact Groups']['contactgroup_' . $contactgroup->id] = $contactgroup->name; } - foreach (Schedule::on(Database::get()) as $schedule) { + foreach (Schedule::on(Database::get())->filter(Filter::equal('deleted', 'n')) as $schedule) { $options['Schedules']['schedule_' . $schedule->id] = $schedule->name; } diff --git a/application/forms/MoveRotationForm.php b/application/forms/MoveRotationForm.php index e79b224b..1e85b050 100644 --- a/application/forms/MoveRotationForm.php +++ b/application/forms/MoveRotationForm.php @@ -87,7 +87,10 @@ protected function onSuccess() /** @var ?Rotation $rotation */ $rotation = Rotation::on($this->db) ->columns(['schedule_id', 'priority']) - ->filter(Filter::equal('id', $rotationId)) + ->filter(Filter::all( + Filter::equal('id', $rotationId), + Filter::equal('deleted', 'n') + )) ->first(); if ($rotation === null) { throw new HttpNotFoundException('Rotation not found'); @@ -100,8 +103,9 @@ protected function onSuccess() $this->scheduleId = $rotation->schedule_id; + $changedAt = time() * 1000; // Free up the current priority used by the rotation in question - $this->db->update('rotation', ['priority' => 9999], ['id = ?' => $rotationId]); + $this->db->update('rotation', ['priority' => null, 'deleted' => 'y'], ['id = ?' => $rotationId]); // Update the priorities of the rotations that are affected by the move if ($newPriority < $rotation->priority) { @@ -110,6 +114,7 @@ protected function onSuccess() ->columns('id') ->from('rotation') ->where([ + 'deleted = ?' => 'n', 'schedule_id = ?' => $rotation->schedule_id, 'priority >= ?' => $newPriority, 'priority < ?' => $rotation->priority @@ -119,7 +124,7 @@ protected function onSuccess() foreach ($affectedRotations as $rotation) { $this->db->update( 'rotation', - ['priority' => new Expression('priority + 1')], + ['priority' => new Expression('priority + 1'), 'changed_at' => $changedAt], ['id = ?' => $rotation->id] ); } @@ -129,6 +134,7 @@ protected function onSuccess() ->columns('id') ->from('rotation') ->where([ + 'deleted = ?' => 'n', 'schedule_id = ?' => $rotation->schedule_id, 'priority > ?' => $rotation->priority, 'priority <= ?' => $newPriority @@ -138,14 +144,17 @@ protected function onSuccess() foreach ($affectedRotations as $rotation) { $this->db->update( 'rotation', - ['priority' => new Expression('priority - 1')], + ['priority' => new Expression('priority - 1'), 'changed_at' => $changedAt], ['id = ?' => $rotation->id] ); } } // Now insert the rotation at the new priority - $this->db->update('rotation', ['priority' => $newPriority], ['id = ?' => $rotationId]); + $this->db->update('rotation', + ['priority' => $newPriority, 'changed_at' => $changedAt, 'deleted' => 'n'], + ['id = ?' => $rotationId] + ); if ($transactionStarted) { $this->db->commitTransaction(); diff --git a/application/forms/RotationConfigForm.php b/application/forms/RotationConfigForm.php index 5ef25954..44d85aa0 100644 --- a/application/forms/RotationConfigForm.php +++ b/application/forms/RotationConfigForm.php @@ -201,7 +201,10 @@ public function loadRotation(int $rotationId): self { /** @var ?Rotation $rotation */ $rotation = Rotation::on($this->db) - ->filter(Filter::equal('id', $rotationId)) + ->filter(Filter::all( + Filter::equal('id', $rotationId), + Filter::equal('deleted', 'n') + )) ->first(); if ($rotation === null) { throw new HttpNotFoundException($this->translate('Rotation not found')); @@ -251,6 +254,8 @@ public function loadRotation(int $rotationId): self $previousShift = TimeperiodEntry::on($this->db) ->columns('until_time') ->filter(Filter::all( + Filter::equal('deleted', 'n'), + Filter::equal('timeperiod.deleted', 'n'), Filter::equal('timeperiod.rotation.schedule_id', $rotation->schedule_id), Filter::equal('timeperiod.rotation.priority', $rotation->priority), Filter::unequal('timeperiod.owned_by_rotation_id', $rotation->id), @@ -267,6 +272,7 @@ public function loadRotation(int $rotationId): self $newerRotation = Rotation::on($this->db) ->columns(['first_handoff', 'options', 'mode']) ->filter(Filter::all( + Filter::equal('deleted', 'n'), Filter::equal('schedule_id', $rotation->schedule_id), Filter::equal('priority', $rotation->priority), Filter::greaterThan('first_handoff', $rotation->first_handoff) @@ -278,8 +284,17 @@ public function loadRotation(int $rotationId): self } } + $membersRes = $rotation + ->member + ->filter(Filter::equal('deleted', 'n')) + ->filter(Filter::any( + Filter::equal('contact.deleted', 'n'), + Filter::equal('contactgroup.deleted', 'n') + )) + ->orderBy('position', SORT_ASC); + $members = []; - foreach ($rotation->member->orderBy('position', SORT_ASC) as $member) { + foreach ($membersRes as $member) { if ($member->contact_id !== null) { $members[] = 'contact:' . $member->contact_id; } else { @@ -397,7 +412,10 @@ public function addRotation(): void (new Select()) ->from('rotation') ->columns(new Expression('MAX(priority) + 1')) - ->where(['schedule_id = ?' => $this->scheduleId]) + ->where([ + 'schedule_id = ?' => $this->scheduleId, + 'deleted = ?' => 'n', + ]) ) ?? 0)->send(true); if ($transactionStarted) { @@ -428,17 +446,22 @@ public function editRotation(int $rotationId): void $createStmt = $this->createRotation((int) $priority); $allEntriesRemoved = true; + $changedAt = time() * 1000; if (self::EXPERIMENTAL_OVERRIDES) { // We only show a single name, even in case of multiple versions of a rotation. // To avoid confusion, we update all versions upon change of the name - $this->db->update('rotation', ['name' => $this->getValue('name')], [ - 'schedule_id = ?' => $this->scheduleId, - 'priority = ?' => $priority - ]); + $this->db->update('rotation', + ['name' => $this->getValue('name'), 'changed_at' => $changedAt], + ['schedule_id = ?' => $this->scheduleId, 'priority = ?' => $priority] + ); $firstHandoff = $createStmt->current(); $timeperiodEntries = TimeperiodEntry::on($this->db) - ->filter(Filter::equal('timeperiod.owned_by_rotation_id', $rotationId)); + ->filter(Filter::all( + Filter::equal('deleted', 'n'), + Filter::equal('timeperiod.deleted', 'n'), + Filter::equal('timeperiod.owned_by_rotation_id', $rotationId) + )); foreach ($timeperiodEntries as $timeperiodEntry) { /** @var TimeperiodEntry $timeperiodEntry */ @@ -473,8 +496,9 @@ public function editRotation(int $rotationId): void } else { $allEntriesRemoved = false; $this->db->update('timeperiod_entry', [ - 'until_time' => $lastShiftEnd->format('U.u') * 1000.0, - 'rrule' => $rrule->setUntil($lastHandoff)->getString(Rule::TZ_FIXED) + 'until_time' => $lastShiftEnd->format('U.u') * 1000.0, + 'rrule' => $rrule->setUntil($lastHandoff)->getString(Rule::TZ_FIXED), + 'changed_at' => $changedAt ], ['id = ?' => $timeperiodEntry->id]); } } @@ -524,32 +548,47 @@ public function removeRotation(int $id): void (new Select()) ->from('timeperiod') ->columns('id') - ->where(['owned_by_rotation_id = ?' => $id]) + ->where([ + 'owned_by_rotation_id = ?' => $id, + 'deleted = ?' => 'n', + ]) ); - $this->db->delete('timeperiod_entry', ['timeperiod_id = ?' => $timeperiodId]); - $this->db->delete('timeperiod', ['id = ?' => $timeperiodId]); - $this->db->delete('rotation_member', ['rotation_id = ?' => $id]); - $this->db->delete('rotation', ['id = ?' => $id]); + $changedAt = time() * 1000; + $markAsDeleted = ['changed_at' => $changedAt, 'deleted' => 'y']; + + $this->db->update('timeperiod_entry', $markAsDeleted, ['timeperiod_id = ?' => $timeperiodId]); + $this->db->update('timeperiod', $markAsDeleted, ['id = ?' => $timeperiodId]); + $this->db->update('rotation_member', $markAsDeleted + ['position' => null], ['rotation_id = ?' => $id]); + + $this->db->update( + 'rotation', + $markAsDeleted + ['priority' => null, 'first_handoff' => null], + ['id = ?' => $id] + ); $rotations = Rotation::on($this->db) - ->filter(Filter::equal('schedule_id', $this->scheduleId)) - ->filter(Filter::equal('priority', $priority)); + ->filter(Filter::all( + Filter::equal('deleted', 'n'), + Filter::equal('schedule_id', $this->scheduleId), + Filter::equal('priority', $priority) + )); if ($rotations->count() === 0) { $affectedRotations = $this->db->select( (new Select()) ->columns('id') ->from('rotation') ->where([ - 'schedule_id = ?' => $this->scheduleId, - 'priority > ?' => $priority + 'deleted = ?' => 'n', + 'schedule_id = ?' => $this->scheduleId, + 'priority > ?' => $priority ]) ->orderBy('priority ASC') ); foreach ($affectedRotations as $rotation) { $this->db->update( 'rotation', - ['priority' => new Expression('priority - 1')], + ['priority' => new Expression('priority - 1'), 'changed_at' => $changedAt], ['id = ?' => $rotation->id] ); } @@ -579,8 +618,15 @@ public function wipeRotation(int $priority = null): void $rotations = Rotation::on($this->db) ->columns('id') - ->filter(Filter::equal('schedule_id', $this->scheduleId)) - ->filter(Filter::equal('priority', $priority)); + ->filter(Filter::all( + Filter::equal('deleted', 'n'), + Filter::equal('schedule_id', $this->scheduleId), + Filter::equal('priority', $priority) + )); + + $changedAt = time() * 1000; + $markAsDeleted = ['changed_at' => $changedAt, 'deleted' => 'y']; + foreach ($rotations as $rotation) { $timeperiodId = $this->db->fetchScalar( (new Select()) @@ -589,10 +635,19 @@ public function wipeRotation(int $priority = null): void ->where(['owned_by_rotation_id = ?' => $rotation->id]) ); - $this->db->delete('timeperiod_entry', ['timeperiod_id = ?' => $timeperiodId]); - $this->db->delete('timeperiod', ['id = ?' => $timeperiodId]); - $this->db->delete('rotation_member', ['rotation_id = ?' => $rotation->id]); - $this->db->delete('rotation', ['id = ?' => $rotation->id]); + $this->db->update('timeperiod_entry', $markAsDeleted, ['timeperiod_id = ?' => $timeperiodId]); + $this->db->update('timeperiod', $markAsDeleted, ['id = ?' => $timeperiodId]); + $this->db->update( + 'rotation_member', + $markAsDeleted + ['position' => null], + ['rotation_id = ?' => $rotation->id] + ); + + $this->db->update( + 'rotation', + $markAsDeleted + ['priority' => null, 'first_handoff' => null], + ['id = ?' => $rotation->id] + ); } $affectedRotations = $this->db->select( @@ -600,15 +655,16 @@ public function wipeRotation(int $priority = null): void ->columns('id') ->from('rotation') ->where([ - 'schedule_id = ?' => $this->scheduleId, - 'priority > ?' => $priority + 'deleted = ?' => 'n', + 'schedule_id = ?' => $this->scheduleId, + 'priority > ?' => $priority ]) ->orderBy('priority ASC') ); foreach ($affectedRotations as $rotation) { $this->db->update( 'rotation', - ['priority' => new Expression('priority - 1')], + ['priority' => new Expression('priority - 1'), 'changed_at' => $changedAt], ['id = ?' => $rotation->id] ); } @@ -985,6 +1041,7 @@ protected function assemble() new CallbackValidator(function ($value, $validator) { $rotations = Rotation::on($this->db) ->columns('id') + ->filter(Filter::equal('deleted', 'n')) ->filter(Filter::equal('schedule_id', $this->scheduleId)) ->filter(Filter::equal('name', $value)); if (($priority = $this->getValue('priority')) !== null) { @@ -1118,6 +1175,7 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando if (! empty($contactTerms)) { $contacts = (Contact::on(Database::get())) + ->filter(Filter::equal('deleted', 'n')) ->filter(Filter::equal('id', array_keys($contactTerms))); foreach ($contacts as $contact) { $contactTerms[$contact->id] @@ -1128,6 +1186,7 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando if (! empty($groupTerms)) { $groups = (Contactgroup::on(Database::get())) + ->filter(Filter::equal('deleted', 'n')) ->filter(Filter::equal('id', array_keys($groupTerms))); foreach ($groups as $group) { $groupTerms[$group->id] diff --git a/application/forms/ScheduleForm.php b/application/forms/ScheduleForm.php index d74e0d01..4af75e85 100644 --- a/application/forms/ScheduleForm.php +++ b/application/forms/ScheduleForm.php @@ -57,7 +57,10 @@ public function loadSchedule(int $id): void $db = Database::get(); $schedule = Schedule::on($db) - ->filter(Filter::equal('id', $id)) + ->filter(Filter::all( + Filter::equal('id', $id), + Filter::equal('deleted', 'n') + )) ->first(); if ($schedule === null) { throw new HttpNotFoundException($this->translate('Schedule not found')); @@ -82,7 +85,8 @@ public function editSchedule(int $id): void $db = Database::get(); $db->update('schedule', [ - 'name' => $this->getValue('name') + 'name' => $this->getValue('name'), + 'changed_at' => time() * 1000 ], ['id = ?' => $id]); } @@ -93,7 +97,10 @@ public function removeSchedule(int $id): void $rotations = Rotation::on($db) ->columns('priority') - ->filter(Filter::equal('schedule_id', $id)) + ->filter(Filter::all( + Filter::equal('schedule_id', $id), + Filter::equal('deleted', 'n') + )) ->orderBy('priority', SORT_DESC); $rotationConfigForm = new RotationConfigForm($id, $db); @@ -102,7 +109,7 @@ public function removeSchedule(int $id): void $rotationConfigForm->wipeRotation($rotation->priority); } - $db->delete('schedule', ['id = ?' => $id]); + $db->update('schedule', ['changed_at' => time() * 1000, 'deleted' => 'y'], ['id = ?' => $id]); $db->commitTransaction(); } diff --git a/application/forms/SourceForm.php b/application/forms/SourceForm.php index d83f5564..085f7687 100644 --- a/application/forms/SourceForm.php +++ b/application/forms/SourceForm.php @@ -288,7 +288,11 @@ protected function onSuccess(): void { $pressedButton = $this->getPressedSubmitElement(); if ($pressedButton && $pressedButton->getName() === 'delete') { - $this->db->delete('source', ['id = ?' => $this->sourceId]); + $this->db->update( + 'source', + ['changed_at' => time() * 1000, 'deleted' => 'y'], + ['id = ?' => $this->sourceId] + ); return; } @@ -306,6 +310,7 @@ protected function onSuccess(): void if ($this->sourceId === null) { $this->db->insert('source', $source); } else { + $source['changed_at'] = time() * 1000; $this->db->update('source', $source, ['id = ?' => $this->sourceId]); } } diff --git a/library/Notifications/Model/Behavior/HasAddress.php b/library/Notifications/Model/Behavior/HasAddress.php index 2e7db3fc..8e356e54 100644 --- a/library/Notifications/Model/Behavior/HasAddress.php +++ b/library/Notifications/Model/Behavior/HasAddress.php @@ -37,7 +37,10 @@ public function rewriteColumn($column, ?string $relation = null) $subQuery = $this->query->createSubQuery(new ContactAddress(), $subQueryRelation) ->limit(1) ->columns([new Expression('1')]) - ->filter(Filter::equal('type', $type)); + ->filter(Filter::all( + Filter::equal('type', $type), + Filter::equal('deleted', 'n') + )); $column = $relation !== null ? str_replace('.', '_', $relation) . "_$column" : $column; @@ -75,7 +78,10 @@ public function rewriteCondition(Filter\Condition $condition, $relation = null) $subQuery = $this->query->createSubQuery(new ContactAddress(), $relation) ->limit(1) ->columns([new Expression('1')]) - ->filter(Filter::equal('type', $type)); + ->filter(Filter::all( + Filter::equal('type', $type), + Filter::equal('deleted', 'n') + )); if ($condition->getValue()) { if ($condition instanceof Filter\Unequal) { diff --git a/library/Notifications/Model/Channel.php b/library/Notifications/Model/Channel.php index 4c1108d8..6414fcc1 100644 --- a/library/Notifications/Model/Channel.php +++ b/library/Notifications/Model/Channel.php @@ -12,6 +12,7 @@ use ipl\Orm\Query; use ipl\Orm\Relations; use ipl\Sql\Connection; +use ipl\Stdlib\Filter; use ipl\Web\Widget\Icon; /** @@ -118,9 +119,10 @@ public function getIcon(): Icon public static function fetchChannelNames(Connection $conn): array { $channels = []; + $query = Channel::on($conn) + ->filter(Filter::equal('deleted', 'n')); /** @var Channel $channel */ - foreach (Channel::on($conn) as $channel) { - /** @var string $name */ + foreach ($query as $channel) { $name = $channel->name; $channels[$channel->id] = $name; } diff --git a/library/Notifications/Model/Contact.php b/library/Notifications/Model/Contact.php index 17f19e3e..1ab8c3e8 100644 --- a/library/Notifications/Model/Contact.php +++ b/library/Notifications/Model/Contact.php @@ -96,6 +96,8 @@ public function createRelations(Relations $relations): void $relations->hasMany('rule_escalation_recipient', RuleEscalationRecipient::class) ->setJoinType('LEFT'); + $relations->hasMany('contactgroup_member', ContactgroupMember::class); + $relations->belongsToMany('contactgroup', Contactgroup::class) ->through('contactgroup_member') ->setJoinType('LEFT'); diff --git a/library/Notifications/Model/RuleEscalationRecipient.php b/library/Notifications/Model/RuleEscalationRecipient.php index 71cfb90f..c935969e 100644 --- a/library/Notifications/Model/RuleEscalationRecipient.php +++ b/library/Notifications/Model/RuleEscalationRecipient.php @@ -11,6 +11,7 @@ use ipl\Orm\Model; use ipl\Orm\Query; use ipl\Orm\Relations; +use ipl\Stdlib\Filter; /** * @property int $id @@ -95,15 +96,15 @@ public function getRecipient(): ?Model { $recipientModel = null; if ($this->contact_id) { - $recipientModel = $this->contact->first(); + $recipientModel = $this->contact->filter(Filter::equal('deleted', 'n'))->first(); } if ($this->contactgroup_id) { - $recipientModel = $this->contactgroup->first(); + $recipientModel = $this->contactgroup->filter(Filter::equal('deleted', 'n'))->first(); } if ($this->schedule_id) { - $recipientModel = $this->schedule->first(); + $recipientModel = $this->schedule->filter(Filter::equal('deleted', 'n'))->first(); } return $recipientModel; diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index 17121f95..7eaa5f37 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -206,11 +206,15 @@ public function addOrUpdateContact(): void ); if (! empty(array_diff_assoc($contact, $contactFromDb))) { + $contact['changed_at'] = time() * 1000; $this->db->update('contact', $contact, ['id = ?' => $this->contactId]); } - $addressObjects = (ContactAddress::on($this->db)) - ->filter(Filter::equal('contact_id', $this->contactId)); + $addressObjects = ContactAddress::on($this->db) + ->filter(Filter::all( + Filter::equal('contact_id', $this->contactId), + Filter::equal('deleted', 'n') + )); foreach ($addressObjects as $addressRow) { $addressFromDb[$addressRow->type] = [$addressRow->id, $addressRow->address]; @@ -227,9 +231,12 @@ public function addOrUpdateContact(): void public function removeContact() { $this->db->beginTransaction(); - $this->db->delete('contactgroup_member', ['contact_id = ?' => $this->contactId]); - $this->db->delete('contact_address', ['contact_id = ?' => $this->contactId]); - $this->db->delete('contact', ['id = ?' => $this->contactId]); + + $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; + $this->db->update('contactgroup_member', $markAsDeleted, ['contact_id = ?' => $this->contactId]); + $this->db->update('contact_address', $markAsDeleted, ['contact_id = ?' => $this->contactId]); + $this->db->update('contact', $markAsDeleted + ['username' => null], ['id = ?' => $this->contactId]); + $this->db->commitTransaction(); } @@ -256,7 +263,7 @@ private function insertOrUpdateAddress(string $type, array $addressFromForm, arr } elseif ($addressFromDb[$type][1] !== $addressFromForm[$type]) { $this->db->update( 'contact_address', - ['address' => $addressFromForm[$type]], + ['address' => $addressFromForm[$type], 'changed_at' => time() * 1000], [ 'id = ?' => $addressFromDb[$type][0], 'contact_id = ?' => $this->contactId @@ -264,7 +271,11 @@ private function insertOrUpdateAddress(string $type, array $addressFromForm, arr ); } } elseif (isset($addressFromDb[$type])) { - $this->db->delete('contact_address', ['id = ?' => $addressFromDb[$type][0]]); + $this->db->update( + 'contact_address', + ['changed_at' => time() * 1000, 'deleted' => 'y'], + ['id = ?' => $addressFromDb[$type][0]] + ); } } diff --git a/library/Notifications/Widget/MemberSuggestions.php b/library/Notifications/Widget/MemberSuggestions.php index 983e41b7..f3ac9db5 100644 --- a/library/Notifications/Widget/MemberSuggestions.php +++ b/library/Notifications/Widget/MemberSuggestions.php @@ -79,7 +79,10 @@ public function forRequest(ServerRequestInterface $request): self protected function assemble(): void { - $contactFilter = Filter::like('full_name', $this->searchTerm); + $contactFilter = Filter::all( + Filter::like('full_name', $this->searchTerm), + Filter::equal('deleted', 'n') + ); if (! empty($this->excludeTerms)) { $contactFilter = Filter::all( diff --git a/library/Notifications/Widget/RecipientSuggestions.php b/library/Notifications/Widget/RecipientSuggestions.php index 803618d0..3b1b09ab 100644 --- a/library/Notifications/Widget/RecipientSuggestions.php +++ b/library/Notifications/Widget/RecipientSuggestions.php @@ -108,7 +108,13 @@ protected function assemble() )); } - foreach (Contact::on(Database::get())->filter($contactFilter) as $contact) { + $query = Contact::on(Database::get()) + ->filter(Filter::all( + $contactFilter, + Filter::equal('deleted', 'n') + )); + + foreach ($query as $contact) { $this->addHtml(new HtmlElement( 'li', null, @@ -125,7 +131,13 @@ protected function assemble() )); } - foreach (Contactgroup::on(Database::get())->filter($groupFilter) as $group) { + $query = Contactgroup::on(Database::get()) + ->filter(Filter::all( + $groupFilter, + Filter::equal('deleted', 'n') + )); + + foreach ($query as $group) { $this->addHtml(new HtmlElement( 'li', null, diff --git a/library/Notifications/Widget/Schedule.php b/library/Notifications/Widget/Schedule.php index 6d0ac2f1..d182bf46 100644 --- a/library/Notifications/Widget/Schedule.php +++ b/library/Notifications/Widget/Schedule.php @@ -10,6 +10,7 @@ use ipl\Html\Attributes; use ipl\Html\BaseHtmlElement; use ipl\Html\HtmlElement; +use ipl\Stdlib\Filter; use ipl\Web\Common\BaseTarget; use ipl\Web\Style; @@ -46,7 +47,16 @@ public function __construct(\Icinga\Module\Notifications\Model\Schedule $schedul */ protected function assembleTimeline(Timeline $timeline): void { - foreach ($this->schedule->rotation->with('timeperiod')->orderBy('first_handoff', SORT_DESC) as $rotation) { + $rotations = $this->schedule + ->rotation + ->with('timeperiod') + ->filter(Filter::all( + Filter::equal('deleted', 'n'), + Filter::equal('timeperiod.deleted', 'n') + )) + ->orderBy('first_handoff', SORT_DESC); + + foreach ($rotations as $rotation) { $timeline->addRotation(new Rotation($rotation)); } } diff --git a/library/Notifications/Widget/Timeline/Rotation.php b/library/Notifications/Widget/Timeline/Rotation.php index 2f6d2bc4..9b9c760a 100644 --- a/library/Notifications/Widget/Timeline/Rotation.php +++ b/library/Notifications/Widget/Timeline/Rotation.php @@ -77,6 +77,11 @@ public function fetchTimeperiodEntries(DateTime $after, DateTime $until): Genera $entries = $this->model->timeperiod->timeperiod_entry ->with(['member.contact', 'member.contactgroup']) ->filter(Filter::all( + Filter::equal('deleted', 'n'), + Filter::any( + Filter::equal('member.contact.deleted', 'n'), + Filter::equal('member.contactgroup.deleted', 'n') + ), Filter::any( Filter::like('rrule', '*'), // It's either a repeating entry Filter::greaterThan('end_time', $after) // Or one whose end time is still visible From 734032350da00174ac2c9682eeb29400b50b1a52 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 2 May 2024 15:47:18 +0200 Subject: [PATCH 05/34] Controller: Add `changed_at` as sortby column --- application/controllers/ChannelsController.php | 5 +++-- application/controllers/ContactGroupsController.php | 3 ++- application/controllers/ContactsController.php | 3 ++- application/controllers/EventRulesController.php | 3 ++- application/controllers/SchedulesController.php | 5 ++++- application/controllers/SourcesController.php | 5 +++-- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/application/controllers/ChannelsController.php b/application/controllers/ChannelsController.php index 4ace1d89..5f445578 100644 --- a/application/controllers/ChannelsController.php +++ b/application/controllers/ChannelsController.php @@ -53,8 +53,9 @@ public function indexAction() $sortControl = $this->createSortControl( $channels, [ - 'name' => t('Name'), - 'type' => t('Type') + 'name' => t('Name'), + 'type' => t('Type'), + 'changed_at' => t('Changed At') ] ); diff --git a/application/controllers/ContactGroupsController.php b/application/controllers/ContactGroupsController.php index 396a85b1..6b50a1a7 100644 --- a/application/controllers/ContactGroupsController.php +++ b/application/controllers/ContactGroupsController.php @@ -47,7 +47,8 @@ public function indexAction(): void $sortControl = $this->createSortControl( $groups, [ - 'name' => t('Group Name'), + 'name' => t('Group Name'), + 'changed_at' => t('Changed At') ] ); diff --git a/application/controllers/ContactsController.php b/application/controllers/ContactsController.php index a2454c3e..49152fb7 100644 --- a/application/controllers/ContactsController.php +++ b/application/controllers/ContactsController.php @@ -51,7 +51,8 @@ public function indexAction() $sortControl = $this->createSortControl( $contacts, [ - 'full_name' => t('Full Name'), + 'full_name' => t('Full Name'), + 'changed_at' => t('Changed At') ] ); diff --git a/application/controllers/EventRulesController.php b/application/controllers/EventRulesController.php index f4b766c1..bc3ed472 100644 --- a/application/controllers/EventRulesController.php +++ b/application/controllers/EventRulesController.php @@ -52,7 +52,8 @@ public function indexAction(): void $sortControl = $this->createSortControl( $eventRules, [ - 'name' => t('Name'), + 'name' => t('Name'), + 'changed_at' => t('Changed At') ] ); diff --git a/application/controllers/SchedulesController.php b/application/controllers/SchedulesController.php index 00182525..e5e2da8c 100644 --- a/application/controllers/SchedulesController.php +++ b/application/controllers/SchedulesController.php @@ -33,7 +33,10 @@ public function indexAction(): void $limitControl = $this->createLimitControl(); $sortControl = $this->createSortControl( $schedules, - ['schedule.name' => t('Name')] + [ + 'schedule.name' => t('Name'), + 'changed_at' => t('Changed At') + ] ); $paginationControl = $this->createPaginationControl($schedules); diff --git a/application/controllers/SourcesController.php b/application/controllers/SourcesController.php index 410a2a67..9ca4f16c 100644 --- a/application/controllers/SourcesController.php +++ b/application/controllers/SourcesController.php @@ -42,8 +42,9 @@ public function indexAction(): void $sortControl = $this->createSortControl( $sources, [ - 'name' => t('Name'), - 'type' => t('Type') + 'name' => t('Name'), + 'type' => t('Type'), + 'changed_at' => t('Changed At') ] ); From 9abeb0f33ab0a451384e0799077b763312819bad Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 6 May 2024 09:16:57 +0200 Subject: [PATCH 06/34] Channel: Update only if changes exist - Cleanup controllers and add return type to methods --- application/controllers/ChannelController.php | 34 +--- .../controllers/ChannelsController.php | 1 + application/forms/ChannelForm.php | 147 +++++++++++++----- 3 files changed, 121 insertions(+), 61 deletions(-) diff --git a/application/controllers/ChannelController.php b/application/controllers/ChannelController.php index daecea8e..6dc48e6a 100644 --- a/application/controllers/ChannelController.php +++ b/application/controllers/ChannelController.php @@ -6,50 +6,30 @@ use Icinga\Module\Notifications\Common\Database; use Icinga\Module\Notifications\Forms\ChannelForm; -use Icinga\Module\Notifications\Model\Channel; use Icinga\Web\Notification; -use ipl\Sql\Connection; -use ipl\Stdlib\Filter; use ipl\Web\Compat\CompatController; class ChannelController extends CompatController { - /** @var Connection */ - private $db; - - public function init() + public function init(): void { $this->assertPermission('config/modules'); - - $this->db = Database::get(); } - public function indexAction() + public function indexAction(): void { $channelId = $this->params->getRequired('id'); - $query = Channel::on($this->db) - ->filter(Filter::all( - Filter::equal('id', $channelId), - Filter::equal('deleted', 'n') - )); - - /** @var Channel $channel */ - $channel = $query->first(); - if ($channel === null) { - $this->httpNotFound(t('Channel not found')); - } - - $this->addTitleTab(sprintf(t('Channel: %s'), $channel->name)); - - $form = (new ChannelForm($this->db, $channelId)) - ->populate($channel) + $form = (new ChannelForm(Database::get())) + ->loadChannel($channelId) ->on(ChannelForm::ON_SUCCESS, function (ChannelForm $form) { if ($form->getPressedSubmitElement()->getName() === 'delete') { + $form->removeChannel(); Notification::success(sprintf( t('Deleted channel "%s" successfully'), $form->getValue('name') )); } else { + $form->editChannel(); Notification::success(sprintf( t('Channel "%s" has successfully been saved'), $form->getValue('name') @@ -59,6 +39,8 @@ public function indexAction() $this->redirectNow('__CLOSE__'); })->handleRequest($this->getServerRequest()); + $this->addTitleTab(sprintf(t('Channel: %s'), $form->getChannelName())); + $this->addContent($form); } } diff --git a/application/controllers/ChannelsController.php b/application/controllers/ChannelsController.php index 5f445578..5fe1669b 100644 --- a/application/controllers/ChannelsController.php +++ b/application/controllers/ChannelsController.php @@ -106,6 +106,7 @@ public function addAction() $this->addTitleTab(t('Add Channel')); $form = (new ChannelForm($this->db)) ->on(ChannelForm::ON_SUCCESS, function (ChannelForm $form) { + $form->addChannel(); Notification::success( sprintf( t('New channel %s has successfully been added'), diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index 83eb8966..7bb56455 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Notifications\Forms; +use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Notifications\Model\Channel; use Icinga\Module\Notifications\Model\AvailableChannelType; use Icinga\Web\Session; @@ -13,6 +14,7 @@ use ipl\I18n\GettextTranslator; use ipl\I18n\StaticTranslator; use ipl\Sql\Connection; +use ipl\Stdlib\Filter; use ipl\Validator\EmailAddressValidator; use ipl\Web\Common\CsrfCounterMeasure; use ipl\Web\Compat\CompatForm; @@ -43,10 +45,9 @@ class ChannelForm extends CompatForm /** @var array */ private $defaultChannelOptions = []; - public function __construct(Connection $db, ?int $channelId = null) + public function __construct(Connection $db) { $this->db = $db; - $this->channelId = $channelId; } protected function assemble() @@ -152,53 +153,79 @@ public function hasBeenSubmitted() return parent::hasBeenSubmitted(); } - public function populate($values) + /** + * Load the channel with given id + * + * @param int $id + * + * @return $this + * + * @throws HttpNotFoundException + */ + public function loadChannel(int $id): self { - if ($values instanceof Channel) { - $values = [ - 'name' => $values->name, - 'type' => $values->type, - 'config' => json_decode($values->config, true) ?? [] - ]; - } - - parent::populate($values); + $this->channelId = $id; + $this->populate($this->fetchDbValues()); return $this; } - protected function onSuccess() + /** + * Add the new channel + */ + public function addChannel(): void { - if ($this->getPressedSubmitElement()->getName() === 'delete') { - $this->db->update( - 'channel', - ['changed_at' => time() * 1000, 'deleted' => 'y'], - ['id = ?' => $this->channelId] - ); + $channel = $this->getValues(); - return; - } + $channel['config'] = json_encode($this->filterConfig($channel['config'])); + + $this->db->insert('channel', $channel); + } + + /** + * Edit the channel + * + * @return void + */ + public function editChannel(): void + { + $this->db->beginTransaction(); $channel = $this->getValues(); - $config = array_filter( - $channel['config'], - function ($configItem, $key) { - if (isset($this->defaultChannelOptions[$key])) { - return $this->defaultChannelOptions[$key] !== $configItem; - } + $storedValues = $this->fetchDbValues(); - return $configItem !== null; - }, - ARRAY_FILTER_USE_BOTH - ); + $channel['config'] = json_encode($this->filterConfig($channel['config'])); + $storedValues['config'] = json_encode($this->filterConfig($storedValues['config'])); - $channel['config'] = json_encode($config); - if ($this->channelId === null) { - $this->db->insert('channel', $channel); - } else { + if(! empty(array_diff_assoc($channel, $storedValues))) { $channel['changed_at'] = time() * 1000; + $this->db->update('channel', $channel, ['id = ?' => $this->channelId]); } + + $this->db->commitTransaction(); + } + + /** + * Remove the channel + */ + public function removeChannel(): void + { + $this->db->update( + 'channel', + ['changed_at' => time() * 1000, 'deleted' => 'y'], + ['id = ?' => $this->channelId] + ); + } + + /** + * Get the channel name + * + * @return string + */ + public function getChannelName(): string + { + return $this->getValue('name'); } /** @@ -337,4 +364,54 @@ protected function fromCurrentLocale(array $localeMap): ?string return $localeMap[$locale] ?? $localeMap[$default] ?? null; } + + /** + * Filter the config array + * + * @param array $config + * + * @return ChannelOptionConfig + */ + private function filterConfig(array $config): array + { + return array_filter( + $config, + function ($configItem, $key) { + if (isset($this->defaultChannelOptions[$key])) { + return $this->defaultChannelOptions[$key] !== $configItem; + } + + return $configItem !== null; + }, + ARRAY_FILTER_USE_BOTH + ); + } + + /** + * Fetch the values from the database + * + * @return array + * + * @throws HttpNotFoundException + */ + private function fetchDbValues(): array + { + /** @var Channel $channel */ + $channel = Channel::on($this->db) + ->filter(Filter::all( + Filter::equal('id', $this->channelId), + Filter::equal('deleted', 'n') + )) + ->first(); + + if ($channel === null) { + throw new HttpNotFoundException($this->translate('Channel not found')); + } + + return [ + 'name' => $channel->name, + 'type' => $channel->type, + 'config' => json_decode($channel->config, true) ?? [] + ]; + } } From 58849c92eedb3b0b9ea48aac569f8faf7986fd4d Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 6 May 2024 15:01:03 +0200 Subject: [PATCH 07/34] Contact: Update only if changes exist --- application/controllers/ContactController.php | 38 +--- .../controllers/ContactsController.php | 6 +- .../Notifications/Web/Form/ContactForm.php | 211 +++++++++++------- 3 files changed, 138 insertions(+), 117 deletions(-) diff --git a/application/controllers/ContactController.php b/application/controllers/ContactController.php index c275bab4..0fc7b429 100644 --- a/application/controllers/ContactController.php +++ b/application/controllers/ContactController.php @@ -15,57 +15,37 @@ class ContactController extends CompatController { - /** @var Connection */ - private $db; - - public function init() + public function init(): void { $this->assertPermission('notifications/config/contacts'); - - $this->db = Database::get(); } - public function indexAction() + public function indexAction(): void { $contactId = $this->params->getRequired('id'); - $query = Contact::on($this->db) - ->filter(Filter::all( - Filter::equal('id', $contactId), - Filter::equal('deleted', 'n') - )); - - /** @var Contact $contact */ - $contact = $query->first(); - if ($contact === null) { - $this->httpNotFound(t('Contact not found')); - } - $this->addTitleTab(sprintf(t('Contact: %s'), $contact->full_name)); - - $form = (new ContactForm($this->db, $contactId)) - ->populate($contact) + $form = (new ContactForm(Database::get())) + ->loadContact($contactId) ->on(ContactForm::ON_SUCCESS, function (ContactForm $form) { - $form->addOrUpdateContact(); - /** @var FieldsetElement $contactElement */ - $contactElement = $form->getElement('contact'); + $form->editContact(); Notification::success(sprintf( t('Contact "%s" has successfully been saved'), - $contactElement->getValue('full_name') + $form->getContactName() )); $this->redirectNow('__CLOSE__'); })->on(ContactForm::ON_REMOVE, function (ContactForm $form) { $form->removeContact(); - /** @var FieldsetElement $contactElement */ - $contactElement = $form->getElement('contact'); Notification::success(sprintf( t('Deleted contact "%s" successfully'), - $contactElement->getValue('full_name') + $form->getContactName() )); $this->redirectNow('__CLOSE__'); })->handleRequest($this->getServerRequest()); + $this->addTitleTab(sprintf(t('Contact: %s'), $form->getContactName())); + $this->addContent($form); } } diff --git a/application/controllers/ContactsController.php b/application/controllers/ContactsController.php index 49152fb7..0fcade7b 100644 --- a/application/controllers/ContactsController.php +++ b/application/controllers/ContactsController.php @@ -103,15 +103,15 @@ public function indexAction() $this->getTabs()->activate('contacts'); } - public function addAction() + public function addAction(): void { $this->addTitleTab(t('Add Contact')); $form = (new ContactForm($this->db)) ->on(ContactForm::ON_SUCCESS, function (ContactForm $form) { - $form->addOrUpdateContact(); + $form->addContact(); Notification::success(t('New contact has successfully been added')); - $this->redirectNow(Url::fromPath('notifications/contacts')); + $this->redirectNow(Links::contacts()); })->handleRequest($this->getServerRequest()); $this->addContent($form); diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index 7eaa5f37..8f3af313 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -4,11 +4,10 @@ namespace Icinga\Module\Notifications\Web\Form; -use Icinga\Module\Notifications\Common\Database; +use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Notifications\Model\AvailableChannelType; use Icinga\Module\Notifications\Model\Channel; use Icinga\Module\Notifications\Model\Contact; -use Icinga\Module\Notifications\Model\ContactAddress; use Icinga\Web\Session; use ipl\Html\Contract\FormSubmitElement; use ipl\Html\FormElement\FieldsetElement; @@ -33,10 +32,9 @@ class ContactForm extends CompatForm /** @var ?string Contact ID*/ private $contactId; - public function __construct(Connection $db, $contactId = null) + public function __construct(Connection $db) { $this->db = $db; - $this->contactId = $contactId; $this->on(self::ON_SENT, function () { if ($this->hasBeenRemoved()) { @@ -82,7 +80,7 @@ protected function assemble() $this->addElement($contact); $channelOptions = ['' => sprintf(' - %s - ', $this->translate('Please choose'))]; - $channelOptions += Channel::fetchChannelNames(Database::get()); + $channelOptions += Channel::fetchChannelNames($this->db); $contact->addElement( 'text', @@ -99,7 +97,11 @@ protected function assemble() 'validators' => [ new StringLengthValidator(['max' => 254]), new CallbackValidator(function ($value, $validator) { - $contact = Contact::on($this->db)->filter(Filter::equal('username', $value)); + $contact = Contact::on($this->db) + ->filter(Filter::all( + Filter::equal('username', $value), + Filter::equal('deleted', 'n') + )); if ($this->contactId) { $contact->filter(Filter::unequal('id', $this->contactId)); } @@ -157,78 +159,106 @@ protected function assemble() } } - public function populate($values) + /** + * Load the contact with given id + * + * @param int $id + * + * @return $this + * + * @throws HttpNotFoundException + */ + public function loadContact(int $id): self { - if ($values instanceof Contact) { - $formValues = [ - 'contact' => [ - 'full_name' => $values->full_name, - 'username' => $values->username, - 'default_channel_id' => $values->default_channel_id - ] - ]; - - foreach ($values->contact_address as $contactInfo) { - $formValues['contact_address'][$contactInfo->type] = $contactInfo->address; - } - - $values = $formValues; - } + $this->contactId = $id; - parent::populate($values); + $this->populate($this->fetchDbValues()); return $this; } /** - * Add or update the contact and its corresponding contact addresses - * - * @return void + * Add the new contact */ - public function addOrUpdateContact(): void + public function addContact(): void { $contactInfo = $this->getValues(); - $contact = $contactInfo['contact']; - $addressFromForm = $contactInfo['contact_address']; - $this->db->beginTransaction(); + $this->db->insert('contact', $contactInfo['contact']); + $this->contactId = $this->db->lastInsertId(); + + foreach (array_filter($contactInfo['contact_address']) as $type => $address) { + $address = [ + 'contact_id' => $this->contactId, + 'type' => $type, + 'address' => $address + ]; - $addressFromDb = []; - if ($this->contactId === null) { - $this->db->insert('contact', $contact); - $this->contactId = $this->db->lastInsertId(); - } else { - $contactFromDb = (array) $this->db->fetchOne( - Contact::on($this->db)->withoutColumns(['id']) - ->filter(Filter::equal('id', $this->contactId)) - ->assembleSelect() - ); + $this->db->insert('contact_address', $address); + } - if (! empty(array_diff_assoc($contact, $contactFromDb))) { - $contact['changed_at'] = time() * 1000; - $this->db->update('contact', $contact, ['id = ?' => $this->contactId]); - } + $this->db->commitTransaction(); + } + + /** + * Edit the contact + * + * @return void + */ + public function editContact(): void + { + $this->db->beginTransaction(); - $addressObjects = ContactAddress::on($this->db) - ->filter(Filter::all( - Filter::equal('contact_id', $this->contactId), - Filter::equal('deleted', 'n') - )); + $values = $this->getValues(); + $storedValues = $this->fetchDbValues(); - foreach ($addressObjects as $addressRow) { - $addressFromDb[$addressRow->type] = [$addressRow->id, $addressRow->address]; - } + $changedAt = time() * 1000; + if ($storedValues['contact'] !== $values['contact']) { + $this->db->update( + 'contact', + $values['contact'] + ['changed_at' => $changedAt], + ['id = ?' => $this->contactId] + ); } - foreach ($addressFromForm as $type => $value) { - $this->insertOrUpdateAddress($type, $addressFromForm, $addressFromDb); + $storedAddresses = $storedValues['contact_address_with_id']; + foreach ($values['contact_address'] as $type => $address) { + if ($address === null) { + if (isset($storedAddresses[$type])) { + $this->db->update( + 'contact_address', + ['changed_at' => $changedAt, 'deleted' => 'y'], + ['id = ?' => $storedAddresses[$type][0]] + ); + } + } elseif (! isset($storedAddresses[$type])) { + $address = [ + 'contact_id' => $this->contactId, + 'type' => $type, + 'address' => $address + ]; + + $this->db->insert('contact_address', $address); + } elseif ($storedAddresses[$type][1] !== $address) { + $this->db->update( + 'contact_address', + ['address' => $address, 'changed_at' => $changedAt], + [ + 'id = ?' => $storedAddresses[$type][0], + 'contact_id = ?' => $this->contactId + ] + ); + } } $this->db->commitTransaction(); } - public function removeContact() + /** + * Remove the contact + */ + public function removeContact(): void { $this->db->beginTransaction(); @@ -241,42 +271,53 @@ public function removeContact() } /** - * Insert / Update contact address for a given contact + * Get the contact name * - * @param string $type - * @param array $addressFromForm - * @param array $addressFromDb [id, address] from `contact_adrress` table + * @return string + */ + public function getContactName(): string + { + return $this->getElement('contact')->getValue('full_name'); + } + + /** + * Fetch the values from the database * - * @return void + * @return array + * + * @throws HttpNotFoundException */ - private function insertOrUpdateAddress(string $type, array $addressFromForm, array $addressFromDb): void + private function fetchDbValues(): array { - if ($addressFromForm[$type] !== null) { - if (! isset($addressFromDb[$type])) { - $address = [ - 'contact_id' => $this->contactId, - 'type' => $type, - 'address' => $addressFromForm[$type] - ]; + /** @var ?Contact $contact */ + $contact = Contact::on($this->db) + ->filter(Filter::all( + Filter::equal('id', $this->contactId), + Filter::equal('deleted', 'n') + )) + ->first(); + + if ($contact === null) { + throw new HttpNotFoundException(t('Contact not found')); + } - $this->db->insert('contact_address', $address); - } elseif ($addressFromDb[$type][1] !== $addressFromForm[$type]) { - $this->db->update( - 'contact_address', - ['address' => $addressFromForm[$type], 'changed_at' => time() * 1000], - [ - 'id = ?' => $addressFromDb[$type][0], - 'contact_id = ?' => $this->contactId - ] - ); - } - } elseif (isset($addressFromDb[$type])) { - $this->db->update( - 'contact_address', - ['changed_at' => time() * 1000, 'deleted' => 'y'], - ['id = ?' => $addressFromDb[$type][0]] - ); + $values['contact'] = [ + 'full_name' => $contact->full_name, + 'username' => $contact->username, + 'default_channel_id' => (string) $contact->default_channel_id + ]; + + $contractAddr = $contact->contact_address + ->filter(Filter::equal('deleted', 'n')); + + $values['contact_address'] = []; + $values['contact_address_with_id'] = []; //TODO: only used in editContact(), find better solution + foreach ($contractAddr as $contactInfo) { + $values['contact_address'][$contactInfo->type] = $contactInfo->address; + $values['contact_address_with_id'][$contactInfo->type] = [$contactInfo->id, $contactInfo->address]; } + + return $values; } /** From 150872368fc84ae7a4b6ceb2c7cc3d5b080da876 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 6 May 2024 16:12:36 +0200 Subject: [PATCH 08/34] Source: Update only if changes exist --- application/controllers/SourceController.php | 28 ++-- application/controllers/SourcesController.php | 5 +- application/forms/SourceForm.php | 130 +++++++++++++----- 3 files changed, 108 insertions(+), 55 deletions(-) diff --git a/application/controllers/SourceController.php b/application/controllers/SourceController.php index 01b4950c..3c442fcc 100644 --- a/application/controllers/SourceController.php +++ b/application/controllers/SourceController.php @@ -14,7 +14,7 @@ class SourceController extends CompatController { - public function init() + public function init(): void { $this->assertPermission('config/modules'); } @@ -23,41 +23,29 @@ public function indexAction(): void { $sourceId = (int) $this->params->getRequired('id'); - /** @var ?Source $source */ - $source = Source::on(Database::get()) - ->filter(Filter::all( - Filter::equal('id', $sourceId), - Filter::equal('deleted', 'n') - )) - ->first(); - if ($source === null) { - $this->httpNotFound($this->translate('Source not found')); - } - - $form = (new SourceForm(Database::get(), $sourceId)) - ->populate($source) + $form = (new SourceForm(Database::get())) + ->loadSource($sourceId) ->on(SourceForm::ON_SUCCESS, function (SourceForm $form) { - /** @var string $sourceName */ - $sourceName = $form->getValue('name'); - /** @var FormSubmitElement $pressedButton */ $pressedButton = $form->getPressedSubmitElement(); if ($pressedButton->getName() === 'delete') { + $form->removeSource(); Notification::success(sprintf( $this->translate('Deleted source "%s" successfully'), - $sourceName + $form->getSourceName() )); } else { + $form->editSource(); Notification::success(sprintf( $this->translate('Updated source "%s" successfully'), - $sourceName + $form->getSourceName() )); } $this->switchToSingleColumnLayout(); })->handleRequest($this->getServerRequest()); - $this->addTitleTab(sprintf($this->translate('Source: %s'), $source->name)); + $this->addTitleTab(sprintf($this->translate('Source: %s'), $form->getSourceName())); $this->addContent($form); } } diff --git a/application/controllers/SourcesController.php b/application/controllers/SourcesController.php index 9ca4f16c..8eb62e69 100644 --- a/application/controllers/SourcesController.php +++ b/application/controllers/SourcesController.php @@ -96,9 +96,8 @@ public function addAction(): void { $form = (new SourceForm(Database::get())) ->on(SourceForm::ON_SUCCESS, function (SourceForm $form) { - /** @var string $sourceName */ - $sourceName = $form->getValue('name'); - Notification::success(sprintf(t('Added new source %s has successfully'), $sourceName)); + $form->addSource(); + Notification::success(sprintf(t('Added new source %s has successfully'), $form->getSourceName())); $this->switchToSingleColumnLayout(); }) ->handleRequest($this->getServerRequest()); diff --git a/application/forms/SourceForm.php b/application/forms/SourceForm.php index 085f7687..32f08a33 100644 --- a/application/forms/SourceForm.php +++ b/application/forms/SourceForm.php @@ -4,12 +4,14 @@ namespace Icinga\Module\Notifications\Forms; +use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Notifications\Model\Source; use Icinga\Web\Session; use ipl\Html\BaseHtmlElement; use ipl\Html\Contract\FormSubmitElement; use ipl\Html\FormElement\CheckboxElement; use ipl\Sql\Connection; +use ipl\Stdlib\Filter; use ipl\Validator\CallbackValidator; use ipl\Validator\X509CertValidator; use ipl\Web\Common\CsrfCounterMeasure; @@ -28,10 +30,9 @@ class SourceForm extends CompatForm /** @var ?int */ private $sourceId; - public function __construct(Connection $db, ?int $sourceId = null) + public function __construct(Connection $db) { $this->db = $db; - $this->sourceId = $sourceId; } protected function assemble(): void @@ -263,42 +264,54 @@ public function hasBeenSubmitted() return parent::hasBeenSubmitted(); } - /** @param iterable|Source $values */ - public function populate($values) + /** + * Load the source with given id + * + * @param int $id + * + * @return $this + */ + public function loadSource(int $id): self { - if ($values instanceof Source) { - $values = [ - 'name' => $values->name, - 'type' => $values->type, - 'icinga2_base_url' => $values->icinga2_base_url, - 'icinga2_auth_user' => $values->icinga2_auth_user, - 'icinga2_auth_pass' => $values->icinga2_auth_pass, - 'icinga2_ca_pem' => $values->icinga2_ca_pem, - 'icinga2_common_name' => $values->icinga2_common_name, - 'icinga2_insecure_tls' => $values->icinga2_insecure_tls - ]; - } + $this->sourceId = $id; - parent::populate($values); + $this->populate($this->fetchDbValues()); return $this; } - protected function onSuccess(): void + /** + * Add the new source + */ + public function addSource(): void { - $pressedButton = $this->getPressedSubmitElement(); - if ($pressedButton && $pressedButton->getName() === 'delete') { - $this->db->update( - 'source', - ['changed_at' => time() * 1000, 'deleted' => 'y'], - ['id = ?' => $this->sourceId] - ); + $source = $this->getValues(); - return; - } + // Not using PASSWORD_DEFAULT, as the used algorithm should + // be kept in sync with what the daemon understands + $source['listener_password_hash'] = password_hash( + $this->getValue('listener_password'), + self::HASH_ALGORITHM + ); + + $this->db->insert('source', $source); + } + + /** + * Edit the source + * + * @return void + */ + public function editSource(): void + { + $this->db->beginTransaction(); $source = $this->getValues(); + if (empty(array_diff_assoc($source, $this->fetchDbValues()))) { + return; + } + /** @var ?string $listenerPassword */ $listenerPassword = $this->getValue('listener_password'); if ($listenerPassword) { @@ -307,11 +320,64 @@ protected function onSuccess(): void $source['listener_password_hash'] = password_hash($listenerPassword, self::HASH_ALGORITHM); } - if ($this->sourceId === null) { - $this->db->insert('source', $source); - } else { - $source['changed_at'] = time() * 1000; - $this->db->update('source', $source, ['id = ?' => $this->sourceId]); + $source['changed_at'] = time() * 1000; + $this->db->update('source', $source, ['id = ?' => $this->sourceId]); + + $this->db->commitTransaction(); + } + + /** + * Remove the source + */ + public function removeSource(): void + { + $this->db->update( + 'source', + ['changed_at' => time() * 1000, 'deleted' => 'y'], + ['id = ?' => $this->sourceId] + ); + } + + /** + * Get the source name + * + * @return string + */ + public function getSourceName(): string + { + return $this->getValue('name'); + } + + /** + * Fetch the values from the database + * + * @return array + * + * @throws HttpNotFoundException + */ + private function fetchDbValues(): array + { + /** @var ?Source $source */ + $source = Source::on($this->db) + ->filter(Filter::all( + Filter::equal('id', $this->sourceId), + Filter::equal('deleted', 'n') + )) + ->first(); + + if ($source === null) { + throw new HttpNotFoundException($this->translate('Source not found')); } + + return [ + 'name' => $source->name, + 'type' => $source->type, + 'icinga2_base_url' => $source->icinga2_base_url, + 'icinga2_auth_user' => $source->icinga2_auth_user, + 'icinga2_auth_pass' => $source->icinga2_auth_pass, + 'icinga2_ca_pem' => $source->icinga2_ca_pem, + 'icinga2_common_name' => $source->icinga2_common_name, + 'icinga2_insecure_tls' => $source->icinga2_insecure_tls + ]; } } From 6081f85846048d553c33fbef45d9da500a6b37be Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 19 Jun 2024 10:03:53 +0200 Subject: [PATCH 09/34] Timeperiod: Fix column name --- library/Notifications/Model/Timeperiod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Notifications/Model/Timeperiod.php b/library/Notifications/Model/Timeperiod.php index 9cbddf6f..c270341a 100644 --- a/library/Notifications/Model/Timeperiod.php +++ b/library/Notifications/Model/Timeperiod.php @@ -38,7 +38,7 @@ public function getKeyName(): string public function getColumns(): array { return [ - 'owned_by_schedule_id', + 'owned_by_rotation_id', 'changed_at', 'deleted' ]; From 9973e68caf991a19727cc7bae00775e41195f6a0 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 19 Jun 2024 16:52:41 +0200 Subject: [PATCH 10/34] Schedule: Update only if changes exist --- .../controllers/ScheduleController.php | 4 +- application/forms/ScheduleForm.php | 86 +++++++++++++------ 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/application/controllers/ScheduleController.php b/application/controllers/ScheduleController.php index 5932e808..30e33d1e 100644 --- a/application/controllers/ScheduleController.php +++ b/application/controllers/ScheduleController.php @@ -72,7 +72,7 @@ public function settingsAction(): void $this->setTitle($this->translate('Edit Schedule')); $scheduleId = (int) $this->params->getRequired('id'); - $form = new ScheduleForm(); + $form = new ScheduleForm(Database::get()); $form->setShowRemoveButton(); $form->loadSchedule($scheduleId); $form->setSubmitLabel($this->translate('Save Changes')); @@ -99,7 +99,7 @@ public function settingsAction(): void public function addAction(): void { $this->setTitle($this->translate('New Schedule')); - $form = (new ScheduleForm()) + $form = (new ScheduleForm(Database::get())) ->setAction($this->getRequest()->getUrl()->getAbsoluteUrl()) ->on(Form::ON_SUCCESS, function (ScheduleForm $form) { $scheduleId = $form->addSchedule(); diff --git a/application/forms/ScheduleForm.php b/application/forms/ScheduleForm.php index 4af75e85..167380f8 100644 --- a/application/forms/ScheduleForm.php +++ b/application/forms/ScheduleForm.php @@ -5,12 +5,11 @@ namespace Icinga\Module\Notifications\Forms; use Icinga\Exception\Http\HttpNotFoundException; -use Icinga\Module\Notifications\Common\Database; use Icinga\Module\Notifications\Model\Rotation; use Icinga\Module\Notifications\Model\Schedule; -use Icinga\Module\Notifications\Model\Timeperiod; use Icinga\Web\Session; use ipl\Html\HtmlDocument; +use ipl\Sql\Connection; use ipl\Stdlib\Filter; use ipl\Web\Common\CsrfCounterMeasure; use ipl\Web\Compat\CompatForm; @@ -25,6 +24,17 @@ class ScheduleForm extends CompatForm /** @var bool */ protected $showRemoveButton = false; + /** @var Connection */ + private $db; + + /** @var ?int */ + private $scheduleId; + + public function __construct(Connection $db) + { + $this->db = $db; + } + public function setSubmitLabel(string $label): self { $this->submitLabel = $label; @@ -54,48 +64,43 @@ public function hasBeenRemoved(): bool public function loadSchedule(int $id): void { - $db = Database::get(); - - $schedule = Schedule::on($db) - ->filter(Filter::all( - Filter::equal('id', $id), - Filter::equal('deleted', 'n') - )) - ->first(); - if ($schedule === null) { - throw new HttpNotFoundException($this->translate('Schedule not found')); - } - - $this->populate(['name' => $schedule->name]); + $this->scheduleId = $id; + $this->populate($this->fetchDbValues()); } public function addSchedule(): int { - $db = Database::get(); - - $db->insert('schedule', [ + $this->db->insert('schedule', [ 'name' => $this->getValue('name') ]); - return $db->lastInsertId(); + return $this->db->lastInsertId(); } public function editSchedule(int $id): void { - $db = Database::get(); + $this->db->beginTransaction(); + + $values = $this->getValues(); + $storedValues = $this->fetchDbValues(); - $db->update('schedule', [ - 'name' => $this->getValue('name'), + if ($values === $storedValues) { + return; + } + + $this->db->update('schedule', [ + 'name' => $values['name'], 'changed_at' => time() * 1000 ], ['id = ?' => $id]); + + $this->db->commitTransaction(); } public function removeSchedule(int $id): void { - $db = Database::get(); - $db->beginTransaction(); + $this->db->beginTransaction(); - $rotations = Rotation::on($db) + $rotations = Rotation::on($this->db) ->columns('priority') ->filter(Filter::all( Filter::equal('schedule_id', $id), @@ -103,15 +108,15 @@ public function removeSchedule(int $id): void )) ->orderBy('priority', SORT_DESC); - $rotationConfigForm = new RotationConfigForm($id, $db); + $rotationConfigForm = new RotationConfigForm($id, $this->db); foreach ($rotations as $rotation) { $rotationConfigForm->wipeRotation($rotation->priority); } - $db->update('schedule', ['changed_at' => time() * 1000, 'deleted' => 'y'], ['id = ?' => $id]); + $this->db->update('schedule', ['changed_at' => time() * 1000, 'deleted' => 'y'], ['id = ?' => $id]); - $db->commitTransaction(); + $this->db->commitTransaction(); } protected function assemble() @@ -138,4 +143,29 @@ protected function assemble() $this->addElement($this->createCsrfCounterMeasure(Session::getSession()->getId())); } + + /** + * Fetch the values from the database + * + * @return string[] + * + * @throws HttpNotFoundException + */ + private function fetchDbValues(): array + { + /** @var ?Schedule $schedule */ + $schedule = Schedule::on($this->db) + ->columns('name') + ->filter(Filter::all( + Filter::equal('id', $this->scheduleId), + Filter::equal('deleted', 'n') + )) + ->first(); + + if ($schedule === null) { + throw new HttpNotFoundException($this->translate('Schedule not found')); + } + + return ['name' => $schedule->name]; + } } From 5b6066087ad28617be319329c7970aeac2226daa Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 21 Jun 2024 11:18:15 +0200 Subject: [PATCH 11/34] Channel: Don't allow to delete channel if in use --- application/forms/ChannelForm.php | 22 +++++++++++++++++++++- public/css/form.less | 8 ++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index 7bb56455..450a33eb 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -7,6 +7,7 @@ use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Notifications\Model\Channel; use Icinga\Module\Notifications\Model\AvailableChannelType; +use Icinga\Module\Notifications\Model\RuleEscalationRecipient; use Icinga\Web\Session; use ipl\Html\Contract\FormSubmitElement; use ipl\Html\FormElement\BaseFormElement; @@ -52,6 +53,7 @@ public function __construct(Connection $db) protected function assemble() { + $this->addAttributes(['class' => 'channel-form']); $this->addElement($this->createCsrfCounterMeasure(Session::getSession()->getId())); $this->addElement( @@ -111,6 +113,17 @@ protected function assemble() ); if ($this->channelId !== null) { + $isInUse = RuleEscalationRecipient::on($this->db) + ->columns('1') + ->filter(Filter::all( + Filter::equal('deleted', 'n'), + Filter::any( + Filter::equal('channel_id', $this->channelId), + Filter::equal('contact.default_channel_id', $this->channelId) + ) + )) + ->first(); + /** @var FormSubmitElement $deleteButton */ $deleteButton = $this->createElement( 'submit', @@ -118,7 +131,14 @@ protected function assemble() [ 'label' => $this->translate('Delete'), 'class' => 'btn-remove', - 'formnovalidate' => true + 'formnovalidate' => true, + 'disabled' => $isInUse !== null, + 'title' => $isInUse + ? $this->translate( + "Channel is still referenced as a contact's default" + . " channel or in an event rule's escalation" + ) + : null ] ); diff --git a/public/css/form.less b/public/css/form.less index ca2a2406..c9626b66 100644 --- a/public/css/form.less +++ b/public/css/form.less @@ -108,3 +108,11 @@ .source-form textarea { .monospace-font(); } + +.channel-form { + .btn-remove:disabled { + background: @gray-light; + color: @disabled-gray; + border-color: transparent; + } +} From 6ab3f5b254b465fb5b6f3423f7a0b9c57a48ec25 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 21 Jun 2024 13:29:36 +0200 Subject: [PATCH 12/34] ObjectSuggestions: Consider `deleted` column for value suggestions --- .../Notifications/Web/Control/SearchBar/ObjectSuggestions.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php b/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php index 6ca9b208..108927fa 100644 --- a/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php +++ b/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php @@ -102,6 +102,10 @@ protected function fetchValueSuggestions($column, $searchTerm, Filter\Chain $sea $query = $model::on(Database::get()); $query->limit(static::DEFAULT_LIMIT); + if ($query->getResolver()->hasSelectableColumn($model, 'deleted')) { + $query->filter(Filter::equal('deleted', 'n')); + } + if (strpos($column, ' ') !== false) { // $column may be a label /** @var string $path */ From 4c9d2634d1dec2e1e734e209c8ffe48faaeccaab Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 24 Jun 2024 14:08:45 +0200 Subject: [PATCH 13/34] RotationConfigForm: Update only if changes exist --- application/forms/RotationConfigForm.php | 140 ++++++++++++++++------- 1 file changed, 98 insertions(+), 42 deletions(-) diff --git a/application/forms/RotationConfigForm.php b/application/forms/RotationConfigForm.php index 44d85aa0..661075ff 100644 --- a/application/forms/RotationConfigForm.php +++ b/application/forms/RotationConfigForm.php @@ -75,6 +75,9 @@ class RotationConfigForm extends CompatForm /** @var ?DateTime The first handoff of a newer version for this rotation */ protected $nextHandoff; + /** @var int The rotation id */ + protected $rotationId; + /** * Set the label for the submit button * @@ -199,27 +202,7 @@ public function __construct(int $scheduleId, Connection $db) */ public function loadRotation(int $rotationId): self { - /** @var ?Rotation $rotation */ - $rotation = Rotation::on($this->db) - ->filter(Filter::all( - Filter::equal('id', $rotationId), - Filter::equal('deleted', 'n') - )) - ->first(); - if ($rotation === null) { - throw new HttpNotFoundException($this->translate('Rotation not found')); - } - - $formData = [ - 'mode' => $rotation->mode, - 'name' => $rotation->name, - 'priority' => $rotation->priority, - 'schedule' => $rotation->schedule_id, - 'options' => $rotation->options - ]; - if (! self::EXPERIMENTAL_OVERRIDES) { - $formData['first_handoff'] = $rotation->first_handoff; - } + $this->rotationId = $rotationId; if (self::EXPERIMENTAL_OVERRIDES) { $getHandoff = function (Rotation $rotation): DateTime { @@ -248,6 +231,17 @@ public function loadRotation(int $rotationId): self return $handoff; }; + /** @var ?Rotation $rotation */ + $rotation = Rotation::on($this->db) + ->filter(Filter::all( + Filter::equal('id', $this->rotationId), + Filter::equal('deleted', 'n') + )) + ->first(); + if ($rotation === null) { + throw new HttpNotFoundException($this->translate('Rotation not found')); + } + $this->previousHandoff = $getHandoff($rotation); /** @var ?TimeperiodEntry $previousShift */ @@ -284,27 +278,7 @@ public function loadRotation(int $rotationId): self } } - $membersRes = $rotation - ->member - ->filter(Filter::equal('deleted', 'n')) - ->filter(Filter::any( - Filter::equal('contact.deleted', 'n'), - Filter::equal('contactgroup.deleted', 'n') - )) - ->orderBy('position', SORT_ASC); - - $members = []; - foreach ($membersRes as $member) { - if ($member->contact_id !== null) { - $members[] = 'contact:' . $member->contact_id; - } else { - $members[] = 'group:' . $member->contactgroup_id; - } - } - - $formData['members'] = implode(',', $members); - - $this->populate($formData); + $this->populate($this->fetchDbValues()); return $this; } @@ -442,6 +416,10 @@ public function editRotation(int $rotationId): void $transactionStarted = $this->db->beginTransaction(); } + if (! $this->hasChanges()) { + return; + } + // Delay the creation, avoids intermediate constraint failures $createStmt = $this->createRotation((int) $priority); @@ -1602,4 +1580,82 @@ private function calculateRemainingHandoffs(Rule $rrule, DateInterval $shiftDura return $result; } + + /** + * Fetch the values from the database + * + * @return array + * + * @throws HttpNotFoundException + */ + private function fetchDbValues(): array + { + /** @var ?Rotation $rotation */ + $rotation = Rotation::on($this->db) + ->filter(Filter::all( + Filter::equal('id', $this->rotationId), + Filter::equal('deleted', 'n') + )) + ->first(); + if ($rotation === null) { + throw new HttpNotFoundException($this->translate('Rotation not found')); + } + + $formData = [ + 'mode' => $rotation->mode, + 'name' => $rotation->name, + 'priority' => $rotation->priority, + 'schedule' => $rotation->schedule_id, + 'options' => $rotation->options + ]; + if (! self::EXPERIMENTAL_OVERRIDES) { + $formData['first_handoff'] = $rotation->first_handoff; + } + + $membersRes = $rotation + ->member + ->filter(Filter::equal('deleted', 'n')) + ->filter(Filter::any( + Filter::equal('contact.deleted', 'n'), + Filter::equal('contactgroup.deleted', 'n') + )) + ->orderBy('position', SORT_ASC); + + $members = []; + foreach ($membersRes as $member) { + if ($member->contact_id !== null) { + $members[] = 'contact:' . $member->contact_id; + } else { + $members[] = 'group:' . $member->contactgroup_id; + } + } + + $formData['members'] = implode(',', $members); + + return $formData; + } + + /** + * Whether the form has changes + * + * @return bool + */ + public function hasChanges(): bool + { + $values = $this->getValues(); + $values['members'] = $this->getValue('members'); + + // only keys that are present in $values + $dbValuesToCompare = array_intersect_key($this->fetchDbValues(), $values); + + $checker = static function ($a, $b) use (&$checker) { + if (! is_array($a) || ! is_array($b)) { + return $a <=> $b; + } + + return empty(array_udiff_assoc($a, $b, $checker)) ? 0 : 1; + }; + + return ! empty(array_udiff_assoc($values, $dbValuesToCompare, $checker)); + } } From d5b9ad6bb17b59c66c243f863c6a4606e52c8b55 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 1 Jul 2024 13:18:25 +0200 Subject: [PATCH 14/34] EventRuleConfig: Consider `changed_at`, `deleted` column Only update if changes have been made --- .../controllers/EventRuleController.php | 17 +- application/forms/SaveEventRuleForm.php | 190 +++++++++++++++--- 2 files changed, 175 insertions(+), 32 deletions(-) diff --git a/application/controllers/EventRuleController.php b/application/controllers/EventRuleController.php index e4d655e2..9d56b64a 100644 --- a/application/controllers/EventRuleController.php +++ b/application/controllers/EventRuleController.php @@ -10,7 +10,6 @@ use Icinga\Module\Notifications\Forms\EventRuleForm; use Icinga\Module\Notifications\Forms\SaveEventRuleForm; use Icinga\Module\Notifications\Model\Incident; -use Icinga\Module\Notifications\Model\ObjectExtraTag; use Icinga\Module\Notifications\Model\Rule; use Icinga\Module\Notifications\Web\Control\SearchBar\ExtraTagSuggestions; use Icinga\Module\Notifications\Widget\EventRuleConfig; @@ -151,7 +150,7 @@ public function indexAction(): void public function fromDb(int $ruleId): array { $query = Rule::on(Database::get()) - ->withoutColumns('timeperiod_id') + ->columns(['id', 'name', 'object_filter', 'is_active']) ->filter(Filter::all( Filter::equal('id', $ruleId), Filter::equal('deleted', 'n') @@ -164,12 +163,22 @@ public function fromDb(int $ruleId): array $config = iterator_to_array($rule); - foreach ($rule->rule_escalation as $re) { + $ruleEscalations = $rule + ->rule_escalation + ->withoutColumns(['changed_at', 'deleted']) + ->filter(Filter::equal('deleted', 'n')); + + foreach ($ruleEscalations as $re) { foreach ($re as $k => $v) { $config[$re->getTableName()][$re->position][$k] = $v; } - foreach ($re->rule_escalation_recipient as $recipient) { + $escalationRecipients = $re + ->rule_escalation_recipient + ->withoutColumns(['changed_at', 'deleted']) + ->filter(Filter::equal('deleted', 'n')); + + foreach ($escalationRecipients as $recipient) { $config[$re->getTableName()][$re->position]['recipient'][] = iterator_to_array($recipient); } } diff --git a/application/forms/SaveEventRuleForm.php b/application/forms/SaveEventRuleForm.php index f7bd7aa0..bd34c30f 100644 --- a/application/forms/SaveEventRuleForm.php +++ b/application/forms/SaveEventRuleForm.php @@ -5,7 +5,9 @@ namespace Icinga\Module\Notifications\Forms; use Exception; +use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Notifications\Common\Database; +use Icinga\Module\Notifications\Model\Rule; use Icinga\Module\Notifications\Model\RuleEscalation; use Icinga\Module\Notifications\Model\RuleEscalationRecipient; use Icinga\Web\Notification; @@ -47,6 +49,9 @@ class SaveEventRuleForm extends Form /** @var bool Whether to disable the remove button */ protected $disableRemoveButton = false; + /** @var int The rule id */ + protected $ruleId; + /** * Create a new SaveEventRuleForm */ @@ -294,6 +299,7 @@ public function addRule(array $config): int */ private function insertOrUpdateEscalations($ruleId, array $escalations, Connection $db, bool $insert = false): void { + $changedAt = time() * 1000; foreach ($escalations as $position => $escalationConfig) { if ($insert) { $db->insert('rule_escalation', [ @@ -307,18 +313,21 @@ private function insertOrUpdateEscalations($ruleId, array $escalations, Connecti $escalationId = $db->lastInsertId(); } else { $escalationId = $escalationConfig['id']; - $db->update('rule_escalation', [ 'position' => $position, 'condition' => $escalationConfig['condition'] ?? null, 'name' => $escalationConfig['name'] ?? null, - 'fallback_for' => $escalationConfig['fallback_for'] ?? null + 'fallback_for' => $escalationConfig['fallback_for'] ?? null, + 'changed_at' => $changedAt ], ['id = ?' => $escalationId, 'rule_id = ?' => $ruleId]); $recipientsToRemove = []; $recipients = RuleEscalationRecipient::on($db) ->columns('id') - ->filter(Filter::equal('rule_escalation_id', $escalationId)); + ->filter(Filter::all( + Filter::equal('rule_escalation_id', $escalationId), + Filter::equal('deleted', 'n') + )); foreach ($recipients as $recipient) { $recipientId = $recipient->id; @@ -336,7 +345,11 @@ function (array $element) use ($recipientId) { } if (! empty($recipientsToRemove)) { - $db->delete('rule_escalation_recipient', ['id IN (?)' => $recipientsToRemove]); + $db->update( + 'rule_escalation_recipient', + ['changed_at' => $changedAt, 'deleted' => 'y'], + ['id IN (?)' => $recipientsToRemove] + ); } } @@ -367,7 +380,11 @@ function (array $element) use ($recipientId) { if (! isset($recipientConfig['id'])) { $db->insert('rule_escalation_recipient', $data); } else { - $db->update('rule_escalation_recipient', $data, ['id = ?' => $recipientConfig['id']]); + $db->update( + 'rule_escalation_recipient', + $data + ['changed_at' => $changedAt], + ['id = ?' => $recipientConfig['id']] + ); } } } @@ -383,27 +400,43 @@ function (array $element) use ($recipientId) { */ public function editRule(int $id, array $config): void { + $this->ruleId = $id; + $db = Database::get(); $db->beginTransaction(); - $db->update('rule', [ - 'name' => $config['name'], - 'timeperiod_id' => $config['timeperiod_id'] ?? null, - 'object_filter' => $config['object_filter'] ?? null, - 'is_active' => $config['is_active'] ?? 'n' - ], ['id = ?' => $id]); + $storedValues = $this->fetchDbValues(); - $escalationsFromDb = RuleEscalation::on($db) - ->filter(Filter::equal('rule_id', $id)); + $values = $this->getChanges($storedValues, $config); + + $data = array_filter([ + 'name' => $values['name'] ?? null, + 'is_active' => $values['is_active'] ?? null + ]); + + if (array_key_exists('object_filter', $values)) { + $data['object_filter'] = $values['object_filter']; + } + + $changedAt = time() * 1000; + if (! empty($data)) { + $db->update('rule', $data + ['changed_at' => $changedAt], ['id = ?' => $id]); + } + + if (! isset($values['rule_escalation'])) { + $db->commitTransaction(); + + return; + } $escalationsInCache = $config['rule_escalation']; $escalationsToUpdate = []; $escalationsToRemove = []; - foreach ($escalationsFromDb as $escalationInDB) { - $escalationId = $escalationInDB->id; + foreach ($storedValues['rule_escalation'] as $escalationInDB) { + $escalationId = $escalationInDB['id']; $escalationInCache = array_filter($escalationsInCache, function (array $element) use ($escalationId) { return (int) $element['id'] === $escalationId; }); @@ -422,9 +455,19 @@ public function editRule(int $id, array $config): void // Escalations to add $escalationsToAdd = $escalationsInCache; + $markAsDeleted = ['changed_at' => $changedAt, 'deleted' => 'y']; if (! empty($escalationsToRemove)) { - $db->delete('rule_escalation_recipient', ['rule_escalation_id IN (?)' => $escalationsToRemove]); - $db->delete('rule_escalation', ['id IN (?)' => $escalationsToRemove]); + $db->update( + 'rule_escalation_recipient', + $markAsDeleted, + ['rule_escalation_id IN (?)' => $escalationsToRemove] + ); + + $db->update( + 'rule_escalation', + $markAsDeleted + ['position' => null], + ['id IN (?)' => $escalationsToRemove] + ); } if (! empty($escalationsToAdd)) { @@ -451,21 +494,26 @@ public function removeRule(int $id): void $db->beginTransaction(); - $escalations = RuleEscalation::on($db) - ->columns('id') - ->filter(Filter::equal('rule_id', $id)); - - $escalationsToRemove = []; - foreach ($escalations as $escalation) { - $escalationsToRemove[] = $escalation->id; - } + $escalationsToRemove = $db->fetchCol( + RuleEscalation::on($db) + ->columns('id') + ->filter(Filter::all( + Filter::equal('rule_id', $id), + Filter::equal('deleted', 'n') + ))->assembleSelect() + ); + $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; if (! empty($escalationsToRemove)) { - $db->delete('rule_escalation_recipient', ['rule_escalation_id IN (?)' => $escalationsToRemove]); + $db->update( + 'rule_escalation_recipient', + $markAsDeleted, + ['rule_escalation_id IN (?)' => $escalationsToRemove] + ); } - $db->delete('rule_escalation', ['rule_id = ?' => $id]); - $db->delete('rule', ['id = ?' => $id]); + $db->update('rule_escalation', $markAsDeleted + ['position' => null], ['rule_id = ?' => $id]); + $db->update('rule', $markAsDeleted, ['id = ?' => $id]); $db->commitTransaction(); } @@ -478,4 +526,90 @@ protected function onError() } } } + + /** + * Fetch the values from the database + * + * @return array + * + * @throws HttpNotFoundException + */ + private function fetchDbValues(): array + { + $query = Rule::on(Database::get()) + ->columns(['id', 'name', 'object_filter', 'is_active']) + ->filter(Filter::all( + Filter::equal('id', $this->ruleId), + Filter::equal('deleted', 'n') + )); + + $rule = $query->first(); + if ($rule === null) { + throw new HttpNotFoundException($this->translate('Rule not found')); + } + + $config = iterator_to_array($rule); + + $ruleEscalations = $rule + ->rule_escalation + ->withoutColumns(['changed_at', 'deleted']) + ->filter(Filter::equal('deleted', 'n')); + + foreach ($ruleEscalations as $re) { + foreach ($re as $k => $v) { + $config[$re->getTableName()][$re->position][$k] = $v; + } + + $escalationRecipients = $re + ->rule_escalation_recipient + ->withoutColumns(['changed_at', 'deleted']) + ->filter(Filter::equal('deleted', 'n')); + + foreach ($escalationRecipients as $recipient) { + $config[$re->getTableName()][$re->position]['recipient'][] = iterator_to_array($recipient); + } + } + + $config['showSearchbar'] = ! empty($config['object_filter']); + + return $config; + } + + /** + * Get the newly made changes + * + * @return array + */ + public function getChanges(array $storedValues, array $formValues): array + { + unset($formValues['conditionPlusButtonPosition']); + $dbValuesToCompare = array_intersect_key($storedValues, $formValues); + + if (count($formValues, COUNT_RECURSIVE) < count($dbValuesToCompare, COUNT_RECURSIVE)) { + // fewer values in the form than in the db, escalation(s) has been removed + if ($formValues['name'] === $dbValuesToCompare['name']) { + unset($formValues['name']); + } + + if ($formValues['object_filter'] === $dbValuesToCompare['object_filter']) { + unset($formValues['object_filter']); + } + + if ($formValues['is_active'] === $dbValuesToCompare['is_active']) { + unset($formValues['is_active']); + } + + return $formValues; + } + + $checker = static function ($a, $b) use (&$checker) { + if (! is_array($a) || ! is_array($b)) { + return $a <=> $b; + } + + return empty(array_udiff_assoc($a, $b, $checker)) ? 0 : 1; + }; + + return array_udiff_assoc($formValues, $dbValuesToCompare, $checker); + } } From e5b62d507fef0e391dc4a26fecc85c176177065e Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 1 Jul 2024 13:26:40 +0200 Subject: [PATCH 15/34] Fix codeSniffer errors --- application/forms/ChannelForm.php | 2 +- application/forms/ContactGroupForm.php | 6 ++++-- application/forms/MoveRotationForm.php | 3 ++- application/forms/RotationConfigForm.php | 3 ++- application/forms/SaveEventRuleForm.php | 2 +- library/Notifications/Widget/Timeline/Rotation.php | 4 ++-- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index 450a33eb..694cf6bc 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -217,7 +217,7 @@ public function editChannel(): void $channel['config'] = json_encode($this->filterConfig($channel['config'])); $storedValues['config'] = json_encode($this->filterConfig($storedValues['config'])); - if(! empty(array_diff_assoc($channel, $storedValues))) { + if (! empty(array_diff_assoc($channel, $storedValues))) { $channel['changed_at'] = time() * 1000; $this->db->update('channel', $channel, ['id = ?' => $this->channelId]); diff --git a/application/forms/ContactGroupForm.php b/application/forms/ContactGroupForm.php index de5a9063..7570f58a 100644 --- a/application/forms/ContactGroupForm.php +++ b/application/forms/ContactGroupForm.php @@ -235,7 +235,8 @@ public function editGroup(): void $toAdd = array_diff($newContacts, $storedContacts); if (! empty($toDelete)) { - $this->db->update('contactgroup_member', + $this->db->update( + 'contactgroup_member', ['changed_at' => $changedAt, 'deleted' => 'y'], [ 'contactgroup_id = ?' => $this->contactgroupId, @@ -270,7 +271,8 @@ public function editGroup(): void } } if (! empty($removeDeletedFlagFromIds)) { - $this->db->update('contactgroup_member', + $this->db->update( + 'contactgroup_member', ['changed_at' => $changedAt, 'deleted' => 'n'], [ 'contactgroup_id = ?' => $this->contactgroupId, diff --git a/application/forms/MoveRotationForm.php b/application/forms/MoveRotationForm.php index 1e85b050..aea1c642 100644 --- a/application/forms/MoveRotationForm.php +++ b/application/forms/MoveRotationForm.php @@ -151,7 +151,8 @@ protected function onSuccess() } // Now insert the rotation at the new priority - $this->db->update('rotation', + $this->db->update( + 'rotation', ['priority' => $newPriority, 'changed_at' => $changedAt, 'deleted' => 'n'], ['id = ?' => $rotationId] ); diff --git a/application/forms/RotationConfigForm.php b/application/forms/RotationConfigForm.php index 661075ff..ee0254dd 100644 --- a/application/forms/RotationConfigForm.php +++ b/application/forms/RotationConfigForm.php @@ -428,7 +428,8 @@ public function editRotation(int $rotationId): void if (self::EXPERIMENTAL_OVERRIDES) { // We only show a single name, even in case of multiple versions of a rotation. // To avoid confusion, we update all versions upon change of the name - $this->db->update('rotation', + $this->db->update( + 'rotation', ['name' => $this->getValue('name'), 'changed_at' => $changedAt], ['schedule_id = ?' => $this->scheduleId, 'priority = ?' => $priority] ); diff --git a/application/forms/SaveEventRuleForm.php b/application/forms/SaveEventRuleForm.php index bd34c30f..c32f690d 100644 --- a/application/forms/SaveEventRuleForm.php +++ b/application/forms/SaveEventRuleForm.php @@ -512,7 +512,7 @@ public function removeRule(int $id): void ); } - $db->update('rule_escalation', $markAsDeleted + ['position' => null], ['rule_id = ?' => $id]); + $db->update('rule_escalation', $markAsDeleted + ['position' => null], ['rule_id = ?' => $id]); $db->update('rule', $markAsDeleted, ['id = ?' => $id]); $db->commitTransaction(); diff --git a/library/Notifications/Widget/Timeline/Rotation.php b/library/Notifications/Widget/Timeline/Rotation.php index 9b9c760a..816f583c 100644 --- a/library/Notifications/Widget/Timeline/Rotation.php +++ b/library/Notifications/Widget/Timeline/Rotation.php @@ -79,8 +79,8 @@ public function fetchTimeperiodEntries(DateTime $after, DateTime $until): Genera ->filter(Filter::all( Filter::equal('deleted', 'n'), Filter::any( - Filter::equal('member.contact.deleted', 'n'), - Filter::equal('member.contactgroup.deleted', 'n') + Filter::equal('member.contact.deleted', 'n'), + Filter::equal('member.contactgroup.deleted', 'n') ), Filter::any( Filter::like('rrule', '*'), // It's either a repeating entry From f305919f72c1d3aa9ff749552b88a8541aaa2a0a Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 3 Jul 2024 09:18:57 +0200 Subject: [PATCH 16/34] Models: Fix phpDoc --- .../Model/AvailableChannelType.php | 4 ++-- library/Notifications/Model/Channel.php | 2 +- library/Notifications/Model/Contact.php | 3 ++- library/Notifications/Model/Contactgroup.php | 9 ++++---- .../Model/ContactgroupMember.php | 8 +++---- library/Notifications/Model/Event.php | 6 ++--- library/Notifications/Model/Incident.php | 14 ++++++------ .../Notifications/Model/IncidentContact.php | 2 +- .../Notifications/Model/IncidentHistory.php | 22 ++++++++++--------- library/Notifications/Model/Objects.php | 14 +++++------- library/Notifications/Model/Rotation.php | 2 +- .../Notifications/Model/RotationMember.php | 2 +- library/Notifications/Model/Rule.php | 2 +- .../Notifications/Model/RuleEscalation.php | 13 ++++++----- .../Model/RuleEscalationRecipient.php | 8 +++---- library/Notifications/Model/Schedule.php | 6 ++--- 16 files changed, 60 insertions(+), 57 deletions(-) diff --git a/library/Notifications/Model/AvailableChannelType.php b/library/Notifications/Model/AvailableChannelType.php index a2d5cae4..0cec05cb 100644 --- a/library/Notifications/Model/AvailableChannelType.php +++ b/library/Notifications/Model/AvailableChannelType.php @@ -12,7 +12,7 @@ * @property string $type * @property string $name * @property string $version - * @property string $author_name + * @property string $author * @property string $config_attrs * * @property Query|Channel $channel @@ -34,7 +34,7 @@ public function getColumns(): array return [ 'name', 'version', - 'author_name', + 'author', 'config_attrs', ]; } diff --git a/library/Notifications/Model/Channel.php b/library/Notifications/Model/Channel.php index 6414fcc1..e602e422 100644 --- a/library/Notifications/Model/Channel.php +++ b/library/Notifications/Model/Channel.php @@ -19,7 +19,7 @@ * @property int $id * @property string $name * @property string $type - * @property string $config + * @property ?string $config * @property DateTime $changed_at * @property bool $deleted * diff --git a/library/Notifications/Model/Contact.php b/library/Notifications/Model/Contact.php index 1ab8c3e8..10f17b01 100644 --- a/library/Notifications/Model/Contact.php +++ b/library/Notifications/Model/Contact.php @@ -18,7 +18,6 @@ * @property string $full_name * @property ?string $username * @property int $default_channel_id - * @property string $color * @property DateTime $changed_at * @property bool $deleted * @@ -29,6 +28,8 @@ * @property Query|RotationMember $rotation_member * @property Query|ContactAddress $contact_address * @property Query|RuleEscalationRecipient $rule_escalation_recipient + * @property Query|ContactgroupMember $contactgroup_member + * @property Query|Contactgroup $contactgroup */ class Contact extends Model { diff --git a/library/Notifications/Model/Contactgroup.php b/library/Notifications/Model/Contactgroup.php index 4eba6e3b..3dc79481 100644 --- a/library/Notifications/Model/Contactgroup.php +++ b/library/Notifications/Model/Contactgroup.php @@ -15,14 +15,15 @@ /** * Contact group * - * @property string $id + * @property int $id * @property string $name * @property DateTime $changed_at * @property bool $deleted * - * @property Query | Contact $contact - * @property Query | RuleEscalationRecipient $rule_escalation_recipient - * @property Query | IncidentHistory $incident_history + * @property Query|Contact $contact + * @property Query|ContactgroupMember $contactgroup_member + * @property Query|RuleEscalationRecipient $rule_escalation_recipient + * @property Query|IncidentHistory $incident_history */ class Contactgroup extends Model { diff --git a/library/Notifications/Model/ContactgroupMember.php b/library/Notifications/Model/ContactgroupMember.php index 46fbfe78..97741c8c 100644 --- a/library/Notifications/Model/ContactgroupMember.php +++ b/library/Notifications/Model/ContactgroupMember.php @@ -15,13 +15,13 @@ /** * Contactgroup Member * - * @param string $contactgroup_id - * @param string $contact_id + * @param int $contactgroup_id + * @param int $contact_id * @param DateTime $changed_at * @param bool $deleted * - * @property Query | Contactgroup $contactgroup - * @property Query | Contact $contact + * @property Query|Contactgroup $contactgroup + * @property Query|Contact $contact */ class ContactgroupMember extends Model { diff --git a/library/Notifications/Model/Event.php b/library/Notifications/Model/Event.php index a59bfef6..6aea53bc 100644 --- a/library/Notifications/Model/Event.php +++ b/library/Notifications/Model/Event.php @@ -31,9 +31,9 @@ * @property ?bool $mute * @property ?string $mute_reason * - * @property Query | Objects $object - * @property Query | IncidentHistory $incident_history - * @property Query | Incident $incident + * @property Query|Objects $object + * @property Query|IncidentHistory $incident_history + * @property Query|Incident $incident */ class Event extends Model { diff --git a/library/Notifications/Model/Incident.php b/library/Notifications/Model/Incident.php index ddac10cb..4f86e1d7 100644 --- a/library/Notifications/Model/Incident.php +++ b/library/Notifications/Model/Incident.php @@ -24,13 +24,13 @@ * @property ?DateTime $recovered_at * @property string $severity * - * @property Query | Objects $object - * @property Query | Event $event - * @property Query | Contact $contact - * @property Query | IncidentContact $incident_contact - * @property Query | IncidentHistory $incident_history - * @property Query | Rule $rule - * @property Query | RuleEscalation $rule_escalation + * @property Query|Objects $object + * @property Query|Event $event + * @property Query|Contact $contact + * @property Query|IncidentContact $incident_contact + * @property Query|IncidentHistory $incident_history + * @property Query|Rule $rule + * @property Query|RuleEscalation $rule_escalation */ class Incident extends Model { diff --git a/library/Notifications/Model/IncidentContact.php b/library/Notifications/Model/IncidentContact.php index 317528d1..c1dfc35f 100644 --- a/library/Notifications/Model/IncidentContact.php +++ b/library/Notifications/Model/IncidentContact.php @@ -10,7 +10,7 @@ /** * @property int $incident_id - * @property int $contact_id + * @property ?int $contact_id * @property string $role * * @property Query|Incident $incident diff --git a/library/Notifications/Model/IncidentHistory.php b/library/Notifications/Model/IncidentHistory.php index b76ebbba..c8bcec66 100644 --- a/library/Notifications/Model/IncidentHistory.php +++ b/library/Notifications/Model/IncidentHistory.php @@ -25,23 +25,25 @@ * @property DateTime $time * @property string $type * @property ?int $contact_id + * @property ?int $schedule_id + * @property ?int $contactgroup_id * @property ?int $channel_id * @property ?string $new_severity * @property ?string $old_severity * @property ?string $new_recipient_role * @property ?string $old_recipient_role * @property ?string $message - * @property string $notification_state - * @property DateTime $sent_at + * @property ?string $notification_state + * @property ?DateTime $sent_at * - * @property Query | Incident $incident - * @property Query | Event $event - * @property Query | Contact $contact - * @property Query | Contactgroup $contactgroup - * @property Query | Schedule $schedule - * @property Query | Rule $rule - * @property Query | RuleEscalation $rule_escalation - * @property Query | Channel $channel + * @property Query|Incident $incident + * @property Query|Event $event + * @property Query|Contact $contact + * @property Query|Contactgroup $contactgroup + * @property Query|Schedule $schedule + * @property Query|Rule $rule + * @property Query|RuleEscalation $rule_escalation + * @property Query|Channel $channel */ class IncidentHistory extends Model { diff --git a/library/Notifications/Model/Objects.php b/library/Notifications/Model/Objects.php index f06f5529..c6210565 100644 --- a/library/Notifications/Model/Objects.php +++ b/library/Notifications/Model/Objects.php @@ -19,17 +19,15 @@ * @property string $id * @property int $source_id * @property string $name - * @property string $host - * @property ?string $service * @property ?string $url * @property ?string $mute_reason * - * @property Query | Event $event - * @property Query | Incident $incident - * @property Query | Tag $tag - * @property Query | ObjectExtraTag $object_extra_tag - * @property Query | ExtraTag $extra_tag - * @property Query | Source $source + * @property Query|Event $event + * @property Query|Incident $incident + * @property Query|Tag $tag + * @property Query|ObjectExtraTag $object_extra_tag + * @property Query|ExtraTag $extra_tag + * @property Query|Source $source * @property array $id_tags */ class Objects extends Model diff --git a/library/Notifications/Model/Rotation.php b/library/Notifications/Model/Rotation.php index 4a4f51ce..276eebe8 100644 --- a/library/Notifications/Model/Rotation.php +++ b/library/Notifications/Model/Rotation.php @@ -19,7 +19,7 @@ * * @property int $id * @property int $schedule_id - * @property int $priority + * @property ?int $priority * @property string $name * @property string $mode * @property string|array $options diff --git a/library/Notifications/Model/RotationMember.php b/library/Notifications/Model/RotationMember.php index c1ac0b74..69573b35 100644 --- a/library/Notifications/Model/RotationMember.php +++ b/library/Notifications/Model/RotationMember.php @@ -19,7 +19,7 @@ * @property int $rotation_id * @property ?int $contact_id * @property ?int $contactgroup_id - * @property int $position + * @property ?int $position * @property DateTime $changed_at * @property bool $deleted * diff --git a/library/Notifications/Model/Rule.php b/library/Notifications/Model/Rule.php index dff07f5e..e76c9b27 100644 --- a/library/Notifications/Model/Rule.php +++ b/library/Notifications/Model/Rule.php @@ -15,7 +15,7 @@ /** * @property int $id * @property string $name - * @property int $timeperiod_id + * @property ?int $timeperiod_id * @property ?string $object_filter * @property string $is_active * @property DateTime $changed_at diff --git a/library/Notifications/Model/RuleEscalation.php b/library/Notifications/Model/RuleEscalation.php index 54b74bbb..5276308e 100644 --- a/library/Notifications/Model/RuleEscalation.php +++ b/library/Notifications/Model/RuleEscalation.php @@ -9,23 +9,24 @@ use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; +use ipl\Orm\Query; use ipl\Orm\Relations; /** * @property int $id * @property int $rule_id - * @property int $position + * @property ?int $position * @property ?string $condition * @property ?string $name * @property ?string $fallback_for * @property DateTime $changed_at * @property bool $deleted * - * @property Rule $rule - * @property Incident $incident - * @property Contact $contact - * @property RuleEscalationRecipient $rule_escalation_recipient - * @property IncidentHistory $incident_history + * @property Query|Rule $rule + * @property Query|Incident $incident + * @property Query|Contact $contact + * @property Query|RuleEscalationRecipient $rule_escalation_recipient + * @property Query|IncidentHistory $incident_history */ class RuleEscalation extends Model { diff --git a/library/Notifications/Model/RuleEscalationRecipient.php b/library/Notifications/Model/RuleEscalationRecipient.php index c935969e..5d579b68 100644 --- a/library/Notifications/Model/RuleEscalationRecipient.php +++ b/library/Notifications/Model/RuleEscalationRecipient.php @@ -16,10 +16,10 @@ /** * @property int $id * @property int $rule_escalation_id - * @property int $contact_id - * @property int $contactgroup_id - * @property int $schedule_id - * @property int $channel_id + * @property ?int $contact_id + * @property ?int $contactgroup_id + * @property ?int $schedule_id + * @property ?int $channel_id * @property DateTime $changed_at * @property bool $deleted * diff --git a/library/Notifications/Model/Schedule.php b/library/Notifications/Model/Schedule.php index b3e948d7..c3f82339 100644 --- a/library/Notifications/Model/Schedule.php +++ b/library/Notifications/Model/Schedule.php @@ -18,9 +18,9 @@ * @property DateTime $changed_at * @property bool $deleted * - * @property Rotation|Query $rotation - * @property RuleEscalationRecipient|Query $rule_escalation_recipient - * @property IncidentHistory|Query $incident_history + * @property Query|Rotation $rotation + * @property Query|RuleEscalationRecipient $rule_escalation_recipient + * @property Query|IncidentHistory $incident_history */ class Schedule extends Model { From eefb79c00f0a7749b179994395c711cc39512bcc Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 3 Jul 2024 10:04:12 +0200 Subject: [PATCH 17/34] Rule: Remove column `is_active` and its usage --- .../controllers/EventRuleController.php | 3 +- application/forms/EventRuleForm.php | 9 ----- application/forms/SaveEventRuleForm.php | 12 ++---- library/Notifications/Model/Rule.php | 3 -- library/Notifications/Widget/CheckboxIcon.php | 40 ------------------- .../Widget/ItemList/EventRuleListItem.php | 6 --- 6 files changed, 4 insertions(+), 69 deletions(-) delete mode 100644 library/Notifications/Widget/CheckboxIcon.php diff --git a/application/controllers/EventRuleController.php b/application/controllers/EventRuleController.php index 9d56b64a..fd9f1c11 100644 --- a/application/controllers/EventRuleController.php +++ b/application/controllers/EventRuleController.php @@ -150,7 +150,7 @@ public function indexAction(): void public function fromDb(int $ruleId): array { $query = Rule::on(Database::get()) - ->columns(['id', 'name', 'object_filter', 'is_active']) + ->columns(['id', 'name', 'object_filter']) ->filter(Filter::all( Filter::equal('id', $ruleId), Filter::equal('deleted', 'n') @@ -260,7 +260,6 @@ public function editAction(): void ->setAction(Url::fromRequest()->getAbsoluteUrl()) ->on(Form::ON_SUCCESS, function ($form) use ($ruleId, $cache, $config) { $config['name'] = $form->getValue('name'); - $config['is_active'] = $form->getValue('is_active'); if ($cache || $ruleId === '-1') { $this->sessionNamespace->set($ruleId, $config); diff --git a/application/forms/EventRuleForm.php b/application/forms/EventRuleForm.php index 9ce497ef..2890098e 100644 --- a/application/forms/EventRuleForm.php +++ b/application/forms/EventRuleForm.php @@ -27,15 +27,6 @@ protected function assemble() ] ); - $this->addElement( - 'checkbox', - 'is_active', - [ - 'label' => $this->translate('Event Rule is active'), - 'value' => 'y' - ] - ); - $this->addElement('submit', 'btn_submit', [ 'label' => $this->translate('Save') ]); diff --git a/application/forms/SaveEventRuleForm.php b/application/forms/SaveEventRuleForm.php index c32f690d..17e39255 100644 --- a/application/forms/SaveEventRuleForm.php +++ b/application/forms/SaveEventRuleForm.php @@ -245,8 +245,7 @@ public function addRule(array $config): int $db->insert('rule', [ 'name' => $config['name'], 'timeperiod_id' => $config['timeperiod_id'] ?? null, - 'object_filter' => $config['object_filter'] ?? null, - 'is_active' => $config['is_active'] ?? 'n' + 'object_filter' => $config['object_filter'] ?? null ]); $ruleId = $db->lastInsertId(); @@ -411,8 +410,7 @@ public function editRule(int $id, array $config): void $values = $this->getChanges($storedValues, $config); $data = array_filter([ - 'name' => $values['name'] ?? null, - 'is_active' => $values['is_active'] ?? null + 'name' => $values['name'] ?? null ]); if (array_key_exists('object_filter', $values)) { @@ -537,7 +535,7 @@ protected function onError() private function fetchDbValues(): array { $query = Rule::on(Database::get()) - ->columns(['id', 'name', 'object_filter', 'is_active']) + ->columns(['id', 'name', 'object_filter']) ->filter(Filter::all( Filter::equal('id', $this->ruleId), Filter::equal('deleted', 'n') @@ -595,10 +593,6 @@ public function getChanges(array $storedValues, array $formValues): array unset($formValues['object_filter']); } - if ($formValues['is_active'] === $dbValuesToCompare['is_active']) { - unset($formValues['is_active']); - } - return $formValues; } diff --git a/library/Notifications/Model/Rule.php b/library/Notifications/Model/Rule.php index e76c9b27..8e69a014 100644 --- a/library/Notifications/Model/Rule.php +++ b/library/Notifications/Model/Rule.php @@ -17,7 +17,6 @@ * @property string $name * @property ?int $timeperiod_id * @property ?string $object_filter - * @property string $is_active * @property DateTime $changed_at * @property bool $deleted * @@ -43,7 +42,6 @@ public function getColumns(): array 'name', 'timeperiod_id', 'object_filter', - 'is_active', 'changed_at', 'deleted' ]; @@ -55,7 +53,6 @@ public function getColumnDefinitions(): array 'name' => t('Name'), 'timeperiod_id' => t('Timeperiod ID'), 'object_filter' => t('Object Filter'), - 'is_active' => t('Is Active'), 'changed_at' => t('Changed At') ]; } diff --git a/library/Notifications/Widget/CheckboxIcon.php b/library/Notifications/Widget/CheckboxIcon.php deleted file mode 100644 index 16c5845c..00000000 --- a/library/Notifications/Widget/CheckboxIcon.php +++ /dev/null @@ -1,40 +0,0 @@ - 'checkbox-icon']; - - /** - * Create the checkbox icon - * - * @param bool $isChecked - */ - public function __construct(bool $isChecked = false) - { - $this->isChecked = $isChecked; - } - - protected function assemble() - { - $this->add(Html::tag('span', ['class' => 'inner-slider'])); - - if ($this->isChecked) { - $this->addAttributes(['class' => 'checked']); - } - } -} diff --git a/library/Notifications/Widget/ItemList/EventRuleListItem.php b/library/Notifications/Widget/ItemList/EventRuleListItem.php index cb4127a0..0e8afd14 100644 --- a/library/Notifications/Widget/ItemList/EventRuleListItem.php +++ b/library/Notifications/Widget/ItemList/EventRuleListItem.php @@ -6,7 +6,6 @@ use Icinga\Module\Notifications\Common\Links; use Icinga\Module\Notifications\Model\Rule; -use Icinga\Module\Notifications\Widget\CheckboxIcon; use Icinga\Module\Notifications\Widget\RuleEscalationRecipientBadge; use ipl\Html\BaseHtmlElement; use ipl\Html\Html; @@ -31,11 +30,6 @@ protected function init(): void ->set('data-action-item', true); } - protected function assembleVisual(BaseHtmlElement $visual): void - { - $visual->add(new CheckboxIcon($this->item->is_active === 'y')); - } - protected function assembleFooter(BaseHtmlElement $footer): void { $meta = Html::tag('span', ['class' => 'meta']); From feb9dd9f0f61c98f5ef6ba696b30187d17e77d75 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 3 Jul 2024 13:29:37 +0200 Subject: [PATCH 18/34] ContactGroupForm: Add missing filter and simplify code --- application/forms/ContactGroupForm.php | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/application/forms/ContactGroupForm.php b/application/forms/ContactGroupForm.php index 7570f58a..1f91bd68 100644 --- a/application/forms/ContactGroupForm.php +++ b/application/forms/ContactGroupForm.php @@ -252,31 +252,29 @@ public function editGroup(): void ->columns(['contact_id']) ->where([ 'contactgroup_id = ?' => $this->contactgroupId, + 'deleted = ?' => 'y', 'contact_id IN (?)' => $toAdd ]) ); - $removeDeletedFlagFromIds = []; + $toAdd = array_diff($toAdd, $contactsMarkedAsDeleted); foreach ($toAdd as $contactId) { - if (in_array($contactId, $contactsMarkedAsDeleted)) { - $removeDeletedFlagFromIds[] = $contactId; - } else { - $this->db->insert( - 'contactgroup_member', - [ - 'contactgroup_id' => $this->contactgroupId, - 'contact_id' => $contactId - ] - ); - } + $this->db->insert( + 'contactgroup_member', + [ + 'contactgroup_id' => $this->contactgroupId, + 'contact_id' => $contactId + ] + ); } - if (! empty($removeDeletedFlagFromIds)) { + + if (! empty($contactsMarkedAsDeleted)) { $this->db->update( 'contactgroup_member', ['changed_at' => $changedAt, 'deleted' => 'n'], [ 'contactgroup_id = ?' => $this->contactgroupId, - 'contact_id IN (?)' => $removeDeletedFlagFromIds + 'contact_id IN (?)' => $contactsMarkedAsDeleted ] ); } From 0b0c26fea9f3e279192f6c931eb2d09d81d81b23 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 4 Jul 2024 16:19:58 +0200 Subject: [PATCH 19/34] SourcesController: Fix typo in success message --- application/controllers/SourcesController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/controllers/SourcesController.php b/application/controllers/SourcesController.php index 8eb62e69..e220f061 100644 --- a/application/controllers/SourcesController.php +++ b/application/controllers/SourcesController.php @@ -97,7 +97,7 @@ public function addAction(): void $form = (new SourceForm(Database::get())) ->on(SourceForm::ON_SUCCESS, function (SourceForm $form) { $form->addSource(); - Notification::success(sprintf(t('Added new source %s has successfully'), $form->getSourceName())); + Notification::success(sprintf(t('Added new source %s successfully'), $form->getSourceName())); $this->switchToSingleColumnLayout(); }) ->handleRequest($this->getServerRequest()); From 23a0e9cce8a302f7f84d68fa24a9cc7fb41dc6bd Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 5 Jul 2024 13:45:18 +0200 Subject: [PATCH 20/34] RuleEscalationRecipientBadge: Return earlier if not recipient found --- library/Notifications/Widget/RuleEscalationRecipientBadge.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/Notifications/Widget/RuleEscalationRecipientBadge.php b/library/Notifications/Widget/RuleEscalationRecipientBadge.php index 698b10b1..ba76c53f 100644 --- a/library/Notifications/Widget/RuleEscalationRecipientBadge.php +++ b/library/Notifications/Widget/RuleEscalationRecipientBadge.php @@ -37,6 +37,10 @@ public function __construct(RuleEscalationRecipient $recipient, ?int $moreCount public function createBadge() { $recipientModel = $this->recipient->getRecipient(); + if ($recipientModel === null) { + return; + } + $nameColumn = 'name'; $icon = 'users'; From bb994ede0767783e4ed0faf4c8745156b69cb3de Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 5 Jul 2024 14:09:24 +0200 Subject: [PATCH 21/34] Contact|Schedule|Contactgroup: Cleanup refereces on deletion --- application/forms/ContactGroupForm.php | 43 +++++++++++++++++++ application/forms/ScheduleForm.php | 5 ++- .../Notifications/Web/Form/ContactForm.php | 40 +++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/application/forms/ContactGroupForm.php b/application/forms/ContactGroupForm.php index 1f91bd68..ed80b942 100644 --- a/application/forms/ContactGroupForm.php +++ b/application/forms/ContactGroupForm.php @@ -9,6 +9,7 @@ use Icinga\Module\Notifications\Common\Links; use Icinga\Module\Notifications\Model\Contact; use Icinga\Module\Notifications\Model\Contactgroup; +use Icinga\Module\Notifications\Model\RotationMember; use Icinga\Web\Session; use ipl\Html\FormElement\SubmitElement; use ipl\Html\HtmlDocument; @@ -291,6 +292,48 @@ public function removeContactgroup(): void $this->db->beginTransaction(); $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; + + $rotationIds = $this->db->fetchCol( + RotationMember::on($this->db) + ->columns('rotation_id') + ->filter(Filter::equal('contactgroup_id', $this->contactgroupId)) + ->assembleSelect() + ); + + $this->db->update( + 'rotation_member', + $markAsDeleted + ['position' => null], + ['contactgroup_id = ?' => $this->contactgroupId] + ); + + if (! empty($rotationIds)) { + $rotationIdsWithOtherMembers = $this->db->fetchCol( + RotationMember::on($this->db) + ->columns('rotation_id') + ->filter(Filter::all( + Filter::equal('rotation_id', $rotationIds), + Filter::unequal('contactgroup_id', $this->contactgroupId), + Filter::equal('deleted', 'n') + ))->assembleSelect() + ); + + $toRemoveRotations = array_diff($rotationIds, $rotationIdsWithOtherMembers); + + if (! empty($toRemoveRotations)) { + $this->db->update( + 'rotation', + $markAsDeleted + ['priority' => null, 'first_handoff' => null], + ['id IN (?)' => $toRemoveRotations] + ); + } + } + + $this->db->update( + 'rule_escalation_recipient', + $markAsDeleted, + ['contactgroup_id = ?' => $this->contactgroupId] + ); + $this->db->update('contactgroup_member', $markAsDeleted, ['contactgroup_id = ?' => $this->contactgroupId]); $this->db->update('contactgroup', $markAsDeleted, ['id = ?' => $this->contactgroupId]); diff --git a/application/forms/ScheduleForm.php b/application/forms/ScheduleForm.php index 167380f8..f4935782 100644 --- a/application/forms/ScheduleForm.php +++ b/application/forms/ScheduleForm.php @@ -114,7 +114,10 @@ public function removeSchedule(int $id): void $rotationConfigForm->wipeRotation($rotation->priority); } - $this->db->update('schedule', ['changed_at' => time() * 1000, 'deleted' => 'y'], ['id = ?' => $id]); + $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; + + $this->db->update('rule_escalation_recipient', $markAsDeleted, ['schedule_id = ?' => $id]); + $this->db->update('schedule', $markAsDeleted, ['id = ?' => $id]); $this->db->commitTransaction(); } diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index 8f3af313..e459f9ea 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -8,6 +8,7 @@ use Icinga\Module\Notifications\Model\AvailableChannelType; use Icinga\Module\Notifications\Model\Channel; use Icinga\Module\Notifications\Model\Contact; +use Icinga\Module\Notifications\Model\RotationMember; use Icinga\Web\Session; use ipl\Html\Contract\FormSubmitElement; use ipl\Html\FormElement\FieldsetElement; @@ -263,6 +264,45 @@ public function removeContact(): void $this->db->beginTransaction(); $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; + + $rotationIds = $this->db->fetchCol( + RotationMember::on($this->db) + ->columns('rotation_id') + ->filter(Filter::equal('contact_id', $this->contactId)) + ->assembleSelect() + ); + + $this->db->update( + 'rotation_member', + $markAsDeleted + ['position' => null], + ['contact_id = ?' => $this->contactId] + ); + + if (! empty($rotationIds)) { + $rotationIdsWithOtherMembers = $this->db->fetchCol( + RotationMember::on($this->db) + ->columns('rotation_id') + ->filter( + Filter::all( + Filter::equal('rotation_id', $rotationIds), + Filter::unequal('contact_id', $this->contactId), + Filter::equal('deleted', 'n') + ) + )->assembleSelect() + ); + + $toRemoveRotations = array_diff($rotationIds, $rotationIdsWithOtherMembers); + + if (! empty($toRemoveRotations)) { + $this->db->update( + 'rotation', + $markAsDeleted + ['priority' => null, 'first_handoff' => null], + ['id IN (?)' => $toRemoveRotations] + ); + } + } + + $this->db->update('rule_escalation_recipient', $markAsDeleted, ['contact_id = ?' => $this->contactId]); $this->db->update('contactgroup_member', $markAsDeleted, ['contact_id = ?' => $this->contactId]); $this->db->update('contact_address', $markAsDeleted, ['contact_id = ?' => $this->contactId]); $this->db->update('contact', $markAsDeleted + ['username' => null], ['id = ?' => $this->contactId]); From 7ec271872639a6e977c779f646218da6be831854 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 5 Jul 2024 14:59:55 +0200 Subject: [PATCH 22/34] (EventRule|Schedule)ListItem: Respect new `deleted` flag --- .../Widget/ItemList/EventRuleListItem.php | 10 ++++++---- .../Notifications/Widget/ItemList/ScheduleListItem.php | 9 ++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/library/Notifications/Widget/ItemList/EventRuleListItem.php b/library/Notifications/Widget/ItemList/EventRuleListItem.php index 0e8afd14..7601d398 100644 --- a/library/Notifications/Widget/ItemList/EventRuleListItem.php +++ b/library/Notifications/Widget/ItemList/EventRuleListItem.php @@ -9,6 +9,7 @@ use Icinga\Module\Notifications\Widget\RuleEscalationRecipientBadge; use ipl\Html\BaseHtmlElement; use ipl\Html\Html; +use ipl\Stdlib\Filter; use ipl\Web\Common\BaseListItem; use ipl\Web\Widget\Icon; use ipl\Web\Widget\Link; @@ -38,7 +39,7 @@ protected function assembleFooter(BaseHtmlElement $footer): void $meta->add(Html::tag('span', new Icon('filter'))); } - $escalationCount = $this->item->rule_escalation->count(); + $escalationCount = $this->item->rule_escalation->filter(Filter::equal('deleted', 'n'))->count(); if ($escalationCount > 1) { $meta->add(Html::tag('span', [new Icon('code-branch'), $escalationCount])); } @@ -55,12 +56,13 @@ protected function assembleHeader(BaseHtmlElement $header): void { $header->add($this->createTitle()); //TODO(sd): need fixes? - $rs = $this->item->rule_escalation->first(); + $rs = $this->item->rule_escalation->filter(Filter::equal('deleted', 'n'))->first(); if ($rs) { - $recipientCount = $rs->rule_escalation_recipient->count(); + $recipients = $rs->rule_escalation_recipient->filter(Filter::equal('deleted', 'n')); + $recipientCount = $recipients->count(); if ($recipientCount) { $header->add(new RuleEscalationRecipientBadge( - $rs->rule_escalation_recipient->first(), + $recipients->first(), $recipientCount - 1 )); } diff --git a/library/Notifications/Widget/ItemList/ScheduleListItem.php b/library/Notifications/Widget/ItemList/ScheduleListItem.php index 5a6ffab6..8bd9ae8f 100644 --- a/library/Notifications/Widget/ItemList/ScheduleListItem.php +++ b/library/Notifications/Widget/ItemList/ScheduleListItem.php @@ -11,6 +11,7 @@ use Icinga\Module\Notifications\Widget\Timeline\Rotation; use Icinga\Util\Csp; use ipl\Html\BaseHtmlElement; +use ipl\Stdlib\Filter; use ipl\Web\Common\BaseListItem; use ipl\Web\Style; use ipl\Web\Widget\Link; @@ -58,7 +59,13 @@ protected function assembleCaption(BaseHtmlElement $caption): void ->setModule('notifications') ); - foreach ($this->item->rotation->with('timeperiod')->orderBy('first_handoff', SORT_DESC) as $rotation) { + $rotations = $this->item + ->rotation + ->with('timeperiod') + ->filter(Filter::equal('deleted', 'n')) + ->orderBy('first_handoff', SORT_DESC); + + foreach ($rotations as $rotation) { $timeline->addRotation(new Rotation($rotation)); } From 477456b642967a9fd2e318edbdc4e5bf9b25a920 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 8 Jul 2024 16:51:23 +0200 Subject: [PATCH 23/34] Database: Use `ON_ASSEMBLE_SELECT` event to add `deleted` flag to queries Only add it to the base table. If the filter is already set for the base table, do nothing (return) - Remove `deleted` filter from all other files. --- .../controllers/ChannelsController.php | 3 +- .../controllers/ContactGroupController.php | 10 +-- .../controllers/ContactGroupsController.php | 3 +- .../controllers/ContactsController.php | 3 +- .../controllers/EventRuleController.php | 11 +-- .../controllers/EventRulesController.php | 3 +- .../controllers/IncidentController.php | 5 +- .../controllers/ScheduleController.php | 33 +-------- .../controllers/SchedulesController.php | 3 +- application/controllers/SourcesController.php | 4 +- application/forms/ChannelForm.php | 14 +--- application/forms/ContactGroupForm.php | 20 +---- application/forms/EscalationRecipientForm.php | 7 +- application/forms/MoveRotationForm.php | 7 +- application/forms/RotationConfigForm.php | 67 ++++------------- application/forms/SaveEventRuleForm.php | 22 ++---- application/forms/ScheduleForm.php | 10 +-- application/forms/SourceForm.php | 5 +- library/Notifications/Common/Database.php | 73 +++++++++++++++++++ .../Model/Behavior/HasAddress.php | 10 +-- library/Notifications/Model/Channel.php | 4 +- .../Model/RuleEscalationRecipient.php | 7 +- .../Control/SearchBar/ObjectSuggestions.php | 4 - .../Notifications/Web/Form/ContactForm.php | 18 +---- .../Widget/ItemList/EventRuleListItem.php | 10 +-- .../Widget/ItemList/ScheduleListItem.php | 9 +-- .../Widget/MemberSuggestions.php | 5 +- .../Widget/RecipientSuggestions.php | 16 +--- library/Notifications/Widget/Schedule.php | 12 +-- .../Widget/Timeline/Rotation.php | 5 -- 30 files changed, 140 insertions(+), 263 deletions(-) diff --git a/application/controllers/ChannelsController.php b/application/controllers/ChannelsController.php index 5fe1669b..90127d36 100644 --- a/application/controllers/ChannelsController.php +++ b/application/controllers/ChannelsController.php @@ -43,8 +43,7 @@ public function init() public function indexAction() { - $channels = Channel::on($this->db) - ->filter(Filter::equal('deleted', 'n')); + $channels = Channel::on($this->db); $this->mergeTabs($this->Module()->getConfigTabs()); $this->getTabs()->activate('channels'); diff --git a/application/controllers/ContactGroupController.php b/application/controllers/ContactGroupController.php index c52672c7..3ccebe36 100644 --- a/application/controllers/ContactGroupController.php +++ b/application/controllers/ContactGroupController.php @@ -35,10 +35,7 @@ public function indexAction(): void $query = Contactgroup::on(Database::get()) ->columns(['id', 'name']) - ->filter(Filter::all( - Filter::equal('id', $groupId), - Filter::equal('deleted', 'n') - )); + ->filter(Filter::equal('id', $groupId)); $group = $query->first(); if ($group === null) { @@ -51,10 +48,7 @@ public function indexAction(): void $contacts = $group ->contact - ->filter(Filter::all( - Filter::equal('contactgroup_member.deleted', 'n'), - Filter::equal('deleted', 'n') - )); + ->filter(Filter::equal('contactgroup_member.deleted', 'n')); $this->addControl($this->createPaginationControl($contacts)); $this->addControl($this->createLimitControl()); diff --git a/application/controllers/ContactGroupsController.php b/application/controllers/ContactGroupsController.php index 6b50a1a7..feb22831 100644 --- a/application/controllers/ContactGroupsController.php +++ b/application/controllers/ContactGroupsController.php @@ -39,8 +39,7 @@ public function init(): void public function indexAction(): void { - $groups = Contactgroup::on(Database::get()) - ->filter(Filter::equal('deleted', 'n')); + $groups = Contactgroup::on(Database::get()); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($groups); diff --git a/application/controllers/ContactsController.php b/application/controllers/ContactsController.php index 0fcade7b..c6c3408d 100644 --- a/application/controllers/ContactsController.php +++ b/application/controllers/ContactsController.php @@ -43,8 +43,7 @@ public function init() public function indexAction() { $contacts = Contact::on($this->db) - ->withColumns('has_email') - ->filter(Filter::equal('deleted', 'n')); + ->withColumns('has_email'); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($contacts); diff --git a/application/controllers/EventRuleController.php b/application/controllers/EventRuleController.php index fd9f1c11..d009531c 100644 --- a/application/controllers/EventRuleController.php +++ b/application/controllers/EventRuleController.php @@ -151,10 +151,7 @@ public function fromDb(int $ruleId): array { $query = Rule::on(Database::get()) ->columns(['id', 'name', 'object_filter']) - ->filter(Filter::all( - Filter::equal('id', $ruleId), - Filter::equal('deleted', 'n') - )); + ->filter(Filter::equal('id', $ruleId)); $rule = $query->first(); if ($rule === null) { @@ -165,8 +162,7 @@ public function fromDb(int $ruleId): array $ruleEscalations = $rule ->rule_escalation - ->withoutColumns(['changed_at', 'deleted']) - ->filter(Filter::equal('deleted', 'n')); + ->withoutColumns(['changed_at', 'deleted']); foreach ($ruleEscalations as $re) { foreach ($re as $k => $v) { @@ -175,8 +171,7 @@ public function fromDb(int $ruleId): array $escalationRecipients = $re ->rule_escalation_recipient - ->withoutColumns(['changed_at', 'deleted']) - ->filter(Filter::equal('deleted', 'n')); + ->withoutColumns(['changed_at', 'deleted']); foreach ($escalationRecipients as $recipient) { $config[$re->getTableName()][$re->position]['recipient'][] = iterator_to_array($recipient); diff --git a/application/controllers/EventRulesController.php b/application/controllers/EventRulesController.php index bc3ed472..8e1a0e6d 100644 --- a/application/controllers/EventRulesController.php +++ b/application/controllers/EventRulesController.php @@ -44,8 +44,7 @@ public function init() public function indexAction(): void { - $eventRules = Rule::on(Database::get()) - ->filter(Filter::equal('deleted', 'n')); + $eventRules = Rule::on(Database::get()); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($eventRules); diff --git a/application/controllers/IncidentController.php b/application/controllers/IncidentController.php index 186232f6..b6695aa2 100644 --- a/application/controllers/IncidentController.php +++ b/application/controllers/IncidentController.php @@ -47,10 +47,7 @@ public function indexAction(): void $contact = Contact::on(Database::get()) ->columns('id') - ->filter(Filter::all( - Filter::equal('username', $this->Auth()->getUser()->getUsername()), - Filter::equal('deleted', 'n') - )) + ->filter(Filter::equal('username', $this->Auth()->getUser()->getUsername())) ->first(); if ($contact !== null) { diff --git a/application/controllers/ScheduleController.php b/application/controllers/ScheduleController.php index 30e33d1e..298a0159 100644 --- a/application/controllers/ScheduleController.php +++ b/application/controllers/ScheduleController.php @@ -4,7 +4,6 @@ namespace Icinga\Module\Notifications\Controllers; -use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Notifications\Common\Database; use Icinga\Module\Notifications\Common\Links; use Icinga\Module\Notifications\Forms\MoveRotationForm; @@ -27,10 +26,7 @@ public function indexAction(): void $id = (int) $this->params->getRequired('id'); $query = Schedule::on(Database::get()) - ->filter(Filter::all( - Filter::equal('schedule.id', $id), - Filter::equal('schedule.deleted', 'n') - )); + ->filter(Filter::equal('schedule.id', $id)); /** @var ?Schedule $schedule */ $schedule = $query->first(); @@ -116,7 +112,6 @@ public function addAction(): void public function addRotationAction(): void { $scheduleId = (int) $this->params->getRequired('schedule'); - $this->assertScheduleExists($scheduleId); $form = new RotationConfigForm($scheduleId, Database::get()); $form->setAction($this->getRequest()->getUrl()->setParam('showCompact')->getAbsoluteUrl()); @@ -150,7 +145,6 @@ public function editRotationAction(): void { $id = (int) $this->params->getRequired('id'); $scheduleId = (int) $this->params->getRequired('schedule'); - $this->assertScheduleExists($scheduleId); $form = new RotationConfigForm($scheduleId, Database::get()); $form->disableModeSelection(); @@ -214,29 +208,4 @@ public function suggestRecipientAction(): void $this->getDocument()->addHtml($suggestions); } - - /** - * Assert that a schedule with the given ID exists - * - * @param int $id - * - * @return void - * - * @throws HttpNotFoundException If the schedule with the given ID does not exist - */ - private function assertScheduleExists(int $id): void - { - $query = Schedule::on(Database::get()) - ->columns('1') - ->filter(Filter::all( - Filter::equal('schedule.id', $id), - Filter::equal('schedule.deleted', 'n') - )); - - /** @var ?Schedule $schedule */ - $schedule = $query->first(); - if ($schedule === null) { - $this->httpNotFound(t('Schedule not found')); - } - } } diff --git a/application/controllers/SchedulesController.php b/application/controllers/SchedulesController.php index e5e2da8c..9ee77003 100644 --- a/application/controllers/SchedulesController.php +++ b/application/controllers/SchedulesController.php @@ -27,8 +27,7 @@ class SchedulesController extends CompatController public function indexAction(): void { - $schedules = Schedule::on(Database::get()) - ->filter(Filter::equal('deleted', 'n')); + $schedules = Schedule::on(Database::get()); $limitControl = $this->createLimitControl(); $sortControl = $this->createSortControl( diff --git a/application/controllers/SourcesController.php b/application/controllers/SourcesController.php index e220f061..65b831fc 100644 --- a/application/controllers/SourcesController.php +++ b/application/controllers/SourcesController.php @@ -12,7 +12,6 @@ use Icinga\Web\Notification; use Icinga\Web\Widget\Tabs; use ipl\Html\ValidHtml; -use ipl\Stdlib\Filter; use ipl\Web\Common\BaseItemList; use ipl\Web\Compat\CompatController; use ipl\Web\Compat\SearchControls; @@ -34,8 +33,7 @@ public function init() public function indexAction(): void { $sources = Source::on(Database::get()) - ->columns(['id', 'type', 'name']) - ->filter(Filter::equal('deleted', 'n')); + ->columns(['id', 'type', 'name']); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($sources); diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index 694cf6bc..f7ccbc27 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -115,12 +115,9 @@ protected function assemble() if ($this->channelId !== null) { $isInUse = RuleEscalationRecipient::on($this->db) ->columns('1') - ->filter(Filter::all( - Filter::equal('deleted', 'n'), - Filter::any( - Filter::equal('channel_id', $this->channelId), - Filter::equal('contact.default_channel_id', $this->channelId) - ) + ->filter(Filter::any( + Filter::equal('channel_id', $this->channelId), + Filter::equal('contact.default_channel_id', $this->channelId) )) ->first(); @@ -418,10 +415,7 @@ private function fetchDbValues(): array { /** @var Channel $channel */ $channel = Channel::on($this->db) - ->filter(Filter::all( - Filter::equal('id', $this->channelId), - Filter::equal('deleted', 'n') - )) + ->filter(Filter::equal('id', $this->channelId)) ->first(); if ($channel === null) { diff --git a/application/forms/ContactGroupForm.php b/application/forms/ContactGroupForm.php index ed80b942..d01a79f5 100644 --- a/application/forms/ContactGroupForm.php +++ b/application/forms/ContactGroupForm.php @@ -5,7 +5,6 @@ namespace Icinga\Module\Notifications\Forms; use Icinga\Exception\Http\HttpNotFoundException; -use Icinga\Module\Notifications\Common\Database; use Icinga\Module\Notifications\Common\Links; use Icinga\Module\Notifications\Model\Contact; use Icinga\Module\Notifications\Model\Contactgroup; @@ -312,8 +311,7 @@ public function removeContactgroup(): void ->columns('rotation_id') ->filter(Filter::all( Filter::equal('rotation_id', $rotationIds), - Filter::unequal('contactgroup_id', $this->contactgroupId), - Filter::equal('deleted', 'n') + Filter::unequal('contactgroup_id', $this->contactgroupId) ))->assembleSelect() ); @@ -351,26 +349,16 @@ private function fetchDbValues(): array { $query = Contactgroup::on($this->db) ->columns(['id', 'name']) - ->filter(Filter::all( - Filter::equal('id', $this->contactgroupId), - Filter::equal('deleted', 'n') - )); + ->filter(Filter::equal('id', $this->contactgroupId)); $group = $query->first(); if ($group === null) { throw new HttpNotFoundException($this->translate('Contact group not found')); } - $contacts = Contact::on(Database::get()) - ->filter(Filter::all( - Filter::equal('contactgroup_member.contactgroup_id', $group->id), - Filter::equal('contactgroup_member.deleted', 'n'), - Filter::equal('deleted', 'n') - )); - $groupMembers = []; - foreach ($contacts as $contact) { - $groupMembers[] = $contact->id; + foreach ($group->contactgroup_member as $contact) { + $groupMembers[] = $contact->contact_id; } return [ diff --git a/application/forms/EscalationRecipientForm.php b/application/forms/EscalationRecipientForm.php index 95a54710..006d71a8 100644 --- a/application/forms/EscalationRecipientForm.php +++ b/application/forms/EscalationRecipientForm.php @@ -11,7 +11,6 @@ use Icinga\Module\Notifications\Model\Schedule; use ipl\Html\Contract\FormElement; use ipl\Html\Html; -use ipl\Stdlib\Filter; use ipl\Web\Widget\Icon; class EscalationRecipientForm extends BaseEscalationForm @@ -26,15 +25,15 @@ public function __construct(?int $count) protected function fetchOptions(): array { $options = []; - foreach (Contact::on(Database::get())->filter(Filter::equal('deleted', 'n')) as $contact) { + foreach (Contact::on(Database::get()) as $contact) { $options['Contacts']['contact_' . $contact->id] = $contact->full_name; } - foreach (Contactgroup::on(Database::get())->filter(Filter::equal('deleted', 'n')) as $contactgroup) { + foreach (Contactgroup::on(Database::get()) as $contactgroup) { $options['Contact Groups']['contactgroup_' . $contactgroup->id] = $contactgroup->name; } - foreach (Schedule::on(Database::get())->filter(Filter::equal('deleted', 'n')) as $schedule) { + foreach (Schedule::on(Database::get()) as $schedule) { $options['Schedules']['schedule_' . $schedule->id] = $schedule->name; } diff --git a/application/forms/MoveRotationForm.php b/application/forms/MoveRotationForm.php index aea1c642..24b9ae73 100644 --- a/application/forms/MoveRotationForm.php +++ b/application/forms/MoveRotationForm.php @@ -87,10 +87,7 @@ protected function onSuccess() /** @var ?Rotation $rotation */ $rotation = Rotation::on($this->db) ->columns(['schedule_id', 'priority']) - ->filter(Filter::all( - Filter::equal('id', $rotationId), - Filter::equal('deleted', 'n') - )) + ->filter(Filter::equal('id', $rotationId)) ->first(); if ($rotation === null) { throw new HttpNotFoundException('Rotation not found'); @@ -114,7 +111,6 @@ protected function onSuccess() ->columns('id') ->from('rotation') ->where([ - 'deleted = ?' => 'n', 'schedule_id = ?' => $rotation->schedule_id, 'priority >= ?' => $newPriority, 'priority < ?' => $rotation->priority @@ -134,7 +130,6 @@ protected function onSuccess() ->columns('id') ->from('rotation') ->where([ - 'deleted = ?' => 'n', 'schedule_id = ?' => $rotation->schedule_id, 'priority > ?' => $rotation->priority, 'priority <= ?' => $newPriority diff --git a/application/forms/RotationConfigForm.php b/application/forms/RotationConfigForm.php index ee0254dd..b86da032 100644 --- a/application/forms/RotationConfigForm.php +++ b/application/forms/RotationConfigForm.php @@ -233,10 +233,7 @@ public function loadRotation(int $rotationId): self /** @var ?Rotation $rotation */ $rotation = Rotation::on($this->db) - ->filter(Filter::all( - Filter::equal('id', $this->rotationId), - Filter::equal('deleted', 'n') - )) + ->filter(Filter::equal('id', $this->rotationId)) ->first(); if ($rotation === null) { throw new HttpNotFoundException($this->translate('Rotation not found')); @@ -248,8 +245,6 @@ public function loadRotation(int $rotationId): self $previousShift = TimeperiodEntry::on($this->db) ->columns('until_time') ->filter(Filter::all( - Filter::equal('deleted', 'n'), - Filter::equal('timeperiod.deleted', 'n'), Filter::equal('timeperiod.rotation.schedule_id', $rotation->schedule_id), Filter::equal('timeperiod.rotation.priority', $rotation->priority), Filter::unequal('timeperiod.owned_by_rotation_id', $rotation->id), @@ -266,7 +261,6 @@ public function loadRotation(int $rotationId): self $newerRotation = Rotation::on($this->db) ->columns(['first_handoff', 'options', 'mode']) ->filter(Filter::all( - Filter::equal('deleted', 'n'), Filter::equal('schedule_id', $rotation->schedule_id), Filter::equal('priority', $rotation->priority), Filter::greaterThan('first_handoff', $rotation->first_handoff) @@ -386,10 +380,7 @@ public function addRotation(): void (new Select()) ->from('rotation') ->columns(new Expression('MAX(priority) + 1')) - ->where([ - 'schedule_id = ?' => $this->scheduleId, - 'deleted = ?' => 'n', - ]) + ->where(['schedule_id = ?' => $this->scheduleId]) ) ?? 0)->send(true); if ($transactionStarted) { @@ -436,11 +427,7 @@ public function editRotation(int $rotationId): void $firstHandoff = $createStmt->current(); $timeperiodEntries = TimeperiodEntry::on($this->db) - ->filter(Filter::all( - Filter::equal('deleted', 'n'), - Filter::equal('timeperiod.deleted', 'n'), - Filter::equal('timeperiod.owned_by_rotation_id', $rotationId) - )); + ->filter(Filter::equal('timeperiod.owned_by_rotation_id', $rotationId)); foreach ($timeperiodEntries as $timeperiodEntry) { /** @var TimeperiodEntry $timeperiodEntry */ @@ -527,10 +514,7 @@ public function removeRotation(int $id): void (new Select()) ->from('timeperiod') ->columns('id') - ->where([ - 'owned_by_rotation_id = ?' => $id, - 'deleted = ?' => 'n', - ]) + ->where(['owned_by_rotation_id = ?' => $id]) ); $changedAt = time() * 1000; @@ -547,20 +531,16 @@ public function removeRotation(int $id): void ); $rotations = Rotation::on($this->db) - ->filter(Filter::all( - Filter::equal('deleted', 'n'), - Filter::equal('schedule_id', $this->scheduleId), - Filter::equal('priority', $priority) - )); + ->filter(Filter::equal('schedule_id', $this->scheduleId)) + ->filter(Filter::equal('priority', $priority)); if ($rotations->count() === 0) { $affectedRotations = $this->db->select( (new Select()) ->columns('id') ->from('rotation') ->where([ - 'deleted = ?' => 'n', - 'schedule_id = ?' => $this->scheduleId, - 'priority > ?' => $priority + 'schedule_id = ?' => $this->scheduleId, + 'priority > ?' => $priority ]) ->orderBy('priority ASC') ); @@ -597,11 +577,8 @@ public function wipeRotation(int $priority = null): void $rotations = Rotation::on($this->db) ->columns('id') - ->filter(Filter::all( - Filter::equal('deleted', 'n'), - Filter::equal('schedule_id', $this->scheduleId), - Filter::equal('priority', $priority) - )); + ->filter(Filter::equal('schedule_id', $this->scheduleId)) + ->filter(Filter::equal('priority', $priority)); $changedAt = time() * 1000; $markAsDeleted = ['changed_at' => $changedAt, 'deleted' => 'y']; @@ -634,9 +611,8 @@ public function wipeRotation(int $priority = null): void ->columns('id') ->from('rotation') ->where([ - 'deleted = ?' => 'n', - 'schedule_id = ?' => $this->scheduleId, - 'priority > ?' => $priority + 'schedule_id = ?' => $this->scheduleId, + 'priority > ?' => $priority ]) ->orderBy('priority ASC') ); @@ -1020,7 +996,6 @@ protected function assemble() new CallbackValidator(function ($value, $validator) { $rotations = Rotation::on($this->db) ->columns('id') - ->filter(Filter::equal('deleted', 'n')) ->filter(Filter::equal('schedule_id', $this->scheduleId)) ->filter(Filter::equal('name', $value)); if (($priority = $this->getValue('priority')) !== null) { @@ -1154,7 +1129,6 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando if (! empty($contactTerms)) { $contacts = (Contact::on(Database::get())) - ->filter(Filter::equal('deleted', 'n')) ->filter(Filter::equal('id', array_keys($contactTerms))); foreach ($contacts as $contact) { $contactTerms[$contact->id] @@ -1165,7 +1139,6 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando if (! empty($groupTerms)) { $groups = (Contactgroup::on(Database::get())) - ->filter(Filter::equal('deleted', 'n')) ->filter(Filter::equal('id', array_keys($groupTerms))); foreach ($groups as $group) { $groupTerms[$group->id] @@ -1593,10 +1566,7 @@ private function fetchDbValues(): array { /** @var ?Rotation $rotation */ $rotation = Rotation::on($this->db) - ->filter(Filter::all( - Filter::equal('id', $this->rotationId), - Filter::equal('deleted', 'n') - )) + ->filter(Filter::equal('id', $this->rotationId)) ->first(); if ($rotation === null) { throw new HttpNotFoundException($this->translate('Rotation not found')); @@ -1613,17 +1583,8 @@ private function fetchDbValues(): array $formData['first_handoff'] = $rotation->first_handoff; } - $membersRes = $rotation - ->member - ->filter(Filter::equal('deleted', 'n')) - ->filter(Filter::any( - Filter::equal('contact.deleted', 'n'), - Filter::equal('contactgroup.deleted', 'n') - )) - ->orderBy('position', SORT_ASC); - $members = []; - foreach ($membersRes as $member) { + foreach ($rotation->member->orderBy('position', SORT_ASC) as $member) { if ($member->contact_id !== null) { $members[] = 'contact:' . $member->contact_id; } else { diff --git a/application/forms/SaveEventRuleForm.php b/application/forms/SaveEventRuleForm.php index 17e39255..b7385d4d 100644 --- a/application/forms/SaveEventRuleForm.php +++ b/application/forms/SaveEventRuleForm.php @@ -323,10 +323,7 @@ private function insertOrUpdateEscalations($ruleId, array $escalations, Connecti $recipients = RuleEscalationRecipient::on($db) ->columns('id') - ->filter(Filter::all( - Filter::equal('rule_escalation_id', $escalationId), - Filter::equal('deleted', 'n') - )); + ->filter(Filter::equal('rule_escalation_id', $escalationId)); foreach ($recipients as $recipient) { $recipientId = $recipient->id; @@ -495,10 +492,8 @@ public function removeRule(int $id): void $escalationsToRemove = $db->fetchCol( RuleEscalation::on($db) ->columns('id') - ->filter(Filter::all( - Filter::equal('rule_id', $id), - Filter::equal('deleted', 'n') - ))->assembleSelect() + ->filter(Filter::equal('rule_id', $id)) + ->assembleSelect() ); $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; @@ -536,10 +531,7 @@ private function fetchDbValues(): array { $query = Rule::on(Database::get()) ->columns(['id', 'name', 'object_filter']) - ->filter(Filter::all( - Filter::equal('id', $this->ruleId), - Filter::equal('deleted', 'n') - )); + ->filter(Filter::equal('id', $this->ruleId)); $rule = $query->first(); if ($rule === null) { @@ -550,8 +542,7 @@ private function fetchDbValues(): array $ruleEscalations = $rule ->rule_escalation - ->withoutColumns(['changed_at', 'deleted']) - ->filter(Filter::equal('deleted', 'n')); + ->withoutColumns(['changed_at', 'deleted']); foreach ($ruleEscalations as $re) { foreach ($re as $k => $v) { @@ -560,8 +551,7 @@ private function fetchDbValues(): array $escalationRecipients = $re ->rule_escalation_recipient - ->withoutColumns(['changed_at', 'deleted']) - ->filter(Filter::equal('deleted', 'n')); + ->withoutColumns(['changed_at', 'deleted']); foreach ($escalationRecipients as $recipient) { $config[$re->getTableName()][$re->position]['recipient'][] = iterator_to_array($recipient); diff --git a/application/forms/ScheduleForm.php b/application/forms/ScheduleForm.php index f4935782..82b3cb78 100644 --- a/application/forms/ScheduleForm.php +++ b/application/forms/ScheduleForm.php @@ -102,10 +102,7 @@ public function removeSchedule(int $id): void $rotations = Rotation::on($this->db) ->columns('priority') - ->filter(Filter::all( - Filter::equal('schedule_id', $id), - Filter::equal('deleted', 'n') - )) + ->filter(Filter::equal('schedule_id', $id)) ->orderBy('priority', SORT_DESC); $rotationConfigForm = new RotationConfigForm($id, $this->db); @@ -159,10 +156,7 @@ private function fetchDbValues(): array /** @var ?Schedule $schedule */ $schedule = Schedule::on($this->db) ->columns('name') - ->filter(Filter::all( - Filter::equal('id', $this->scheduleId), - Filter::equal('deleted', 'n') - )) + ->filter(Filter::equal('id', $this->scheduleId)) ->first(); if ($schedule === null) { diff --git a/application/forms/SourceForm.php b/application/forms/SourceForm.php index 32f08a33..41587e16 100644 --- a/application/forms/SourceForm.php +++ b/application/forms/SourceForm.php @@ -359,10 +359,7 @@ private function fetchDbValues(): array { /** @var ?Source $source */ $source = Source::on($this->db) - ->filter(Filter::all( - Filter::equal('id', $this->sourceId), - Filter::equal('deleted', 'n') - )) + ->filter(Filter::equal('id', $this->sourceId)) ->first(); if ($source === null) { diff --git a/library/Notifications/Common/Database.php b/library/Notifications/Common/Database.php index a067d7a7..abfefa50 100644 --- a/library/Notifications/Common/Database.php +++ b/library/Notifications/Common/Database.php @@ -18,6 +18,28 @@ final class Database { + /** + * @var string[] Tables with a deleted flag + * + * The filter `deleted=n` is automatically added to these tables. + */ + private const TABLES_WITH_DELETED_FLAG = [ + 'channel', + 'contact', + 'contact_address', + 'contactgroup', + 'contactgroup_member', + 'rotation', + 'rotation_member', + 'rule', + 'rule_escalation', + 'rule_escalation_recipient', + 'schedule', + 'source', + 'timeperiod', + 'timeperiod_entry', + ]; + /** @var Connection Database connection */ private static $instance; @@ -104,6 +126,30 @@ private static function getConnection(): Connection }); } + $db->getQueryBuilder() + ->on(QueryBuilder::ON_ASSEMBLE_SELECT, function (Select $select) { + $from = $select->getFrom(); + $baseTableName = reset($from); + + if (! in_array($baseTableName, self::TABLES_WITH_DELETED_FLAG, true)) { + return; + } + + $baseTableAlias = key($from); + if (! is_string($baseTableAlias)) { + $baseTableAlias = $baseTableName; + } + + $condition = 'deleted = ?'; + $where = $select->getWhere(); + + if ($where && self::hasCondition($baseTableAlias, $condition, $where)) { + return; + } + + $select->where([$baseTableAlias . '.' . $condition => 'n']); + }); + return $db; } @@ -144,4 +190,31 @@ public static function registerGroupBy(Query $query, Select $select): void $select->groupBy($groupBy); } + + /** + * Check if the given condition is part of the where clause with value 'y' + * + * @param string $conditionToFind + * @param array $where + * + * @return bool + */ + private static function hasCondition(string $baseTable, string $conditionToFind, array $where): bool + { + foreach ($where as $condition => $value) { + if (is_array($value)) { + $found = self::hasCondition($baseTable, $conditionToFind, $value); + } else { + $found = ( + $condition === $conditionToFind || $condition === $baseTable . '.' . $conditionToFind + ) && $value === 'y'; + } + + if ($found) { + return true; + } + } + + return false; + } } diff --git a/library/Notifications/Model/Behavior/HasAddress.php b/library/Notifications/Model/Behavior/HasAddress.php index 8e356e54..2e7db3fc 100644 --- a/library/Notifications/Model/Behavior/HasAddress.php +++ b/library/Notifications/Model/Behavior/HasAddress.php @@ -37,10 +37,7 @@ public function rewriteColumn($column, ?string $relation = null) $subQuery = $this->query->createSubQuery(new ContactAddress(), $subQueryRelation) ->limit(1) ->columns([new Expression('1')]) - ->filter(Filter::all( - Filter::equal('type', $type), - Filter::equal('deleted', 'n') - )); + ->filter(Filter::equal('type', $type)); $column = $relation !== null ? str_replace('.', '_', $relation) . "_$column" : $column; @@ -78,10 +75,7 @@ public function rewriteCondition(Filter\Condition $condition, $relation = null) $subQuery = $this->query->createSubQuery(new ContactAddress(), $relation) ->limit(1) ->columns([new Expression('1')]) - ->filter(Filter::all( - Filter::equal('type', $type), - Filter::equal('deleted', 'n') - )); + ->filter(Filter::equal('type', $type)); if ($condition->getValue()) { if ($condition instanceof Filter\Unequal) { diff --git a/library/Notifications/Model/Channel.php b/library/Notifications/Model/Channel.php index e602e422..cc4f8103 100644 --- a/library/Notifications/Model/Channel.php +++ b/library/Notifications/Model/Channel.php @@ -12,7 +12,6 @@ use ipl\Orm\Query; use ipl\Orm\Relations; use ipl\Sql\Connection; -use ipl\Stdlib\Filter; use ipl\Web\Widget\Icon; /** @@ -119,8 +118,7 @@ public function getIcon(): Icon public static function fetchChannelNames(Connection $conn): array { $channels = []; - $query = Channel::on($conn) - ->filter(Filter::equal('deleted', 'n')); + $query = Channel::on($conn); /** @var Channel $channel */ foreach ($query as $channel) { $name = $channel->name; diff --git a/library/Notifications/Model/RuleEscalationRecipient.php b/library/Notifications/Model/RuleEscalationRecipient.php index 5d579b68..a3cc2660 100644 --- a/library/Notifications/Model/RuleEscalationRecipient.php +++ b/library/Notifications/Model/RuleEscalationRecipient.php @@ -11,7 +11,6 @@ use ipl\Orm\Model; use ipl\Orm\Query; use ipl\Orm\Relations; -use ipl\Stdlib\Filter; /** * @property int $id @@ -96,15 +95,15 @@ public function getRecipient(): ?Model { $recipientModel = null; if ($this->contact_id) { - $recipientModel = $this->contact->filter(Filter::equal('deleted', 'n'))->first(); + $recipientModel = $this->contact->first(); } if ($this->contactgroup_id) { - $recipientModel = $this->contactgroup->filter(Filter::equal('deleted', 'n'))->first(); + $recipientModel = $this->contactgroup->first(); } if ($this->schedule_id) { - $recipientModel = $this->schedule->filter(Filter::equal('deleted', 'n'))->first(); + $recipientModel = $this->schedule->first(); } return $recipientModel; diff --git a/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php b/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php index 108927fa..6ca9b208 100644 --- a/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php +++ b/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php @@ -102,10 +102,6 @@ protected function fetchValueSuggestions($column, $searchTerm, Filter\Chain $sea $query = $model::on(Database::get()); $query->limit(static::DEFAULT_LIMIT); - if ($query->getResolver()->hasSelectableColumn($model, 'deleted')) { - $query->filter(Filter::equal('deleted', 'n')); - } - if (strpos($column, ' ') !== false) { // $column may be a label /** @var string $path */ diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index e459f9ea..07fdc968 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -99,10 +99,7 @@ protected function assemble() new StringLengthValidator(['max' => 254]), new CallbackValidator(function ($value, $validator) { $contact = Contact::on($this->db) - ->filter(Filter::all( - Filter::equal('username', $value), - Filter::equal('deleted', 'n') - )); + ->filter(Filter::equal('username', $value)); if ($this->contactId) { $contact->filter(Filter::unequal('id', $this->contactId)); } @@ -285,8 +282,7 @@ public function removeContact(): void ->filter( Filter::all( Filter::equal('rotation_id', $rotationIds), - Filter::unequal('contact_id', $this->contactId), - Filter::equal('deleted', 'n') + Filter::unequal('contact_id', $this->contactId) ) )->assembleSelect() ); @@ -331,10 +327,7 @@ private function fetchDbValues(): array { /** @var ?Contact $contact */ $contact = Contact::on($this->db) - ->filter(Filter::all( - Filter::equal('id', $this->contactId), - Filter::equal('deleted', 'n') - )) + ->filter(Filter::equal('id', $this->contactId)) ->first(); if ($contact === null) { @@ -347,12 +340,9 @@ private function fetchDbValues(): array 'default_channel_id' => (string) $contact->default_channel_id ]; - $contractAddr = $contact->contact_address - ->filter(Filter::equal('deleted', 'n')); - $values['contact_address'] = []; $values['contact_address_with_id'] = []; //TODO: only used in editContact(), find better solution - foreach ($contractAddr as $contactInfo) { + foreach ($contact->contact_address as $contactInfo) { $values['contact_address'][$contactInfo->type] = $contactInfo->address; $values['contact_address_with_id'][$contactInfo->type] = [$contactInfo->id, $contactInfo->address]; } diff --git a/library/Notifications/Widget/ItemList/EventRuleListItem.php b/library/Notifications/Widget/ItemList/EventRuleListItem.php index 7601d398..0e8afd14 100644 --- a/library/Notifications/Widget/ItemList/EventRuleListItem.php +++ b/library/Notifications/Widget/ItemList/EventRuleListItem.php @@ -9,7 +9,6 @@ use Icinga\Module\Notifications\Widget\RuleEscalationRecipientBadge; use ipl\Html\BaseHtmlElement; use ipl\Html\Html; -use ipl\Stdlib\Filter; use ipl\Web\Common\BaseListItem; use ipl\Web\Widget\Icon; use ipl\Web\Widget\Link; @@ -39,7 +38,7 @@ protected function assembleFooter(BaseHtmlElement $footer): void $meta->add(Html::tag('span', new Icon('filter'))); } - $escalationCount = $this->item->rule_escalation->filter(Filter::equal('deleted', 'n'))->count(); + $escalationCount = $this->item->rule_escalation->count(); if ($escalationCount > 1) { $meta->add(Html::tag('span', [new Icon('code-branch'), $escalationCount])); } @@ -56,13 +55,12 @@ protected function assembleHeader(BaseHtmlElement $header): void { $header->add($this->createTitle()); //TODO(sd): need fixes? - $rs = $this->item->rule_escalation->filter(Filter::equal('deleted', 'n'))->first(); + $rs = $this->item->rule_escalation->first(); if ($rs) { - $recipients = $rs->rule_escalation_recipient->filter(Filter::equal('deleted', 'n')); - $recipientCount = $recipients->count(); + $recipientCount = $rs->rule_escalation_recipient->count(); if ($recipientCount) { $header->add(new RuleEscalationRecipientBadge( - $recipients->first(), + $rs->rule_escalation_recipient->first(), $recipientCount - 1 )); } diff --git a/library/Notifications/Widget/ItemList/ScheduleListItem.php b/library/Notifications/Widget/ItemList/ScheduleListItem.php index 8bd9ae8f..5a6ffab6 100644 --- a/library/Notifications/Widget/ItemList/ScheduleListItem.php +++ b/library/Notifications/Widget/ItemList/ScheduleListItem.php @@ -11,7 +11,6 @@ use Icinga\Module\Notifications\Widget\Timeline\Rotation; use Icinga\Util\Csp; use ipl\Html\BaseHtmlElement; -use ipl\Stdlib\Filter; use ipl\Web\Common\BaseListItem; use ipl\Web\Style; use ipl\Web\Widget\Link; @@ -59,13 +58,7 @@ protected function assembleCaption(BaseHtmlElement $caption): void ->setModule('notifications') ); - $rotations = $this->item - ->rotation - ->with('timeperiod') - ->filter(Filter::equal('deleted', 'n')) - ->orderBy('first_handoff', SORT_DESC); - - foreach ($rotations as $rotation) { + foreach ($this->item->rotation->with('timeperiod')->orderBy('first_handoff', SORT_DESC) as $rotation) { $timeline->addRotation(new Rotation($rotation)); } diff --git a/library/Notifications/Widget/MemberSuggestions.php b/library/Notifications/Widget/MemberSuggestions.php index f3ac9db5..983e41b7 100644 --- a/library/Notifications/Widget/MemberSuggestions.php +++ b/library/Notifications/Widget/MemberSuggestions.php @@ -79,10 +79,7 @@ public function forRequest(ServerRequestInterface $request): self protected function assemble(): void { - $contactFilter = Filter::all( - Filter::like('full_name', $this->searchTerm), - Filter::equal('deleted', 'n') - ); + $contactFilter = Filter::like('full_name', $this->searchTerm); if (! empty($this->excludeTerms)) { $contactFilter = Filter::all( diff --git a/library/Notifications/Widget/RecipientSuggestions.php b/library/Notifications/Widget/RecipientSuggestions.php index 3b1b09ab..803618d0 100644 --- a/library/Notifications/Widget/RecipientSuggestions.php +++ b/library/Notifications/Widget/RecipientSuggestions.php @@ -108,13 +108,7 @@ protected function assemble() )); } - $query = Contact::on(Database::get()) - ->filter(Filter::all( - $contactFilter, - Filter::equal('deleted', 'n') - )); - - foreach ($query as $contact) { + foreach (Contact::on(Database::get())->filter($contactFilter) as $contact) { $this->addHtml(new HtmlElement( 'li', null, @@ -131,13 +125,7 @@ protected function assemble() )); } - $query = Contactgroup::on(Database::get()) - ->filter(Filter::all( - $groupFilter, - Filter::equal('deleted', 'n') - )); - - foreach ($query as $group) { + foreach (Contactgroup::on(Database::get())->filter($groupFilter) as $group) { $this->addHtml(new HtmlElement( 'li', null, diff --git a/library/Notifications/Widget/Schedule.php b/library/Notifications/Widget/Schedule.php index d182bf46..6d0ac2f1 100644 --- a/library/Notifications/Widget/Schedule.php +++ b/library/Notifications/Widget/Schedule.php @@ -10,7 +10,6 @@ use ipl\Html\Attributes; use ipl\Html\BaseHtmlElement; use ipl\Html\HtmlElement; -use ipl\Stdlib\Filter; use ipl\Web\Common\BaseTarget; use ipl\Web\Style; @@ -47,16 +46,7 @@ public function __construct(\Icinga\Module\Notifications\Model\Schedule $schedul */ protected function assembleTimeline(Timeline $timeline): void { - $rotations = $this->schedule - ->rotation - ->with('timeperiod') - ->filter(Filter::all( - Filter::equal('deleted', 'n'), - Filter::equal('timeperiod.deleted', 'n') - )) - ->orderBy('first_handoff', SORT_DESC); - - foreach ($rotations as $rotation) { + foreach ($this->schedule->rotation->with('timeperiod')->orderBy('first_handoff', SORT_DESC) as $rotation) { $timeline->addRotation(new Rotation($rotation)); } } diff --git a/library/Notifications/Widget/Timeline/Rotation.php b/library/Notifications/Widget/Timeline/Rotation.php index 816f583c..2f6d2bc4 100644 --- a/library/Notifications/Widget/Timeline/Rotation.php +++ b/library/Notifications/Widget/Timeline/Rotation.php @@ -77,11 +77,6 @@ public function fetchTimeperiodEntries(DateTime $after, DateTime $until): Genera $entries = $this->model->timeperiod->timeperiod_entry ->with(['member.contact', 'member.contactgroup']) ->filter(Filter::all( - Filter::equal('deleted', 'n'), - Filter::any( - Filter::equal('member.contact.deleted', 'n'), - Filter::equal('member.contactgroup.deleted', 'n') - ), Filter::any( Filter::like('rrule', '*'), // It's either a repeating entry Filter::greaterThan('end_time', $after) // Or one whose end time is still visible From 6b5e71f91074dbbf5e67dc6f66c7ca809dfb6148 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 9 Jul 2024 14:27:00 +0200 Subject: [PATCH 24/34] Rotation: Add `delete` method to remove rotation and all its refereces --- library/Notifications/Model/Rotation.php | 71 ++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/library/Notifications/Model/Rotation.php b/library/Notifications/Model/Rotation.php index 276eebe8..bc079da0 100644 --- a/library/Notifications/Model/Rotation.php +++ b/library/Notifications/Model/Rotation.php @@ -5,6 +5,8 @@ namespace Icinga\Module\Notifications\Model; use DateTime; +use Icinga\Module\Notifications\Common\Database; +use Icinga\Module\Notifications\Forms\RotationConfigForm; use Icinga\Util\Json; use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; @@ -13,6 +15,8 @@ use ipl\Orm\Model; use ipl\Orm\Query; use ipl\Orm\Relations; +use ipl\Sql\Expression; +use ipl\Stdlib\Filter; /** * Rotation @@ -99,4 +103,71 @@ public function retrieve(Model $model): void } }); } + + /** + * Delete rotation and all related references + * + * The call must be wrapped in a transaction + * + * @return void + */ + public function delete(): void + { + $db = Database::get(); + $transactionStarted = false; + if (! $db->inTransaction()) { + $transactionStarted = true; + $db->beginTransaction(); + } + + if ($this->timeperiod instanceof Timeperiod) { + $timeperiodId = $this->timeperiod->id; + } else { + $timeperiodId = $this->timeperiod->columns('id')->first(); + } + + $changedAt = time() * 1000; + $markAsDeleted = ['changed_at' => $changedAt, 'deleted' => 'y']; + + $db->update('timeperiod_entry', $markAsDeleted, ['timeperiod_id = ?' => $timeperiodId]); + $db->update('timeperiod', $markAsDeleted, ['id = ?' => $timeperiodId]); + $db->update('rotation_member', $markAsDeleted + ['position' => null], ['rotation_id = ?' => $this->id]); + + $db->update( + 'rotation', + $markAsDeleted + ['priority' => null, 'first_handoff' => null], + ['id = ?' => $this->id] + ); + + $requirePriorityUpdate = true; + if (RotationConfigForm::EXPERIMENTAL_OVERRIDES) { + $rotations = self::on($db) + ->columns('1') + ->filter(Filter::equal('schedule_id', $this->schedule_id)) + ->filter(Filter::equal('priority', $this->priority)) + ->first(); + + $requirePriorityUpdate = $rotations === null; + } + + if ($requirePriorityUpdate) { + $affectedRotations = self::on($db) + ->columns('id') + ->filter(Filter::equal('schedule_id', $this->schedule_id)) + ->filter(Filter::greaterThan('priority', $this->priority)) + ->orderBy('priority', SORT_ASC); + + foreach ($affectedRotations as $rotation) { + $db->update( + 'rotation', + ['priority' => new Expression('priority - 1'), 'changed_at' => $changedAt], + ['id = ?' => $rotation->id] + ); + } + } + + if ($transactionStarted) { + $db->commitTransaction(); + } + } } From b3dbaa26d3b7d74cc124177dae809539e950b044 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 9 Jul 2024 14:38:30 +0200 Subject: [PATCH 25/34] Use rotation::delete() to delete rotations and its references --- application/forms/ContactGroupForm.php | 14 ++- application/forms/RotationConfigForm.php | 92 ++----------------- application/forms/ScheduleForm.php | 7 +- library/Notifications/Model/Rotation.php | 4 +- .../Notifications/Web/Form/ContactForm.php | 14 ++- 5 files changed, 31 insertions(+), 100 deletions(-) diff --git a/application/forms/ContactGroupForm.php b/application/forms/ContactGroupForm.php index d01a79f5..92881a4e 100644 --- a/application/forms/ContactGroupForm.php +++ b/application/forms/ContactGroupForm.php @@ -8,6 +8,7 @@ use Icinga\Module\Notifications\Common\Links; use Icinga\Module\Notifications\Model\Contact; use Icinga\Module\Notifications\Model\Contactgroup; +use Icinga\Module\Notifications\Model\Rotation; use Icinga\Module\Notifications\Model\RotationMember; use Icinga\Web\Session; use ipl\Html\FormElement\SubmitElement; @@ -318,11 +319,14 @@ public function removeContactgroup(): void $toRemoveRotations = array_diff($rotationIds, $rotationIdsWithOtherMembers); if (! empty($toRemoveRotations)) { - $this->db->update( - 'rotation', - $markAsDeleted + ['priority' => null, 'first_handoff' => null], - ['id IN (?)' => $toRemoveRotations] - ); + $rotations = Rotation::on($this->db) + ->columns(['id', 'schedule_id', 'priority', 'timeperiod.id']) + ->filter(Filter::equal('id', $toRemoveRotations)); + + /** @var Rotation $rotation */ + foreach ($rotations as $rotation) { + $rotation->delete(); + } } } diff --git a/application/forms/RotationConfigForm.php b/application/forms/RotationConfigForm.php index b86da032..22d8717f 100644 --- a/application/forms/RotationConfigForm.php +++ b/application/forms/RotationConfigForm.php @@ -510,48 +510,13 @@ public function removeRotation(int $id): void $transactionStarted = $this->db->beginTransaction(); } - $timeperiodId = $this->db->fetchScalar( - (new Select()) - ->from('timeperiod') - ->columns('id') - ->where(['owned_by_rotation_id = ?' => $id]) - ); - - $changedAt = time() * 1000; - $markAsDeleted = ['changed_at' => $changedAt, 'deleted' => 'y']; - - $this->db->update('timeperiod_entry', $markAsDeleted, ['timeperiod_id = ?' => $timeperiodId]); - $this->db->update('timeperiod', $markAsDeleted, ['id = ?' => $timeperiodId]); - $this->db->update('rotation_member', $markAsDeleted + ['position' => null], ['rotation_id = ?' => $id]); - - $this->db->update( - 'rotation', - $markAsDeleted + ['priority' => null, 'first_handoff' => null], - ['id = ?' => $id] - ); + /** @var Rotation $rotation */ + $rotation = Rotation::on($this->db) + ->columns(['id', 'schedule_id', 'priority', 'timeperiod.id']) + ->filter(Filter::equal('id', $id)) + ->first(); - $rotations = Rotation::on($this->db) - ->filter(Filter::equal('schedule_id', $this->scheduleId)) - ->filter(Filter::equal('priority', $priority)); - if ($rotations->count() === 0) { - $affectedRotations = $this->db->select( - (new Select()) - ->columns('id') - ->from('rotation') - ->where([ - 'schedule_id = ?' => $this->scheduleId, - 'priority > ?' => $priority - ]) - ->orderBy('priority ASC') - ); - foreach ($affectedRotations as $rotation) { - $this->db->update( - 'rotation', - ['priority' => new Expression('priority - 1'), 'changed_at' => $changedAt], - ['id = ?' => $rotation->id] - ); - } - } + $rotation->delete(); if ($transactionStarted) { $this->db->commitTransaction(); @@ -576,52 +541,13 @@ public function wipeRotation(int $priority = null): void } $rotations = Rotation::on($this->db) - ->columns('id') + ->columns(['id', 'schedule_id', 'priority', 'timeperiod.id']) ->filter(Filter::equal('schedule_id', $this->scheduleId)) ->filter(Filter::equal('priority', $priority)); - $changedAt = time() * 1000; - $markAsDeleted = ['changed_at' => $changedAt, 'deleted' => 'y']; - + /** @var Rotation $rotation */ foreach ($rotations as $rotation) { - $timeperiodId = $this->db->fetchScalar( - (new Select()) - ->from('timeperiod') - ->columns('id') - ->where(['owned_by_rotation_id = ?' => $rotation->id]) - ); - - $this->db->update('timeperiod_entry', $markAsDeleted, ['timeperiod_id = ?' => $timeperiodId]); - $this->db->update('timeperiod', $markAsDeleted, ['id = ?' => $timeperiodId]); - $this->db->update( - 'rotation_member', - $markAsDeleted + ['position' => null], - ['rotation_id = ?' => $rotation->id] - ); - - $this->db->update( - 'rotation', - $markAsDeleted + ['priority' => null, 'first_handoff' => null], - ['id = ?' => $rotation->id] - ); - } - - $affectedRotations = $this->db->select( - (new Select()) - ->columns('id') - ->from('rotation') - ->where([ - 'schedule_id = ?' => $this->scheduleId, - 'priority > ?' => $priority - ]) - ->orderBy('priority ASC') - ); - foreach ($affectedRotations as $rotation) { - $this->db->update( - 'rotation', - ['priority' => new Expression('priority - 1'), 'changed_at' => $changedAt], - ['id = ?' => $rotation->id] - ); + $rotation->delete(); } if ($transactionStarted) { diff --git a/application/forms/ScheduleForm.php b/application/forms/ScheduleForm.php index 82b3cb78..3f98e942 100644 --- a/application/forms/ScheduleForm.php +++ b/application/forms/ScheduleForm.php @@ -101,14 +101,13 @@ public function removeSchedule(int $id): void $this->db->beginTransaction(); $rotations = Rotation::on($this->db) - ->columns('priority') + ->columns(['id', 'schedule_id', 'priority', 'timeperiod.id']) ->filter(Filter::equal('schedule_id', $id)) ->orderBy('priority', SORT_DESC); - $rotationConfigForm = new RotationConfigForm($id, $this->db); - + /** @var Rotation $rotation */ foreach ($rotations as $rotation) { - $rotationConfigForm->wipeRotation($rotation->priority); + $rotation->delete(); } $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; diff --git a/library/Notifications/Model/Rotation.php b/library/Notifications/Model/Rotation.php index bc079da0..61a00098 100644 --- a/library/Notifications/Model/Rotation.php +++ b/library/Notifications/Model/Rotation.php @@ -107,8 +107,6 @@ public function retrieve(Model $model): void /** * Delete rotation and all related references * - * The call must be wrapped in a transaction - * * @return void */ public function delete(): void @@ -123,7 +121,7 @@ public function delete(): void if ($this->timeperiod instanceof Timeperiod) { $timeperiodId = $this->timeperiod->id; } else { - $timeperiodId = $this->timeperiod->columns('id')->first(); + $timeperiodId = $this->timeperiod->columns('id')->first()->id; } $changedAt = time() * 1000; diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index 07fdc968..e8a002cf 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -8,6 +8,7 @@ use Icinga\Module\Notifications\Model\AvailableChannelType; use Icinga\Module\Notifications\Model\Channel; use Icinga\Module\Notifications\Model\Contact; +use Icinga\Module\Notifications\Model\Rotation; use Icinga\Module\Notifications\Model\RotationMember; use Icinga\Web\Session; use ipl\Html\Contract\FormSubmitElement; @@ -290,11 +291,14 @@ public function removeContact(): void $toRemoveRotations = array_diff($rotationIds, $rotationIdsWithOtherMembers); if (! empty($toRemoveRotations)) { - $this->db->update( - 'rotation', - $markAsDeleted + ['priority' => null, 'first_handoff' => null], - ['id IN (?)' => $toRemoveRotations] - ); + $rotations = Rotation::on($this->db) + ->columns(['id', 'schedule_id', 'priority', 'timeperiod.id']) + ->filter(Filter::equal('id', $toRemoveRotations)); + + /** @var Rotation $rotation */ + foreach ($rotations as $rotation) { + $rotation->delete(); + } } } From 7ebd14ed8186ba7f3fc443b5d2c2a3996a5e6863 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 9 Jul 2024 16:15:46 +0200 Subject: [PATCH 26/34] Contact|Schedule|Contactgroup: Remove `rule_escalation` if the removed recipient was the only one --- application/forms/ContactGroupForm.php | 30 +++++++++++++++++++ application/forms/ScheduleForm.php | 30 +++++++++++++++++++ .../Notifications/Web/Form/ContactForm.php | 30 +++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/application/forms/ContactGroupForm.php b/application/forms/ContactGroupForm.php index 92881a4e..903be85d 100644 --- a/application/forms/ContactGroupForm.php +++ b/application/forms/ContactGroupForm.php @@ -10,6 +10,8 @@ use Icinga\Module\Notifications\Model\Contactgroup; use Icinga\Module\Notifications\Model\Rotation; use Icinga\Module\Notifications\Model\RotationMember; +use Icinga\Module\Notifications\Model\RuleEscalation; +use Icinga\Module\Notifications\Model\RuleEscalationRecipient; use Icinga\Web\Session; use ipl\Html\FormElement\SubmitElement; use ipl\Html\HtmlDocument; @@ -330,12 +332,40 @@ public function removeContactgroup(): void } } + $escalationIds = $this->db->fetchCol( + RuleEscalationRecipient::on($this->db) + ->columns('rule_escalation_id') + ->filter(Filter::equal('contactgroup_id', $this->contactgroupId)) + ->assembleSelect() + ); + $this->db->update( 'rule_escalation_recipient', $markAsDeleted, ['contactgroup_id = ?' => $this->contactgroupId] ); + if (! empty($escalationIds)) { + $escalationIdsWithOtherRecipients = $this->db->fetchCol( + RuleEscalationRecipient::on($this->db) + ->columns('rule_escalation_id') + ->filter(Filter::all( + Filter::equal('rule_escalation_id', $escalationIds), + Filter::unequal('contactgroup_id', $this->contactgroupId) + ))->assembleSelect() + ); + + $toRemoveEscalations = array_diff($escalationIds, $escalationIdsWithOtherRecipients); + + if (! empty($toRemoveEscalations)) { + $this->db->update( + 'rule_escalation', + $markAsDeleted + ['position' => null], + ['id IN (?)' => $toRemoveEscalations] + ); + } + } + $this->db->update('contactgroup_member', $markAsDeleted, ['contactgroup_id = ?' => $this->contactgroupId]); $this->db->update('contactgroup', $markAsDeleted, ['id = ?' => $this->contactgroupId]); diff --git a/application/forms/ScheduleForm.php b/application/forms/ScheduleForm.php index 3f98e942..01e7d21f 100644 --- a/application/forms/ScheduleForm.php +++ b/application/forms/ScheduleForm.php @@ -6,6 +6,7 @@ use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Notifications\Model\Rotation; +use Icinga\Module\Notifications\Model\RuleEscalationRecipient; use Icinga\Module\Notifications\Model\Schedule; use Icinga\Web\Session; use ipl\Html\HtmlDocument; @@ -112,7 +113,36 @@ public function removeSchedule(int $id): void $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; + $escalationIds = $this->db->fetchCol( + RuleEscalationRecipient::on($this->db) + ->columns('rule_escalation_id') + ->filter(Filter::equal('schedule_id', $id)) + ->assembleSelect() + ); + $this->db->update('rule_escalation_recipient', $markAsDeleted, ['schedule_id = ?' => $id]); + + if (! empty($escalationIds)) { + $escalationIdsWithOtherRecipients = $this->db->fetchCol( + RuleEscalationRecipient::on($this->db) + ->columns('rule_escalation_id') + ->filter(Filter::all( + Filter::equal('rule_escalation_id', $escalationIds), + Filter::unequal('schedule_id', $id) + ))->assembleSelect() + ); + + $toRemoveEscalations = array_diff($escalationIds, $escalationIdsWithOtherRecipients); + + if (! empty($toRemoveEscalations)) { + $this->db->update( + 'rule_escalation', + $markAsDeleted + ['position' => null], + ['id IN (?)' => $toRemoveEscalations] + ); + } + } + $this->db->update('schedule', $markAsDeleted, ['id = ?' => $id]); $this->db->commitTransaction(); diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index e8a002cf..296fbde1 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -10,6 +10,7 @@ use Icinga\Module\Notifications\Model\Contact; use Icinga\Module\Notifications\Model\Rotation; use Icinga\Module\Notifications\Model\RotationMember; +use Icinga\Module\Notifications\Model\RuleEscalationRecipient; use Icinga\Web\Session; use ipl\Html\Contract\FormSubmitElement; use ipl\Html\FormElement\FieldsetElement; @@ -302,7 +303,36 @@ public function removeContact(): void } } + $escalationIds = $this->db->fetchCol( + RuleEscalationRecipient::on($this->db) + ->columns('rule_escalation_id') + ->filter(Filter::equal('contact_id', $this->contactId)) + ->assembleSelect() + ); + $this->db->update('rule_escalation_recipient', $markAsDeleted, ['contact_id = ?' => $this->contactId]); + + if (! empty($escalationIds)) { + $escalationIdsWithOtherRecipients = $this->db->fetchCol( + RuleEscalationRecipient::on($this->db) + ->columns('rule_escalation_id') + ->filter(Filter::all( + Filter::equal('rule_escalation_id', $escalationIds), + Filter::unequal('contact_id', $this->contactId) + ))->assembleSelect() + ); + + $toRemoveEscalations = array_diff($escalationIds, $escalationIdsWithOtherRecipients); + + if (! empty($toRemoveEscalations)) { + $this->db->update( + 'rule_escalation', + $markAsDeleted + ['position' => null], + ['id IN (?)' => $toRemoveEscalations] + ); + } + } + $this->db->update('contactgroup_member', $markAsDeleted, ['contact_id = ?' => $this->contactId]); $this->db->update('contact_address', $markAsDeleted, ['contact_id = ?' => $this->contactId]); $this->db->update('contact', $markAsDeleted + ['username' => null], ['id = ?' => $this->contactId]); From f841484239da0a1ae32130668a7999bf1d6f1c55 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 10 Jul 2024 08:53:20 +0200 Subject: [PATCH 27/34] ContactGroupController: Don't fetch deleted contactgroup_member --- application/controllers/ContactGroupController.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/application/controllers/ContactGroupController.php b/application/controllers/ContactGroupController.php index 3ccebe36..b1c31aa4 100644 --- a/application/controllers/ContactGroupController.php +++ b/application/controllers/ContactGroupController.php @@ -46,9 +46,11 @@ public function indexAction(): void $this->addControl(new HtmlElement('div', new Attributes(['class' => 'header']), Text::create($group->name))); - $contacts = $group - ->contact - ->filter(Filter::equal('contactgroup_member.deleted', 'n')); + $contacts = Contact::on(Database::get()) + ->filter(Filter::all( + Filter::equal('contactgroup_member.contactgroup_id', $groupId), + Filter::equal('contactgroup_member.deleted', 'n') + )); $this->addControl($this->createPaginationControl($contacts)); $this->addControl($this->createLimitControl()); From 81ad3c15172b18bca1572c1b29686879702db513 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 10 Jul 2024 11:14:44 +0200 Subject: [PATCH 28/34] Forms: Do not update changed_at column of deleted entries again --- application/forms/ContactGroupForm.php | 25 +++++++++---------- application/forms/SaveEventRuleForm.php | 6 ++--- library/Notifications/Model/Rotation.php | 9 +++++-- .../Notifications/Web/Form/ContactForm.php | 16 ++++++------ 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/application/forms/ContactGroupForm.php b/application/forms/ContactGroupForm.php index 903be85d..07608ab1 100644 --- a/application/forms/ContactGroupForm.php +++ b/application/forms/ContactGroupForm.php @@ -243,7 +243,8 @@ public function editGroup(): void ['changed_at' => $changedAt, 'deleted' => 'y'], [ 'contactgroup_id = ?' => $this->contactgroupId, - 'contact_id IN (?)' => $toDelete + 'contact_id IN (?)' => $toDelete, + 'deleted = ?' => 'n' ] ); } @@ -294,6 +295,7 @@ public function removeContactgroup(): void $this->db->beginTransaction(); $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; + $updateCondition = ['contactgroup_id = ?' => $this->contactgroupId, 'deleted = ?' => 'n']; $rotationIds = $this->db->fetchCol( RotationMember::on($this->db) @@ -302,11 +304,7 @@ public function removeContactgroup(): void ->assembleSelect() ); - $this->db->update( - 'rotation_member', - $markAsDeleted + ['position' => null], - ['contactgroup_id = ?' => $this->contactgroupId] - ); + $this->db->update('rotation_member', $markAsDeleted + ['position' => null], $updateCondition); if (! empty($rotationIds)) { $rotationIdsWithOtherMembers = $this->db->fetchCol( @@ -339,11 +337,7 @@ public function removeContactgroup(): void ->assembleSelect() ); - $this->db->update( - 'rule_escalation_recipient', - $markAsDeleted, - ['contactgroup_id = ?' => $this->contactgroupId] - ); + $this->db->update('rule_escalation_recipient', $markAsDeleted, $updateCondition); if (! empty($escalationIds)) { $escalationIdsWithOtherRecipients = $this->db->fetchCol( @@ -366,8 +360,13 @@ public function removeContactgroup(): void } } - $this->db->update('contactgroup_member', $markAsDeleted, ['contactgroup_id = ?' => $this->contactgroupId]); - $this->db->update('contactgroup', $markAsDeleted, ['id = ?' => $this->contactgroupId]); + $this->db->update('contactgroup_member', $markAsDeleted, $updateCondition); + + $this->db->update( + 'contactgroup', + $markAsDeleted, + ['id = ?' => $this->contactgroupId, 'deleted = ?' => 'n'] + ); $this->db->commitTransaction(); } diff --git a/application/forms/SaveEventRuleForm.php b/application/forms/SaveEventRuleForm.php index b7385d4d..dc46dcf7 100644 --- a/application/forms/SaveEventRuleForm.php +++ b/application/forms/SaveEventRuleForm.php @@ -344,7 +344,7 @@ function (array $element) use ($recipientId) { $db->update( 'rule_escalation_recipient', ['changed_at' => $changedAt, 'deleted' => 'y'], - ['id IN (?)' => $recipientsToRemove] + ['id IN (?)' => $recipientsToRemove, 'deleted = ?' => 'n'] ); } } @@ -455,7 +455,7 @@ public function editRule(int $id, array $config): void $db->update( 'rule_escalation_recipient', $markAsDeleted, - ['rule_escalation_id IN (?)' => $escalationsToRemove] + ['rule_escalation_id IN (?)' => $escalationsToRemove, 'deleted = ?' => 'n'] ); $db->update( @@ -501,7 +501,7 @@ public function removeRule(int $id): void $db->update( 'rule_escalation_recipient', $markAsDeleted, - ['rule_escalation_id IN (?)' => $escalationsToRemove] + ['rule_escalation_id IN (?)' => $escalationsToRemove, 'deleted = ?' => 'n'] ); } diff --git a/library/Notifications/Model/Rotation.php b/library/Notifications/Model/Rotation.php index 61a00098..46a3901b 100644 --- a/library/Notifications/Model/Rotation.php +++ b/library/Notifications/Model/Rotation.php @@ -127,9 +127,14 @@ public function delete(): void $changedAt = time() * 1000; $markAsDeleted = ['changed_at' => $changedAt, 'deleted' => 'y']; - $db->update('timeperiod_entry', $markAsDeleted, ['timeperiod_id = ?' => $timeperiodId]); + $db->update('timeperiod_entry', $markAsDeleted, ['timeperiod_id = ?' => $timeperiodId, 'deleted = ?' => 'n']); $db->update('timeperiod', $markAsDeleted, ['id = ?' => $timeperiodId]); - $db->update('rotation_member', $markAsDeleted + ['position' => null], ['rotation_id = ?' => $this->id]); + + $db->update( + 'rotation_member', + $markAsDeleted + ['position' => null], + ['rotation_id = ?' => $this->id, 'deleted = ?' => 'n'] + ); $db->update( 'rotation', diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index 296fbde1..de8b5cd4 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -229,7 +229,7 @@ public function editContact(): void $this->db->update( 'contact_address', ['changed_at' => $changedAt, 'deleted' => 'y'], - ['id = ?' => $storedAddresses[$type][0]] + ['id = ?' => $storedAddresses[$type][0], 'deleted = ?' => 'n'] ); } } elseif (! isset($storedAddresses[$type])) { @@ -263,6 +263,7 @@ public function removeContact(): void $this->db->beginTransaction(); $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; + $updateCondition = ['contact_id = ?' => $this->contactId, 'deleted = ?' => 'n']; $rotationIds = $this->db->fetchCol( RotationMember::on($this->db) @@ -271,11 +272,7 @@ public function removeContact(): void ->assembleSelect() ); - $this->db->update( - 'rotation_member', - $markAsDeleted + ['position' => null], - ['contact_id = ?' => $this->contactId] - ); + $this->db->update('rotation_member', $markAsDeleted + ['position' => null], $updateCondition); if (! empty($rotationIds)) { $rotationIdsWithOtherMembers = $this->db->fetchCol( @@ -310,7 +307,7 @@ public function removeContact(): void ->assembleSelect() ); - $this->db->update('rule_escalation_recipient', $markAsDeleted, ['contact_id = ?' => $this->contactId]); + $this->db->update('rule_escalation_recipient', $markAsDeleted, $updateCondition); if (! empty($escalationIds)) { $escalationIdsWithOtherRecipients = $this->db->fetchCol( @@ -333,8 +330,9 @@ public function removeContact(): void } } - $this->db->update('contactgroup_member', $markAsDeleted, ['contact_id = ?' => $this->contactId]); - $this->db->update('contact_address', $markAsDeleted, ['contact_id = ?' => $this->contactId]); + $this->db->update('contactgroup_member', $markAsDeleted, $updateCondition); + $this->db->update('contact_address', $markAsDeleted, $updateCondition); + $this->db->update('contact', $markAsDeleted + ['username' => null], ['id = ?' => $this->contactId]); $this->db->commitTransaction(); From 266487c9fa3d98f1eedf7225b87c03bbeb785f6e Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 10 Jul 2024 11:45:13 +0200 Subject: [PATCH 29/34] Allow to delete event rule --- .../controllers/EventRuleController.php | 12 -------- application/forms/SaveEventRuleForm.php | 30 ------------------- 2 files changed, 42 deletions(-) diff --git a/application/controllers/EventRuleController.php b/application/controllers/EventRuleController.php index d009531c..e331e3a2 100644 --- a/application/controllers/EventRuleController.php +++ b/application/controllers/EventRuleController.php @@ -61,21 +61,9 @@ public function indexAction(): void ); } - $disableRemoveButton = false; - if (ctype_digit($ruleId)) { - $incidents = Incident::on(Database::get()) - ->with('rule') - ->filter(Filter::equal('rule.id', $ruleId)); - - if ($incidents->count() > 0) { - $disableRemoveButton = true; - } - } - $saveForm = (new SaveEventRuleForm()) ->setShowRemoveButton() ->setShowDismissChangesButton($cache !== null) - ->setRemoveButtonDisabled($disableRemoveButton) ->setSubmitButtonDisabled($cache === null) ->setSubmitLabel($this->translate('Save Changes')) ->on(SaveEventRuleForm::ON_SUCCESS, function ($form) use ($ruleId, $eventRuleConfig) { diff --git a/application/forms/SaveEventRuleForm.php b/application/forms/SaveEventRuleForm.php index dc46dcf7..89dc645f 100644 --- a/application/forms/SaveEventRuleForm.php +++ b/application/forms/SaveEventRuleForm.php @@ -46,9 +46,6 @@ class SaveEventRuleForm extends Form /** @var bool Whether to show a button to dismiss cached changes */ protected $showDismissChangesButton = false; - /** @var bool Whether to disable the remove button */ - protected $disableRemoveButton = false; - /** @var int The rule id */ protected $ruleId; @@ -83,20 +80,6 @@ public function setSubmitButtonDisabled(bool $state = true): self return $this; } - /** - * Set whether to enable or disable the remove button - * - * @param bool $state - * - * @return $this - */ - public function setRemoveButtonDisabled(bool $state = true): self - { - $this->disableRemoveButton = $state; - - return $this; - } - /** * Set the submit label * @@ -196,19 +179,6 @@ protected function assemble() ]); $this->registerElement($removeBtn); - $this->getElement('remove') - ->getAttributes() - ->registerAttributeCallback('disabled', function () { - return $this->disableRemoveButton; - }) - ->registerAttributeCallback('title', function () { - if ($this->disableRemoveButton) { - return $this->translate( - 'There exist active incidents for this event rule and hence cannot be deleted' - ); - } - }); - $additionalButtons[] = $removeBtn; } From 2ebe803baba5fdd35db445d7eb07bb5ed9e42aae Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 10 Jul 2024 15:12:13 +0200 Subject: [PATCH 30/34] SaveEventRuleForm: Fix Undefined array key `rule_escalation` error --- application/forms/SaveEventRuleForm.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/forms/SaveEventRuleForm.php b/application/forms/SaveEventRuleForm.php index 89dc645f..09bd80a9 100644 --- a/application/forms/SaveEventRuleForm.php +++ b/application/forms/SaveEventRuleForm.php @@ -528,6 +528,10 @@ private function fetchDbValues(): array } } + if (! isset($config['rule_escalation'])) { + $config['rule_escalation'] = []; + } + $config['showSearchbar'] = ! empty($config['object_filter']); return $config; From 2bf142a5f817478ad43d0fbd8e171446877b08a5 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 11 Jul 2024 10:38:39 +0200 Subject: [PATCH 31/34] Contact(group)Form: Delete `timeperiod_entry` when the member is deleted --- application/forms/ContactGroupForm.php | 15 +++++++++++++-- library/Notifications/Web/Form/ContactForm.php | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/application/forms/ContactGroupForm.php b/application/forms/ContactGroupForm.php index 07608ab1..33ab81c0 100644 --- a/application/forms/ContactGroupForm.php +++ b/application/forms/ContactGroupForm.php @@ -297,15 +297,26 @@ public function removeContactgroup(): void $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; $updateCondition = ['contactgroup_id = ?' => $this->contactgroupId, 'deleted = ?' => 'n']; - $rotationIds = $this->db->fetchCol( + $rotationAndMemberIds = $this->db->fetchPairs( RotationMember::on($this->db) - ->columns('rotation_id') + ->columns(['id', 'rotation_id']) ->filter(Filter::equal('contactgroup_id', $this->contactgroupId)) ->assembleSelect() ); + $rotationMemberIds = array_keys($rotationAndMemberIds); + $rotationIds = array_values($rotationAndMemberIds); + $this->db->update('rotation_member', $markAsDeleted + ['position' => null], $updateCondition); + if (! empty($rotationMemberIds)) { + $this->db->update( + 'timeperiod_entry', + $markAsDeleted, + ['rotation_member_id IN (?)' => $rotationMemberIds, 'deleted = ?' => 'n'] + ); + } + if (! empty($rotationIds)) { $rotationIdsWithOtherMembers = $this->db->fetchCol( RotationMember::on($this->db) diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index de8b5cd4..5f2e2076 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -265,15 +265,26 @@ public function removeContact(): void $markAsDeleted = ['changed_at' => time() * 1000, 'deleted' => 'y']; $updateCondition = ['contact_id = ?' => $this->contactId, 'deleted = ?' => 'n']; - $rotationIds = $this->db->fetchCol( + $rotationAndMemberIds = $this->db->fetchPairs( RotationMember::on($this->db) - ->columns('rotation_id') + ->columns(['id', 'rotation_id']) ->filter(Filter::equal('contact_id', $this->contactId)) ->assembleSelect() ); + $rotationMemberIds = array_keys($rotationAndMemberIds); + $rotationIds = array_values($rotationAndMemberIds); + $this->db->update('rotation_member', $markAsDeleted + ['position' => null], $updateCondition); + if (! empty($rotationMemberIds)) { + $this->db->update( + 'timeperiod_entry', + $markAsDeleted, + ['rotation_member_id IN (?)' => $rotationMemberIds, 'deleted = ?' => 'n'] + ); + } + if (! empty($rotationIds)) { $rotationIdsWithOtherMembers = $this->db->fetchCol( RotationMember::on($this->db) From 54c6408f7078b5074b6730d5d96c3868db078514 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 11 Jul 2024 12:51:46 +0200 Subject: [PATCH 32/34] ChannelForm: Disable delete button if a contact has the channel as default_channel --- application/forms/ChannelForm.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index f7ccbc27..ccffdb45 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -7,6 +7,7 @@ use Icinga\Exception\Http\HttpNotFoundException; use Icinga\Module\Notifications\Model\Channel; use Icinga\Module\Notifications\Model\AvailableChannelType; +use Icinga\Module\Notifications\Model\Contact; use Icinga\Module\Notifications\Model\RuleEscalationRecipient; use Icinga\Web\Session; use ipl\Html\Contract\FormSubmitElement; @@ -113,14 +114,18 @@ protected function assemble() ); if ($this->channelId !== null) { - $isInUse = RuleEscalationRecipient::on($this->db) + $isInUse = Contact::on($this->db) ->columns('1') - ->filter(Filter::any( - Filter::equal('channel_id', $this->channelId), - Filter::equal('contact.default_channel_id', $this->channelId) - )) + ->filter(Filter::equal('default_channel_id', $this->channelId)) ->first(); + if ($isInUse === null) { + $isInUse = RuleEscalationRecipient::on($this->db) + ->columns('1') + ->filter(Filter::equal('channel_id', $this->channelId)) + ->first(); + } + /** @var FormSubmitElement $deleteButton */ $deleteButton = $this->createElement( 'submit', From ebfc217c4d984a7ff27f13bbef8926bd53602f30 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 11 Jul 2024 13:37:38 +0200 Subject: [PATCH 33/34] RotationConfigForm: Mark entries as deleted instead of actually deleting them --- application/forms/RotationConfigForm.php | 36 +++++++++++++++++------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/application/forms/RotationConfigForm.php b/application/forms/RotationConfigForm.php index 22d8717f..6a80ec6d 100644 --- a/application/forms/RotationConfigForm.php +++ b/application/forms/RotationConfigForm.php @@ -416,6 +416,7 @@ public function editRotation(int $rotationId): void $allEntriesRemoved = true; $changedAt = time() * 1000; + $markAsDeleted = ['changed_at' => $changedAt, 'deleted' => 'y']; if (self::EXPERIMENTAL_OVERRIDES) { // We only show a single name, even in case of multiple versions of a rotation. // To avoid confusion, we update all versions upon change of the name @@ -458,7 +459,7 @@ public function editRotation(int $rotationId): void if ($lastHandoff === null) { // If the handoff didn't happen at all, the entry can safely be removed - $this->db->delete('timeperiod_entry', ['id = ?' => $timeperiodEntry->id]); + $this->db->update('timeperiod_entry', $markAsDeleted, ['id = ?' => $timeperiodEntry->id]); } else { $allEntriesRemoved = false; $this->db->update('timeperiod_entry', [ @@ -469,18 +470,33 @@ public function editRotation(int $rotationId): void } } } else { - $this->db->delete('timeperiod_entry', [ - 'timeperiod_id = ?' => (new Select()) - ->from('timeperiod') - ->columns('id') - ->where(['owned_by_rotation_id = ?' => $rotationId]) - ]); + $this->db->update( + 'timeperiod_entry', + $markAsDeleted, + [ + 'deleted = ?' => 'n', + 'timeperiod_id = ?' => (new Select()) + ->from('timeperiod') + ->columns('id') + ->where(['owned_by_rotation_id = ?' => $rotationId]) + ] + ); } if ($allEntriesRemoved) { - $this->db->delete('timeperiod', ['owned_by_rotation_id = ?' => $rotationId]); - $this->db->delete('rotation_member', ['rotation_id = ?' => $rotationId]); - $this->db->delete('rotation', ['id = ?' => $rotationId]); + $this->db->update('timeperiod', $markAsDeleted, ['owned_by_rotation_id = ?' => $rotationId]); + + $this->db->update( + 'rotation_member', + $markAsDeleted + ['position' => null], + ['rotation_id = ?' => $rotationId, 'deleted = ?' => 'n'] + ); + + $this->db->update( + 'rotation', + $markAsDeleted + ['priority' => null, 'first_handoff' => null], + ['id = ?' => $rotationId] + ); } // Once constraint failures are impossible, create the new version From 013a57d9e9a45daad9a3e0bbb31bebeebdb2fcb8 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 11 Jul 2024 14:35:43 +0200 Subject: [PATCH 34/34] Forms: Add `changed_at` column manualy on db::insert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mariadb ≥10.1 does not support the default (UNIX_TIMESTAMP() * 1000) More info: https://github.com/Icinga/icinga-notifications/pull/191#discussion_r1673762896 --- application/forms/ChannelForm.php | 1 + application/forms/ContactGroupForm.php | 11 +++++++---- application/forms/RotationConfigForm.php | 14 ++++++++++---- application/forms/SaveEventRuleForm.php | 16 +++++++++++----- application/forms/ScheduleForm.php | 3 ++- application/forms/SourceForm.php | 2 ++ library/Notifications/Web/Form/ContactForm.php | 10 ++++++---- 7 files changed, 39 insertions(+), 18 deletions(-) diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index ccffdb45..f710de4a 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -200,6 +200,7 @@ public function addChannel(): void $channel = $this->getValues(); $channel['config'] = json_encode($this->filterConfig($channel['config'])); + $channel['changed_at'] = time() * 1000; $this->db->insert('channel', $channel); } diff --git a/application/forms/ContactGroupForm.php b/application/forms/ContactGroupForm.php index 33ab81c0..d7d63ed8 100644 --- a/application/forms/ContactGroupForm.php +++ b/application/forms/ContactGroupForm.php @@ -178,7 +178,8 @@ public function addGroup(): int $this->db->beginTransaction(); - $this->db->insert('contactgroup', ['name' => trim($data['group_name'])]); + $changedAt = time() * 1000; + $this->db->insert('contactgroup', ['name' => trim($data['group_name']), 'changed_at' => $changedAt]); $groupIdentifier = $this->db->lastInsertId(); @@ -191,8 +192,9 @@ public function addGroup(): int $this->db->insert( 'contactgroup_member', [ - 'contactgroup_id' => $groupIdentifier, - 'contact_id' => $contactId + 'contactgroup_id' => $groupIdentifier, + 'contact_id' => $contactId, + 'changed_at' => $changedAt ] ); } @@ -267,7 +269,8 @@ public function editGroup(): void 'contactgroup_member', [ 'contactgroup_id' => $this->contactgroupId, - 'contact_id' => $contactId + 'contact_id' => $contactId, + 'changed_at' => $changedAt ] ); } diff --git a/application/forms/RotationConfigForm.php b/application/forms/RotationConfigForm.php index 6a80ec6d..8b1f2fb9 100644 --- a/application/forms/RotationConfigForm.php +++ b/application/forms/RotationConfigForm.php @@ -310,10 +310,12 @@ private function createRotation(int $priority): Generator $data['actual_handoff'] = $firstHandoff->format('U.u') * 1000.0; } + $changedAt = time() * 1000; + $data['changed_at'] = $changedAt; $this->db->insert('rotation', $data); $rotationId = $this->db->lastInsertId(); - $this->db->insert('timeperiod', ['owned_by_rotation_id' => $rotationId]); + $this->db->insert('timeperiod', ['owned_by_rotation_id' => $rotationId, 'changed_at' => $changedAt]); $timeperiodId = $this->db->lastInsertId(); $knownMembers = []; @@ -330,13 +332,15 @@ private function createRotation(int $priority): Generator $this->db->insert('rotation_member', [ 'rotation_id' => $rotationId, 'contact_id' => $id, - 'position' => $position + 'position' => $position, + 'changed_at' => $changedAt ]); } elseif ($type === 'group') { $this->db->insert('rotation_member', [ 'rotation_id' => $rotationId, 'contactgroup_id' => $id, - 'position' => $position + 'position' => $position, + 'changed_at' => $changedAt ]); } @@ -360,6 +364,7 @@ private function createRotation(int $priority): Generator 'until_time' => $untilTime, 'timezone' => $rrule->getStartDate()->getTimezone()->getName(), 'rrule' => $rrule->getString(Rule::TZ_FIXED), + 'changed_at' => $changedAt ]); } } @@ -448,7 +453,8 @@ public function editRotation(int $rotationId): void 'start_time' => $gapStart->format('U.u') * 1000.0, 'end_time' => $gapEnd->format('U.u') * 1000.0, 'until_time' => $gapEnd->format('U.u') * 1000.0, - 'timezone' => $gapStart->getTimezone()->getName() + 'timezone' => $gapStart->getTimezone()->getName(), + 'changed_at' => $changedAt ]); } diff --git a/application/forms/SaveEventRuleForm.php b/application/forms/SaveEventRuleForm.php index 09bd80a9..f19f25b1 100644 --- a/application/forms/SaveEventRuleForm.php +++ b/application/forms/SaveEventRuleForm.php @@ -212,10 +212,12 @@ public function addRule(array $config): int $db->beginTransaction(); + $changedAt = time() * 1000; $db->insert('rule', [ 'name' => $config['name'], 'timeperiod_id' => $config['timeperiod_id'] ?? null, - 'object_filter' => $config['object_filter'] ?? null + 'object_filter' => $config['object_filter'] ?? null, + 'changed_at' => $changedAt ]); $ruleId = $db->lastInsertId(); @@ -225,14 +227,16 @@ public function addRule(array $config): int 'position' => $position, 'condition' => $escalationConfig['condition'] ?? null, 'name' => $escalationConfig['name'] ?? null, - 'fallback_for' => $escalationConfig['fallback_for'] ?? null + 'fallback_for' => $escalationConfig['fallback_for'] ?? null, + 'changed_at' => $changedAt ]); $escalationId = $db->lastInsertId(); foreach ($escalationConfig['recipient'] ?? [] as $recipientConfig) { $data = [ 'rule_escalation_id' => $escalationId, - 'channel_id' => $recipientConfig['channel_id'] + 'channel_id' => $recipientConfig['channel_id'], + 'changed_at' => $changedAt ]; switch (true) { @@ -276,7 +280,8 @@ private function insertOrUpdateEscalations($ruleId, array $escalations, Connecti 'position' => $position, 'condition' => $escalationConfig['condition'] ?? null, 'name' => $escalationConfig['name'] ?? null, - 'fallback_for' => $escalationConfig['fallback_for'] ?? null + 'fallback_for' => $escalationConfig['fallback_for'] ?? null, + 'changed_at' => $changedAt ]); $escalationId = $db->lastInsertId(); @@ -322,7 +327,8 @@ function (array $element) use ($recipientId) { foreach ($escalationConfig['recipient'] ?? [] as $recipientConfig) { $data = [ 'rule_escalation_id' => $escalationId, - 'channel_id' => $recipientConfig['channel_id'] + 'channel_id' => $recipientConfig['channel_id'], + 'changed_at' => $changedAt ]; switch (true) { diff --git a/application/forms/ScheduleForm.php b/application/forms/ScheduleForm.php index 01e7d21f..5d9b1f4b 100644 --- a/application/forms/ScheduleForm.php +++ b/application/forms/ScheduleForm.php @@ -72,7 +72,8 @@ public function loadSchedule(int $id): void public function addSchedule(): int { $this->db->insert('schedule', [ - 'name' => $this->getValue('name') + 'name' => $this->getValue('name'), + 'changed_at' => time() * 1000 ]); return $this->db->lastInsertId(); diff --git a/application/forms/SourceForm.php b/application/forms/SourceForm.php index 41587e16..ca8f9ee0 100644 --- a/application/forms/SourceForm.php +++ b/application/forms/SourceForm.php @@ -294,6 +294,8 @@ public function addSource(): void self::HASH_ALGORITHM ); + $source['changed_at'] = time() * 1000; + $this->db->insert('source', $source); } diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index 5f2e2076..246f015a 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -183,16 +183,17 @@ public function loadContact(int $id): self public function addContact(): void { $contactInfo = $this->getValues(); - + $changedAt = time() * 1000; $this->db->beginTransaction(); - $this->db->insert('contact', $contactInfo['contact']); + $this->db->insert('contact', $contactInfo['contact'] + ['changed_at' => $changedAt]); $this->contactId = $this->db->lastInsertId(); foreach (array_filter($contactInfo['contact_address']) as $type => $address) { $address = [ 'contact_id' => $this->contactId, 'type' => $type, - 'address' => $address + 'address' => $address, + 'changed_at' => $changedAt ]; $this->db->insert('contact_address', $address); @@ -236,7 +237,8 @@ public function editContact(): void $address = [ 'contact_id' => $this->contactId, 'type' => $type, - 'address' => $address + 'address' => $address, + 'changed_at' => $changedAt ]; $this->db->insert('contact_address', $address);