N°2039 - Add iTop notifications to newsroom (#590)

* N°2039 - Add iTop notifications to newsroom

* Update sources/Controller/Newsroom/iTopNewsroomController.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Update sources/Controller/Newsroom/iTopNewsroomController.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Update sources/Service/Notification/Event/EventiTopNotificationGC.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Add a default value to Action url attribute, check if there's an object in the context before adding it to the event

* Phpdoc

* Fix default config values

* Replace MetaModel::EnumPlugins calls for iNewsroomProvider

* Replace hardcoded url with generated routes

* Add dict entries

* Correclty throw error when trying to display a non existing event

* Fix unit test

* Migrate old action email language values to its parent table

* Migrate Action and Event class to XML, generate their dictionary entries, add meta data for ActionNotification and EventNotification

* Fix issue in dictionary definition

* Allows Action to create Events for users that the current user can't see

* Dump autoloader

* Remove classes from homemade "autoloader" as they are now loaded through XML

* Apply suggestions from code review

* Remove class from homemade "autoloader" as they are now loaded through GetClassesForInterfaces

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
This commit is contained in:
Stephen Abello
2023-12-22 12:02:19 +01:00
committed by GitHub
parent 2681abbeed
commit 05db99aa69
17 changed files with 778 additions and 21 deletions

View File

@@ -202,12 +202,39 @@ abstract class ActionNotification extends Action
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list'));
// - Attributes to be displayed for a list
MetaModel::Init_SetZListItems('list', array('finalclass', 'description', 'status'));
MetaModel::Init_AddAttribute(new AttributeApplicationLanguage("language", array("sql"=>"language", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Search criteria
// - Criteria of the std search form
// MetaModel::Init_SetZListItems('standard_search', array('name'));
// - Default criteria of the search form
// MetaModel::Init_SetZListItems('default_search', array('name'));
}
/**
* @param $sLanguage
* @param $sLanguageCode
*
* @return array [$sPreviousLanguage, $aPreviousPluginProperties]
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \DictExceptionUnknownLanguage
* @since 3.2.0
*/
public function SetNotificationLanguage($sLanguage = null, $sLanguageCode = null){
$sPreviousLanguage = Dict::GetUserLanguage();
$aPreviousPluginProperties = ApplicationContext::GetPluginProperties('QueryLocalizerPlugin');
$sLanguage = $sLanguage ?? $this->Get('language');
$sLanguageCode = $sLanguageCode ?? $sLanguage;
if (!utils::IsNullOrEmptyString($sLanguage)) {
// If a language is specified for this action, force this language
// when rendering all placeholders inside this message
Dict::SetUserLanguage($sLanguage);
AttributeDateTime::LoadFormatFromConfig();
ApplicationContext::SetPluginProperty('QueryLocalizerPlugin', 'language_code', $sLanguageCode);
}
return [$sPreviousLanguage, $aPreviousPluginProperties];
}
}
/**
@@ -274,7 +301,6 @@ class ActionEmail extends ActionNotification
MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("allowed_values" => null, "sql" => "subject", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeTemplateHTML("body", array("allowed_values" => null, "sql" => "body", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values" => new ValueSetEnum('low,normal,high'), "sql" => "importance", "default_value" => 'normal', "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeApplicationLanguage("language", array("sql"=>"language", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeBlob("html_template", array("is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeEnum("ignore_notify", array("allowed_values" => new ValueSetEnum('yes,no'), "sql" => "ignore_notify", "default_value" => 'yes', "is_null_allowed" => false, "depends_on" => array())));
@@ -562,15 +588,7 @@ class ActionEmail extends ActionNotification
'attachments' => [],
];
$sPreviousUrlMaker = ApplicationContext::SetUrlMakerClass();
$sPreviousLanguage = Dict::GetUserLanguage();
$aPreviousPluginProperties = ApplicationContext::GetPluginProperties('QueryLocalizerPlugin');
if ($this->Get('language') !== '') {
// If a language is specified for this action, force this language
// when rendering all placeholders inside this message
Dict::SetUserLanguage($this->Get('language'));
AttributeDateTime::LoadFormatFromConfig();
ApplicationContext::SetPluginProperty('QueryLocalizerPlugin', 'language_code', $this->Get('language'));
}
[$sPreviousLanguage, $aPreviousPluginProperties] = $this->SetNotificationLanguage();
try
{
@@ -602,9 +620,7 @@ class ActionEmail extends ActionNotification
}
finally {
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
Dict::SetUserLanguage($sPreviousLanguage);
AttributeDateTime::LoadFormatFromConfig();
ApplicationContext::SetPluginProperty('QueryLocalizerPlugin', 'language_code', $aPreviousPluginProperties['language_code'] ?? null);
$this->SetNotificationLanguage($sPreviousLanguage, $aPreviousPluginProperties['language_code'] ?? null);
}
if (!is_null($oLog)) {

View File

@@ -25,6 +25,7 @@ MetaModel::IncludeModule('application/audit.domain.class.inc.php');
MetaModel::IncludeModule('application/query.class.inc.php');
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
MetaModel::IncludeModule('core/event.class.inc.php');
MetaModel::IncludeModule('core/action.class.inc.php');
MetaModel::IncludeModule('core/trigger.class.inc.php');

View File

@@ -1603,6 +1603,22 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'notifications.itop.read_notification_retention' => [
'type' => 'integer',
'description' => 'Duration in days after which iTop read notifications will be deleted',
'default' => 182,
'value' => 182,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'notifications.itop.newsroom_cache_time' => [
'type' => 'integer',
'description' => 'Duration in min between each fetch for notifications in newsroom',
'default' => 5,
'value' => 5,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'regenerate_session_id_enabled' => [
'type' => 'bool',
'description' => 'If true then session id will be regenerated on each login, to prevent session fixation.',

View File

@@ -1,5 +1,268 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.2">
<classes>
<class id="ActioniTopNotification" _delta="define">
<php_parent>
<name>ActionNotification</name>
</php_parent>
<parent>cmdbAbstractObject</parent>
<properties>
<category>grant_by_profile,core/cmdb,application</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
<db_table>priv_action_itop_notif</db_table>
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<attributes>
<attribute id="title"/>
</attributes>
</naming>
</properties>
<fields>
<field id="title" xsi:type="AttributeString">
<sql>title</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
</field>
<field id="message" xsi:type="AttributeText">
<sql>message</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
</field>
<field id="icon" xsi:type="AttributeImage">
<sql>icon</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
<display_max_width>96</display_max_width>
<display_max_height>96</display_max_height>
<storage_max_width>256</storage_max_width>
<storage_max_height>256</storage_max_height>
<default_image>null</default_image>
</field>
<field id="priority" xsi:type="AttributeEnum">
<sql>priority</sql>
<values>
<value id="1">
<code>1</code>
</value>
<value id="2">
<code>2</code>
</value>
<value id="3">
<code>3</code>
</value>
<value id="4">
<code>4</code>
</value>
</values>
<default_value>4</default_value>
<is_null_allowed>false</is_null_allowed>
</field>
<field id="recipients" xsi:type="AttributeOQL">
<sql>recipients</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
</field>
<field id="url" xsi:type="AttributeString">
<sql>url</sql>
<default_value>$this->url()$</default_value>
<is_null_allowed>false</is_null_allowed>
</field>
</fields>
<presentation>
<details>
<items>
<item id="col:col1">
<items>
<item id="fieldset:ActioniTopNotification:content">
<items>
<item id="name">
<rank>10</rank>
</item>
<item id="status">
<rank>20</rank>
</item>
<item id="language">
<rank>30</rank>
</item>
<item id="title">
<rank>40</rank>
</item>
<item id="message">
<rank>50</rank>
</item>
</items>
</item>
</items>
</item>
<item id="col:col2">
<items>
<item id="fieldset:ActioniTopNotification:settings">
<items>
<item id="priority">
<rank>10</rank>
</item>
<item id="icon">
<rank>20</rank>
</item>
<item id="recipients">
<rank>30</rank>
</item>
<item id="url">
<rank>40</rank>
</item>
</items>
</item>
</items>
</item>
</items>
</details>
<list>
<items>
<item id="title">
<rank>10</rank>
</item>
<item id="status">
<rank>20</rank>
</item>
<item id="language">
<rank>30</rank>
</item>
</items>
</list>
</presentation>
<methods>
<method id="ComputePriority">
<comment> /**
*
* Create EventiTopNotification for each recipient
* @param $oTrigger
* @param $aContextArgs
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
*/</comment>
<static>false</static>
<access>public</access>
<code><![CDATA[
public function DoExecute($oTrigger, $aContextArgs)
{
$oRecipientsSearch = DBObjectSearch::FromOQL($this->Get('recipients'));
$oRecipientsSearch->AllowAllDate();
$oRecipientsSet = new DBObjectSet($oRecipientsSearch);
[$sPreviousLanguage, $aPreviousPluginProperties] = $this->SetNotificationLanguage();
while ($oRecipient = $oRecipientsSet->Fetch()) {
$oEvent = new EventiTopNotification();
$oEvent->Set('title', MetaModel::ApplyParams($this->Get('title'), $aContextArgs));
$oEvent->Set('message', MetaModel::ApplyParams($this->Get('message'), $aContextArgs));
$oIcon = !$this->Get('icon')->IsEmpty() ? $this->Get('icon') : MetaModel::GetAttributeDef('EventiTopNotification', 'icon')->MakeRealValue(Combodo\iTop\Application\Branding::GetCompactMainLogoAbsoluteUrl(), $oEvent);
$oEvent->Set('icon', $oIcon);
$oEvent->Set('priority', $this->Get('priority'));
$oEvent->Set('contact_id', $oRecipient->GetKey());
$oEvent->Set('trigger_id', $oTrigger->GetKey());
$oEvent->Set('action_id', $this->GetKey());
$iObjectId = array_key_exists('this->object()', $aContextArgs) ? $aContextArgs['this->object()']->GetKey() : 0;
$oEvent->Set('object_id', $iObjectId);
$oEvent->Set('url', MetaModel::ApplyParams($this->Get('url'), $aContextArgs));
$oEvent->DBInsertNoReload();
}
$this->SetNotificationLanguage($sPreviousLanguage, $aPreviousPluginProperties['language_code'] ?? null);
}
]]></code>
</method>
</methods>
</class>
<class id="EventiTopNotification" _delta="define">
<php_parent>
<name>EventNotification</name>
</php_parent>
<parent>cmdbAbstractObject</parent>
<properties>
<category>core/cmdb,view_in_gui</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
<db_table>priv_event_itop_notif</db_table>
<db_key_field>id</db_key_field>
<db_final_class_field/>
<naming>
<attributes>
<attribute id="title"/>
</attributes>
</naming>
</properties>
<fields>
<field id="title" xsi:type="AttributeString">
<sql>title</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
</field>
<field id="icon" xsi:type="AttributeImage">
<sql>icon</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
<default_image>null</default_image>
</field>
<field id="priority" xsi:type="AttributeEnum">
<sql>priority</sql>
<values>
<value id="1">
<code>1</code>
</value>
<value id="2">
<code>2</code>
</value>
<value id="3">
<code>3</code>
</value>
<value id="4">
<code>4</code>
</value>
</values>
<default_value>4</default_value>
<is_null_allowed>false</is_null_allowed>
</field>
<field id="url" xsi:type="AttributeURL">
<sql>url</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
<target>_blank</target>
</field>
<field id="read" xsi:type="AttributeEnum">
<sql>read</sql>
<values>
<value id="yes">
<code>yes</code>
</value>
<value id="no">
<code>no</code>
</value>
</values>
<default_value>no</default_value>
<is_null_allowed>false</is_null_allowed>
</field>
<field id="read_date" xsi:type="AttributeDateTime">
<sql>read_date</sql>
<default_value/>
<is_null_allowed>true</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>
</fields>
<presentation/>
<methods/>
</class>
</classes>
<user_rights>
<profiles>
<profile id="1024" _delta="define">
@@ -287,6 +550,43 @@
<interface id="iWorkingTimeComputer"/>
</interfaces>
</class>
<class id="ActionNotification" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>Action</parent>
<properties>
<category>grant_by_profile,core/cmdb</category>
</properties>
<fields>
<field id="name" xsi:type="AttributeString"/>
<field id="description" xsi:type="AttributeString"/>
<field id="status" xsi:type="AttributeEnum"/>
<field id="trigger_list" xsi:type="AttributeLinkedSetIndirect"/>
<field id="language" xsi:type="AttributeApplicationLanguage"/>
</fields>
</class>
<class id="EventNotification" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>Event</parent>
<properties>
<category>core/cmdb,view_in_gui</category>
</properties>
<fields>
<field id="message" xsi:type="AttributeText"/>
<field id="date" xsi:type="AttributeDateTime"/>
<field id="userinfo" xsi:type="AttributeString"/>
<field id="trigger_id" xsi:type="AttributeExternalKey">
<target_class>Trigger</target_class>
</field>
<field id="action_id" xsi:type="AttributeExternalKey">
<target_class>Action</target_class>
</field>
<field id="object_id" xsi:type="AttributeInteger"/>
<field id="trigger_id_friendlyname" xsi:type="AttributeExternalField"/>
<field id="trigger_id_finalclass_recall" xsi:type="AttributeExternalField"/>
<field id="action_id_friendlyname" xsi:type="AttributeExternalField"/>
<field id="action_id_finalclass_recall" xsi:type="AttributeExternalField"/>
</fields>
</class>
</classes>
<attribute_properties_definition _delta="define">
<properties>

View File

@@ -68,6 +68,14 @@ if (!class_exists('StructureInstaller'))
{
if (strlen($sPreviousVersion) > 0)
{
// Search for existing ActionEmail where the language attribute was defined on its child
if (version_compare($sPreviousVersion, '3.2.0', '<')) {
SetupLog::Info("| Migrate ActionEmail language attribute values to its parent.");
$sTableToRead = MetaModel::DBGetTable('ActionEmail');
$sTableToSet = MetaModel::DBGetTable('ActionNotification');
self::MoveColumnInDB($sTableToRead, 'language', $sTableToSet, 'language');
SetupLog::Info("| ActionEmail migration done.");
}
// If you want to migrate data from one format to another, do it here
self::RenameEnumValueInDB('Software', 'type', 'DBserver', 'DBServer');
self::RenameEnumValueInDB('Software', 'type', 'Webserver', 'WebServer');

View File

@@ -479,6 +479,41 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:EventLoginUsage/Attribute:contact_email+' => 'Email Address of the User',
));
//
// Class: EventiTopNotification
//
Dict::Add('EN US', 'English', 'English', array(
'Class:EventiTopNotification' => ITOP_APPLICATION_SHORT.' Notification',
'Class:EventiTopNotification+' => '',
'Class:EventiTopNotification/Attribute:title' => 'Title',
'Class:EventiTopNotification/Attribute:title+' => '',
'Class:EventiTopNotification/Attribute:icon' => 'Icon',
'Class:EventiTopNotification/Attribute:icon+' => '',
'Class:EventiTopNotification/Attribute:priority' => 'Priority',
'Class:EventiTopNotification/Attribute:priority+' => '',
'Class:EventiTopNotification/Attribute:priority/Value:1' => 'Critical',
'Class:EventiTopNotification/Attribute:priority/Value:1+' => 'Critical',
'Class:EventiTopNotification/Attribute:priority/Value:2' => 'Urgent',
'Class:EventiTopNotification/Attribute:priority/Value:2+' => 'Urgent',
'Class:EventiTopNotification/Attribute:priority/Value:3' => 'Important',
'Class:EventiTopNotification/Attribute:priority/Value:3+' => 'Important',
'Class:EventiTopNotification/Attribute:priority/Value:4' => 'Standard',
'Class:EventiTopNotification/Attribute:priority/Value:4+' => 'Standard',
'Class:EventiTopNotification/Attribute:url' => 'URL',
'Class:EventiTopNotification/Attribute:url+' => '',
'Class:EventiTopNotification/Attribute:read' => 'Read',
'Class:EventiTopNotification/Attribute:read+' => '',
'Class:EventiTopNotification/Attribute:read/Value:no' => 'No',
'Class:EventiTopNotification/Attribute:read/Value:no+' => 'No',
'Class:EventiTopNotification/Attribute:read/Value:yes' => 'Yes',
'Class:EventiTopNotification/Attribute:read/Value:yes+' => 'Yes',
'Class:EventiTopNotification/Attribute:read_date' => 'Read date',
'Class:EventiTopNotification/Attribute:read_date+' => '',
'Class:EventiTopNotification/Attribute:contact_id' => 'Contact',
'Class:EventiTopNotification/Attribute:contact_id+' => '',
));
//
// Class: Action
//
@@ -588,6 +623,36 @@ While editing, click on the magnifier to get pertinent examples',
'ActionEmail:content_placeholder_missing' => 'The placeholder "%1$s" was not found in the HTML template. The content of the field "%2$s" will not be included in the generated emails.',
));
//
// Class: ActioniTopNotification
//
Dict::Add('EN US', 'English', 'English', array(
'Class:ActioniTopNotification' => 'ActioniTopNotification',
'Class:ActioniTopNotification+' => '',
'Class:ActioniTopNotification/Attribute:title' => 'Title',
'Class:ActioniTopNotification/Attribute:title+' => '',
'Class:ActioniTopNotification/Attribute:message' => 'Message',
'Class:ActioniTopNotification/Attribute:message+' => '',
'Class:ActioniTopNotification/Attribute:icon' => 'Icon',
'Class:ActioniTopNotification/Attribute:icon+' => '',
'Class:ActioniTopNotification/Attribute:priority' => 'Priority',
'Class:ActioniTopNotification/Attribute:priority+' => '',
'Class:ActioniTopNotification/Attribute:priority/Value:1' => 'Critical',
'Class:ActioniTopNotification/Attribute:priority/Value:1+' => 'Critical',
'Class:ActioniTopNotification/Attribute:priority/Value:2' => 'Urgent',
'Class:ActioniTopNotification/Attribute:priority/Value:2+' => 'Urgent',
'Class:ActioniTopNotification/Attribute:priority/Value:3' => 'Important',
'Class:ActioniTopNotification/Attribute:priority/Value:3+' => 'Important',
'Class:ActioniTopNotification/Attribute:priority/Value:4' => 'Standard',
'Class:ActioniTopNotification/Attribute:priority/Value:4+' => 'Standard',
'Class:ActioniTopNotification/Attribute:recipients' => 'Recipients',
'Class:ActioniTopNotification/Attribute:recipients+' => '',
'Class:ActioniTopNotification/Attribute:url' => 'URL',
'Class:ActioniTopNotification/Attribute:url+' => '',
));
//
// Class: Trigger
//

