mirror of
https://github.com/Combodo/iTop.git
synced 2026-06-03 14:42:16 +02:00
Compare commits
19 Commits
feature/95
...
feature/89
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d88c89c9f | ||
|
|
41726b0cc9 | ||
|
|
f236dd6c4d | ||
|
|
ba2af7ed63 | ||
|
|
48e6203869 | ||
|
|
ab6c50d52d | ||
|
|
51e7ef32dc | ||
|
|
5ac675a587 | ||
|
|
769afb2715 | ||
|
|
b529a61bc5 | ||
|
|
c501280f53 | ||
|
|
e662370c32 | ||
|
|
3c39c6d8d1 | ||
|
|
a7d0262b21 | ||
|
|
c56617abf5 | ||
|
|
ff94639a61 | ||
|
|
7676115725 | ||
|
|
43ceaeb5a3 | ||
|
|
7cac280b83 |
15
.github/workflows/action.yml
vendored
15
.github/workflows/action.yml
vendored
@@ -1,9 +1,13 @@
|
||||
name: Add PRs to Combodo PRs Dashboard
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
@@ -31,23 +35,22 @@ jobs:
|
||||
run: |
|
||||
curl -X POST -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/Combodo/iTop/issues/${{ github.event.pull_request.number }}/labels \
|
||||
https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels \
|
||||
-d '{"labels":["internal"]}'
|
||||
|
||||
- name: Set PR author as assignee if member of the organization
|
||||
if: env.is_member == 'true'
|
||||
if: env.is_member == 'true' && github.event_name == 'pull_request'
|
||||
run: |
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
|
||||
https://api.github.com/repos/Combodo/iTop/issues/${{ github.event.pull_request.number }}/assignees \
|
||||
https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/assignees \
|
||||
-d '{"assignees":["${{ github.event.pull_request.user.login }}"]}'
|
||||
env:
|
||||
is_member: ${{ env.is_member }}
|
||||
|
||||
- name: Add PR to the appropriate project
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
uses: actions/add-to-project@v2
|
||||
with:
|
||||
project-url: ${{ env.project_url }}
|
||||
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}
|
||||
|
||||
@@ -658,6 +658,16 @@ abstract class User extends cmdbAbstractObject
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow a user to be impersonated by another one (generally an administrator) in order to troubleshoot rights issues.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function CanBeImpersonated(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1063,6 +1073,9 @@ class UserRights
|
||||
$oUser = self::FindUser($sLogin);
|
||||
if ($oUser) {
|
||||
$bRet = true;
|
||||
if (!$oUser->CanBeImpersonated()) {
|
||||
throw new Exception($oUser->GetName().' cannot be impersonated');
|
||||
}
|
||||
if (is_null(self::$m_oRealUser)) {
|
||||
// First impersonation
|
||||
self::$m_oRealUser = self::$m_oUser;
|
||||
@@ -1528,8 +1541,8 @@ class UserRights
|
||||
*/
|
||||
public static function IsActionAllowed($sClass, $iActionCode, $oInstanceSet = null, $oUser = null)
|
||||
{
|
||||
// When initializing, we need to let everything pass trough
|
||||
if (!self::CheckLogin()) {
|
||||
// When initializing, we need to let everything pass through
|
||||
if (is_null($oUser) && !self::CheckLogin()) {
|
||||
return UR_ALLOWED_YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,15 @@
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-flow-map</extension_code>
|
||||
<title>Application Data Flows</title>
|
||||
<description>Modelize flows between your applications, with impacts analysis</description>
|
||||
<modules type="array">
|
||||
<module>itop-flow-map</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-storage</extension_code>
|
||||
<title>Storage Devices</title>
|
||||
@@ -80,11 +89,20 @@
|
||||
<modules type="array">
|
||||
<module>itop-container-mgmt</module>
|
||||
</modules>
|
||||
<default>false</default>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
</options>
|
||||
</sub_options>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-flow-map</extension_code>
|
||||
<title>Data flow</title>
|
||||
<description>Map data flows between applications</description>
|
||||
<modules type="array">
|
||||
<module>itop-flow-map</module>
|
||||
</modules>
|
||||
<default>false</default>
|
||||
</choice>
|
||||
</options>
|
||||
</step>
|
||||
<step>
|
||||
|
||||
@@ -85,11 +85,13 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -84,11 +84,13 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -84,11 +84,13 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (beim Herunterladen eines Attachment eines Objekts)',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger für das Herunterladen des Attachments der angegebenen Klasse oder einer Unterklasse',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -91,11 +91,13 @@ Dict::Add('EN US', 'English', 'English', [
|
||||
Dict::Add('EN US', 'English', 'English', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger',
|
||||
]);
|
||||
|
||||
@@ -81,11 +81,13 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Disparador (al descargar el archivo adjunto del objeto)',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Disparador al descargar el archivo adjunto del objeto de [una clase secundaria de] la clase dada',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -89,5 +89,7 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'Si coché, le fichier sera automatiquement attaché à l\'email quand l\'action email est lancée',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Déclencheur sur la suppression d\'une pièce jointe',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Déclencheur sur la suppression d\'une pièce jointe d\'un objet',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Ajoute le fichier supprimé dans l\'email',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Les Triggers sur les objets ne sont pas autorisés sur la classe Attachement. Veuillez utiliser les triggers spécifiques pour cette classe',
|
||||
]);
|
||||
|
||||
@@ -81,11 +81,13 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -83,11 +83,13 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (al download di un allegato dell\'oggetto)',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger al download di un allegato di un oggetto di [una sottoclasse di] la classe data',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -83,11 +83,13 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -91,5 +91,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (Bij het verwijderen van een bijlage)',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger bij het verwijderen van een bijlage van een object van de opgegeven klasse (of subklasse ervan)',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Bestand toevoegen in e-mail',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -83,11 +83,13 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Wyzwalacz (po pobraniu załącznika obiektu)',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Wyzwalacz po pobraniu załącznika obiektu [klasy podrzędnej] danej klasy',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -83,11 +83,13 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -84,11 +84,13 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -83,11 +83,13 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
|
||||
Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -83,11 +83,13 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
|
||||
Dict::Add('TR TR', 'Turkish', 'Türkçe', [
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -83,11 +83,13 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:TriggerOnAttachmentDownload' => '触发器 (于对象附件下载时)',
|
||||
'Class:TriggerOnAttachmentDownload+' => '触发器于指定类型 [子类型] 对象附件下载时',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment create)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment create~~',
|
||||
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
|
||||
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
|
||||
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment delete)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment delete~~',
|
||||
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
|
||||
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
|
||||
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
|
||||
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
|
||||
]);
|
||||
|
||||
@@ -34,9 +34,11 @@ class TriggerOnAttachmentDelete extends TriggerOnObject
|
||||
];
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeBoolean("file_in_email", ["sql" => 'file_in_email', "is_null_allowed" => false, "default_value" => 'true', "allowed_values" => null, "depends_on" => [], "always_load_in_tables" => false]));
|
||||
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', ['description', 'context', 'filter', 'action_list', 'target_class']); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', ['description', 'context', 'filter', 'action_list', 'target_class','file_in_email']); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', ['finalclass', 'target_class']); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', ['description', 'target_class']); // Criteria of the std search form
|
||||
|
||||
@@ -167,7 +167,7 @@ Dict::Add('EN US', 'English', 'English', [
|
||||
|
||||
Dict::Add('EN US', 'English', 'English', [
|
||||
'Class:Rack' => 'Rack',
|
||||
'Class:Rack+' => 'A physical cabinet for Datacenter Devices and Chassis.',
|
||||
'Class:Rack+' => 'A physical cabinet for Datacenter Devices and Enclosures.',
|
||||
'Class:Rack/ComplementaryName' => '%1$s - %2$s',
|
||||
'Class:Rack/Attribute:nb_u' => 'Rack units',
|
||||
'Class:Rack/Attribute:nb_u+' => '',
|
||||
|
||||
@@ -1480,19 +1480,19 @@
|
||||
<cell id="1" _delta="must_exist">
|
||||
<rank>1</rank>
|
||||
<dashlets>
|
||||
<dashlet id="container_43" xsi:type="DashletBadge" _delta="define">
|
||||
<dashlet id="ContainerApplication" xsi:type="DashletBadge" _delta="define">
|
||||
<rank>5</rank>
|
||||
<class>ContainerApplication</class>
|
||||
</dashlet>
|
||||
<dashlet id="container_44" xsi:type="DashletBadge" _delta="define">
|
||||
<dashlet id="ContainerHost" xsi:type="DashletBadge" _delta="define">
|
||||
<rank>6</rank>
|
||||
<class>ContainerHost</class>
|
||||
</dashlet>
|
||||
<dashlet id="container_45" xsi:type="DashletBadge" _delta="define">
|
||||
<dashlet id="ContainerCluster" xsi:type="DashletBadge" _delta="define">
|
||||
<rank>7</rank>
|
||||
<class>ContainerCluster</class>
|
||||
</dashlet>
|
||||
<dashlet id="container_46" xsi:type="DashletBadge" _delta="define">
|
||||
<dashlet id="ContainerImage" xsi:type="DashletBadge" _delta="define">
|
||||
<rank>8</rank>
|
||||
<class>ContainerImage</class>
|
||||
</dashlet>
|
||||
@@ -1507,11 +1507,11 @@
|
||||
<cell id="0">
|
||||
<rank>0</rank>
|
||||
<dashlets>
|
||||
<dashlet id="container_21" xsi:type="DashletBadge" _delta="define">
|
||||
<dashlet id="ContainerType" xsi:type="DashletBadge" _delta="define">
|
||||
<rank>21</rank>
|
||||
<class>ContainerType</class>
|
||||
</dashlet>
|
||||
<dashlet id="container_22" xsi:type="DashletBadge" _delta="define">
|
||||
<dashlet id="ContainerImageType" xsi:type="DashletBadge" _delta="define">
|
||||
<rank>22</rank>
|
||||
<class>ContainerImageType</class>
|
||||
</dashlet>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Set>
|
||||
<DataFlowType alias="DataFlowType" id="1">
|
||||
<name>http</name>
|
||||
</DataFlowType>
|
||||
<DataFlowType alias="DataFlowType" id="2">
|
||||
<name>https</name>
|
||||
</DataFlowType>
|
||||
<DataFlowType alias="DataFlowType" id="3">
|
||||
<name>ftp</name>
|
||||
</DataFlowType>
|
||||
<DataFlowType alias="DataFlowType" id="4">
|
||||
<name>sftp</name>
|
||||
</DataFlowType>
|
||||
<DataFlowType alias="DataFlowType" id="5">
|
||||
<name>AS2</name>
|
||||
</DataFlowType>
|
||||
<DataFlowType alias="DataFlowType" id="6">
|
||||
<name>X.400</name>
|
||||
</DataFlowType>
|
||||
</Set>
|
||||
615
datamodels/2.x/itop-flow-map/datamodel.itop-flow-map.xml
Normal file
615
datamodels/2.x/itop-flow-map/datamodel.itop-flow-map.xml
Normal file
@@ -0,0 +1,615 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
|
||||
<classes>
|
||||
<class id="DataFlow" _delta="define">
|
||||
<parent>FunctionalCI</parent>
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dataflow</db_table>
|
||||
<style>
|
||||
<icon>images/icons8-sorting-arrows-horizontal.svg</icon>
|
||||
</style>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
<complementary_attributes>
|
||||
<attribute id="source_id"/>
|
||||
<attribute id="destination_id"/>
|
||||
</complementary_attributes>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
<attribute id="destination_id"/>
|
||||
<attribute id="org_id"/>
|
||||
<attribute id="source_id"/>
|
||||
<attribute id="dataflowtype_id"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition>status='inactive'</condition>
|
||||
</obsolescence>
|
||||
<fields_semantic>
|
||||
<state_attribute>status</state_attribute>
|
||||
</fields_semantic>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="source_id" xsi:type="AttributeExternalKey">
|
||||
<sql>source_id</sql>
|
||||
<filter><![CDATA[SELECT FunctionalCI WHERE finalclass != 'DataFlow']]></filter>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>FunctionalCI</target_class>
|
||||
<on_target_delete>DEL_MANUAL</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="source_impact" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="yes">
|
||||
<code>yes</code>
|
||||
<rank>10</rank>
|
||||
</value>
|
||||
<value id="no">
|
||||
<code>no</code>
|
||||
<rank>20</rank>
|
||||
</value>
|
||||
</values>
|
||||
<sql>source_impact</sql>
|
||||
<default_value>yes</default_value>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<display_style>radio_horizontal</display_style>
|
||||
</field>
|
||||
<field id="destination_id" xsi:type="AttributeExternalKey">
|
||||
<sql>destination_id</sql>
|
||||
<filter><![CDATA[SELECT FunctionalCI WHERE finalclass != 'DataFlow']]></filter>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>FunctionalCI</target_class>
|
||||
<on_target_delete>DEL_MANUAL</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="destination_impact" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="yes">
|
||||
<code>yes</code>
|
||||
<rank>10</rank>
|
||||
</value>
|
||||
<value id="no">
|
||||
<code>no</code>
|
||||
<rank>20</rank>
|
||||
</value>
|
||||
</values>
|
||||
<sql>destination_impact</sql>
|
||||
<default_value>no</default_value>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<display_style>radio_horizontal</display_style>
|
||||
</field>
|
||||
<field id="dataflowtype_id" xsi:type="AttributeExternalKey">
|
||||
<sql>dataflowtype_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<target_class>DataFlowType</target_class>
|
||||
<on_target_delete>DEL_MANUAL</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sql>status</sql>
|
||||
<values>
|
||||
<value id="active">
|
||||
<code>active</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="inactive">
|
||||
<code>inactive</code>
|
||||
<rank>20</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sort_type>label</sort_type>
|
||||
<default_value>active</default_value>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<display_style>list</display_style>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="execution_frequency" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="realtime">
|
||||
<code>realtime</code>
|
||||
<rank>10</rank>
|
||||
</value>
|
||||
<value id="ondemand">
|
||||
<code>ondemand</code>
|
||||
<rank>20</rank>
|
||||
</value>
|
||||
<value id="hourly">
|
||||
<code>hourly</code>
|
||||
<rank>30</rank>
|
||||
</value>
|
||||
<value id="daily">
|
||||
<code>daily</code>
|
||||
<rank>40</rank>
|
||||
</value>
|
||||
<value id="weekly">
|
||||
<code>weekly</code>
|
||||
<rank>50</rank>
|
||||
</value>
|
||||
<value id="monthly">
|
||||
<code>monthly</code>
|
||||
<rank>60</rank>
|
||||
</value>
|
||||
<value id="yearly">
|
||||
<code>yearly</code>
|
||||
<rank>70</rank>
|
||||
</value>
|
||||
</values>
|
||||
<sql>execution_frequency</sql>
|
||||
<default_value>daily</default_value>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<display_style>list</display_style>
|
||||
</field>
|
||||
</fields>
|
||||
<event_listeners>
|
||||
<event_listener id="evtCheckSourceAndDestination">
|
||||
<event>EVENT_DB_CHECK_TO_WRITE</event>
|
||||
<rank>10</rank>
|
||||
<callback>evtCheckSourceAndDestination</callback>
|
||||
</event_listener>
|
||||
</event_listeners>
|
||||
<methods>
|
||||
<method id="evtCheckSourceAndDestination" _delta="define">
|
||||
<comment> /**
|
||||
* Ensure that the source and destination of a data flow are not DataFlow themselves
|
||||
*
|
||||
*/</comment>
|
||||
<static>false</static>
|
||||
<access>public</access>
|
||||
<type>EventListener</type>
|
||||
<code><![CDATA[ public function evtCheckSourceAndDestination(Combodo\iTop\Service\Events\EventData $oEventData)
|
||||
{
|
||||
$oSource = MetaModel::GetObject(FunctionalCI::class, $this->Get('source_id'), false, true);
|
||||
$oDestination = MetaModel::GetObject(FunctionalCI::class, $this->Get('destination_id'), false, true);
|
||||
if ($oSource instanceof DataFlow) {
|
||||
$this->AddCheckIssue(Dict::Format('Class:DataFlow/Error:CheckSource', $oSource->GetName()));
|
||||
}
|
||||
if ($oDestination instanceof DataFlow) {
|
||||
$this->AddCheckIssue(Dict::Format('Class:DataFlow/Error:CheckDestination', $oDestination->GetName()));
|
||||
}
|
||||
} ]]></code>
|
||||
</method>
|
||||
</methods>
|
||||
<presentation>
|
||||
<list>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="source_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="destination_id">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
<item id="dataflowtype_id">
|
||||
<rank>40</rank>
|
||||
</item>
|
||||
<item id="business_criticity">
|
||||
<rank>50</rank>
|
||||
</item>
|
||||
</items>
|
||||
</list>
|
||||
<search>
|
||||
<items>
|
||||
<item id="org_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="source_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="destination_id">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
<item id="status">
|
||||
<rank>40</rank>
|
||||
</item>
|
||||
</items>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="col:col1">
|
||||
<items>
|
||||
<item id="fieldset:ConfigMgmt:baseinfo">
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="org_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="status">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
<item id="business_criticity">
|
||||
<rank>40</rank>
|
||||
</item>
|
||||
</items>
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="fieldset:DataFlow:moreinfo">
|
||||
<items>
|
||||
<item id="source_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="source_impact">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="destination_id">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
<item id="destination_impact">
|
||||
<rank>40</rank>
|
||||
</item>
|
||||
<item id="dataflowtype_id">
|
||||
<rank>50</rank>
|
||||
</item>
|
||||
<item id="execution_frequency">
|
||||
<rank>60</rank>
|
||||
</item>
|
||||
</items>
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="col:col2">
|
||||
<items>
|
||||
<item id="fieldset:ConfigMgmt:dates">
|
||||
<items>
|
||||
<item id="move2production">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="fieldset:ConfigMgmt:otherinfo">
|
||||
<items>
|
||||
<item id="description">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="groups_list">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="contacts_list">
|
||||
<rank>70</rank>
|
||||
</item>
|
||||
<item id="documents_list">
|
||||
<rank>80</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
<default_search>
|
||||
<items>
|
||||
<item id="org_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="source_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="destination_id">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
<item id="dataflowtype_id">
|
||||
<rank>40</rank>
|
||||
</item>
|
||||
<item id="status">
|
||||
<rank>50</rank>
|
||||
</item>
|
||||
</items>
|
||||
</default_search>
|
||||
<summary>
|
||||
<items>
|
||||
<item id="business_criticity">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="source_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
<item id="destination_id">
|
||||
<rank>30</rank>
|
||||
</item>
|
||||
<item id="execution_frequency">
|
||||
<rank>40</rank>
|
||||
</item>
|
||||
</items>
|
||||
</summary>
|
||||
</presentation>
|
||||
<relations>
|
||||
<relation id="impacts">
|
||||
<neighbours>
|
||||
<neighbour id="functionalci">
|
||||
<query_down><![CDATA[SELECT FunctionalCI WHERE :this->destination_impact = 'yes' AND id = :this->destination_id]]></query_down>
|
||||
<query_up><![CDATA[SELECT DataFlow AS f JOIN FunctionalCI AS ci ON f.destination_id = ci.id WHERE f.destination_impact = 'yes' AND ci.id=:this->id]]></query_up>
|
||||
<direction>both</direction>
|
||||
</neighbour>
|
||||
<neighbour id="contact">
|
||||
<attribute>contacts_list</attribute>
|
||||
<direction>down</direction>
|
||||
</neighbour>
|
||||
</neighbours>
|
||||
</relation>
|
||||
<relation id="dataflows">
|
||||
<neighbours>
|
||||
<neighbour id="functionalci">
|
||||
<query_down><![CDATA[SELECT FunctionalCI WHERE id = :this->destination_id]]></query_down>
|
||||
<query_up><![CDATA[SELECT DataFlow AS f WHERE f.destination_id = :this->id]]></query_up>
|
||||
<direction>both</direction>
|
||||
</neighbour>
|
||||
</neighbours>
|
||||
</relation>
|
||||
</relations>
|
||||
</class>
|
||||
<class id="DataFlowType" _delta="define">
|
||||
<parent>Typology</parent>
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dataflowtype</db_table>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<uniqueness_rules>
|
||||
<rule id="name">
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
<filter><![CDATA[]]></filter>
|
||||
<disabled>false</disabled>
|
||||
<is_blocking>true</is_blocking>
|
||||
</rule>
|
||||
</uniqueness_rules>
|
||||
</properties>
|
||||
<fields/>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items>
|
||||
<item id="finalclass">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</list>
|
||||
<search>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
</class>
|
||||
<class id="FunctionalCI" _delta="must_exist">
|
||||
<fields>
|
||||
<field id="dataflows" xsi:type="AttributeDashboard" _delta="define">
|
||||
<is_user_editable>true</is_user_editable>
|
||||
<definition>
|
||||
<layout>DashboardLayoutTwoCols</layout>
|
||||
<title>FunctionalCI:DataFlow:Title</title>
|
||||
<auto_reload>
|
||||
<enabled>false</enabled>
|
||||
<interval>300</interval>
|
||||
</auto_reload>
|
||||
<cells>
|
||||
<cell id="0">
|
||||
<rank>0</rank>
|
||||
<dashlets>
|
||||
<dashlet id="DataFlow_Inbound" xsi:type="DashletObjectList">
|
||||
<rank>0</rank>
|
||||
<title>FunctionalCI:DataFlow:Inbound</title>
|
||||
<query>SELECT DataFlow WHERE destination_id=:this->id</query>
|
||||
<menu>true</menu>
|
||||
</dashlet>
|
||||
</dashlets>
|
||||
</cell>
|
||||
<cell id="1">
|
||||
<rank>1</rank>
|
||||
<dashlets>
|
||||
<dashlet id="DataFlow_Outbound" xsi:type="DashletObjectList">
|
||||
<rank>0</rank>
|
||||
<title>FunctionalCI:DataFlow:Outbound</title>
|
||||
<query>SELECT DataFlow WHERE source_id=:this->id</query>
|
||||
<menu>true</menu>
|
||||
</dashlet>
|
||||
</dashlets>
|
||||
</cell>
|
||||
</cells>
|
||||
</definition>
|
||||
</field>
|
||||
</fields>
|
||||
<relations>
|
||||
<relation id="impacts">
|
||||
<neighbours>
|
||||
<neighbour id="flow" _delta="define">
|
||||
<query_down><![CDATA[SELECT DataFlow WHERE source_id = :this->id AND source_impact = 'yes']]></query_down>
|
||||
<query_up><![CDATA[SELECT FunctionalCI AS ci JOIN DataFlow AS f ON f.source_id = ci.id WHERE f.source_impact = 'yes' AND f.id = :this->id]]></query_up>
|
||||
<direction>both</direction>
|
||||
</neighbour>
|
||||
</neighbours>
|
||||
</relation>
|
||||
<relation id="dataflows" _delta="define">
|
||||
<neighbours>
|
||||
<neighbour id="flow">
|
||||
<query_down><![CDATA[SELECT DataFlow WHERE source_id = :this->id]]></query_down>
|
||||
<query_up><![CDATA[SELECT FunctionalCI AS ci JOIN DataFlow AS f ON f.source_id = ci.id WHERE f.id = :this->id]]></query_up>
|
||||
<direction>both</direction>
|
||||
</neighbour>
|
||||
</neighbours>
|
||||
</relation>
|
||||
</relations>
|
||||
</class>
|
||||
<class id="ApplicationSolution" _delta="must_exist">
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="dataflows" _delta="define">
|
||||
<rank>25</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
</class>
|
||||
<class id="DatabaseSchema" _delta="must_exist">
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="dataflows" _delta="define">
|
||||
<rank>25</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
</class>
|
||||
<class id="DBServer" _delta="must_exist">
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="dataflows" _delta="define">
|
||||
<rank>25</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
</class>
|
||||
<class id="Middleware" _delta="must_exist">
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="dataflows" _delta="define">
|
||||
<rank>25</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
</class>
|
||||
<class id="MiddlewareInstance" _delta="must_exist">
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="dataflows" _delta="define">
|
||||
<rank>25</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
</class>
|
||||
<class id="WebApplication" _delta="must_exist">
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="dataflows" _delta="define">
|
||||
<rank>25</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
</class>
|
||||
<class id="WebServer" _delta="must_exist">
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="dataflows" _delta="define">
|
||||
<rank>25</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
</class>
|
||||
<class id="OtherSoftware" _delta="must_exist">
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="dataflows" _delta="define">
|
||||
<rank>25</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
</class>
|
||||
</classes>
|
||||
<menus>
|
||||
<menu id="ConfigManagementOverview" xsi:type="DashboardMenuNode" _delta="must_exist">
|
||||
<definition>
|
||||
<cells>
|
||||
<cell id="3" delta="if_exists">
|
||||
<dashlets>
|
||||
<dashlet id="DataFlow" xsi:type="DashletBadge" _delta="define">
|
||||
<rank>20</rank>
|
||||
<class>DataFlow</class>
|
||||
</dashlet>
|
||||
</dashlets>
|
||||
</cell>
|
||||
</cells>
|
||||
</definition>
|
||||
</menu>
|
||||
<menu id="Typology" xsi:type="DashboardMenuNode" _delta="must_exist">
|
||||
<definition>
|
||||
<cells>
|
||||
<cell id="0">
|
||||
<rank>0</rank>
|
||||
<dashlets>
|
||||
<dashlet id="DataFlowType" xsi:type="DashletBadge" _delta="define">
|
||||
<rank>23</rank>
|
||||
<class>DataFlowType</class>
|
||||
</dashlet>
|
||||
</dashlets>
|
||||
</cell>
|
||||
</cells>
|
||||
</definition>
|
||||
</menu>
|
||||
</menus>
|
||||
<user_rights>
|
||||
<groups>
|
||||
<group id="Configuration">
|
||||
<classes>
|
||||
<class id="DataFlow" _delta="define_if_not_exists"/>
|
||||
</classes>
|
||||
</group>
|
||||
</groups>
|
||||
<profiles>
|
||||
</profiles>
|
||||
</user_rights>
|
||||
</itop_design>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Module combodo-flow-map
|
||||
*
|
||||
* @copyright Copyright (C) 2013 XXXXX
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
Dict::Add('EN US', 'English', 'English', [
|
||||
|
||||
'Relation:dataflows/Description' => 'DataFlows between CIs',
|
||||
'Relation:dataflows/DownStream' => 'Outbound flows...',
|
||||
'Relation:dataflows/DownStream+' => 'Outbound flows map from',
|
||||
'Relation:dataflows/UpStream' => 'Inbound flows...',
|
||||
'Relation:dataflows/UpStream+' => 'Inbound flows map to',
|
||||
|
||||
'Class:FunctionalCI/Attribute:dataflows' => 'Data flows',
|
||||
'Class:FunctionalCI/Attribute:dataflows+' => 'Data flows for which this object is the source or the destination',
|
||||
'FunctionalCI:DataFlow:Title' => 'Data flows',
|
||||
'FunctionalCI:DataFlow:Inbound' => 'Inbound flows',
|
||||
'FunctionalCI:DataFlow:Outbound' => 'Outbound flows',
|
||||
|
||||
'DataFlow:moreinfo' => 'Flow specifics',
|
||||
|
||||
'Class:DataFlow' => 'Flow',
|
||||
'Class:DataFlow+' => 'For application flow for example',
|
||||
'Class:DataFlow/ComplementaryName' => '%1$s - %2$s',
|
||||
'Class:DataFlow/Attribute:name' => 'Name',
|
||||
'Class:DataFlow/Attribute:name+' => 'Identify the transferred data flow',
|
||||
'Class:DataFlow/Attribute:source_id' => 'Source',
|
||||
'Class:DataFlow/Attribute:source_id+' => 'Source Ci of the flow',
|
||||
'Class:DataFlow/Attribute:source_impact' => 'Source impacts?',
|
||||
'Class:DataFlow/Attribute:source_impact+' => 'Does the source impact the flow?',
|
||||
'Class:DataFlow/Attribute:source_impact/Value:yes' => 'yes',
|
||||
'Class:DataFlow/Attribute:source_impact/Value:yes+' => 'If the source falls down, the flow is impacted',
|
||||
'Class:DataFlow/Attribute:source_impact/Value:no' => 'no',
|
||||
'Class:DataFlow/Attribute:source_impact/Value:no+' => 'If the source falls down, the flow is not impacted',
|
||||
'Class:DataFlow/Attribute:destination_id' => 'Destination',
|
||||
'Class:DataFlow/Attribute:destination_id+' => 'Destination Ci for the flow',
|
||||
'Class:DataFlow/Attribute:destination_impact' => 'Destination impacted',
|
||||
'Class:DataFlow/Attribute:destination_impact+' => 'Is the destination impacted by the flow ?',
|
||||
'Class:DataFlow/Attribute:destination_impact/Value:yes' => 'yes',
|
||||
'Class:DataFlow/Attribute:destination_impact/Value:yes+' => 'If the flow stops, the destination is impacted',
|
||||
'Class:DataFlow/Attribute:destination_impact/Value:no' => 'no',
|
||||
'Class:DataFlow/Attribute:destination_impact/Value:no+' => 'If the flow stops, the destination is not impacted',
|
||||
'Class:DataFlow/Attribute:dataflowtype_id' => 'Flow type',
|
||||
'Class:DataFlow/Attribute:dataflowtype_id+' => 'Typology of Flow.',
|
||||
'Class:DataFlow/Attribute:status' => 'Status',
|
||||
'Class:DataFlow/Attribute:status+' => '',
|
||||
'Class:DataFlow/Attribute:status/Value:active' => 'active',
|
||||
'Class:DataFlow/Attribute:status/Value:inactive' => 'inactive',
|
||||
'Class:DataFlow/Attribute:execution_frequency' => 'Execution frequency',
|
||||
'Class:DataFlow/Attribute:execution_frequency+' => 'How often the data flow is executed',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:realtime' => 'real-time',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:realtime+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:ondemand' => 'on demand',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:ondemand+' => 'on the fly, not scheduled',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:hourly' => 'hourly',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:hourly+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:daily' => 'daily',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:daily+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:weekly' => 'weekly',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:weekly+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:monthly' => 'monthly',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:monthly+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:yearly' => 'yearly',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:yearly+' => '',
|
||||
'Class:DataFlow/Attribute:documents_list+' => 'Eg: technical specifications, runbooks, etc.',
|
||||
'Class:DataFlow/Attribute:contacts_list+' => 'Eg: flow owner, technical support, etc.',
|
||||
'Class:DataFlow/Error:CheckSource' => 'The source of a data flow cannot be a data flow itself. Choose another source CI than %1$s',
|
||||
'Class:DataFlow/Error:CheckDestination' => 'The destination of a data flow cannot be a data flow itself. Choose another destination CI than %1$s',
|
||||
|
||||
'Class:DataFlowType' => 'Data Flow Type',
|
||||
'Class:DataFlowType+' => 'Typology of Data Flow',
|
||||
|
||||
/*
|
||||
'Class:DataFlow/Attribute:source_id_friendlyname' => 'source_id_friendlyname',
|
||||
'Class:DataFlow/Attribute:source_id_friendlyname+' => 'Full name',
|
||||
'Class:DataFlow/Attribute:source_id_finalclass_recall' => 'source_id->CI sub-class',
|
||||
'Class:DataFlow/Attribute:source_id_finalclass_recall+' => 'Name of the final class',
|
||||
'Class:DataFlow/Attribute:source_id_obsolescence_flag' => 'source_id->Obsolete',
|
||||
'Class:DataFlow/Attribute:source_id_obsolescence_flag+' => 'Computed dynamically on other attributes',
|
||||
'Class:DataFlow/Attribute:destination_id_friendlyname' => 'destination_id_friendlyname',
|
||||
'Class:DataFlow/Attribute:destination_id_friendlyname+' => 'Full name',
|
||||
'Class:DataFlow/Attribute:destination_id_finalclass_recall' => 'destination_id->CI sub-class',
|
||||
'Class:DataFlow/Attribute:destination_id_finalclass_recall+' => 'Name of the final class',
|
||||
'Class:DataFlow/Attribute:destination_id_obsolescence_flag' => 'destination_id->Obsolete',
|
||||
'Class:DataFlow/Attribute:destination_id_obsolescence_flag+' => 'Computed dynamically on other attributes',
|
||||
*/
|
||||
]);
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Module combodo-flow-map
|
||||
*
|
||||
* @copyright Copyright (C) 2013 XXXXX
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
Dict::Add('FR FR', 'French', 'Français', [
|
||||
|
||||
'Relation:dataflows/Description' => 'Flux de données entre CIs',
|
||||
'Relation:dataflows/DownStream' => 'Flux sortants...',
|
||||
'Relation:dataflows/DownStream+' => 'Carte des flux sortants depuis',
|
||||
'Relation:dataflows/UpStream' => 'Flux entrants...',
|
||||
'Relation:dataflows/UpStream+' => 'Carte des flux entrants vers',
|
||||
|
||||
'Class:FunctionalCI/Attribute:dataflows' => 'Flux de données',
|
||||
'Class:FunctionalCI/Attribute:dataflows+' => 'Flux de données dont cet objet est la source ou la destination',
|
||||
'FunctionalCI:DataFlow:Title' => 'Flux de données',
|
||||
'FunctionalCI:DataFlow:Inbound' => 'Flux entrants',
|
||||
'FunctionalCI:DataFlow:Outbound' => 'Flux sortants',
|
||||
|
||||
'DataFlow:moreinfo' => 'Spécificités du flux',
|
||||
|
||||
'Class:DataFlow' => 'Flux de Données',
|
||||
'Class:DataFlow+' => 'Modélise les données transférées entre instances d\'application ou plus généralement entre CIs.',
|
||||
'Class:DataFlow/ComplementaryName' => '%1$s - %2$s',
|
||||
'Class:DataFlow/Attribute:name' => 'Nom',
|
||||
'Class:DataFlow/Attribute:name+' => 'Identifie le flux de données',
|
||||
'Class:DataFlow/Attribute:source_id' => 'Source',
|
||||
'Class:DataFlow/Attribute:source_id+' => 'Instance d\application à la source du flux de données',
|
||||
'Class:DataFlow/Attribute:source_impact' => 'Source impactante ?',
|
||||
'Class:DataFlow/Attribute:source_impact+' => 'La source impacte-t-elle le flux de données ?',
|
||||
'Class:DataFlow/Attribute:source_impact/Value:yes' => 'oui',
|
||||
'Class:DataFlow/Attribute:source_impact/Value:yes+' => 'Si la source tombe en panne, le flux de données est impacté',
|
||||
'Class:DataFlow/Attribute:source_impact/Value:no' => 'non',
|
||||
'Class:DataFlow/Attribute:source_impact/Value:no+' => 'Si la source tombe en panne, le flux de données n\'est pas impacté',
|
||||
'Class:DataFlow/Attribute:destination_id' => 'Destinataire',
|
||||
'Class:DataFlow/Attribute:destination_id+' => 'Destinataire des données, à choisir parmi les instances d\'application',
|
||||
'Class:DataFlow/Attribute:destination_impact' => 'Destinataire impacté ?',
|
||||
'Class:DataFlow/Attribute:destination_impact+' => 'Le destinataire est-il impacté si le flux de données s\'arrête ?',
|
||||
'Class:DataFlow/Attribute:destination_impact/Value:yes' => 'oui',
|
||||
'Class:DataFlow/Attribute:destination_impact/Value:yes+' => 'Si le flux s\'arrête, le destinataire est impacté',
|
||||
'Class:DataFlow/Attribute:destination_impact/Value:no' => 'non',
|
||||
'Class:DataFlow/Attribute:destination_impact/Value:no+' => 'Si le flux s\'arrête, le destinataire n\'est pas impacté',
|
||||
'Class:DataFlow/Attribute:dataflowtype_id' => 'Type de flux',
|
||||
'Class:DataFlow/Attribute:dataflowtype_id+' => 'Typologie du flux',
|
||||
'Class:DataFlow/Attribute:status' => 'Etat',
|
||||
'Class:DataFlow/Attribute:status+' => '',
|
||||
'Class:DataFlow/Attribute:status/Value:active' => 'actif',
|
||||
'Class:DataFlow/Attribute:status/Value:inactive' => 'inactif',
|
||||
'Class:DataFlow/Attribute:execution_frequency' => 'Fréquence d\'exécution',
|
||||
'Class:DataFlow/Attribute:execution_frequency+' => 'À quelle fréquence le transfert de données est-il exécuté',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:realtime' => 'temps réel',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:realtime+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:ondemand' => 'à la demande',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:ondemand+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:hourly' => 'horaire',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:hourly+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:daily' => 'journalière',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:daily+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:weekly' => 'hebdomadaire',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:weekly+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:monthly' => 'mensuelle',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:monthly+' => '',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:yearly' => 'annuelle',
|
||||
'Class:DataFlow/Attribute:execution_frequency/Value:yearly+' => '',
|
||||
'Class:DataFlow/Attribute:documents_list+' => 'Eg: technical specifications, runbooks, etc.',
|
||||
'Class:DataFlow/Attribute:contacts_list+' => 'Eg: flow owner, technical support, etc.',
|
||||
'Class:DataFlow/Error:CheckSource' => 'La source d\'un flux de données ne peut pas être un flux de données elle-même. Choisissez un autre CI source que %1$s',
|
||||
'Class:DataFlow/Error:CheckDestination' => 'La destination d\'un flux de données ne peut pas être un flux de données elle-même. Choisissez un autre CI destination que %1$s',
|
||||
|
||||
'Class:DataFlowType' => 'Type de flux',
|
||||
'Class:DataFlowType+' => 'Typologie des flux de données',
|
||||
|
||||
/*
|
||||
'Class:DataFlow/Attribute:source_id_friendlyname' => 'source_id_friendlyname',
|
||||
'Class:DataFlow/Attribute:source_id_friendlyname+' => 'Nom complet',
|
||||
'Class:DataFlow/Attribute:source_id_finalclass_recall' => 'source_id->CI sub-class',
|
||||
'Class:DataFlow/Attribute:source_id_finalclass_recall+' => 'Classe finale',
|
||||
'Class:DataFlow/Attribute:source_id_obsolescence_flag' => 'source_id->Obsolete',
|
||||
'Class:DataFlow/Attribute:source_id_obsolescence_flag+' => 'Computed dynamically on other attributes',
|
||||
'Class:DataFlow/Attribute:destination_id_friendlyname' => 'destination_id_friendlyname',
|
||||
'Class:DataFlow/Attribute:destination_id_friendlyname+' => 'Nom complet',
|
||||
'Class:DataFlow/Attribute:destination_id_finalclass_recall' => 'destination_id->CI sub-class',
|
||||
'Class:DataFlow/Attribute:destination_id_finalclass_recall+' => 'Classe finale',
|
||||
'Class:DataFlow/Attribute:destination_id_obsolescence_flag' => 'destination_id->Obsolete',
|
||||
'Class:DataFlow/Attribute:destination_id_obsolescence_flag+' => 'Computed dynamically on other attributes',
|
||||
*/
|
||||
]);
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="mv_DwPz_GcV~datTQ_sP3a" x1="27.258" x2="38.501" y1="18.189" y2="44.314" gradientTransform="rotate(90 23.5 24)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#mv_DwPz_GcV~datTQ_sP3a)" d="M14,41.19V37h14c0.552,0,1-0.448,1-1v-4c0-0.552-0.448-1-1-1H14v-4.19 c0-0.72-0.87-1.08-1.379-0.571L5.92,32.939c-0.586,0.586-0.586,1.536,0,2.121l6.701,6.701C13.13,42.271,14,41.91,14,41.19z"/><linearGradient id="mv_DwPz_GcV~datTQ_sP3b" x1="32.674" x2="34.456" y1="9.581" y2="13.722" gradientTransform="rotate(90 23.5 24)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#mv_DwPz_GcV~datTQ_sP3b)" d="M35,36v-4c0-0.552,0.448-1,1-1l0,0c0.552,0,1,0.448,1,1v4c0,0.552-0.448,1-1,1l0,0 C35.448,37,35,36.552,35,36z"/><linearGradient id="mv_DwPz_GcV~datTQ_sP3c" x1="32.674" x2="34.456" y1="5.581" y2="9.722" gradientTransform="rotate(90 23.5 24)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#mv_DwPz_GcV~datTQ_sP3c)" d="M39,36v-4c0-0.552,0.448-1,1-1l0,0c0.552,0,1,0.448,1,1v4c0,0.552-0.448,1-1,1l0,0 C39.448,37,39,36.552,39,36z"/><linearGradient id="mv_DwPz_GcV~datTQ_sP3d" x1="32.674" x2="34.456" y1="13.581" y2="17.722" gradientTransform="rotate(90 23.5 24)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#mv_DwPz_GcV~datTQ_sP3d)" d="M31,36v-4c0-0.552,0.448-1,1-1h0c0.552,0,1,0.448,1,1v4c0,0.552-0.448,1-1,1h0 C31.448,37,31,36.552,31,36z"/><linearGradient id="mv_DwPz_GcV~datTQ_sP3e" x1="551.258" x2="562.501" y1="-252.291" y2="-226.167" gradientTransform="rotate(-90 421.24 151.26)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1ea2e4"/><stop offset="1" stop-color="#32bdef"/></linearGradient><path fill="url(#mv_DwPz_GcV~datTQ_sP3e)" d="M33,7.81V12H19c-0.552,0-1,0.448-1,1v4c0,0.552,0.448,1,1,1h14v4.19 c0,0.72,0.87,1.08,1.379,0.571l6.701-6.701c0.586-0.586,0.586-1.536,0-2.121l-6.701-6.701C33.87,6.729,33,7.09,33,7.81z"/><linearGradient id="mv_DwPz_GcV~datTQ_sP3f" x1="556.674" x2="558.456" y1="-260.899" y2="-256.759" gradientTransform="rotate(-90 421.24 151.26)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1ea2e4"/><stop offset="1" stop-color="#32bdef"/></linearGradient><path fill="url(#mv_DwPz_GcV~datTQ_sP3f)" d="M12,13v4c0,0.552-0.448,1-1,1h0c-0.552,0-1-0.448-1-1v-4c0-0.552,0.448-1,1-1h0 C11.552,12,12,12.448,12,13z"/><linearGradient id="mv_DwPz_GcV~datTQ_sP3g" x1="556.674" x2="558.456" y1="-264.899" y2="-260.759" gradientTransform="rotate(-90 421.24 151.26)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1ea2e4"/><stop offset="1" stop-color="#32bdef"/></linearGradient><path fill="url(#mv_DwPz_GcV~datTQ_sP3g)" d="M8,13v4c0,0.552-0.448,1-1,1h0c-0.552,0-1-0.448-1-1v-4c0-0.552,0.448-1,1-1h0 C7.552,12,8,12.448,8,13z"/><linearGradient id="mv_DwPz_GcV~datTQ_sP3h" x1="556.674" x2="558.456" y1="-256.899" y2="-252.758" gradientTransform="rotate(-90 421.24 151.26)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1ea2e4"/><stop offset="1" stop-color="#32bdef"/></linearGradient><path fill="url(#mv_DwPz_GcV~datTQ_sP3h)" d="M16,13v4c0,0.552-0.448,1-1,1h0c-0.552,0-1-0.448-1-1v-4c0-0.552,0.448-1,1-1h0 C15.552,12,16,12.448,16,13z"/></svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
50
datamodels/2.x/itop-flow-map/module.itop-flow-map.php
Normal file
50
datamodels/2.x/itop-flow-map/module.itop-flow-map.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
//
|
||||
// iTop module definition file
|
||||
//
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-flow-map/3.3.0',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
'label' => 'Applications data flows',
|
||||
'category' => 'business',
|
||||
|
||||
// Setup
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-config-mgmt/3.2.0',
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
|
||||
// Components
|
||||
//
|
||||
'datamodel' => [
|
||||
|
||||
],
|
||||
'webservice' => [
|
||||
|
||||
],
|
||||
'data.struct' => [
|
||||
'data/en_us.data.itop-flow-map.xml',
|
||||
],
|
||||
'data.sample' => [
|
||||
// add your sample data XML files here,
|
||||
],
|
||||
|
||||
// Documentation
|
||||
//
|
||||
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
|
||||
'doc.more_information' => '', // hyperlink to more information, if any
|
||||
|
||||
// Default settings
|
||||
//
|
||||
'settings' => [
|
||||
// Module specific settings go here, if any
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -85,6 +85,21 @@
|
||||
<class id="Attachment"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="Ticket" _delta="define">
|
||||
<classes>
|
||||
<class id="Ticket"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="FunctionalCI" _delta="define">
|
||||
<classes>
|
||||
<class id="FunctionalCI"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="ServiceFamily" _delta="define">
|
||||
<classes>
|
||||
<class id="ServiceFamily"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="Portal" _delta="define">
|
||||
<classes>
|
||||
<class id="lnkFunctionalCIToTicket"/>
|
||||
@@ -205,6 +220,42 @@
|
||||
</group>
|
||||
</groups>
|
||||
<profiles>
|
||||
<profile id="5500" _delta="define">
|
||||
<name>ReadOnlyCI</name>
|
||||
<description>This read-only profile allows to see CIs objects.</description>
|
||||
<groups>
|
||||
<group id="FunctionalCI">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
</groups>
|
||||
</profile>
|
||||
<profile id="5501" _delta="define">
|
||||
<name>ReadOnlyTicket</name>
|
||||
<description>This read-only profile allows to see Ticket objects.</description>
|
||||
<groups>
|
||||
<group id="Ticket">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
</groups>
|
||||
</profile>
|
||||
<profile id="5502" _delta="define">
|
||||
<name>ReadOnlyCatalog</name>
|
||||
<description>This read-only profile allows to see ServiceFamily objects.</description>
|
||||
<groups>
|
||||
<group id="ServiceFamily">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
</groups>
|
||||
</profile>
|
||||
<profile id="117" _delta="define">
|
||||
<name>SuperUser</name>
|
||||
<description>This profile allows all actions which are not Administrator restricted.</description>
|
||||
|
||||
@@ -216,6 +216,14 @@ Operators:<br/>
|
||||
'Core:Context=GUI:Console' => 'Console',
|
||||
'Core:Context=CRON' => 'cron',
|
||||
'Core:Context=GUI:Portal' => 'Portal',
|
||||
|
||||
'Core:GetQuota:Error' => 'Error while getting %1$s quota',
|
||||
'Core:ConsoleUsers' => 'console users',
|
||||
'Core:DisabledUsers' => 'disabled users',
|
||||
'Core:PortalUsers' => 'portal users',
|
||||
'Core:BusinessPartnerUser' => 'business partner users',
|
||||
'Core:ReadOnlyUsers' => 'read-only users',
|
||||
'Core:ApplicationUsers' => 'application users',
|
||||
]);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -161,6 +161,14 @@ Opérateurs :<br/>
|
||||
'Core:Context=CRON+' => 'cron',
|
||||
'Core:Context=GUI:Portal' => 'Portal',
|
||||
'Core:Context=GUI:Portal+' => 'GUI:Portal',
|
||||
|
||||
'Core:GetQuota:Error' => 'Erreur lors de la récupération du quota des %1$s',
|
||||
'Core:ConsoleUsers' => 'utilisateurs console',
|
||||
'Core:DisabledUsers' => 'utilisateurs désactivés',
|
||||
'Core:PortalUsers' => 'utilisateurs du portail',
|
||||
'Core:BusinessPartnerUser' => 'utilisateurs partenaires business',
|
||||
'Core:ReadOnlyUsers' => 'utilisateurs en lecture seule',
|
||||
'Core:ApplicationUsers' => 'utilisateurs applicatifs',
|
||||
]);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -643,6 +643,7 @@ return array(
|
||||
'Combodo\\iTop\\SessionTracker\\SessionGC' => $baseDir . '/sources/SessionTracker/SessionGC.php',
|
||||
'Combodo\\iTop\\SessionTracker\\SessionHandler' => $baseDir . '/sources/SessionTracker/SessionHandler.php',
|
||||
'Combodo\\iTop\\SessionTracker\\iSessionHandlerExtension' => $baseDir . '/sources/SessionTracker/iSessionHandlerExtension.php',
|
||||
'Combodo\\iTop\\Users\\ITopUserQuotaRepository' => $baseDir . '/sources/Users/ITopUserQuotaRepository.php',
|
||||
'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'Config' => $baseDir . '/core/config.class.inc.php',
|
||||
|
||||
@@ -1044,6 +1044,7 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
|
||||
'Combodo\\iTop\\SessionTracker\\SessionGC' => __DIR__ . '/../..' . '/sources/SessionTracker/SessionGC.php',
|
||||
'Combodo\\iTop\\SessionTracker\\SessionHandler' => __DIR__ . '/../..' . '/sources/SessionTracker/SessionHandler.php',
|
||||
'Combodo\\iTop\\SessionTracker\\iSessionHandlerExtension' => __DIR__ . '/../..' . '/sources/SessionTracker/iSessionHandlerExtension.php',
|
||||
'Combodo\\iTop\\Users\\ITopUserQuotaRepository' => __DIR__ . '/../..' . '/sources/Users/ITopUserQuotaRepository.php',
|
||||
'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',
|
||||
|
||||
@@ -153,8 +153,12 @@ class Extension
|
||||
return twig_array_filter($oTwigEnv, $array, $arrow);
|
||||
}, ['needs_environment' => true]);
|
||||
|
||||
// @since 3.3.0 N°8579
|
||||
// Filter to remove spaces between HTML tags, overwrite the deprecated core "spaceless" filter
|
||||
/**
|
||||
* Filter to remove spaces between HTML tags, overwrite the deprecated core "spaceless" filter
|
||||
* Usage in twig: {% apply spaceless %}some html{% endapply %}
|
||||
*
|
||||
* @since 3.2.3 3.3.0 N°8579
|
||||
*/
|
||||
$aFilters[] = new TwigFilter('spaceless', function (?string $content) {
|
||||
return trim(preg_replace('/>\s+</', '><', $content ?? ''));
|
||||
}, ['is_safe' => ['html']]);
|
||||
|
||||
@@ -29,7 +29,9 @@ use Symfony\Component\CssSelector\Exception\ParseException;
|
||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||
use Symfony\Component\Mailer\Transport;
|
||||
use Symfony\Component\Mailer\Mailer;
|
||||
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
|
||||
use Symfony\Component\Mime\Email as SymfonyEmail;
|
||||
use Symfony\Component\Mime\HtmlToTextConverter\DefaultHtmlToTextConverter;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\RelatedPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\MixedPart;
|
||||
@@ -183,18 +185,7 @@ class EMailSymfony extends Email
|
||||
$sDsn = sprintf('smtp://%s%s@%s%s', $sDsnUser, $sDsnPassword, $sDsnPort, $sEncQuery);
|
||||
}
|
||||
|
||||
$oTransport = Transport::fromDsn($sDsn);
|
||||
|
||||
// Handle peer verification
|
||||
$oStream = $oTransport->getStream();
|
||||
$aOptions = $oStream->getStreamOptions();
|
||||
if (!$bVerifyPeer && array_key_exists('ssl', $aOptions)) {
|
||||
// Disable verification
|
||||
$aOptions['ssl']['verify_peer'] = false;
|
||||
$aOptions['ssl']['verify_peer_name'] = false;
|
||||
$aOptions['ssl']['allow_self_signed'] = true;
|
||||
}
|
||||
$oStream->setStreamOptions($aOptions);
|
||||
$oTransport = $this->CreateSmtpTransport($sDsn, $bVerifyPeer);
|
||||
|
||||
$oMailer = new Mailer($oTransport);
|
||||
break;
|
||||
@@ -260,6 +251,36 @@ class EMailSymfony extends Email
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and configure an SMTP transport from a DSN string.
|
||||
*
|
||||
* Extracted from {@see SendSynchronous} to make SSL option handling independently testable.
|
||||
* When $bVerifyPeer is false, the ssl stream context options must be written unconditionally:
|
||||
* with STARTTLS the connection starts unencrypted, so the 'ssl' key is absent from the stream
|
||||
* options at construction time and only used later when stream_socket_enable_crypto() is called.
|
||||
*
|
||||
* @param string $sDsn Full Symfony Mailer DSN (smtp:// or smtps://)
|
||||
* @param bool $bVerifyPeer Whether to verify the peer SSL certificate
|
||||
*
|
||||
* @return \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport
|
||||
*/
|
||||
protected function CreateSmtpTransport(string $sDsn, bool $bVerifyPeer): EsmtpTransport
|
||||
{
|
||||
/** @var EsmtpTransport $oTransport */
|
||||
$oTransport = Transport::fromDsn($sDsn);
|
||||
|
||||
$oStream = $oTransport->getStream();
|
||||
$aOptions = $oStream->getStreamOptions();
|
||||
if (!$bVerifyPeer) {
|
||||
$aOptions['ssl']['verify_peer'] = false;
|
||||
$aOptions['ssl']['verify_peer_name'] = false;
|
||||
$aOptions['ssl']['allow_self_signed'] = true;
|
||||
}
|
||||
$oStream->setStreamOptions($aOptions);
|
||||
|
||||
return $oTransport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reprocess the body of the message (if it is an HTML message)
|
||||
* to replace the URL of images based on attachments by a link
|
||||
@@ -416,13 +437,13 @@ class EMailSymfony extends Email
|
||||
|
||||
$this->m_aData['body'] = ['body' => $sBody, 'mimeType' => $sMimeType];
|
||||
|
||||
$oTextPart = new TextPart(strip_tags($sBody), 'utf-8', 'plain', 'base64');
|
||||
|
||||
// Embed inline images and store them in attachments (so BuildSymfonyMessageFromInternal can pick them)
|
||||
if ($sPrimaryMimeType === 'text/html') {
|
||||
$aAdditionalParts = $this->EmbedInlineImages($sBody);
|
||||
$oTextPart = new TextPart((new DefaultHtmlToTextConverter())->convert($sBody, 'utf-8'), 'utf-8', 'plain', 'base64');
|
||||
$oHtmlPart = new TextPart($sBody, 'utf-8', 'html', 'base64');
|
||||
$oAlternativePart = new AlternativePart($oHtmlPart, $oTextPart);
|
||||
// It's important de order parts from least prefered to most prefered as per RFC 2046 {@see https://www.rfc-editor.org/rfc/rfc2046.html#section-5.1.4}
|
||||
$oAlternativePart = new AlternativePart($oTextPart, $oHtmlPart);
|
||||
// Default root part is the HTML body
|
||||
$oRootPart = $oAlternativePart;
|
||||
|
||||
|
||||
221
sources/Users/ITopUserQuotaRepository.php
Normal file
221
sources/Users/ITopUserQuotaRepository.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Users;
|
||||
|
||||
use CoreException;
|
||||
use CoreUnexpectedValue;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use DBSearch;
|
||||
use DBUnionSearch;
|
||||
use Dict;
|
||||
use DictExceptionMissingString;
|
||||
use Exception;
|
||||
use IssueLog;
|
||||
use MetaModel;
|
||||
use MySQLException;
|
||||
use User;
|
||||
use UserRights;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ITopUserQuotaRepository
|
||||
{
|
||||
/**
|
||||
* @param string $sExcludedUsers
|
||||
* @param string $sExcludedProfiles
|
||||
* @param bool $bAllData
|
||||
* @param string $sExcludedFinalClasses
|
||||
*
|
||||
* @return DBObjectSearch|DBUnionSearch|null
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetConsoleUsers(string $sExcludedUsers = '', string $sExcludedProfiles = '', bool $bAllData = true, string $sExcludedFinalClasses = 'UserToken, UserRemoteSaaS'): null|DBObjectSearch|DBUnionSearch
|
||||
{
|
||||
$sOQLInQuotaUser = "
|
||||
SELECT User AS u
|
||||
WHERE u.status != 'disabled'
|
||||
AND u.login NOT IN ('$sExcludedUsers')
|
||||
AND u.finalclass != ' $sExcludedFinalClasses '
|
||||
AND id NOT IN (
|
||||
SELECT User AS uex
|
||||
JOIN URP_UserProfile AS uup ON uup.userid = uex.id
|
||||
JOIN URP_Profiles AS up ON uup.profileid = up.id
|
||||
WHERE up.name IN ('$sExcludedProfiles'))
|
||||
";
|
||||
try {
|
||||
$oFilter = $bAllData ? DBObjectSearch::FromOQL_AllData($sOQLInQuotaUser) : DBObjectSearch::FromOQL($sOQLInQuotaUser);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
IssueLog::Error('Core:GetConsoleUsersQuota:Error : '.$e->getMessage());
|
||||
throw new Exception(Dict::Format('Core:GetQuota:Error', Dict::S('Core:ConsoleUsers')));
|
||||
}
|
||||
|
||||
// TODO remove read only users
|
||||
return $oFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetApplicationUsers(bool $bAllData = true): null|DBObjectSearch|DBUnionSearch
|
||||
{
|
||||
$sOQLApplicationUser = 'SELECT UserToken';
|
||||
try {
|
||||
$oFilter = $bAllData ? DBObjectSearch::FromOQL_AllData($sOQLApplicationUser) : DBObjectSearch::FromOQL($sOQLApplicationUser);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
IssueLog::Error('Core:GetConsoleUsersQuota:Error : '.$e->getMessage());
|
||||
throw new Exception(Dict::Format('Core:GetQuota:Error', Dict::S('Core:ApplicationUsers')));
|
||||
}
|
||||
|
||||
return $oFilter;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetDisabledUsers(bool $bAllData = true): null|DBObjectSearch|DBUnionSearch
|
||||
{
|
||||
$sOQLDisabledUser = "
|
||||
SELECT User AS u
|
||||
WHERE u.status = 'disabled'
|
||||
";
|
||||
try {
|
||||
$oFilter = $bAllData ? DBObjectSearch::FromOQL_AllData($sOQLDisabledUser) : DBObjectSearch::FromOQL($sOQLDisabledUser);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
IssueLog::Error('Core:GetDisabledUsersQuota:Error : '.$e->getMessage());
|
||||
throw new Exception(Dict::Format('Core:GetQuota:Error', Dict::S('Core:DisabledUsers')));
|
||||
}
|
||||
|
||||
return $oFilter;
|
||||
}
|
||||
|
||||
private function IsUserReadOnly(User $oUser, string $sClassCategory)
|
||||
{
|
||||
UserRights::Login($oUser->GetName());
|
||||
|
||||
foreach (MetaModel::GetClasses($sClassCategory) as $sClass) {
|
||||
$aClassStimuli = MetaModel::EnumStimuli($sClass);
|
||||
if (count($aClassStimuli) > 0) {
|
||||
$aStimuli = [];
|
||||
foreach ($aClassStimuli as $sStimulusCode => $oStimulus) {
|
||||
if (UserRights::IsStimulusAllowed($sClass, $sStimulusCode, null, $oUser)) {
|
||||
$aStimuli[] =
|
||||
$oStimulus->GetLabel();
|
||||
}
|
||||
}
|
||||
$sStimuli = implode(', ', $aStimuli);
|
||||
} else {
|
||||
$sStimuli = '';
|
||||
}
|
||||
|
||||
if (
|
||||
UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, null, $oUser) ||
|
||||
UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, null, $oUser) ||
|
||||
UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, null, $oUser) ||
|
||||
UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, null, $oUser) ||
|
||||
$sStimuli != ''
|
||||
) {
|
||||
UserRights::Logoff();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
UserRights::Logoff();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetReadOnlyUsers(): array
|
||||
{
|
||||
$aReadOnlyUsers = [];
|
||||
$oAllUsersFilter = $this->GetAllUsers();
|
||||
$aAllUsers = $this->GetUsersFromFilter($oAllUsersFilter);
|
||||
/** @var User $oUser */
|
||||
foreach ($aAllUsers as $oUser) {
|
||||
$bIsReadOnlyUser = true;
|
||||
|
||||
if (!$this->IsUserReadOnly($oUser, 'bizmodel') ||
|
||||
!$this->IsUserReadOnly($oUser, 'grant_by_profile')) {
|
||||
$bIsReadOnlyUser = false;
|
||||
}
|
||||
|
||||
if ($bIsReadOnlyUser) {
|
||||
$aReadOnlyUsers[] = $oUser;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO remove disabled users
|
||||
return $aReadOnlyUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getPortalUsers(bool $bAllData = true): null|DBObjectSearch|DBUnionSearch
|
||||
{
|
||||
$sOQLPortalUser = '
|
||||
SELECT User AS u
|
||||
JOIN URP_UserProfile AS uup ON uup.userid = u.id
|
||||
JOIN URP_Profiles AS up ON uup.profileid = up.id
|
||||
WHERE up.name = \' '.PORTAL_PROFILE_NAME.'\'';
|
||||
|
||||
try {
|
||||
$oFilter = $bAllData ? DBObjectSearch::FromOQL_AllData($sOQLPortalUser) : DBObjectSearch::FromOQL($sOQLPortalUser);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
IssueLog::Error('combodo-users-quota-slave/GetUsersInQuota : '.$e->getMessage(), 'combodo-users-quota');
|
||||
throw new Exception(Dict::Format('Core:GetQuota:Error', Dict::S('Core:PortalUsers')));
|
||||
}
|
||||
|
||||
// TODO remove read only users
|
||||
return $oFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws CoreException
|
||||
* @throws MySQLException
|
||||
*/
|
||||
public function GetUsersFromFilter(DBObjectSearch|DBUnionSearch|null $oFilter, array $aOrderBy = [], array $aArgs = []): array
|
||||
{
|
||||
$aUsers = [];
|
||||
if (is_null($oFilter)) {
|
||||
return $aUsers;
|
||||
}
|
||||
$oSet = new DBObjectSet($oFilter, $aOrderBy, $aArgs);
|
||||
while ($oUser = $oSet->fetch()) {
|
||||
$aUsers[] = $oUser;
|
||||
}
|
||||
|
||||
return $aUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetAllUsers(bool $bAllData = true): DBUnionSearch|DBObjectSearch|DBSearch|null
|
||||
{
|
||||
$sOqlUser = 'SELECT User';
|
||||
|
||||
try {
|
||||
$oFilter = $bAllData ? DBObjectSearch::FromOQL_AllData($sOqlUser) : DBObjectSearch::FromOQL($sOqlUser);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
IssueLog::Error('combodo-users-quota-slave/GetUsersNotInQuota : '.$e->getMessage(), 'combodo-users-quota');
|
||||
throw new Exception(Dict::S('CombodoUserQuota:Error'));
|
||||
}
|
||||
|
||||
return $oFilter;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,6 +34,7 @@ use DBObject;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use DeleteException;
|
||||
use Dict;
|
||||
use MetaModel;
|
||||
use UserLocal;
|
||||
use UserRights;
|
||||
@@ -96,6 +97,127 @@ class UserRightsTest extends ItopDataTestCase
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aProfileIds
|
||||
* @param array $aShouldBeAllowedToSeeClass
|
||||
* @param array $aShouldBeAllowedToEditClass
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
* @dataProvider ReadOnlyProvider
|
||||
*/
|
||||
public function testReadOnlyUser(array $aProfileIds, array $aShouldBeAllowedToSeeClass, array $aShouldBeAllowedToEditClass): void
|
||||
{
|
||||
|
||||
$oUser = $this->GivenUserWithProfiles('test1', $aProfileIds);
|
||||
$oUser->DBInsert();
|
||||
$_SESSION = [];
|
||||
UserRights::Login($oUser->Get('login'));
|
||||
|
||||
$aClassesToTest = ['FunctionalCI', 'Ticket', 'ServiceFamily'];
|
||||
|
||||
foreach ($aClassesToTest as $sClass) {
|
||||
$bShouldBeAllowedToSee = in_array($sClass, $aShouldBeAllowedToSeeClass);
|
||||
$bIsAllowedReading = (bool)UserRights::IsActionAllowed($sClass, UR_ACTION_READ);
|
||||
|
||||
$this->assertSame(
|
||||
$bShouldBeAllowedToSee,
|
||||
$bIsAllowedReading,
|
||||
"User with profiles ".implode(',', $aProfileIds)." should ".($bShouldBeAllowedToSee ? "" : "NOT ")."be allowed to see class $sClass"
|
||||
);
|
||||
|
||||
$bShouldBeAllowedToEdit = in_array($sClass, $aShouldBeAllowedToEditClass);
|
||||
|
||||
$bIsAllowedEditing = (bool)UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY);
|
||||
|
||||
$this->assertSame($bIsAllowedEditing, $bShouldBeAllowedToEdit,
|
||||
"User with profiles ".implode(',', $aProfileIds)." should ".($bShouldBeAllowedToEdit ? "" : "NOT ")."be allowed to edit class $sClass"
|
||||
);
|
||||
}
|
||||
}
|
||||
protected function ReadOnlyProvider() : array {
|
||||
return [
|
||||
'CI' => [
|
||||
'ProfilesId' => [
|
||||
5500,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'FunctionalCI',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => []
|
||||
],
|
||||
'Tickets' => [
|
||||
'ProfilesId' => [
|
||||
5501,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'Ticket',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => []
|
||||
],
|
||||
'Catalog' => [
|
||||
'ProfilesId' => [
|
||||
5502,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'ServiceFamily',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => []
|
||||
],
|
||||
'CI and Tickets' => [
|
||||
'ProfilesId' => [
|
||||
5500, 5501,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'FunctionalCI', 'Ticket',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => []
|
||||
],
|
||||
'CI and Catalog' => [
|
||||
'ProfilesId' => [
|
||||
5500, 5502,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'FunctionalCI', 'ServiceFamily',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => []
|
||||
],
|
||||
'Tickets and Catalog' => [
|
||||
'ProfilesId' => [
|
||||
5501, 5502,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'Ticket', 'ServiceFamily',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => []
|
||||
],
|
||||
'Tickets and Catalog + profile Ccnfiguration Manager' => [
|
||||
'ProfilesId' => [
|
||||
5501, 5502, 3
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'FunctionalCI', 'Ticket', 'ServiceFamily',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => ['FunctionalCI']
|
||||
],
|
||||
'CI, Tickets and Catalog' => [
|
||||
'ProfilesId' => [
|
||||
5500, 5501, 5502,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'FunctionalCI', 'Ticket', 'ServiceFamily',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => []
|
||||
],
|
||||
];
|
||||
}
|
||||
public function testIsLoggedIn()
|
||||
{
|
||||
$this->assertFalse(UserRights::IsLoggedIn());
|
||||
@@ -433,7 +555,7 @@ class UserRightsTest extends ItopDataTestCase
|
||||
$oUser = $this->GivenUserWithProfiles('test1', [$iProfileId, 2]);
|
||||
|
||||
$this->expectException(CoreCannotSaveObjectException::class);
|
||||
$this->expectExceptionMessage('Profile "Portal user" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)');
|
||||
$this->expectExceptionMessage(Dict::Format('Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice', PORTAL_PROFILE_NAME));
|
||||
$oUser->DBInsert();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace Users;
|
||||
|
||||
use CMDBObjectSet;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use Combodo\iTop\Users\ITopUserQuotaRepository;
|
||||
use DBObjectSearch;
|
||||
use MetaModel;
|
||||
use User;
|
||||
|
||||
class ITopUserQuotaRepositoryTest extends ItopDataTestCase{
|
||||
|
||||
private static bool $bDatasetInitialized = false;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (self::$bDatasetInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->createUsersQuotaDataset();
|
||||
self::$bDatasetInitialized = true;
|
||||
}
|
||||
|
||||
|
||||
/** * Creates a deterministic dataset for quota tests. * Users are created only once (idempotent on login). */
|
||||
private function createUsersQuotaDataset(): void
|
||||
{
|
||||
// Keep names unique and easy to clean up later if needed.
|
||||
$sPrefix = 'quota_test_';
|
||||
|
||||
// Create one user per quota "kind".
|
||||
// NOTE: profile names can vary by iTop distribution; we try common ones.
|
||||
$this->createUserIfMissing($sPrefix.'console', true, ['Administrator', 'Configuration Administrator']);
|
||||
$this->createUserIfMissing($sPrefix.'portal', true, ['Portal user', 'Portal User']);
|
||||
$this->createUserIfMissing($sPrefix.'readonly', true, ['ReadOnlyCI']);
|
||||
$this->createUserIfMissing($sPrefix.'application', true, ['Service Desk Agent', 'Change Manager', 'Administrator']);
|
||||
$this->createUserIfMissing($sPrefix.'disabled', false, ['Service Desk Agent', 'Administrator']);
|
||||
$this->createUserIfMissing($sPrefix.'disabled', false, ['Service Desk Agent', 'Administrator']);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private function createUserIfMissing(string $sLogin, bool $bEnabled, array $aCandidateProfileNames): void
|
||||
{
|
||||
if ($this->findUserByLogin($sLogin) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$iProfileId = $this->findFirstProfileIdByNames($aCandidateProfileNames);
|
||||
$this->assertNotNull(
|
||||
$iProfileId,
|
||||
sprintf('Could not find any profile among: %s', implode(', ', $aCandidateProfileNames))
|
||||
);
|
||||
|
||||
$oOrg = MetaModel::NewObject('Organization');
|
||||
$oOrg->Set('name', 'Quota Test Org');
|
||||
$oOrg->DBInsert();
|
||||
|
||||
$oPerson = MetaModel::NewObject('Person');
|
||||
$oPerson->Set('name', strtoupper($sLogin));
|
||||
$oPerson->Set('first_name', 'Quota');
|
||||
$oPerson->Set('org_id', $oOrg->GetKey());
|
||||
$oPerson->Set('email', $sLogin.'@example.invalid');
|
||||
$oPerson->DBInsert();
|
||||
|
||||
$oUser = MetaModel::NewObject('UserLocal');
|
||||
$oUser->Set('login', $sLogin);
|
||||
$oUser->Set('password', 'QuotaTest#123');
|
||||
$oUser->Set('contactid', $oPerson->GetKey());
|
||||
$oUser->Set('status', $bEnabled ? 'enabled' : 'disabled');
|
||||
|
||||
$oProfileList = $oUser->Get('profile_list');
|
||||
$oLink = MetaModel::NewObject('URP_UserProfile');
|
||||
$oLink->Set('profileid', $iProfileId);
|
||||
$oProfileList->AddItem($oLink);
|
||||
$oUser->Set('profile_list', $oProfileList);
|
||||
|
||||
$oUser->DBInsert();
|
||||
}
|
||||
|
||||
private function findFirstProfileIdByNames(array $aProfileNames): ?int
|
||||
{
|
||||
foreach ($aProfileNames as $sProfileName) {
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT URP_Profiles WHERE name = :name');
|
||||
$oSet = new CMDBObjectSet($oSearch, [], ['name' => $sProfileName]);
|
||||
$oProfile = $oSet->Fetch();
|
||||
if ($oProfile !== false && $oProfile !== null) {
|
||||
return (int) $oProfile->GetKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function findUserByLogin(string $sLogin): ?User
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT User WHERE login = :login');
|
||||
$oSet = new CMDBObjectSet($oSearch, [], ['login' => $sLogin]);
|
||||
$oUser = $oSet->Fetch();
|
||||
return ($oUser instanceof User) ? $oUser : null;
|
||||
}
|
||||
|
||||
|
||||
public function testNotDuplicateInDifferentQuotas(): void
|
||||
{
|
||||
$oITopUserRepository = new ITopUserQuotaRepository();
|
||||
|
||||
$aQuotaUsers = [
|
||||
'console' => $oITopUserRepository->GetUsersFromFilter($oITopUserRepository->GetConsoleUsers()),
|
||||
'portal' => $oITopUserRepository->GetUsersFromFilter($oITopUserRepository->GetPortalUsers()),
|
||||
'disabled' => $oITopUserRepository->GetUsersFromFilter($oITopUserRepository->GetDisabledUsers()),
|
||||
'readonly' => $oITopUserRepository->GetReadOnlyUsers(),
|
||||
'application' => $oITopUserRepository->GetUsersFromFilter($oITopUserRepository->GetApplicationUsers()),
|
||||
];
|
||||
|
||||
$aUserToQuotas = [];
|
||||
foreach ($aQuotaUsers as $sQuota => $aUsers) {
|
||||
foreach ($aUsers as $oUser) {
|
||||
$sUserId = (string) $oUser->GetKey();
|
||||
$aUserToQuotas[$sUserId][$sQuota] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$aDuplicates = [];
|
||||
foreach ($aUserToQuotas as $sUserId => $aQuotas) {
|
||||
$aQuotaNames = array_keys($aQuotas);
|
||||
if (count($aQuotaNames) > 1) {
|
||||
sort($aQuotaNames);
|
||||
$aDuplicates[] = sprintf('User #%s appears in: %s', $sUserId, implode(', ', $aQuotaNames));
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertEmpty(
|
||||
$aDuplicates,
|
||||
"Some users are counted in multiple quotas:\n- ".implode("\n- ", $aDuplicates)
|
||||
);
|
||||
}
|
||||
|
||||
public function testAllUsersAreInQuota () {
|
||||
$oITopUserRepository = new ITopUserQuotaRepository();
|
||||
|
||||
$oConsoleUsersFilter = $oITopUserRepository->GetConsoleUsers();
|
||||
$aConsoleUsers = $oITopUserRepository->GetUsersFromFilter($oConsoleUsersFilter);
|
||||
$oPortalUsersFilter = $oITopUserRepository->GetPortalUsers();
|
||||
$aPortalUsers = $oITopUserRepository->GetUsersFromFilter($oPortalUsersFilter);
|
||||
$oDisabledUsersFilter = $oITopUserRepository->GetDisabledUsers();
|
||||
$aDisabledUsers = $oITopUserRepository->GetUsersFromFilter($oDisabledUsersFilter);
|
||||
$aReadOnlyUsers = $oITopUserRepository->GetReadOnlyUsers();
|
||||
$oApplicationUsersFilter = $oITopUserRepository->GetApplicationUsers();
|
||||
$aApplicationUsers = $oITopUserRepository->GetUsersFromFilter($oApplicationUsersFilter);
|
||||
|
||||
$aAllUsersFromQuota = array_merge($aConsoleUsers, $aPortalUsers, $aDisabledUsers, $aReadOnlyUsers, $aApplicationUsers);
|
||||
|
||||
$oAllUsersFilter = $oITopUserRepository->GetAllUsers();
|
||||
$aAllUsersFromOQL = $oITopUserRepository->GetUsersFromFilter($oAllUsersFilter);
|
||||
|
||||
$this->assertEmpty(array_merge(array_diff($aAllUsersFromQuota, $aAllUsersFromOQL), array_diff($aAllUsersFromOQL, $aAllUsersFromQuota)));
|
||||
}
|
||||
|
||||
public function testAllUsersInQuotaAreUsersObjects ()
|
||||
{
|
||||
$oITopUserRepository = new ITopUserQuotaRepository();
|
||||
|
||||
$oConsoleUsersFilter = $oITopUserRepository->GetConsoleUsers();
|
||||
$aConsoleUsers = $oITopUserRepository->GetUsersFromFilter($oConsoleUsersFilter);
|
||||
$oPortalUsersFilter = $oITopUserRepository->GetPortalUsers();
|
||||
$aPortalUsers = $oITopUserRepository->GetUsersFromFilter($oPortalUsersFilter);
|
||||
$oDisabledUsersFilter = $oITopUserRepository->GetDisabledUsers();
|
||||
$aDisabledUsers = $oITopUserRepository->GetUsersFromFilter($oDisabledUsersFilter);
|
||||
$aReadOnlyUsers = $oITopUserRepository->GetReadOnlyUsers();
|
||||
$oApplicationUsersFilter = $oITopUserRepository->GetApplicationUsers();
|
||||
$aApplicationUsers = $oITopUserRepository->GetUsersFromFilter($oApplicationUsersFilter);
|
||||
|
||||
$aAllQuotaUsers = array_merge($aConsoleUsers, $aPortalUsers, $aDisabledUsers, $aReadOnlyUsers, $aApplicationUsers);
|
||||
|
||||
foreach ($aAllQuotaUsers as $oUser) {
|
||||
$this->assertInstanceOf(User::class, $oUser);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Core\Email\EMailSymfony;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\AlternativePart;
|
||||
use Symfony\Component\Mime\Part\Multipart\RelatedPart;
|
||||
use Symfony\Component\Mime\Part\TextPart;
|
||||
|
||||
class EmailSymfonyTest extends ItopTestCase
|
||||
{
|
||||
@@ -135,4 +141,221 @@ HTML;
|
||||
|
||||
$this->assertSame($sExpectedBody, $sActualBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parts of the AlternativePart produced by SetBody() for an HTML email.
|
||||
*
|
||||
* Handles both the simple case (AlternativePart at root) and the inline-images case
|
||||
* where the root is a RelatedPart whose first child is the AlternativePart.
|
||||
*
|
||||
* @return AbstractPart[]
|
||||
*/
|
||||
private function GetAlternativePartsFromHtmlEmail(EMailSymfony $oEmail): array
|
||||
{
|
||||
$oSymfonyMessage = $this->GetNonPublicProperty($oEmail, 'm_oMessage');
|
||||
$oBody = $oSymfonyMessage->getBody();
|
||||
|
||||
// With inline images the root is a RelatedPart; the AlternativePart is its first child.
|
||||
if ($oBody instanceof RelatedPart) {
|
||||
$oBody = $oBody->getParts()[0];
|
||||
}
|
||||
|
||||
$this->assertInstanceOf(AlternativePart::class, $oBody, 'Body should be a multipart/alternative for HTML emails');
|
||||
|
||||
return $oBody->getParts();
|
||||
}
|
||||
|
||||
/**
|
||||
* RFC 2046 §5.1.4: parts in multipart/alternative must be ordered from least to most preferred.
|
||||
* Email clients display the last part they support, so text/plain must come first and text/html last.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc2046.html#section-5.1.4
|
||||
* @covers \Combodo\iTop\Core\Email\EmailSymfony::SetBody()
|
||||
* @since N°9574
|
||||
*/
|
||||
public function testSetBodyAlternativePartOrderForHtmlEmailIsPlainThenHtml(): void
|
||||
{
|
||||
$oEmail = new EMailSymfony();
|
||||
$oEmail->SetBody('<p>Hello there!</p>', 'text/html');
|
||||
|
||||
[$oFirstPart, $oSecondPart] = $this->GetAlternativePartsFromHtmlEmail($oEmail);
|
||||
|
||||
$this->assertSame('plain', $oFirstPart->getMediaSubtype(), 'First part must be text/plain (least preferred per RFC 2046)');
|
||||
$this->assertSame('html', $oSecondPart->getMediaSubtype(), 'Last part must be text/html (most preferred per RFC 2046)');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideSetBodyPlainTextDoesNotContainCss
|
||||
*
|
||||
* @covers \Combodo\iTop\Core\Email\EmailSymfony::SetBody()
|
||||
* @since N°9574
|
||||
*/
|
||||
public function testSetBodyPlainTextDoesNotContainCss(string $sHtml, ?string $sCustomStyles): void
|
||||
{
|
||||
$oEmail = new EMailSymfony();
|
||||
$oEmail->SetBody($sHtml, 'text/html', $sCustomStyles);
|
||||
|
||||
// We locate the plain text part by subtype to be order-agnostic and isolate this assertion from the order bug.
|
||||
$aParts = $this->GetAlternativePartsFromHtmlEmail($oEmail);
|
||||
$oPlainPart = null;
|
||||
foreach ($aParts as $oPart) {
|
||||
if ($oPart instanceof TextPart && $oPart->getMediaSubtype() === 'plain') {
|
||||
$oPlainPart = $oPart;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertNotNull($oPlainPart, 'No text/plain part found in the message');
|
||||
|
||||
$sPlainText = $oPlainPart->getBody();
|
||||
|
||||
$this->assertStringNotContainsString('<style>', $sPlainText, 'Style tag must not appear in plain text');
|
||||
$this->assertStringNotContainsString('color:', $sPlainText, 'CSS color rule must not appear in plain text');
|
||||
$this->assertStringNotContainsString('font-size:', $sPlainText, 'CSS font-size rule must not appear in plain text');
|
||||
$this->assertStringNotContainsString('@media', $sPlainText, 'CSS @media rule must not appear in plain text');
|
||||
$this->assertStringContainsString('Hello there!', $sPlainText, 'Actual content must be preserved in plain text');
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML part must contain the body content and the CSS inlined by Emogrifier.
|
||||
* This guards against regressions where the wrong body (e.g. the plain-text version)
|
||||
* would end up in the HTML part.
|
||||
*
|
||||
* @covers \Combodo\iTop\Core\Email\EmailSymfony::SetBody()
|
||||
* @since N°9574
|
||||
*/
|
||||
public function testSetBodyHtmlPartContainsBodyAndInlinedCss(): void
|
||||
{
|
||||
$oEmail = new EMailSymfony();
|
||||
$oEmail->SetBody('<html><body><p>Hello there!</p></body></html>', 'text/html', 'p { color: red; }');
|
||||
|
||||
$aParts = $this->GetAlternativePartsFromHtmlEmail($oEmail);
|
||||
|
||||
$oHtmlPart = null;
|
||||
foreach ($aParts as $oPart) {
|
||||
if ($oPart instanceof TextPart && $oPart->getMediaSubtype() === 'html') {
|
||||
$oHtmlPart = $oPart;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertNotNull($oHtmlPart, 'No text/html part found in the message');
|
||||
|
||||
$sHtmlContent = $oHtmlPart->getBody();
|
||||
$this->assertStringContainsString('Hello there!', $sHtmlContent, 'HTML part must preserve the original text content');
|
||||
$this->assertStringContainsString('color: red', $sHtmlContent, 'HTML part must contain the CSS inlined by Emogrifier');
|
||||
}
|
||||
|
||||
/**
|
||||
* With inline images, SetBody() wraps the AlternativePart in a RelatedPart.
|
||||
* The AlternativePart must still be correctly ordered (plain first, HTML last)
|
||||
* and the plain-text part must not contain CSS.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc2046.html#section-5.1.4
|
||||
* @covers \Combodo\iTop\Core\Email\EmailSymfony::SetBody()
|
||||
* @since N°9574
|
||||
*/
|
||||
public function testSetBodyWithInlineImagesHasCorrectPartStructure(): void
|
||||
{
|
||||
// Anonymous subclass so we can inject a fake inline image part without a real inline image in DB
|
||||
$oEmail = new class () extends EMailSymfony {
|
||||
protected function EmbedInlineImages(string &$sBody): array
|
||||
{
|
||||
return [new DataPart('fake-image-data', 'image.png', 'image/png')];
|
||||
}
|
||||
};
|
||||
$oEmail->SetBody('<html><head><style>p { color: red; }</style></head><body><p>Hello there!</p></body></html>', 'text/html');
|
||||
|
||||
$oSymfonyMessage = $this->GetNonPublicProperty($oEmail, 'm_oMessage');
|
||||
$oBody = $oSymfonyMessage->getBody();
|
||||
|
||||
// Root must be a RelatedPart when inline images are present
|
||||
$this->assertInstanceOf(RelatedPart::class, $oBody, 'Root part must be multipart/related when inline images are present');
|
||||
|
||||
// The AlternativePart must be the first child of the RelatedPart
|
||||
$aRelatedParts = $oBody->getParts();
|
||||
$this->assertInstanceOf(AlternativePart::class, $aRelatedParts[0], 'First child of RelatedPart must be the AlternativePart');
|
||||
|
||||
// Order and CSS checks are delegated to the shared helper, which now handles RelatedPart
|
||||
[$oFirstPart, $oSecondPart] = $this->GetAlternativePartsFromHtmlEmail($oEmail);
|
||||
$this->assertSame('plain', $oFirstPart->getMediaSubtype(), 'First part must be text/plain (least preferred per RFC 2046)');
|
||||
$this->assertSame('html', $oSecondPart->getMediaSubtype(), 'Last part must be text/html (most preferred per RFC 2046)');
|
||||
}
|
||||
|
||||
public function provideSetBodyPlainTextDoesNotContainCss(): array
|
||||
{
|
||||
$sCustomStyles = 'p { color: blue; font-size: 14px; }';
|
||||
|
||||
return [
|
||||
'<style> tag in HTML, no custom styles' => [
|
||||
'<html><head><style>body { color: red; font-size: 12px; } @media print { p { color: black; } }</style></head><body><p>Hello there!</p></body></html>',
|
||||
null,
|
||||
],
|
||||
'<style> tag in HTML with custom styles' => [
|
||||
'<html><head><style>body { color: red; font-size: 12px; } @media print { p { color: black; } }</style></head><body><p>Hello there!</p></body></html>',
|
||||
$sCustomStyles,
|
||||
],
|
||||
'custom styles only, no <style> tag' => [
|
||||
'<html><body><p>Hello there!</p></body></html>',
|
||||
$sCustomStyles,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideCreateSmtpTransportSslOptions
|
||||
*/
|
||||
public function testCreateSmtpTransportSslOptions(string $sDsn, bool $bVerifyPeer, array $aExpectedSslOptions): void
|
||||
{
|
||||
$oEmail = new EMailSymfony();
|
||||
/** @var EsmtpTransport $oTransport */
|
||||
$oTransport = $this->InvokeNonPublicMethod(EMailSymfony::class, 'CreateSmtpTransport', $oEmail, [$sDsn, $bVerifyPeer]);
|
||||
|
||||
$aActualSslOptions = $oTransport->getStream()->getStreamOptions()['ssl'] ?? [];
|
||||
|
||||
$this->assertSame($aExpectedSslOptions, $aActualSslOptions);
|
||||
}
|
||||
|
||||
public function provideCreateSmtpTransportSslOptions(): array
|
||||
{
|
||||
$aDisabledVerification = [
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
'allow_self_signed' => true,
|
||||
];
|
||||
|
||||
return [
|
||||
// Regression scenario (N°9584): STARTTLS starts the connection unencrypted, so the 'ssl' key
|
||||
// is absent from stream options at construction time. verify_peer=false must still be applied.
|
||||
'STARTTLS, verify_peer=false' => [
|
||||
'smtp://localhost:587?encryption=starttls',
|
||||
false,
|
||||
$aDisabledVerification,
|
||||
],
|
||||
'implicit TLS (smtps), verify_peer=false' => [
|
||||
'smtps://localhost:465',
|
||||
false,
|
||||
$aDisabledVerification,
|
||||
],
|
||||
'plain SMTP, verify_peer=false' => [
|
||||
'smtp://localhost:25',
|
||||
false,
|
||||
$aDisabledVerification,
|
||||
],
|
||||
// Default behavior: verify_peer=true must leave stream options untouched (empty).
|
||||
'STARTTLS, verify_peer=true (default)' => [
|
||||
'smtp://localhost:587?encryption=starttls',
|
||||
true,
|
||||
[],
|
||||
],
|
||||
'implicit TLS (smtps), verify_peer=true (default)' => [
|
||||
'smtps://localhost:465',
|
||||
true,
|
||||
[],
|
||||
],
|
||||
'plain SMTP, verify_peer=true (default)' => [
|
||||
'smtp://localhost:25',
|
||||
true,
|
||||
[],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user