diff --git a/core/action.class.inc.php b/core/action.class.inc.php index d706f6296..eea2d6f52 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -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)) { diff --git a/core/autoload.php b/core/autoload.php index f9551c400..49decab22 100644 --- a/core/autoload.php +++ b/core/autoload.php @@ -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'); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index a01d4d4aa..c80e22cfc 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -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.', diff --git a/core/datamodel.core.xml b/core/datamodel.core.xml index e47d5dcae..e0ae50829 100644 --- a/core/datamodel.core.xml +++ b/core/datamodel.core.xml @@ -1,5 +1,268 @@ + + + + ActionNotification + + cmdbAbstractObject + + grant_by_profile,core/cmdb,application + false + autoincrement + priv_action_itop_notif + id + + + + + + + + + + title + + false + + + message + + true + + + icon + + true + 96 + 96 + 256 + 256 + null + + + priority + + + 1 + + + 2 + + + 3 + + + 4 + + + 4 + false + + + recipients + + false + + + url + $this->url()$ + false + + + +
+ + + + + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + + + + + + + + + 10 + + + 20 + + + 30 + + + 40 + + + + + + +
+ + + + 10 + + + 20 + + + 30 + + + +
+ + + /** + * + * 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 + */ + false + public + 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); + } +]]> + + +
+ + + EventNotification + + cmdbAbstractObject + + core/cmdb,view_in_gui + false + autoincrement + priv_event_itop_notif + id + + + + + + + + + + title + + false + + + icon + + true + null + + + priority + + + 1 + + + 2 + + + 3 + + + 4 + + + 4 + false + + + url + + false + _blank + + + read + + + yes + + + no + + + no + false + + + read_date + + true + + + contact_id + Contact + + false + + + + + +
@@ -287,6 +550,43 @@ + + + Action + + grant_by_profile,core/cmdb + + + + + + + + + + + + Event + + core/cmdb,view_in_gui + + + + + + + Trigger + + + Action + + + + + + + + diff --git a/datamodels/2.x/itop-structure/module.itop-structure.php b/datamodels/2.x/itop-structure/module.itop-structure.php index 76824864b..e0743c13c 100644 --- a/datamodels/2.x/itop-structure/module.itop-structure.php +++ b/datamodels/2.x/itop-structure/module.itop-structure.php @@ -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'); diff --git a/dictionaries/en.dictionary.itop.core.php b/dictionaries/en.dictionary.itop.core.php index b29d629b7..2c05a1500 100644 --- a/dictionaries/en.dictionary.itop.core.php +++ b/dictionaries/en.dictionary.itop.core.php @@ -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 // diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index 88d51d651..d55717217 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -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 // diff --git a/dictionaries/ui/application/newsroom/en.dictionary.itop.newsroom.php b/dictionaries/ui/application/newsroom/en.dictionary.itop.newsroom.php new file mode 100644 index 000000000..80cca5d96 --- /dev/null +++ b/dictionaries/ui/application/newsroom/en.dictionary.itop.newsroom.php @@ -0,0 +1,23 @@ + ITOP_APPLICATION_SHORT, + 'UI:Newsroom:iTopNotification:ViewAllPage:Title' => ITOP_APPLICATION_SHORT.' notifications', +)); \ No newline at end of file diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 8f6695738..23f25184b 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -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', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 5bc4917bd..66e4e8c2d 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -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', diff --git a/pages/preferences.php b/pages/preferences.php index 43c2a5fee..4dce26961 100644 --- a/pages/preferences.php +++ b/pages/preferences.php @@ -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++; } diff --git a/sources/Application/Newsroom/iTopNewsroomProvider.php b/sources/Application/Newsroom/iTopNewsroomProvider.php new file mode 100644 index 000000000..bc10318ed --- /dev/null +++ b/sources/Application/Newsroom/iTopNewsroomProvider.php @@ -0,0 +1,46 @@ + + * @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; + } +} \ No newline at end of file diff --git a/sources/Application/UI/Base/Component/PopoverMenu/NewsroomMenu/NewsroomMenuFactory.php b/sources/Application/UI/Base/Component/PopoverMenu/NewsroomMenu/NewsroomMenuFactory.php index ddc86570c..a363073f2 100644 --- a/sources/Application/UI/Base/Component/PopoverMenu/NewsroomMenu/NewsroomMenuFactory.php +++ b/sources/Application/UI/Base/Component/PopoverMenu/NewsroomMenu/NewsroomMenuFactory.php @@ -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); diff --git a/sources/Application/WebPage/WebPage.php b/sources/Application/WebPage/WebPage.php index e0d6ca5b1..9c708b53f 100644 --- a/sources/Application/WebPage/WebPage.php +++ b/sources/Application/WebPage/WebPage.php @@ -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; + } } diff --git a/sources/Controller/Newsroom/iTopNewsroomController.php b/sources/Controller/Newsroom/iTopNewsroomController.php new file mode 100644 index 000000000..bf56f2171 --- /dev/null +++ b/sources/Controller/Newsroom/iTopNewsroomController.php @@ -0,0 +1,148 @@ + + * @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("
"); + 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 = <<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(); + } + } + } +} \ No newline at end of file diff --git a/sources/Service/Notification/Event/EventiTopNotificationGC.php b/sources/Service/Notification/Event/EventiTopNotificationGC.php new file mode 100644 index 000000000..2b2d19424 --- /dev/null +++ b/sources/Service/Notification/Event/EventiTopNotificationGC.php @@ -0,0 +1,36 @@ +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 + } +} \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/application/applicationextension/ApplicationExtensionTest.php b/tests/php-unit-tests/unitary-tests/application/applicationextension/ApplicationExtensionTest.php index 98f1e5c0e..db3474b25 100644 --- a/tests/php-unit-tests/unitary-tests/application/applicationextension/ApplicationExtensionTest.php +++ b/tests/php-unit-tests/unitary-tests/application/applicationextension/ApplicationExtensionTest.php @@ -159,7 +159,7 @@ class ApplicationExtensionTest extends ItopCustomDatamodelTestCase ], \iNewsroomProvider::class => [ \iNewsroomProvider::class, - static::ENUM_API_CALL_METHOD_ENUMPLUGINS, + static::ENUM_API_CALL_METHOD_GETCLASSESFORINTERFACE, ], ]; }