View File

@@ -431,6 +431,41 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:EventLoginUsage/Attribute:contact_email+' => '',
));
//
// Class: EventiTopNotification
//
Dict::Add('FR FR', 'French', 'Français', array(
'Class:EventiTopNotification' => 'Notification '.ITOP_APPLICATION_SHORT,
'Class:EventiTopNotification+' => '',
'Class:EventiTopNotification/Attribute:title' => 'Titre',
'Class:EventiTopNotification/Attribute:title+' => '',
'Class:EventiTopNotification/Attribute:icon' => 'Icône',
'Class:EventiTopNotification/Attribute:icon+' => '',
'Class:EventiTopNotification/Attribute:priority' => 'Priorité',
'Class:EventiTopNotification/Attribute:priority+' => '',
'Class:EventiTopNotification/Attribute:priority/Value:1' => 'Critique',
'Class:EventiTopNotification/Attribute:priority/Value:1+' => 'Critique',
'Class:EventiTopNotification/Attribute:priority/Value:2' => 'Urgent',
'Class:EventiTopNotification/Attribute:priority/Value:2+' => 'Urgent',
'Class:EventiTopNotification/Attribute:priority/Value:3' => 'Important',
'Class:EventiTopNotification/Attribute:priority/Value:3+' => 'Important',
'Class:EventiTopNotification/Attribute:priority/Value:4' => 'Standard',
'Class:EventiTopNotification/Attribute:priority/Value:4+' => 'Standard',
'Class:EventiTopNotification/Attribute:url' => 'URL',
'Class:EventiTopNotification/Attribute:url+' => '',
'Class:EventiTopNotification/Attribute:read' => 'Lu',
'Class:EventiTopNotification/Attribute:read+' => '',
'Class:EventiTopNotification/Attribute:read/Value:no' => 'Non',
'Class:EventiTopNotification/Attribute:read/Value:no+' => 'Non',
'Class:EventiTopNotification/Attribute:read/Value:yes' => 'Oui',
'Class:EventiTopNotification/Attribute:read/Value:yes+' => 'Oui',
'Class:EventiTopNotification/Attribute:read_date' => 'Date de lecture',
'Class:EventiTopNotification/Attribute:read_date+' => '',
'Class:EventiTopNotification/Attribute:contact_id' => 'Contact',
'Class:EventiTopNotification/Attribute:contact_id+' => '',
));
//
// Class: Action
//
@@ -543,6 +578,36 @@ En édition, cliquez sur la loupe pour obtenir des exemples pertinents.',
'ActionEmail:content_placeholder_missing' => 'The mot-clé "%1$s" ne figure pas dans le modèle HTML. Le contenu du champ "%2$s" ne sera pas intégré dans les mèls générés.',
));
//
// Class: ActioniTopNotification
//
Dict::Add('FR FR', 'French', 'Français', array(
'Class:ActioniTopNotification' => 'ActioniTopNotification',
'Class:ActioniTopNotification+' => '',
'Class:ActioniTopNotification/Attribute:title' => 'Titre',
'Class:ActioniTopNotification/Attribute:title+' => '',
'Class:ActioniTopNotification/Attribute:message' => 'Message',
'Class:ActioniTopNotification/Attribute:message+' => '',
'Class:ActioniTopNotification/Attribute:icon' => 'Icône',
'Class:ActioniTopNotification/Attribute:icon+' => '',
'Class:ActioniTopNotification/Attribute:priority' => 'Priorité',
'Class:ActioniTopNotification/Attribute:priority+' => '',
'Class:ActioniTopNotification/Attribute:priority/Value:1' => 'Critique',
'Class:ActioniTopNotification/Attribute:priority/Value:1+' => 'Critique',
'Class:ActioniTopNotification/Attribute:priority/Value:2' => 'Urgent',
'Class:ActioniTopNotification/Attribute:priority/Value:2+' => 'Urgent',
'Class:ActioniTopNotification/Attribute:priority/Value:3' => 'Important',
'Class:ActioniTopNotification/Attribute:priority/Value:3+' => 'Important',
'Class:ActioniTopNotification/Attribute:priority/Value:4' => 'Standard',
'Class:ActioniTopNotification/Attribute:priority/Value:4+' => 'Standard',
'Class:ActioniTopNotification/Attribute:recipients' => 'Destinataires',
'Class:ActioniTopNotification/Attribute:recipients+' => '',
'Class:ActioniTopNotification/Attribute:url' => 'URL',
'Class:ActioniTopNotification/Attribute:url+' => '',
));
//
// Class: Trigger
//

