mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-24 11:08:45 +02:00
N°7157 - Allow users to unsubscribe from notification channels (#611)
* N°7157 - Allow users to unsubscribe from notification channels * Fix type hinting * Add missing dict entries * Allows to subscribe/unsubscribe from notifications individually * Refactor NotificationsService to singleton pattern * Refactor NotificationsRepository to singleton pattern and rename methods to a more functional naming * Add PHPDoc and type hints * Dump autoloaders * Replace modals with toasts * Add dict entry --------- Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
This commit is contained in:
@@ -19,6 +19,8 @@
|
||||
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use Combodo\iTop\Service\Notification\NotificationsRepository;
|
||||
use Combodo\iTop\Service\Notification\NotificationsService;
|
||||
use Combodo\iTop\Service\Router\Router;
|
||||
|
||||
/**
|
||||
@@ -342,6 +344,7 @@ class ActionEmail extends ActionNotification
|
||||
"db_table" => "priv_action_email",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-mailing.svg'),
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
@@ -412,18 +415,20 @@ class ActionEmail extends ActionNotification
|
||||
protected $m_aMailErrors; //array of strings explaining the issue
|
||||
|
||||
/**
|
||||
* Return a the list of emails as a string, or a detailed error description
|
||||
* Return the list of emails as a string, or a detailed error description
|
||||
*
|
||||
* @param string $sRecipAttCode
|
||||
* @param array $aArgs
|
||||
* @param \Trigger|null $oTrigger
|
||||
*
|
||||
* @return string
|
||||
* @since 3.2.0 $oTrigger parameter added
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
protected function FindRecipients($sRecipAttCode, $aArgs)
|
||||
protected function FindRecipients($sRecipAttCode, $aArgs, ?Trigger $oTrigger = null)
|
||||
{
|
||||
$sOQL = $this->Get($sRecipAttCode);
|
||||
if (utils::IsNullOrEmptyString($sOQL)) return '';
|
||||
@@ -432,7 +437,7 @@ class ActionEmail extends ActionNotification
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL($sOQL);
|
||||
if ($this->Get('ignore_notify') === 'no') {
|
||||
// In theory it is possible to notify *any* kind of object,
|
||||
// In theory, it is possible to notify *any* kind of object,
|
||||
// as long as there is an email attribute in the class
|
||||
// So let's not assume that the selected class is a Person
|
||||
$sFirstSelectedClass = $oSearch->GetClass();
|
||||
@@ -465,6 +470,16 @@ class ActionEmail extends ActionNotification
|
||||
return "The objects of the class '$sClass' do not have any email attribute";
|
||||
}
|
||||
|
||||
if(in_array('Contact', MetaModel::EnumParentClasses($sClass, ENUM_CHILD_CLASSES_ALL), true)) {
|
||||
$aArgs['trigger_id'] = $oTrigger->GetKey();
|
||||
$aArgs['action_id'] = $this->GetKey();
|
||||
|
||||
$sSubscribedContactsOQL = NotificationsRepository::GetInstance()->GetSearchOQLContactUnsubscribedByTriggerAndAction();
|
||||
$sSubscribedContactsOQL->ApplyParameters($aArgs);
|
||||
$sAlias = $oSearch->GetClassAlias();
|
||||
$oSearch->AddConditionExpression(Expression::FromOQL("`$sAlias`.id NOT IN ($sSubscribedContactsOQL)"));
|
||||
}
|
||||
|
||||
$oSet = new DBObjectSet($oSearch, array() /* order */, $aArgs);
|
||||
$aRecipients = array();
|
||||
while ($oObj = $oSet->Fetch())
|
||||
@@ -475,6 +490,9 @@ class ActionEmail extends ActionNotification
|
||||
$aRecipients[] = $sAddress;
|
||||
$this->m_iRecipients++;
|
||||
}
|
||||
if (in_array('Contact', MetaModel::EnumParentClasses($sClass, ENUM_CHILD_CLASSES_ALL), true)) {
|
||||
NotificationsService::GetInstance()->RegisterSubscription($oTrigger, $this, $oObj);
|
||||
}
|
||||
}
|
||||
return implode(', ', $aRecipients);
|
||||
}
|
||||
@@ -569,7 +587,7 @@ class ActionEmail extends ActionNotification
|
||||
|
||||
$oEmail = new EMail();
|
||||
|
||||
$aEmailContent = $this->PrepareMessageContent($aContextArgs, $oLog);
|
||||
$aEmailContent = $this->PrepareMessageContent($aContextArgs, $oLog, $oTrigger);
|
||||
$oEmail->SetSubject($aEmailContent['subject']);
|
||||
$oEmail->SetBody($aEmailContent['body'], 'text/html', $sStyles);
|
||||
$oEmail->SetRecipientTO($aEmailContent['to']);
|
||||
@@ -621,13 +639,19 @@ class ActionEmail extends ActionNotification
|
||||
/**
|
||||
* @param array $aContextArgs
|
||||
* @param \EventNotification $oLog
|
||||
* @param \Trigger|null $oTrigger
|
||||
*
|
||||
* @return array
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \MySQLException
|
||||
* @since 3.1.0 N°918
|
||||
* @since 3.2.0 Added $oTrigger parameter
|
||||
*/
|
||||
protected function PrepareMessageContent($aContextArgs, &$oLog): array
|
||||
protected function PrepareMessageContent($aContextArgs, &$oLog, ?Trigger $oTrigger = null): array
|
||||
{
|
||||
$aMessageContent = [
|
||||
'to' => '',
|
||||
@@ -654,9 +678,9 @@ class ActionEmail extends ActionNotification
|
||||
|
||||
// Determine recipients
|
||||
//
|
||||
$aMessageContent['to'] = $this->FindRecipients('to', $aContextArgs);
|
||||
$aMessageContent['cc'] = $this->FindRecipients('cc', $aContextArgs);
|
||||
$aMessageContent['bcc'] = $this->FindRecipients('bcc', $aContextArgs);
|
||||
$aMessageContent['to'] = $this->FindRecipients('to', $aContextArgs, $oTrigger);
|
||||
$aMessageContent['cc'] = $this->FindRecipients('cc', $aContextArgs, $oTrigger);
|
||||
$aMessageContent['bcc'] = $this->FindRecipients('bcc', $aContextArgs, $oTrigger);
|
||||
|
||||
$aMessageContent['from'] = MetaModel::ApplyParams($this->Get('from'), $aContextArgs);
|
||||
$aMessageContent['from_label'] = MetaModel::ApplyParams($this->Get('from_label'), $aContextArgs);
|
||||
|
||||
@@ -1,6 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.2">
|
||||
<classes>
|
||||
<class id="lnkActionNotificationToContact" _delta="define">
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>core/cmdb,application</category>
|
||||
<abstract>false</abstract>
|
||||
<key_type>autoincrement</key_type>
|
||||
<db_table>priv_lnk_action_notif_to_contact</db_table>
|
||||
<db_key_field>id</db_key_field>
|
||||
<db_final_class_field/>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="action_id"/>
|
||||
<attribute id="contact_id"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<uniqueness_rules>
|
||||
<rule>
|
||||
<attributes>
|
||||
<attribute id="action_id"/>
|
||||
<attribute id="contact_id"/>
|
||||
<attribute id="trigger_id"/>
|
||||
</attributes>
|
||||
<filter/>
|
||||
<disabled>false</disabled>
|
||||
<is_blocking>true</is_blocking>
|
||||
</rule>
|
||||
</uniqueness_rules>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="action_id" xsi:type="AttributeExternalKey">
|
||||
<sql>action_id</sql>
|
||||
<target_class>ActionNotification</target_class>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
<field id="contact_id" xsi:type="AttributeExternalKey">
|
||||
<sql>contact_id</sql>
|
||||
<target_class>Contact</target_class>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
<field id="trigger_id" xsi:type="AttributeExternalKey">
|
||||
<sql>trigger_id</sql>
|
||||
<target_class>Trigger</target_class>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
<field id="subscribed" xsi:type="AttributeBoolean">
|
||||
<sql>subscribed</sql>
|
||||
<default_value>true</default_value>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
</fields>
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="col:col1">
|
||||
<items>
|
||||
<item id="fieldset:lnkActionNotificationToContact:content">
|
||||
<items>
|
||||
<item id="action_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="contact_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="title">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
</items>
|
||||
</item>
|
||||
</items>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
<list>
|
||||
<items>
|
||||
<item id="action_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="contact_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="title">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
</items>
|
||||
</list>
|
||||
</presentation>
|
||||
<methods/>
|
||||
</class>
|
||||
<class id="ActioniTopNotification" _delta="define">
|
||||
<php_parent>
|
||||
<name>ActionNotification</name>
|
||||
@@ -18,6 +109,9 @@
|
||||
<attribute id="title"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<style>
|
||||
<icon>../../images/icons/icons8-notification.svg</icon>
|
||||
</style>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="title" xsi:type="AttributeString">
|
||||
@@ -167,7 +261,10 @@
|
||||
[$sPreviousLanguage, $aPreviousPluginProperties] = $this->SetNotificationLanguage();
|
||||
while ($oRecipient = $oRecipientsSet->Fetch()) {
|
||||
// Skip recipients that have no users
|
||||
if(get_class($oRecipient) === Person::class && UserRights::GetUserFromPerson($oRecipient) === null) {
|
||||
if (get_class($oRecipient) === Person::class && UserRights::GetUserFromPerson($oRecipient) === null) {
|
||||
continue;
|
||||
}
|
||||
if (!\Combodo\iTop\Service\Notification\NotificationsService::GetInstance()->IsSubscribed($oTrigger, $this, $oRecipient)) {
|
||||
continue;
|
||||
}
|
||||
$oEvent = new EventiTopNotification();
|
||||
@@ -183,6 +280,8 @@
|
||||
$oEvent->Set('object_id', $iObjectId);
|
||||
$oEvent->Set('url', MetaModel::ApplyParams($this->Get('url'), $aContextArgs));
|
||||
$oEvent->DBInsertNoReload();
|
||||
|
||||
\Combodo\iTop\Service\Notification\NotificationsService::GetInstance()->RegisterSubscription($oTrigger, $this, $oRecipient);
|
||||
}
|
||||
$this->SetNotificationLanguage($sPreviousLanguage, $aPreviousPluginProperties['language_code'] ?? null);
|
||||
}
|
||||
|
||||
@@ -53,9 +53,10 @@ abstract class Trigger extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeEnumSet("context", array("allowed_values" => null, "possible_values" => new ValueSetEnumPadded($aTags, true), "sql" => "context", "depends_on" => array(), "is_null_allowed" => true, "max_items" => 12)));
|
||||
// "complement" is a computed field, fed by Trigger sub-classes, in general in ComputeValues method, for eg. the TriggerOnObject fed it with target_class info
|
||||
MetaModel::Init_AddAttribute(new AttributeString("complement", array("allowed_values" => null, "sql" => "complement", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("subscription_policy", array("allowed_values" => new ValueSetEnum('allow_no_channel,force_at_least_one_channel,force_all_channels'), "sql" => "subscription_policy", "default_value" => 'allow_no_channel', "is_null_allowed" => false, "depends_on" => array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('finalclass', 'description', 'context', 'action_list', 'complement')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('finalclass', 'description', 'context', 'subscription_policy', 'action_list', 'complement')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'complement')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
|
||||
@@ -438,7 +439,7 @@ class TriggerOnStateEnter extends TriggerOnStateChange
|
||||
MetaModel::Init_InheritAttributes();
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'state', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'state', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('target_class', 'state')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class', 'state')); // Criteria of the std search form
|
||||
@@ -471,7 +472,7 @@ class TriggerOnStateLeave extends TriggerOnStateChange
|
||||
MetaModel::Init_InheritAttributes();
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'state', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'state', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('target_class', 'state')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class', 'state')); // Criteria of the std search form
|
||||
@@ -504,7 +505,7 @@ class TriggerOnObjectCreate extends TriggerOnObject
|
||||
MetaModel::Init_InheritAttributes();
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
|
||||
@@ -537,7 +538,7 @@ class TriggerOnObjectDelete extends TriggerOnObject
|
||||
MetaModel::Init_InheritAttributes();
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
|
||||
@@ -572,7 +573,7 @@ class TriggerOnObjectUpdate extends TriggerOnObject
|
||||
MetaModel::Init_AddAttribute(new AttributeClassAttCodeSet('target_attcodes', array("allowed_values" => null, "class_field" => "target_class", "sql" => "target_attcodes", "default_value" => null, "is_null_allowed" => true, "max_items" => 20, "min_items" => 0, "attribute_definition_exclusion_list" => "AttributeDashboard,AttributeExternalField,AttributeFinalClass,AttributeFriendlyName,AttributeObsolescenceDate,AttributeObsolescenceFlag,AttributeSubItem", "attribute_definition_list" => null, "depends_on" => array('target_class'))));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'target_attcodes', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'target_attcodes', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
|
||||
@@ -668,7 +669,7 @@ class TriggerOnObjectMention extends TriggerOnObject
|
||||
MetaModel::Init_AddAttribute(new AttributeOQL("mentioned_filter", array("allowed_values" => null, "sql" => "mentioned_filter", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'mentioned_filter', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'mentioned_filter', 'subscription_policy', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
|
||||
|
||||
Reference in New Issue
Block a user