From 6dabb0a5c5e8086581c134180394e7772531de5f Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 12 Oct 2023 12:16:23 +0200 Subject: [PATCH 1/9] Use plugin table data to create channelForm - A channel plugin now defines required config elements and insert them into plugin table's `config_attrs` column as json --- application/forms/ChannelForm.php | 302 ++++++++++-------- .../Model/AvailableChannelType.php | 37 +++ library/Notifications/Model/Channel.php | 24 ++ .../Widget/ItemList/ChannelListItem.php | 6 +- 4 files changed, 233 insertions(+), 136 deletions(-) create mode 100644 library/Notifications/Model/AvailableChannelType.php diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index a8a31dec..d6fec7a8 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -5,11 +5,16 @@ namespace Icinga\Module\Notifications\Forms; use Icinga\Module\Notifications\Model\Channel; +use Icinga\Module\Notifications\Model\AvailableChannelType; use Icinga\Web\Session; use ipl\Html\Contract\FormSubmitElement; +use ipl\Html\FormElement\FieldsetElement; +use ipl\I18n\StaticTranslator; use ipl\Sql\Connection; +use ipl\Validator\EmailAddressValidator; use ipl\Web\Common\CsrfCounterMeasure; use ipl\Web\Compat\CompatForm; +use stdClass; class ChannelForm extends CompatForm { @@ -21,10 +26,14 @@ class ChannelForm extends CompatForm /** @var ?int Channel ID */ private $channelId; + /** @var string The current locale */ + private $locale; + public function __construct(Connection $db, ?int $channelId = null) { $this->db = $db; $this->channelId = $channelId; + $this->locale = StaticTranslator::$instance->getLocale(); } protected function assemble() @@ -41,115 +50,35 @@ protected function assemble() ] ); - $type = [ - '' => sprintf(' - %s - ', $this->translate('Please choose')), - 'email' => $this->translate('Email'), - 'rocketchat' => 'Rocket.Chat' - ]; + $query = AvailableChannelType::on($this->db)->columns(['type', 'name', 'config_attrs']); + + $typesConfig = []; + $typeNamePair = []; + $defaultType = null; + foreach ($query as $channel) { + if ($defaultType === null) { + $defaultType = $channel->type; + } + + $typesConfig[$channel->type] = $channel->config_attrs; + $typeNamePair[$channel->type] = $channel->name; + } $this->addElement( 'select', 'type', [ - 'label' => $this->translate('Type'), - 'class' => 'autosubmit', - 'required' => true, - 'disable' => [''], - 'options' => $type + 'label' => $this->translate('Type'), + 'class' => 'autosubmit', + 'required' => true, + 'disabledOptions' => [''], + 'value' => $defaultType, + 'options' => $typeNamePair ] ); $selectedType = $this->getValue('type'); - - if ($selectedType === 'email') { - $this->addElement( - 'text', - 'host', - [ - 'label' => $this->translate('SMTP Host'), - 'autocomplete' => 'off', - 'placeholder' => 'localhost' - ] - )->addElement( - 'select', - 'port', - [ - 'label' => $this->translate('SMTP Port'), - 'options' => [ - 25 => 25, - 465 => 465, - 587 => 587, - 2525 => 2525 - ] - ] - )->addElement( - 'text', - 'from', - [ - 'label' => $this->translate('From'), - 'autocomplete' => 'off', - 'placeholder' => 'notifications@icinga' - ] - )->addElement( - 'password', - 'password', - [ - 'label' => $this->translate('Password'), - 'autocomplete' => 'off', - ] - ); - - $this->addElement( - 'checkbox', - 'tls', - [ - 'label' => 'TLS / SSL', - 'class' => 'autosubmit', - 'checkedValue' => '1', - 'uncheckedValue' => '0', - 'value' => '1' - ] - ); - - if ($this->getElement('tls')->getValue() === '1') { - $this->addElement( - 'checkbox', - 'tls_certcheck', - [ - 'label' => $this->translate('Certificate Check'), - 'class' => 'autosubmit', - 'checkedValue' => '1', - 'uncheckedValue' => '0', - 'value' => '0' - ] - ); - } - } elseif ($selectedType === 'rocketchat') { - $this->addElement( - 'text', - 'url', - [ - 'label' => $this->translate('Rocket.Chat URL'), - 'required' => true - ] - )->addElement( - 'text', - 'user_id', - [ - 'autocomplete' => 'off', - 'label' => $this->translate('User ID'), - 'required' => true - ] - )->addElement( - 'password', - 'token', - [ - 'autocomplete' => 'off', - 'label' => $this->translate('Personal Access Token'), - 'required' => true - ] - ); - } + $this->createConfigElements($selectedType, $typesConfig[$selectedType]); $this->addElement( 'submit', @@ -207,13 +136,11 @@ public function hasBeenSubmitted() public function populate($values) { if ($values instanceof Channel) { - $values = array_merge( - [ - 'name' => $values->name, - 'type' => $values->type - ], - json_decode($values->config, JSON_OBJECT_AS_ARRAY) ?? [] - ); + $values = [ + 'name' => $values->name, + 'type' => $values->type, + 'config' => json_decode($values->config, true) ?? [] + ]; } parent::populate($values); @@ -229,30 +156,7 @@ protected function onSuccess() return; } - $channel = [ - 'name' => $this->getValue('name'), - 'type' => $this->getValue('type') - ]; - - if ($this->getValue('type') === 'email') { - $channel['config'] = [ - 'host' => $this->getValue('host'), - 'port' => $this->getValue('port'), - 'from' => $this->getValue('from'), - 'password' => $this->getValue('password') - ]; - if ($this->getElement('tls')->isChecked()) { - $channel['config']['tls'] = true; - $channel['config']['tls_certcheck'] = $this->getValue('tls_certcheck'); - } - } else { - $channel['config'] = [ - 'url' => $this->getValue('url'), - 'user_id' => $this->getValue('user_id'), - 'token' => $this->getValue('token') - ]; - } - + $channel = $this->getValues(); $channel['config'] = json_encode($channel['config']); if ($this->channelId === null) { $this->db->insert('channel', $channel); @@ -260,4 +164,140 @@ protected function onSuccess() $this->db->update('channel', $channel, ['id = ?' => $this->channelId]); } } + + /** + * Create config elements for the given channel type + * + * @param string $type The channel type + * @param string $config The channel type config + */ + protected function createConfigElements(string $type, string $config): void + { + $elementsConfig = json_decode($config, false); + if (empty($elementsConfig)) { + return; + } + + $configFieldset = new FieldsetElement('config'); + $this->addElement($configFieldset); + + foreach ($elementsConfig as $elementConfig) { + $elem = $this->createElement( + $this->getElementType($elementConfig), + $elementConfig->name, + $this->getElementOptions($elementConfig) + ); + + if ($type === "email" && $elem->getName() === "sender_mail") { + $elem->getValidators()->add(new EmailAddressValidator()); + } + + $configFieldset->addElement($elem); + } + } + + /** + * Get the element type from given element config + * + * @param stdClass $elementConfig The config object of an element + * + * @return string + */ + protected function getElementType(stdClass $elementConfig): string + { + switch ($elementConfig->type) { + case 'string': + $elementType = 'text'; + break; + case 'number': + $elementType = 'number'; + break; + case 'text': + $elementType = 'textarea'; + break; + case 'bool': + $elementType = 'checkbox'; + break; + case 'option': + case 'options': + $elementType = 'select'; + break; + case 'secret': + $elementType = 'password'; + break; + default: + $elementType = 'text'; + } + + return $elementType; + } + + /** + * Get the element options from the given element config + * + * @param stdClass $elementConfig + * + * @return array + */ + protected function getElementOptions(stdClass $elementConfig): array + { + $options = [ + 'label' => $this->fromCurrentLocale($elementConfig->label) + ]; + + if (isset($elementConfig->help)) { + $options['description'] = $this->fromCurrentLocale($elementConfig->help); + } + + if (isset($elementConfig->required)) { + $options['required'] = $elementConfig->required; + } + + if (isset($elementConfig->placeholder)) { + $options['placeholder'] = $elementConfig->placeholder; + } + + if (isset($elementConfig->default)) { + $options['value'] = $elementConfig->default; + } + + if ( + isset($elementConfig->options) + && ($elementConfig->type === 'option' || $elementConfig->type === 'options')) + { + $options['options'] = (array) $elementConfig->options; + if ($elementConfig->type === 'options') { + $options['multiple'] = true; + } + } + + if ($elementConfig->type === "number") { + if (isset($elementConfig->min)) { + $options['min'] = $elementConfig->min; + } + + if (isset($elementConfig->max)) { + $options['max'] = $elementConfig->max; + } + } + + return $options; + } + + /** + * Get the current locale based string from given locale map + * + * Fallback to locale `en_US` if the current locale isn't provided + * + * @param stdClass $localeMap + * + * @return ?string Only returns null if the fallback locale is also not specified + */ + protected function fromCurrentLocale(stdClass $localeMap): ?string + { + $default = "en_US"; + $locale = $this->locale; + + return $localeMap->$locale ?? $localeMap->$default ?? null; + } } diff --git a/library/Notifications/Model/AvailableChannelType.php b/library/Notifications/Model/AvailableChannelType.php new file mode 100644 index 00000000..84fa77df --- /dev/null +++ b/library/Notifications/Model/AvailableChannelType.php @@ -0,0 +1,37 @@ +hasMany('channel', Channel::class) + ->setForeignKey('type'); + } +} diff --git a/library/Notifications/Model/Channel.php b/library/Notifications/Model/Channel.php index 5e879446..fb221526 100644 --- a/library/Notifications/Model/Channel.php +++ b/library/Notifications/Model/Channel.php @@ -7,6 +7,7 @@ use ipl\Orm\Model; use ipl\Orm\Relations; use ipl\Sql\Connection; +use ipl\Web\Widget\Icon; class Channel extends Model { @@ -55,6 +56,29 @@ public function createRelations(Relations $relations) $relations->hasMany('contact', Contact::class) ->setJoinType('LEFT') ->setForeignKey('default_channel_id'); + $relations->belongsTo('available_channel_type', AvailableChannelType::class) + ->setCandidateKey('type'); + } + + /** + * Get the channel icon + * + * @return Icon + */ + public function getIcon(): Icon + { + switch ($this->type) { + case 'rocketchat': + $icon = new Icon('comment-dots'); + break; + case 'email': + $icon = new Icon('at'); + break; + default: + $icon = new Icon('envelope'); + } + + return $icon; } /** diff --git a/library/Notifications/Widget/ItemList/ChannelListItem.php b/library/Notifications/Widget/ItemList/ChannelListItem.php index 0c43308b..47b8b766 100644 --- a/library/Notifications/Widget/ItemList/ChannelListItem.php +++ b/library/Notifications/Widget/ItemList/ChannelListItem.php @@ -30,11 +30,7 @@ protected function init(): void protected function assembleVisual(BaseHtmlElement $visual): void { - if ($this->item->type === 'email') { - $visual->addHtml(new Icon('envelope')); - } elseif ($this->item->type === 'rocketchat') { - $visual->addHtml(new Icon('comment-dots')); - } + $visual->addHtml($this->item->getIcon()); } protected function assembleTitle(BaseHtmlElement $title): void From 08a52dd0667da60e2e7e3173d8a5b0f2b28b6608 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 25 Oct 2023 10:00:12 +0200 Subject: [PATCH 2/9] HasAddress: Remove rocketchat support As the plugins provide there own config elements, hardcoded check for rocketchat and email isn't ideal. But exceptionally, the email check remains as it is the most used at the moment. --- application/controllers/ContactsController.php | 10 ++-------- library/Notifications/Model/Behavior/HasAddress.php | 10 ++++------ .../Notifications/Widget/ItemList/ContactListItem.php | 4 ---- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/application/controllers/ContactsController.php b/application/controllers/ContactsController.php index df0b1adf..c2e1e29e 100644 --- a/application/controllers/ContactsController.php +++ b/application/controllers/ContactsController.php @@ -42,14 +42,8 @@ public function init() public function indexAction() { - $contacts = Contact::on($this->db); - - $contacts->withColumns( - [ - 'has_email', - 'has_rc' - ] - ); + $contacts = Contact::on($this->db) + ->withColumns('has_email'); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($contacts); diff --git a/library/Notifications/Model/Behavior/HasAddress.php b/library/Notifications/Model/Behavior/HasAddress.php index 78584075..2e7db3fc 100644 --- a/library/Notifications/Model/Behavior/HasAddress.php +++ b/library/Notifications/Model/Behavior/HasAddress.php @@ -30,7 +30,7 @@ public function setQuery(Query $query) public function rewriteColumn($column, ?string $relation = null) { if ($this->isSelectableColumn($column)) { - $type = $column === 'has_email' ? 'email' : 'rocketchat'; + $type = 'email'; $subQueryRelation = $relation !== null ? $relation . '.contact.contact_address' : 'contact.contact_address'; @@ -53,7 +53,7 @@ public function rewriteColumn($column, ?string $relation = null) public function isSelectableColumn(string $name): bool { - return $name === 'has_email' || $name === 'has_rc'; + return $name === 'has_email'; } public function rewriteColumnDefinition(ColumnDefinition $def, string $relation): void @@ -61,9 +61,7 @@ public function rewriteColumnDefinition(ColumnDefinition $def, string $relation) $name = $def->getName(); if ($this->isSelectableColumn($name)) { - $label = $name === 'has_email' ? t('Has Email Address') : t('Has Rocket.Chat Username'); - - $def->setLabel($label); + $def->setLabel(t('Has Email Address')); } } @@ -72,7 +70,7 @@ public function rewriteCondition(Filter\Condition $condition, $relation = null) $column = substr($condition->getColumn(), strlen($relation)); if ($this->isSelectableColumn($column)) { - $type = $column === 'has_email' ? 'email' : 'rocketchat'; + $type = 'email'; $subQuery = $this->query->createSubQuery(new ContactAddress(), $relation) ->limit(1) diff --git a/library/Notifications/Widget/ItemList/ContactListItem.php b/library/Notifications/Widget/ItemList/ContactListItem.php index a6917856..c065e96f 100644 --- a/library/Notifications/Widget/ItemList/ContactListItem.php +++ b/library/Notifications/Widget/ItemList/ContactListItem.php @@ -47,10 +47,6 @@ protected function assembleFooter(BaseHtmlElement $footer): void $contactIcons->addHtml(new Icon('envelope')); } - if ($this->item->has_rc) { - $contactIcons->addHtml(new Icon('comment-dots')); - } - $footer->addHtml($contactIcons); } From df2978d561c9b7de472b53ea8198402691019119 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 25 Oct 2023 10:35:55 +0200 Subject: [PATCH 3/9] Channel: Fetch channel name instead of type Multiple channels can be defined with same type, so the channel name is more relevant. --- application/forms/EscalationRecipientForm.php | 2 +- library/Notifications/Model/Channel.php | 19 ++++--------------- .../Notifications/Web/Form/ContactForm.php | 2 +- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/application/forms/EscalationRecipientForm.php b/application/forms/EscalationRecipientForm.php index 48baf434..006d71a8 100644 --- a/application/forms/EscalationRecipientForm.php +++ b/application/forms/EscalationRecipientForm.php @@ -71,7 +71,7 @@ protected function assembleElements(): void $this->registerElement($col); $options = ['' => sprintf(' - %s - ', $this->translate('Please choose'))]; - $options += Channel::fetchChannelTypes(Database::get()); + $options += Channel::fetchChannelNames(Database::get()); $val = $this->createElement( 'select', diff --git a/library/Notifications/Model/Channel.php b/library/Notifications/Model/Channel.php index fb221526..ee0eea2d 100644 --- a/library/Notifications/Model/Channel.php +++ b/library/Notifications/Model/Channel.php @@ -82,28 +82,17 @@ public function getIcon(): Icon } /** - * Fetch and map all the configured channel types to a key => value array + * Fetch and map all the configured channel names to a key => value array * * @param Connection $conn * - * @return array All the channel types mapped as id => type + * @return array All the channel names mapped as id => name */ - public static function fetchChannelTypes(Connection $conn): array + public static function fetchChannelNames(Connection $conn): array { $channels = []; foreach (Channel::on($conn) as $channel) { - switch ($channel->type) { - case 'rocketchat': - $name = 'Rocket.Chat'; - break; - case 'email': - $name = t('E-Mail'); - break; - default: - $name = $channel->type; - } - - $channels[$channel->id] = $name; + $channels[$channel->id] = $channel->name; } return $channels; diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index 1d59b42d..f97b1dd7 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -80,7 +80,7 @@ protected function assemble() $this->addElement($contact); $channelOptions = ['' => sprintf(' - %s - ', $this->translate('Please choose'))]; - $channelOptions += Channel::fetchChannelTypes(Database::get()); + $channelOptions += Channel::fetchChannelNames(Database::get()); $contact->addElement( 'text', From ce23a3ea5cf5341c70670b98917de5d1d5f9a925 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 25 Oct 2023 13:26:48 +0200 Subject: [PATCH 4/9] ContactForm: Add address elements based on existing plugins --- .../Notifications/Web/Form/ContactForm.php | 85 +++++++++---------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/library/Notifications/Web/Form/ContactForm.php b/library/Notifications/Web/Form/ContactForm.php index f97b1dd7..5835ded3 100644 --- a/library/Notifications/Web/Form/ContactForm.php +++ b/library/Notifications/Web/Form/ContactForm.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Notifications\Web\Form; use Icinga\Module\Notifications\Common\Database; +use Icinga\Module\Notifications\Model\AvailableChannelType; use Icinga\Module\Notifications\Model\Channel; use Icinga\Module\Notifications\Model\Contact; use Icinga\Module\Notifications\Model\ContactAddress; @@ -120,37 +121,14 @@ protected function assemble() 'select', 'default_channel_id', [ - 'label' => $this->translate('Default Channel'), - 'required' => true, - 'disable' => [''], - 'options' => $channelOptions + 'label' => $this->translate('Default Channel'), + 'required' => true, + 'disabledOptions' => [''], + 'options' => $channelOptions ] ); - // Fieldset for addresses - $address = (new FieldsetElement( - 'contact_address', - [ - 'label' => $this->translate('Addresses'), - ] - )); - - $this->addElement($address); - - $address->addElement( - 'text', - 'email', - [ - 'label' => $this->translate('Email Address'), - 'validators' => [new EmailAddressValidator()] - ] - )->addElement( - 'text', - 'rocketchat', - [ - 'label' => $this->translate('Rocket.Chat Username'), - ] - ); + $this->addAddressElements(); $this->addElement( 'submit', @@ -227,11 +205,7 @@ public function addOrUpdateContact() $this->db->beginTransaction(); - $addressFromDb = [ - 'email' => null, - 'rocketchat' => null - ]; - + $addressFromDb = []; if ($this->contactId === null) { $this->db->insert('contact', $contact); @@ -244,17 +218,12 @@ public function addOrUpdateContact() $addressObjects->filter(Filter::equal('contact_id', $this->contactId)); foreach ($addressObjects as $addressRow) { - if ($addressRow->type === 'email') { - $addressFromDb['email'] = [$addressRow->id, $addressRow->address]; - } - - if ($addressRow->type === 'rocketchat') { - $addressFromDb['rocketchat'] = [$addressRow->id, $addressRow->address]; - } + $addressFromDb[$addressRow->type] = [$addressRow->id, $addressRow->address]; } } - foreach ($addressFromDb as $type => $value) { + $addr = ! empty($addressFromDb) ? $addressFromDb : $addressFromForm; + foreach ($addr as $type => $value) { $this->insertOrUpdateAddress($type, $addressFromForm, $addressFromDb); } @@ -281,7 +250,7 @@ public function removeContact() private function insertOrUpdateAddress(string $type, array $addressFromForm, array $addressFromDb): void { if ($addressFromForm[$type] !== null) { - if ($addressFromDb[$type] === null) { + if (! isset($addressFromDb[$type])) { $address = [ 'contact_id' => $this->contactId, 'type' => $type, @@ -299,8 +268,38 @@ private function insertOrUpdateAddress(string $type, array $addressFromForm, arr ] ); } - } elseif ($addressFromDb[$type] !== null) { + } elseif (isset($addressFromDb[$type])) { $this->db->delete('contact_address', ['id = ?' => $addressFromDb[$type][0]]); } } + + /** + * Add address elements for all existing channel plugins + * + * @return void + */ + private function addAddressElements(): void + { + $plugins = $this->db->fetchPairs( + AvailableChannelType::on($this->db) + ->columns(['type', 'name']) + ->assembleSelect() + ); + + if (empty($plugins)) { + return; + } + + $address = new FieldsetElement('contact_address', ['label' => $this->translate('Addresses')]); + $this->addElement($address); + + foreach ($plugins as $type => $label) { + $element = $this->createElement('text', $type, ['label' => $label]); + if ($type === 'email') { + $element->addAttributes(['validators' => [new EmailAddressValidator()]]); + } + + $address->addElement($element); + } + } } From 70b7f8b6767d01b6ee6aa5e35af3b219f1e26400 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 4 Dec 2023 11:53:38 +0100 Subject: [PATCH 5/9] ContactListItem: Change mail icon to `at` --- library/Notifications/Widget/ItemList/ContactListItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Notifications/Widget/ItemList/ContactListItem.php b/library/Notifications/Widget/ItemList/ContactListItem.php index c065e96f..b73cbab1 100644 --- a/library/Notifications/Widget/ItemList/ContactListItem.php +++ b/library/Notifications/Widget/ItemList/ContactListItem.php @@ -44,7 +44,7 @@ protected function assembleFooter(BaseHtmlElement $footer): void { $contactIcons = new HtmlElement('div', Attributes::create(['class' => 'contact-icons'])); if ($this->item->has_email) { - $contactIcons->addHtml(new Icon('envelope')); + $contactIcons->addHtml(new Icon('at')); } $footer->addHtml($contactIcons); From e985e7d1b72ba1f59c7b63cd8a30ea4b440c5415 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 28 Nov 2023 12:16:36 +0100 Subject: [PATCH 6/9] ChannelForm: Fix code style --- application/forms/ChannelForm.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index d6fec7a8..ba1fb5cb 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -263,8 +263,8 @@ protected function getElementOptions(stdClass $elementConfig): array if ( isset($elementConfig->options) - && ($elementConfig->type === 'option' || $elementConfig->type === 'options')) - { + && ($elementConfig->type === 'option' || $elementConfig->type === 'options') + ) { $options['options'] = (array) $elementConfig->options; if ($elementConfig->type === 'options') { $options['multiple'] = true; From 26601aedf469b55095c9a2c52f2eafc26b698033 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 30 Nov 2023 11:54:15 +0100 Subject: [PATCH 7/9] Fix phpstan issues and update baseline files --- application/forms/ChannelForm.php | 18 +++++++++++++++-- library/Notifications/Model/Channel.php | 7 +++++-- phpstan-baseline-7x.neon | 10 ---------- phpstan-baseline-8x.neon | 10 ---------- phpstan-baseline-standard.neon | 26 +++---------------------- 5 files changed, 24 insertions(+), 47 deletions(-) diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index ba1fb5cb..5af563af 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -8,7 +8,9 @@ use Icinga\Module\Notifications\Model\AvailableChannelType; use Icinga\Web\Session; use ipl\Html\Contract\FormSubmitElement; +use ipl\Html\FormElement\BaseFormElement; use ipl\Html\FormElement\FieldsetElement; +use ipl\I18n\GettextTranslator; use ipl\I18n\StaticTranslator; use ipl\Sql\Connection; use ipl\Validator\EmailAddressValidator; @@ -33,7 +35,10 @@ public function __construct(Connection $db, ?int $channelId = null) { $this->db = $db; $this->channelId = $channelId; - $this->locale = StaticTranslator::$instance->getLocale(); + + /** @var GettextTranslator $translateInstance */ + $translateInstance = StaticTranslator::$instance; + $this->locale = $translateInstance->getLocale(); } protected function assemble() @@ -52,9 +57,14 @@ protected function assemble() $query = AvailableChannelType::on($this->db)->columns(['type', 'name', 'config_attrs']); + /** @var string[] $typesConfig */ $typesConfig = []; + + /** @var string[] $typeNamePair */ $typeNamePair = []; + $defaultType = null; + /** @var Channel $channel */ foreach ($query as $channel) { if ($defaultType === null) { $defaultType = $channel->type; @@ -77,6 +87,7 @@ protected function assemble() ] ); + /** @var string $selectedType */ $selectedType = $this->getValue('type'); $this->createConfigElements($selectedType, $typesConfig[$selectedType]); @@ -173,7 +184,9 @@ protected function onSuccess() */ protected function createConfigElements(string $type, string $config): void { + /** @var array $elementsConfig */ $elementsConfig = json_decode($config, false); + if (empty($elementsConfig)) { return; } @@ -182,6 +195,7 @@ protected function createConfigElements(string $type, string $config): void $this->addElement($configFieldset); foreach ($elementsConfig as $elementConfig) { + /** @var BaseFormElement $elem */ $elem = $this->createElement( $this->getElementType($elementConfig), $elementConfig->name, @@ -237,7 +251,7 @@ protected function getElementType(stdClass $elementConfig): string * * @param stdClass $elementConfig * - * @return array + * @return string[] */ protected function getElementOptions(stdClass $elementConfig): array { diff --git a/library/Notifications/Model/Channel.php b/library/Notifications/Model/Channel.php index ee0eea2d..e857e277 100644 --- a/library/Notifications/Model/Channel.php +++ b/library/Notifications/Model/Channel.php @@ -86,13 +86,16 @@ public function getIcon(): Icon * * @param Connection $conn * - * @return array All the channel names mapped as id => name + * @return string[] All the channel names mapped as id => name */ public static function fetchChannelNames(Connection $conn): array { $channels = []; + /** @var Channel $channel */ foreach (Channel::on($conn) as $channel) { - $channels[$channel->id] = $channel->name; + /** @var string $name */ + $name = $channel->name; + $channels[$channel->id] = $name; } return $channels; diff --git a/phpstan-baseline-7x.neon b/phpstan-baseline-7x.neon index 79c2cba4..5be0f381 100644 --- a/phpstan-baseline-7x.neon +++ b/phpstan-baseline-7x.neon @@ -1,15 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Parameter \\#2 \\$assoc of function json_decode expects bool\\|null, int given\\.$#" - count: 1 - path: application/forms/ChannelForm.php - - - - message: "#^Parameter \\#2 \\.\\.\\.\\$args of function array_merge expects array, mixed given\\.$#" - count: 1 - path: application/forms/ChannelForm.php - - message: "#^Parameter \\#2 \\$str of function explode expects string, mixed given\\.$#" count: 2 diff --git a/phpstan-baseline-8x.neon b/phpstan-baseline-8x.neon index 55ee9a38..99ed68cf 100644 --- a/phpstan-baseline-8x.neon +++ b/phpstan-baseline-8x.neon @@ -1,15 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Parameter \\#2 \\$associative of function json_decode expects bool\\|null, int given\\.$#" - count: 1 - path: application/forms/ChannelForm.php - - - - message: "#^Parameter \\#2 \\.\\.\\.\\$arrays of function array_merge expects array, mixed given\\.$#" - count: 1 - path: application/forms/ChannelForm.php - - message: "#^Parameter \\#2 \\$string of function explode expects string, mixed given\\.$#" count: 2 diff --git a/phpstan-baseline-standard.neon b/phpstan-baseline-standard.neon index 3cf5dfa3..863e0252 100644 --- a/phpstan-baseline-standard.neon +++ b/phpstan-baseline-standard.neon @@ -295,11 +295,6 @@ parameters: count: 1 path: application/forms/BaseEscalationForm.php - - - message: "#^Call to an undefined method ipl\\\\Html\\\\Contract\\\\FormElement\\:\\:isChecked\\(\\)\\.$#" - count: 1 - path: application/forms/ChannelForm.php - - message: "#^Cannot call method getName\\(\\) on ipl\\\\Html\\\\Contract\\\\FormSubmitElement\\|null\\.$#" count: 2 @@ -715,26 +710,11 @@ parameters: count: 1 path: library/Notifications/Model/Behavior/HasAddress.php - - - message: "#^Cannot access property \\$id on mixed\\.$#" - count: 1 - path: library/Notifications/Model/Channel.php - - - - message: "#^Cannot access property \\$type on mixed\\.$#" - count: 2 - path: library/Notifications/Model/Channel.php - - message: "#^Method Icinga\\\\Module\\\\Notifications\\\\Model\\\\Channel\\:\\:createRelations\\(\\) has no return type specified\\.$#" count: 1 path: library/Notifications/Model/Channel.php - - - message: "#^Method Icinga\\\\Module\\\\Notifications\\\\Model\\\\Channel\\:\\:fetchChannelTypes\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: library/Notifications/Model/Channel.php - - message: "#^Method Icinga\\\\Module\\\\Notifications\\\\Model\\\\Channel\\:\\:getColumnDefinitions\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 @@ -1067,17 +1047,17 @@ parameters: - message: "#^Cannot access property \\$address on mixed\\.$#" - count: 4 + count: 3 path: library/Notifications/Web/Form/ContactForm.php - message: "#^Cannot access property \\$id on mixed\\.$#" - count: 2 + count: 1 path: library/Notifications/Web/Form/ContactForm.php - message: "#^Cannot access property \\$type on mixed\\.$#" - count: 4 + count: 3 path: library/Notifications/Web/Form/ContactForm.php - From 35decadec22a2468e3985068b45403fbeb0e5665 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 12 Dec 2023 11:25:23 +0100 Subject: [PATCH 8/9] ChannelForm: Use configOptions:default as placeholder/value --- application/forms/ChannelForm.php | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index 5af563af..d1361510 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -267,24 +267,23 @@ protected function getElementOptions(stdClass $elementConfig): array $options['required'] = $elementConfig->required; } - if (isset($elementConfig->placeholder)) { - $options['placeholder'] = $elementConfig->placeholder; - } - - if (isset($elementConfig->default)) { - $options['value'] = $elementConfig->default; - } - - if ( - isset($elementConfig->options) - && ($elementConfig->type === 'option' || $elementConfig->type === 'options') - ) { + $isSelectElement = isset($elementConfig->options) + && ($elementConfig->type === 'option' || $elementConfig->type === 'options'); + if ($isSelectElement) { $options['options'] = (array) $elementConfig->options; if ($elementConfig->type === 'options') { $options['multiple'] = true; } } + if (isset($elementConfig->default)) { + if ($isSelectElement || $elementConfig->type === 'bool') { + $options['value'] = $elementConfig->default; + } else { + $options['placeholder'] = $elementConfig->default; + } + } + if ($elementConfig->type === "number") { if (isset($elementConfig->min)) { $options['min'] = $elementConfig->min; From d2da6e454603f6c614c4f1188827708774877100 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 13 Dec 2023 12:59:02 +0100 Subject: [PATCH 9/9] ChannelForm: Use translator's default locale as default Remove class locale property --- application/forms/ChannelForm.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/application/forms/ChannelForm.php b/application/forms/ChannelForm.php index d1361510..b1388b95 100644 --- a/application/forms/ChannelForm.php +++ b/application/forms/ChannelForm.php @@ -28,17 +28,10 @@ class ChannelForm extends CompatForm /** @var ?int Channel ID */ private $channelId; - /** @var string The current locale */ - private $locale; - public function __construct(Connection $db, ?int $channelId = null) { $this->db = $db; $this->channelId = $channelId; - - /** @var GettextTranslator $translateInstance */ - $translateInstance = StaticTranslator::$instance; - $this->locale = $translateInstance->getLocale(); } protected function assemble() @@ -308,8 +301,10 @@ protected function getElementOptions(stdClass $elementConfig): array */ protected function fromCurrentLocale(stdClass $localeMap): ?string { - $default = "en_US"; - $locale = $this->locale; + /** @var GettextTranslator $translator */ + $translator = StaticTranslator::$instance; + $default = $translator->getDefaultLocale(); + $locale = $translator->getLocale(); return $localeMap->$locale ?? $localeMap->$default ?? null; }