View File

@@ -0,0 +1,23 @@
<?php
/**
* Copyright (C) 2013-2023 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
Dict::Add('EN US', 'English', 'English', array(
'UI:Newsroom:iTopNotification:Label' => ITOP_APPLICATION_SHORT,
'UI:Newsroom:iTopNotification:ViewAllPage:Title' => ITOP_APPLICATION_SHORT.' notifications',
));

View File

@@ -489,6 +489,7 @@ return array(
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => $baseDir . '/sources/Service/Links/LinkSetRepository.php',
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => $baseDir . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
'Combodo\\iTop\\Service\\Module\\ModuleService' => $baseDir . '/sources/Service/Module/ModuleService.php',
'Combodo\\iTop\\Service\\Notification\\Event\\EventiTopNotificationGC' => $baseDir . '/sources/Service/Notification/Event/EventiTopNotificationGC.php',
'Combodo\\iTop\\Service\\Router\\Exception\\RouteNotFoundException' => $baseDir . '/sources/Service/Router/Exception/RouteNotFoundException.php',
'Combodo\\iTop\\Service\\Router\\Exception\\RouterException' => $baseDir . '/sources/Service/Router/Exception/RouterException.php',
'Combodo\\iTop\\Service\\Router\\Router' => $baseDir . '/sources/Service/Router/Router.php',
@@ -3127,6 +3128,8 @@ return array(
'iSelfRegister' => $baseDir . '/core/userrights.class.inc.php',
'iTopConfigParser' => $baseDir . '/core/iTopConfigParser.php',
'iTopMutex' => $baseDir . '/core/mutex.class.inc.php',
'iTopNewsroomController' => $baseDir . '/sources/Controller/Newsroom/iTopNewsroomController.php',
'iTopNewsroomProvider' => $baseDir . '/sources/Application/Newsroom/iTopNewsroomProvider.php',
'iTopOwnershipLock' => $baseDir . '/core/ownershiplock.class.inc.php',
'iTopOwnershipToken' => $baseDir . '/core/ownershiplock.class.inc.php',
'iTopStandardURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php',

View File

@@ -863,6 +863,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetRepository.php',
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => __DIR__ . '/../..' . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
'Combodo\\iTop\\Service\\Module\\ModuleService' => __DIR__ . '/../..' . '/sources/Service/Module/ModuleService.php',
'Combodo\\iTop\\Service\\Notification\\Event\\EventiTopNotificationGC' => __DIR__ . '/../..' . '/sources/Service/Notification/Event/EventiTopNotificationGC.php',
'Combodo\\iTop\\Service\\Router\\Exception\\RouteNotFoundException' => __DIR__ . '/../..' . '/sources/Service/Router/Exception/RouteNotFoundException.php',
'Combodo\\iTop\\Service\\Router\\Exception\\RouterException' => __DIR__ . '/../..' . '/sources/Service/Router/Exception/RouterException.php',
'Combodo\\iTop\\Service\\Router\\Router' => __DIR__ . '/../..' . '/sources/Service/Router/Router.php',
@@ -3501,6 +3502,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'iSelfRegister' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',
'iTopConfigParser' => __DIR__ . '/../..' . '/core/iTopConfigParser.php',
'iTopMutex' => __DIR__ . '/../..' . '/core/mutex.class.inc.php',
'iTopNewsroomController' => __DIR__ . '/../..' . '/sources/Controller/Newsroom/iTopNewsroomController.php',
'iTopNewsroomProvider' => __DIR__ . '/../..' . '/sources/Application/Newsroom/iTopNewsroomProvider.php',
'iTopOwnershipLock' => __DIR__ . '/../..' . '/core/ownershiplock.class.inc.php',
'iTopOwnershipToken' => __DIR__ . '/../..' . '/core/ownershiplock.class.inc.php',
'iTopStandardURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php',

View File

@@ -240,9 +240,10 @@ JS
//////////////////////////////////////////////////////////////////////////
$iCountProviders = 0;
$oUser = UserRights::GetUserObject();
$aProviders = MetaModel::EnumPlugins('iNewsroomProvider');
foreach($aProviders as $oProvider)
$aProviders = utils::GetClassesForInterface('iNewsroomProvider', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]'));
foreach($aProviders as $cProvider)
{
$oProvider = new $cProvider();
if ($oProvider->IsApplicable($oUser))
{
$iCountProviders++;
@@ -268,8 +269,9 @@ JS
* @var iNewsroomProvider[] $aProviders
*/
$sAppRootUrl = utils::GetAbsoluteUrlAppRoot();
foreach($aProviders as $oProvider)
foreach($aProviders as $cProvider)
{
$oProvider = new $cProvider();
if ($oProvider->IsApplicable($oUser))
{
$sUrl = $oProvider->GetPreferencesUrl();
@@ -830,8 +832,9 @@ try {
case 'apply_newsroom_preferences':
$iCountProviders = 0;
$oUser = UserRights::GetUserObject();
$aProviders = MetaModel::EnumPlugins('iNewsroomProvider');
foreach ($aProviders as $oProvider) {
$aProviders = utils::GetClassesForInterface('iNewsroomProvider', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]'));
foreach ($aProviders as $cProvider) {
$oProvider = new $cProvider();
if ($oProvider->IsApplicable($oUser)) {
$iCountProviders++;
}

View File

@@ -0,0 +1,46 @@
<?php
use Combodo\iTop\Service\Router\Router;
/**
* Class iTopNewsroomProvider
*
* @author Stephen Abello <stephen.abello@combodo.com>
* @package Combodo\iTop\Application\Newsroom
* @since 3.2.0
*/
class iTopNewsroomProvider extends NewsroomProviderBase {
public function IsApplicable(User $oUser = null){
return true;
}
public function GetLabel()
{
return Dict::S('UI:Newsroom:iTopNotification:Label');
}
public function GetFetchURL()
{
return self::MakeURL('fetch_unread_messages');
}
public function GetMarkAllAsReadURL()
{
return self::MakeURL('mark_all_as_read_messages');
}
public function GetViewAllURL()
{
return self::MakeURL('view_all');
}
private static function MakeURL($sRouteCode)
{
return Router::GetInstance()->GenerateUrl(iTopNewsroomController::ROUTE_NAMESPACE . '.' . $sRouteCode);
}
public function GetTTL()
{
return MetaModel::GetConfig()->Get('notifications.itop.newsroom_cache_time') * 60;
}
}

View File

@@ -65,8 +65,9 @@ class NewsroomMenuFactory
/**
* @var \iNewsroomProvider[] $aProviders
*/
$aProviders = MetaModel::EnumPlugins('iNewsroomProvider');
foreach($aProviders as $oProvider) {
$aProviders = utils::GetClassesForInterface('iNewsroomProvider', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]'));
foreach($aProviders as $cProvider) {
$oProvider = new $cProvider();
$oConfig = MetaModel::GetConfig();
$oProvider->SetConfig($oConfig);
$bProviderEnabled = appUserPreferences::GetPref('newsroom_provider_'.get_class($oProvider), true);

View File

@@ -1815,4 +1815,27 @@ EOD
{
$this->aPreloadedFonts = array_merge($this->aPreloadedFonts, $aFonts);
}
/**
* @return bool
*
* @since 3.2.0
*/
public function GetAddJSDict(): bool
{
return $this->bAddJSDict;
}
/**
* @param bool $bAddJSDict
*
* @return $this
*
* @since 3.2.0
*/
public function SetAddJSDict(bool $bAddJSDict)
{
$this->bAddJSDict = $bAddJSDict;
return $this;
}
}

View File

@@ -0,0 +1,148 @@
<?php
use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\TwigBase\Controller\Controller;
use Combodo\iTop\Service\Router\Router;
/**
* Class iTopNewsroomController
*
* @author Stephen Abello <stephen.abello@combodo.com>
* @package Combodo\iTop\Controller\Newsroom
* @since 3.2.0
*/
class iTopNewsroomController extends Controller
{
public const ROUTE_NAMESPACE = 'itopnewsroom';
/**
* @return \iTopWebPage
* @throws \ApplicationException
* @throws \CoreException
* @throws \OQLException
*/
public function OperationViewAll()
{
$oPage = new iTopWebPage(Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Title'));
$oSearch = DBObjectSearch::FromOQL('SELECT EventiTopNotification WHERE read = "no"');
$oSearch->AddCondition('contact_id', UserRights::GetContactId(), '=');
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, []);
$oBlock->Display($oPage, 0);
$oPage->add("<div class='sf_results_area ibo-add-margin-top-250' data-target='search_results'>");
return $oPage;
}
/**
* @return \AjaxPage
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \OQLException
*/
public function OperationFetchUnreadMessages()
{
$sCallback = utils::ReadParam('callback', '');
$oPage = new AjaxPage('');
$aMessages = [];
$iContactId = UserRights::GetContactId();
if (\utils::IsNotNullOrEmptyString($iContactId)) {
$oSearch = DBObjectSearch::FromOQL('SELECT EventiTopNotification WHERE contact_id = :contact_id AND read = "no"');
$oSet = new DBObjectSet($oSearch, array(), array('contact_id' => $iContactId));
while($oMessage = $oSet->Fetch())
{
$sTitle = $oMessage->Get('title');
$Message = $oMessage->Get('message');
$sText = <<<HTML
**$sTitle**
$Message
HTML;
$sIcon = $oMessage->Get('icon') !== null ?
$oMessage->Get('icon')->GetDisplayURL('EventiTopNotification', $oMessage->GetKey(), 'icon') :
Branding::GetCompactMainLogoAbsoluteUrl();
$aMessages[] = array(
'id' => $oMessage->GetKey(),
'text' => $sText,
'url' => Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE . '.view_event', ['event_id' => $oMessage->GetKey()]),
'start_date' => $oMessage->Get('date'),
'priority' => $oMessage->Get('priority'),
'image' => $sIcon,
);
}
}
$sOutput = $sCallback . '(' . json_encode($aMessages) . ')';
echo $sOutput;
$oPage->SetContentType('application/jsonp');
$oPage->SetAddJSDict(false);
return $oPage;
}
/**
* @return int
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
*/
public function OperationMarkAllAsReadMessages()
{
$iCount = 0;
$iContactId = UserRights::GetContactId();
if (\utils::IsNotNullOrEmptyString($iContactId)) {
$oSearch = DBObjectSearch::FromOQL('SELECT EventiTopNotification WHERE contact_id = :contact_id AND read = "no"');
$oSet = new DBObjectSet($oSearch, array(), array('contact_id' => $iContactId));
while($oMessage = $oSet->Fetch())
{
$oMessage->Set('read', 'yes');
$oMessage->Set('read_date', time());
$oMessage->DBWrite();
$iCount++;
}
}
return $iCount;
}
/**
* @return void
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
* @throws \Exception
*/
public function OperationViewEvent(){
$sEventId = utils::ReadParam('event_id', 0);
if($sEventId > 0) {
try {
$oEvent = MetaModel::GetObject('EventiTopNotification', $sEventId);
if($oEvent !== null && $oEvent->Get('contact_id') === UserRights::GetContactId()){
$oEvent->Set('read', 'yes');
$oEvent->Set('read_date', time());
$oEvent->DBWrite();
$sUrl = $oEvent->Get('url');
header("Location: $sUrl");
}
}
catch (ArchivedObjectException|CoreException $e) {
$this->DisplayPageNotFound();
}
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Combodo\iTop\Service\Notification\Event;
use DBObjectSearch;
use DBObjectSet;
use Exception;
use ExceptionLog;
use iBackgroundProcess;
use MetaModel;
class EventiTopNotificationGC implements iBackgroundProcess
{
public function Process($iUnixTimeLimit)
{
try {
$iDeletionTime = (int) MetaModel::GetConfig()->Get('notifications.itop.read_notification_retention');
$oDBObjectSearch = DBObjectSearch::FromOQL("SELECT EventiTopNotification WHERE read='yes' AND read_date < DATE_SUB(NOW(), INTERVAL :deletion_time DAY)", ['deletion_time' => $iDeletionTime]);
$oEventiTopNotificationSet = new DBObjectSet($oDBObjectSearch);
while($oEventiTopNotification = $oEventiTopNotificationSet->Fetch()){
$oEventiTopNotification->DBDelete();
}
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return false;
}
return true;
}
public function GetPeriodicity()
{
return 24*3600; // Every day
}
}

View File

@@ -159,7 +159,7 @@ class ApplicationExtensionTest extends ItopCustomDatamodelTestCase
],
\iNewsroomProvider::class => [
\iNewsroomProvider::class,
static::ENUM_API_CALL_METHOD_ENUMPLUGINS,
static::ENUM_API_CALL_METHOD_GETCLASSESFORINTERFACE,
],
];
}