mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-16 15:18:44 +02:00
Compare commits
3 Commits
feature/77
...
feature/85
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3000cc024 | ||
|
|
6e48b07e36 | ||
|
|
e042717fa4 |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -9,7 +9,7 @@ Any PRs not following the guidelines or with missing information will not be con
|
||||
## Base information
|
||||
| Question | Answer
|
||||
|---------------------------------------------------------------|--------
|
||||
| Related to a SourceForge thread / Another PR / Combodo ticket? | <!-- Put the URL -->
|
||||
| Related to a SourceForge thead / Another PR / Combodo ticket? | <!-- Put the URL -->
|
||||
| Type of change? | Bug fix / Enhancement / Translations
|
||||
|
||||
|
||||
|
||||
@@ -536,7 +536,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
// Cache
|
||||
$this->m_aObjectActionGrants = [];
|
||||
$this->m_aAdministrators = null;
|
||||
$this->aUsersProfilesList = [];
|
||||
}
|
||||
|
||||
public function LoadCache()
|
||||
|
||||
@@ -48,7 +48,7 @@ class AuditCategory extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeString("name", ["description" => "Short name for this category", "allowed_values" => null, "sql" => "name", "default_value" => "", "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("description", ["allowed_values" => null, "sql" => "description", "default_value" => "", "is_null_allowed" => true, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeOQL("definition_set", ["allowed_values" => null, "sql" => "definition_set", "default_value" => "", "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", ["linked_class" => "AuditRule", "ext_key_to_me" => "category_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => [], "edit_mode" => LINKSET_EDITMODE_INPLACE, "edit_when" => LINKSET_EDITWHEN_ALWAYS, "tracking_level" => LINKSET_TRACKING_ALL]));
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", ["linked_class" => "AuditRule", "ext_key_to_me" => "category_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => [], "edit_mode" => LINKSET_EDITMODE_INPLACE, "tracking_level" => LINKSET_TRACKING_ALL]));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("ok_error_tolerance", ["allowed_values" => null, "sql" => "ok_error_tolerance", "default_value" => 5, "is_null_allowed" => true, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("warning_error_tolerance", ["allowed_values" => null, "sql" => "warning_error_tolerance", "default_value" => 25, "is_null_allowed" => true, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect(
|
||||
|
||||
@@ -52,14 +52,13 @@ class AuditRule extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("valid_flag", ["allowed_values" => new ValueSetEnum('true,false'), "sql" => "valid_flag", "default_value" => "true", "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", ["allowed_values" => null, "sql" => "category_id", "targetclass" => "AuditCategory", "is_null_allowed" => false, "on_target_delete" => DEL_MANUAL, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", ["allowed_values" => null, "extkey_attcode" => 'category_id', "target_attcode" => "name"]));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", ["allowed_values" => null, "sql" => "contact_id", "targetclass" => "Contact", "is_null_allowed" => true, "on_target_delete" => DEL_MANUAL, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeHTML("process", ["allowed_values" => null, "sql" => "process", "default_value" => "", "is_null_allowed" => true, "depends_on" => []]));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', ['category_id', 'name', 'description', 'query', 'valid_flag', 'process', 'contact_id']); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', ['category_id', 'description', 'query']); // Attributes to be displayed for a list
|
||||
MetaModel::Init_SetZListItems('details', ['category_id', 'name', 'description', 'query', 'valid_flag']); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', ['category_id', 'description', 'valid_flag']); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', ['category_id', 'name', 'description', 'valid_flag', 'query']); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('default_search', ['name', 'description', 'category_id', 'contact_id', 'query']); // Criteria of the advanced search form
|
||||
MetaModel::Init_SetZListItems('default_search', ['name', 'description', 'category_id']); // Criteria of the advanced search form
|
||||
}
|
||||
|
||||
public static function GetShortcutActions($sFinalClass)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
@@ -10,4 +9,5 @@
|
||||
*/
|
||||
class CoreOqlException extends CoreException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
@@ -10,4 +9,5 @@
|
||||
*/
|
||||
class CoreOqlMultipleResultsForbiddenException extends CoreOqlException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -211,14 +211,6 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'allowed_login_types' => [
|
||||
'type' => 'string',
|
||||
'description' => 'List of login types allowed (separated by | ): form, external, basic, token',
|
||||
'default' => DEFAULT_ALLOWED_LOGIN_TYPES,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'app_icon_url' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Hyperlink to redirect the user when clicking on the application icon (in the main window, or login/logoff pages)',
|
||||
|
||||
@@ -71,9 +71,12 @@ class DesignDocument extends DOMDocument
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param string $source
|
||||
* @param int $options
|
||||
*
|
||||
* @return bool|\DOMDocument
|
||||
*/
|
||||
public function loadXML(string $source, int $options = 0): bool
|
||||
public function loadXML(string $source, int $options = 0): bool|DOMDocument
|
||||
{
|
||||
return parent::loadXML($source, $options | LIBXML_BIGLINES);
|
||||
}
|
||||
|
||||
@@ -404,14 +404,14 @@ abstract class User extends cmdbAbstractObject
|
||||
}
|
||||
|
||||
if (!in_array(ADMIN_PROFILE_NAME, $aProfiles)) {
|
||||
// Prevent a User to lose the right to modify Users
|
||||
// Check if the user is yet allowed to modify Users
|
||||
if (method_exists($oAddon, 'ResetCache')) {
|
||||
$aCurrentProfiles = Session::Get('profile_list');
|
||||
// Set the current profiles into a session variable (not yet in the database)
|
||||
Session::Set('profile_list', $aProfiles);
|
||||
|
||||
$oAddon->ResetCache();
|
||||
if (!$oAddon->IsActionAllowed($this, get_class($this), UR_ACTION_MODIFY, null)) {
|
||||
if (!$oAddon->IsActionAllowed($this, 'User', UR_ACTION_MODIFY, null)) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:CurrentProfilesHaveInsufficientRights');
|
||||
}
|
||||
$oAddon->ResetCache();
|
||||
@@ -422,19 +422,6 @@ abstract class User extends cmdbAbstractObject
|
||||
Session::Set('profile_list', $aCurrentProfiles);
|
||||
}
|
||||
}
|
||||
// Prevent an administrator to remove their own admin profile
|
||||
if (UserRights::IsAdministrator($this)) {
|
||||
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AdminProfileCannotBeRemovedBySelf');
|
||||
}
|
||||
}
|
||||
} elseif ($this->IsPrivilegedUser()) {
|
||||
// Prevent Privileged User to be saved with profiles denying the access to the backoffice
|
||||
$oSet->Rewind();
|
||||
while ($oUserProfile = $oSet->Fetch()) {
|
||||
$sProfile = $oUserProfile->Get('profile');
|
||||
if (in_array($sProfile, $aForbiddenProfiles)) {
|
||||
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice', $sProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -648,21 +635,6 @@ abstract class User extends cmdbAbstractObject
|
||||
}
|
||||
return UserRights::GetUserId() == $this->GetKey();
|
||||
}
|
||||
|
||||
private function IsPrivilegedUser(): bool
|
||||
{
|
||||
$aPrivilegedProfiles = ['Administrator' => '1', 'REST Services User' => '1024', 'SuperUser' => '117'];
|
||||
|
||||
$oSet = $this->Get('profile_list');
|
||||
$oSet->Rewind();
|
||||
while ($oUserProfile = $oSet->Fetch()) {
|
||||
$iProfile = $oUserProfile->Get('profileid');
|
||||
if (in_array($iProfile, $aPrivilegedProfiles)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -311,35 +311,29 @@ fieldset {
|
||||
}
|
||||
|
||||
.module-selection-body {
|
||||
overflow: auto;
|
||||
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, .06) !important;
|
||||
background-color: #F7FAFC;
|
||||
padding: 10px;
|
||||
.wiz-choice{
|
||||
&:checked ~ .description {
|
||||
#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked),
|
||||
#itop-ticket-mgmt-itil-enhanced-portal:not(:checked) {
|
||||
~ .description::after {
|
||||
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
|
||||
display: block;
|
||||
margin-top: 0.5em;
|
||||
font-weight: bold;
|
||||
color: $legacy-portal-removal-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(:checked) ~ label .setup-extension-tag.checked{
|
||||
display:none;
|
||||
}
|
||||
&:checked ~ label .setup-extension-tag.unchecked{
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
overflow: auto;
|
||||
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, .06) !important;
|
||||
background-color: #F7FAFC;
|
||||
padding: 10px;
|
||||
|
||||
.wiz-choice:checked ~ .description {
|
||||
#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked),
|
||||
#itop-ticket-mgmt-itil-enhanced-portal:not(:checked) {
|
||||
~ .description::after {
|
||||
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
|
||||
display: block;
|
||||
margin-top: 0.5em;
|
||||
font-weight: bold;
|
||||
color: $legacy-portal-removal-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
body {
|
||||
font-size: 1.17rem;
|
||||
font-family: "Raleway";
|
||||
@@ -601,35 +595,6 @@ body {
|
||||
color: $ibo-color-blue-700;
|
||||
font-size: $ibo-font-size-200;
|
||||
}
|
||||
.setup-extension--missing .setup-extension--icon{
|
||||
color:#a00000;
|
||||
}
|
||||
.setup-extension-tag {
|
||||
background-color: grey;
|
||||
border-radius: 8px;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
margin-right: 3px;
|
||||
&.installed{
|
||||
background-color:#9eff9e
|
||||
}
|
||||
&.notinstalled{
|
||||
background-color:#ed9eff
|
||||
}
|
||||
&.tobeinstalled{
|
||||
background-color:#9ef0ff
|
||||
}
|
||||
&.tobeuninstalled{
|
||||
background-color:#ff9e9e
|
||||
}
|
||||
&.notuninstallable{
|
||||
background-color:#ffc98c
|
||||
}
|
||||
&.removed{
|
||||
background-color: #969594
|
||||
}
|
||||
}
|
||||
|
||||
.setup--wizard-choice--label + .setup--wizard-choice--more-info {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -72,15 +72,6 @@
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</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>
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Service\Events\iEventServiceSetup;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
|
||||
class AttachmentPlugIn implements iApplicationUIExtension, iEventServiceSetup
|
||||
{
|
||||
@@ -238,14 +236,10 @@ class AttachmentPlugIn implements iApplicationUIExtension, iEventServiceSetup
|
||||
}
|
||||
$oAttachmentsRenderer = AttachmentsRendererFactory::GetInstance($oPage, $sObjClass, $iObjKey, $sTransactionId);
|
||||
|
||||
$iCount = $oAttachmentsRenderer->GetAttachmentsSet()->Count() + $oAttachmentsRenderer->GetTempAttachmentsSet()->Count();
|
||||
$sTitle = ($iCount > 0) ? Dict::Format('Attachments:TabTitle_Count', $iCount) : Dict::S('Attachments:EmptyTabTitle');
|
||||
if ($this->GetAttachmentsPosition() === 'relations') {
|
||||
$iCount = $oAttachmentsRenderer->GetAttachmentsSet()->Count() + $oAttachmentsRenderer->GetTempAttachmentsSet()->Count();
|
||||
$sTitle = ($iCount > 0) ? Dict::Format('Attachments:TabTitle_Count', $iCount) : Dict::S('Attachments:EmptyTabTitle');
|
||||
$oPage->SetCurrentTab('Attachments:Tab', $sTitle);
|
||||
} else {
|
||||
$oBlock = FieldSetUIBlockFactory::MakeStandard($sTitle);
|
||||
$oBlock->AddSubBlock(new Html(''));
|
||||
$oPage->AddUiBlock($oBlock);
|
||||
}
|
||||
|
||||
$bIsReadOnlyState = self::IsReadonlyState($oObject, $oObject->GetState(), AttachmentPlugIn::ENUM_GUI_BACKOFFICE);
|
||||
|
||||
@@ -67,17 +67,15 @@ class EventListener implements iEventServiceSetup
|
||||
/** @var \DBObject $oAttachment */
|
||||
$oAttachment = $oEventData->Get('object');
|
||||
$oHostObj = MetaModel::GetObject($oAttachment->Get('item_class'), $oAttachment->Get('item_id'), false /* false to avoid exception during trigger */, true);
|
||||
if ($oHostObj != null) {
|
||||
/** @var \ormDocument $oDocument */
|
||||
$oDocument = $oEventData->Get('document');
|
||||
/** @var \ormDocument $oDocument */
|
||||
$oDocument = $oEventData->Get('document');
|
||||
|
||||
$this->OnAttachmentActivateTriggers(
|
||||
$oHostObj,
|
||||
$oAttachment,
|
||||
$oDocument,
|
||||
TriggerOnAttachmentDownload::class
|
||||
);
|
||||
}
|
||||
$this->OnAttachmentActivateTriggers(
|
||||
$oHostObj,
|
||||
$oAttachment,
|
||||
$oDocument,
|
||||
TriggerOnAttachmentDownload::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,747 +0,0 @@
|
||||
<?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>cmdbAbstractObject</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>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
<attribute id="destination_id"/>
|
||||
<attribute id="org_id"/>
|
||||
<attribute id="source_id"/>
|
||||
<attribute id="flowtype_id"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition>status='inactive'</condition>
|
||||
</obsolescence>
|
||||
<fields_semantic>
|
||||
<state_attribute>status</state_attribute>
|
||||
</fields_semantic>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
<field id="org_id" xsi:type="AttributeExternalKey">
|
||||
<sql>org_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>Organization</target_class>
|
||||
<on_target_delete>DEL_MANUAL</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="source_id" xsi:type="AttributeExternalKey">
|
||||
<sql>source_id</sql>
|
||||
<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/>
|
||||
<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="description" xsi:type="AttributeHTML">
|
||||
<sql>description</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<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="business_criticity" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="high">
|
||||
<code>high</code>
|
||||
<rank>10</rank>
|
||||
</value>
|
||||
<value id="medium">
|
||||
<code>medium</code>
|
||||
<rank>20</rank>
|
||||
</value>
|
||||
<value id="low">
|
||||
<code>low</code>
|
||||
<rank>30</rank>
|
||||
</value>
|
||||
</values>
|
||||
<sql>business_criticity</sql>
|
||||
<default_value>low</default_value>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<display_style>list</display_style>
|
||||
</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>
|
||||
<field id="contacts_list" xsi:type="AttributeLinkedSetIndirect">
|
||||
<linked_class>lnkContactToDataFlow</linked_class>
|
||||
<ext_key_to_me>dataflow_id</ext_key_to_me>
|
||||
<count_min>0</count_min>
|
||||
<count_max>0</count_max>
|
||||
<ext_key_to_remote>contact_id</ext_key_to_remote>
|
||||
<duplicates/>
|
||||
</field>
|
||||
<field id="documents_list" xsi:type="AttributeLinkedSetIndirect">
|
||||
<linked_class>lnkDocumentToDataFlow</linked_class>
|
||||
<ext_key_to_me>dataflow_id</ext_key_to_me>
|
||||
<count_min>0</count_min>
|
||||
<count_max>0</count_max>
|
||||
<ext_key_to_remote>document_id</ext_key_to_remote>
|
||||
<duplicates/>
|
||||
</field>
|
||||
</fields>
|
||||
<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:DataFlow: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:DataFlow:otherinfo">
|
||||
<items>
|
||||
<item id="description">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
<rank>10</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="org_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="description">
|
||||
<rank>20</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>
|
||||
</neighbours>
|
||||
</relation>
|
||||
</relations>
|
||||
</class>
|
||||
<class id="lnkDocumentToDataFlow" _delta="define">
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<is_link>1</is_link>
|
||||
<category>bizmodel</category>
|
||||
<abstract>false</abstract>
|
||||
<key_type>autoincrement</key_type>
|
||||
<db_table>lnkdocumenttodataflow</db_table>
|
||||
<db_key_field>id</db_key_field>
|
||||
<db_final_class_field/>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="document_id_friendlyname"/>
|
||||
<attribute id="dataflow_id_friendlyname"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<style>
|
||||
<icon/>
|
||||
</style>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="dataflow_id"/>
|
||||
<attribute id="document_id"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<uniqueness_rules>
|
||||
<rule id="no_duplicate">
|
||||
<attributes>
|
||||
<attribute id="document_id"/>
|
||||
<attribute id="dataflow_id"/>
|
||||
</attributes>
|
||||
<filter><![CDATA[]]></filter>
|
||||
<disabled>false</disabled>
|
||||
<is_blocking>true</is_blocking>
|
||||
</rule>
|
||||
</uniqueness_rules>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="dataflow_id" xsi:type="AttributeExternalKey">
|
||||
<sql>dataflow_id</sql>
|
||||
<target_class>DataFlow</target_class>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
</field>
|
||||
<field id="document_id" xsi:type="AttributeExternalKey">
|
||||
<sql>document_id</sql>
|
||||
<target_class>Document</target_class>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="document_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="dataflow_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
<search>
|
||||
<items>
|
||||
<item id="dataflow_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="document_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</search>
|
||||
<list>
|
||||
<items>
|
||||
<item id="dataflow_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="document_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</list>
|
||||
</presentation>
|
||||
</class>
|
||||
<class id="lnkContactToDataFlow" _delta="define">
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<is_link>1</is_link>
|
||||
<category>bizmodel</category>
|
||||
<abstract>false</abstract>
|
||||
<key_type>autoincrement</key_type>
|
||||
<db_table>lnkcontacttodataflow</db_table>
|
||||
<db_key_field>id</db_key_field>
|
||||
<db_final_class_field/>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="contact_id_friendlyname"/>
|
||||
<attribute id="dataflow_id_friendlyname"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<style>
|
||||
<icon/>
|
||||
</style>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="dataflow_id"/>
|
||||
<attribute id="contact_id"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<uniqueness_rules>
|
||||
<rule id="no_duplicate">
|
||||
<attributes>
|
||||
<attribute id="contact_id"/>
|
||||
<attribute id="dataflow_id"/>
|
||||
</attributes>
|
||||
<filter><![CDATA[]]></filter>
|
||||
<disabled>false</disabled>
|
||||
<is_blocking>true</is_blocking>
|
||||
</rule>
|
||||
</uniqueness_rules>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="dataflow_id" xsi:type="AttributeExternalKey">
|
||||
<sql>dataflow_id</sql>
|
||||
<target_class>DataFlow</target_class>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
</field>
|
||||
<field id="contact_id" xsi:type="AttributeExternalKey">
|
||||
<sql>contact_id</sql>
|
||||
<target_class>Contact</target_class>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
<item id="contact_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="dataflow_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
<search>
|
||||
<items>
|
||||
<item id="dataflow_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="contact_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</search>
|
||||
<list>
|
||||
<items>
|
||||
<item id="dataflow_id">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="contact_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</list>
|
||||
</presentation>
|
||||
</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>
|
||||
</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>
|
||||
</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="must_exist">
|
||||
<dashlets>
|
||||
<dashlet id="DataFlow_20" xsi:type="DashletBadge" _delta="define">
|
||||
<rank>20</rank>
|
||||
<class>DataFlow</class>
|
||||
</dashlet>
|
||||
</dashlets>
|
||||
</cell>
|
||||
</cells>
|
||||
</definition>
|
||||
</menu>
|
||||
</menus>
|
||||
<user_rights>
|
||||
<groups>
|
||||
<group id="Configuration">
|
||||
<classes>
|
||||
<class id="DataFlow" _delta="define"/>
|
||||
<class id="DataFlowType" _delta="define"/>
|
||||
</classes>
|
||||
</group>
|
||||
</groups>
|
||||
<profiles>
|
||||
</profiles>
|
||||
</user_rights>
|
||||
</itop_design>
|
||||
@@ -1,96 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Module combodo-flow-map
|
||||
*
|
||||
* @copyright Copyright (C) 2013 XXXXX
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
Dict::Add('EN US', 'English', 'English', [
|
||||
|
||||
'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:baseinfo' => 'General information',
|
||||
'DataFlow:otherinfo' => 'Other information',
|
||||
'DataFlow:moreinfo' => 'Flow specifics',
|
||||
|
||||
'Class:DataFlow' => 'Flow',
|
||||
'Class:DataFlow+' => 'For application flow for example',
|
||||
'Class:DataFlow/Name' => '%1$s',
|
||||
'Class:DataFlow/Attribute:name' => 'Name',
|
||||
'Class:DataFlow/Attribute:name_id+' => 'Data that are transferred',
|
||||
'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:description' => 'Description',
|
||||
'Class:DataFlow/Attribute:description+' => '',
|
||||
'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:org_id' => 'Organization',
|
||||
'Class:DataFlow/Attribute:org_id+' => '',
|
||||
'Class:DataFlow/Attribute:business_criticity' => 'Business criticality',
|
||||
'Class:DataFlow/Attribute:business_criticity+' => '',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:high' => 'high',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:high+' => '',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:low' => 'low',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:low+' => '',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:medium' => 'medium',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:medium+' => '',
|
||||
'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' => 'Documents',
|
||||
'Class:DataFlow/Attribute:documents_list+' => 'Eg: technical specifications, runbooks, etc.',
|
||||
'Class:DataFlow/Attribute:contacts_list' => 'Contacts',
|
||||
'Class:DataFlow/Attribute:contacts_list+' => 'Eg: flow owner, technical support, etc.',
|
||||
|
||||
/*
|
||||
'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',
|
||||
*/
|
||||
]);
|
||||
@@ -1,96 +0,0 @@
|
||||
<?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', [
|
||||
|
||||
'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:baseinfo' => 'Informations générales',
|
||||
'DataFlow:otherinfo' => 'Autres informations',
|
||||
'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',
|
||||
'Class:DataFlow/Name' => '%1$s',
|
||||
'Class:DataFlow/Attribute:name' => 'Nom',
|
||||
'Class:DataFlow/Attribute:name_id+' => 'Type de données transferé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:description' => 'Description',
|
||||
'Class:DataFlow/Attribute:description+' => '',
|
||||
'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:org_id' => 'Organisation',
|
||||
'Class:DataFlow/Attribute:org_id+' => '',
|
||||
'Class:DataFlow/Attribute:business_criticity' => 'Criticité',
|
||||
'Class:DataFlow/Attribute:business_criticity+' => '',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:high' => 'haute',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:high+' => '',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:low' => 'basse',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:low+' => '',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:medium' => 'moyenne',
|
||||
'Class:DataFlow/Attribute:business_criticity/Value:medium+' => '',
|
||||
'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' => 'Documents',
|
||||
'Class:DataFlow/Attribute:documents_list+' => 'Eg: technical specifications, runbooks, etc.',
|
||||
'Class:DataFlow/Attribute:contacts_list' => 'Contacts',
|
||||
'Class:DataFlow/Attribute:contacts_list+' => 'Eg: flow owner, technical support, etc.',
|
||||
|
||||
/*
|
||||
'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',
|
||||
*/
|
||||
]);
|
||||
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,50 +0,0 @@
|
||||
<?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' => 'Map 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
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
* @param string $sEnvironment
|
||||
@@ -10,18 +9,21 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
public function __construct($sEnvironment = 'production', $bAutoCommit = true)
|
||||
{
|
||||
parent::__construct($sEnvironment, $bAutoCommit);
|
||||
|
||||
if ($sEnvironment != $this->sTargetEnv) {
|
||||
if (is_dir(APPROOT.'/env-'.$this->sTargetEnv)) {
|
||||
SetupUtils::rrmdir(APPROOT.'/env-'.$this->sTargetEnv);
|
||||
}
|
||||
if (is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) {
|
||||
SetupUtils::rrmdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules');
|
||||
}
|
||||
|
||||
if ($sEnvironment != $this->sTargetEnv)
|
||||
{
|
||||
if (is_dir(APPROOT.'/env-'.$this->sTargetEnv))
|
||||
{
|
||||
SetupUtils::rrmdir(APPROOT.'/env-'.$this->sTargetEnv);
|
||||
}
|
||||
if (is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules'))
|
||||
{
|
||||
SetupUtils::rrmdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules');
|
||||
}
|
||||
SetupUtils::copydir(APPROOT.'/data/'.$sEnvironment.'-modules', APPROOT.'/data/'.$this->sTargetEnv.'-modules');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the includes for the target environment
|
||||
* @param Config $oConfig
|
||||
@@ -30,7 +32,7 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
$oConfig->UpdateIncludes('env-'.$this->sTargetEnv); // TargetEnv != FinalEnv
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move an extension (path to folder of this extension) to the target environment
|
||||
* @param string $sExtensionDirectory The folder of the extension
|
||||
@@ -38,23 +40,21 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
*/
|
||||
public function MoveExtension($sExtensionDirectory)
|
||||
{
|
||||
if (!is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) {
|
||||
if (!mkdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) {
|
||||
throw new Exception("ERROR: failed to create directory:'".(APPROOT.'/data/'.$this->sTargetEnv.'-modules')."'");
|
||||
}
|
||||
if (!is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules'))
|
||||
{
|
||||
if (!mkdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) throw new Exception("ERROR: failed to create directory:'".(APPROOT.'/data/'.$this->sTargetEnv.'-modules')."'");
|
||||
}
|
||||
$sDestinationPath = APPROOT.'/data/'.$this->sTargetEnv.'-modules/';
|
||||
|
||||
|
||||
// Make sure that the destination directory of the extension does not already exist
|
||||
if (is_dir($sDestinationPath.basename($sExtensionDirectory))) {
|
||||
// Cleanup before moving...
|
||||
SetupUtils::rrmdir($sDestinationPath.basename($sExtensionDirectory));
|
||||
}
|
||||
if (!rename($sExtensionDirectory, $sDestinationPath.basename($sExtensionDirectory))) {
|
||||
throw new Exception("ERROR: failed move directory:'$sExtensionDirectory' to '".$sDestinationPath.basename($sExtensionDirectory)."'");
|
||||
if (is_dir($sDestinationPath.basename($sExtensionDirectory)))
|
||||
{
|
||||
// Cleanup before moving...
|
||||
SetupUtils::rrmdir($sDestinationPath.basename($sExtensionDirectory));
|
||||
}
|
||||
if (!rename($sExtensionDirectory, $sDestinationPath.basename($sExtensionDirectory))) throw new Exception("ERROR: failed move directory:'$sExtensionDirectory' to '".$sDestinationPath.basename($sExtensionDirectory)."'");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move the selected extensions located in the given directory in data/<target-env>-modules
|
||||
* @param string $sDownloadedExtensionsDir The directory to scan
|
||||
@@ -63,8 +63,10 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
*/
|
||||
public function MoveSelectedExtensions($sDownloadedExtensionsDir, $aSelectedExtensionDirs)
|
||||
{
|
||||
foreach (glob($sDownloadedExtensionsDir.'*', GLOB_ONLYDIR) as $sExtensionDir) {
|
||||
if (in_array(basename($sExtensionDir), $aSelectedExtensionDirs)) {
|
||||
foreach(glob($sDownloadedExtensionsDir.'*', GLOB_ONLYDIR) as $sExtensionDir)
|
||||
{
|
||||
if (in_array(basename($sExtensionDir), $aSelectedExtensionDirs))
|
||||
{
|
||||
$this->MoveExtension($sExtensionDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ function DisplayStatus(WebPage $oPage)
|
||||
if (is_dir($sPath)) {
|
||||
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
|
||||
}
|
||||
$oExtensionsMap = new iTopExtensionsMap('production', $aExtraDirs);
|
||||
$oExtensionsMap = new iTopExtensionsMap('production', true, $aExtraDirs);
|
||||
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
|
||||
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
@@ -154,7 +154,7 @@ function DoInstall(WebPage $oPage)
|
||||
if (is_dir($sPath)) {
|
||||
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
|
||||
}
|
||||
$oExtensionsMap = new iTopExtensionsMap('production', $aExtraDirs);
|
||||
$oExtensionsMap = new iTopExtensionsMap('production', true, $aExtraDirs);
|
||||
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
|
||||
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
|
||||
@@ -17,7 +17,6 @@ SetupWebPage::AddModule(
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-welcome-itil/3.1.0,',
|
||||
'itop-profiles-itil/3.1.0', //SuperUser id 117
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
|
||||
@@ -28,7 +28,6 @@ SetupWebPage::AddModule(
|
||||
'category' => 'Portal',
|
||||
// Setup
|
||||
'dependencies' => [
|
||||
'itop-attachments/3.2.1', //CMDBChangeOpAttachmentRemoved
|
||||
],
|
||||
'mandatory' => true,
|
||||
'visible' => false,
|
||||
|
||||
@@ -13,7 +13,6 @@ SetupWebPage::AddModule(
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-structure/2.7.1',
|
||||
'itop-portal/3.0.0', // module_design_itop_design->module_designs->itop-portal
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
|
||||
@@ -40,8 +40,6 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
'Class:AuditRule/Attribute:name+' => 'Krátký název pro toto pravidlo',
|
||||
'Class:AuditRule/Attribute:description' => 'Popis pravidla',
|
||||
'Class:AuditRule/Attribute:description+' => 'Dlouhý popis tohoto pravidla auditu',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Dotaz ke spuštění',
|
||||
'Class:AuditRule/Attribute:query+' => 'OQL výraz ke spuštění',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Interpretace',
|
||||
@@ -54,9 +52,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'Kategorie pro toto pravidlo',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Kategorie',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Název kategorie pro toto pravidlo',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
]);
|
||||
|
||||
//
|
||||
// Class: AuditDomain
|
||||
@@ -168,11 +164,9 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Neaktivní',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Uživatelské jméno musí být jedinečné - "%1s" je již použito.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Uživateli musí být přidělen alespoň jeden profil.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Profil "%1$s" nemůže být přidán, byl by mu odepřen přístup do backoffice',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Změna není povolena pro vašeho vlastního uživatele',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Přístupné organizace musí obsahovat organizaci uživatele.',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Aktuální seznam profilů neposkytuje dostatečná přístupová práva (uživatele již nelze upravovat)',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Profil Portal power user neposkytuje dostatečná přístupová práva (je třeba přidat jiný profil)',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Uživatel musí být přiřazen minimálně do jedné organizace.',
|
||||
|
||||
@@ -40,8 +40,6 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
'Class:AuditRule/Attribute:name+' => 'Kort navn for denne regel',
|
||||
'Class:AuditRule/Attribute:description' => 'Audit-regel beskrivelse',
|
||||
'Class:AuditRule/Attribute:description+' => 'Udførlig beskrivelse af denne Audit-regel',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Søgning at udføre',
|
||||
'Class:AuditRule/Attribute:query+' => 'Den OQL forespørgsel, der skal udføres',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Gyldige objekter?',
|
||||
@@ -54,8 +52,6 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'Kategori for denne regel',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Kategori',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Kategorinavn for denne regel',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -168,12 +164,10 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Disabled~~',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Login skal være entydig - "%1s" er allerede i brug.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Mindst en profil skal knyttes til denne bruger.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.~~',
|
||||
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.~~',
|
||||
|
||||
@@ -40,8 +40,6 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:AuditRule/Attribute:name+' => 'Kurzname für diese Regel',
|
||||
'Class:AuditRule/Attribute:description' => 'Beschreibung der Audit-Regel',
|
||||
'Class:AuditRule/Attribute:description+' => 'Ausführliche Beschreibung dieser Audit-Regel',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Durchzuführende Abfrage',
|
||||
'Class:AuditRule/Attribute:query+' => 'Die auszuführende OQL-Abfrage',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Gültiges Objekt?',
|
||||
@@ -54,8 +52,6 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'Kategorie für diese Regel',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Kategorie',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Kategoriename für diese Regel',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -167,11 +163,9 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Inaktiv',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Login-Namen müssen unterschiedlich sein - "%1s" benutzt diesen Login-Name bereits.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Mindestens ein Profil muss diesem Benutzer zugewiesen sein.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Profil "%1$s" kann nicht hinzugefügt werde, es verhindert den Zugriff auf das Backoffice.',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Statusänderungen sind für den eigenen Benutzer nicht erlaubt.',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Die Organisation des Benutzers muss in den erlaubten Organisationen enthalten sein.',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Die aktuelle Liste an Profilen vergibt unzureichende Berechtigungen (Benutzer können nicht mehr geändert werden)',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Das Profil des Portal-Power-Benutzers hat nicht ausreichend Zugriffsrechte (ein weiteres Profil muss hinzugefügt werden)',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Mindestens eine Organisation muss diesem Benutzer zugewiesen sein.',
|
||||
|
||||
@@ -50,9 +50,7 @@ It is applied on the scope of objects defined by the audit category',
|
||||
'Class:AuditRule/Attribute:name' => 'Rule name',
|
||||
'Class:AuditRule/Attribute:name+' => 'Short name for this rule',
|
||||
'Class:AuditRule/Attribute:description' => 'Description',
|
||||
'Class:AuditRule/Attribute:description+' => 'What is checked?',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...',
|
||||
'Class:AuditRule/Attribute:description+' => 'What is checked? How should it be fixed? Who should do it? ...',
|
||||
'Class:AuditRule/Attribute:query' => 'Query to run',
|
||||
'Class:AuditRule/Attribute:query+' => 'The OQL expression to run. Returned classes must be aligned with those of the category\'s scope',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Returned objects: ',
|
||||
@@ -67,8 +65,6 @@ It is applied on the scope of objects defined by the audit category',
|
||||
'Class:AuditRule/Attribute:category_id+' => 'The category of this rule',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Category name',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Name of the category of this rule',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -177,17 +173,15 @@ Dict::Add('EN US', 'English', 'English', [
|
||||
'Class:User/Attribute:allowed_org_list+' => 'The end user is allowed to see data belonging to the following organizations. If no organization is specified, there is no restriction.',
|
||||
'Class:User/Attribute:status' => 'Status',
|
||||
'Class:User/Attribute:status+' => 'Whether the user account is enabled or disabled.',
|
||||
'Class:User/Attribute:status/Value:enabled' => 'Enabled',
|
||||
'Class:User/Attribute:status/Value:enabled' => 'Enabled',
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Disabled',
|
||||
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Login must be unique - "%1$s" is already being used.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'At least one profile must be assigned to this user.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.',
|
||||
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.',
|
||||
|
||||
@@ -50,9 +50,7 @@ It is applied on the scope of objects defined by the audit category',
|
||||
'Class:AuditRule/Attribute:name' => 'Rule name',
|
||||
'Class:AuditRule/Attribute:name+' => 'Short name for this rule',
|
||||
'Class:AuditRule/Attribute:description' => 'Description',
|
||||
'Class:AuditRule/Attribute:description+' => 'What is checked?~~',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:description+' => 'What is checked? How should it be fixed? Who should do it? ...',
|
||||
'Class:AuditRule/Attribute:query' => 'Query to run',
|
||||
'Class:AuditRule/Attribute:query+' => 'The OQL expression to run. Returned classes must be aligned with those of the category\'s scope',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Returned objects: ',
|
||||
@@ -67,8 +65,6 @@ It is applied on the scope of objects defined by the audit category',
|
||||
'Class:AuditRule/Attribute:category_id+' => 'The category of this rule',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Category name',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Name of the category of this rule',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -182,11 +178,9 @@ Dict::Add('EN GB', 'British English', 'British English', [
|
||||
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Login must be unique - "%1$s" is already being used.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'At least one profile must be assigned to this user.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added as it will deny access to the back office.',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organisations must contain User organisation',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable any more)',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organisation must be assigned to this user.',
|
||||
|
||||
@@ -38,8 +38,6 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'Class:AuditRule/Attribute:name+' => 'Nombre corto para esta regla',
|
||||
'Class:AuditRule/Attribute:description' => 'Descripción de regla de auditoría',
|
||||
'Class:AuditRule/Attribute:description+' => 'Descripción larga para esta regla de auditoría',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Consulta a Ejecutar',
|
||||
'Class:AuditRule/Attribute:query+' => 'Expresión OQL a ejecutar',
|
||||
'Class:AuditRule/Attribute:valid_flag' => '¿Objetos Válidos?',
|
||||
@@ -52,8 +50,6 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'La categoría para esta regla',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Categoría',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Nombre de la categoría para esta regla',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -166,11 +162,9 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Deshabilitado',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Usuario debe ser único - "%1s" ya se encuentra en uso.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Al menos un Perfil debe ser asignado a este usuario.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'No se puede agregar el perfil "%1$s"; denegará el acceso al backoffice',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Cambiar estatus no está permitido para su propio usuario',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Las organizaciones permitidas deben contener una organización de usuario',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'La lista actual de perfiles no otorga suficientes permisos de acceso (los usuarios ya no son modificables)',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'El perfil de usuario avanzado del Portal no otorga suficientes derechos de acceso (se debe agregar otro perfil)',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Al menos una organización debe ser asignada a este usuario.',
|
||||
|
||||
@@ -41,9 +41,7 @@ Elle s\'applique à tous les objets dans le périmètre de sa catégorie d\'audi
|
||||
'Class:AuditRule/Attribute:name' => 'Nom',
|
||||
'Class:AuditRule/Attribute:name+' => 'Une vérification particulière',
|
||||
'Class:AuditRule/Attribute:description' => 'Description',
|
||||
'Class:AuditRule/Attribute:description+' => 'Qu\'est ce qu\'on vérifie ?',
|
||||
'Class:AuditRule/Attribute:process' => 'Processus de correction',
|
||||
'Class:AuditRule/Attribute:process+' => 'Comment le corriger ? Qui doit le faire ? ...',
|
||||
'Class:AuditRule/Attribute:description+' => 'Qu\'est ce qu\'on vérifie ? Comment le corriger ? Qui doit le faire ? ...',
|
||||
'Class:AuditRule/Attribute:query' => 'Requête',
|
||||
'Class:AuditRule/Attribute:query+' => 'Requête OQL à executer. Les classes retournées doivent être cohérentes avec celles définies dans le périmètre de la catégorie',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Objets retournés :',
|
||||
@@ -58,8 +56,6 @@ Elle s\'applique à tous les objets dans le périmètre de sa catégorie d\'audi
|
||||
'Class:AuditRule/Attribute:category_id+' => '',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Nom de la catégorie',
|
||||
'Class:AuditRule/Attribute:category_name+' => '',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Responsable',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Personne ou équipe responsable de la correction des erreurs détectées par cette règle',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -168,17 +164,15 @@ Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Class:User/Attribute:allowed_org_list' => 'Organisations permises',
|
||||
'Class:User/Attribute:allowed_org_list+' => 'L\'utilisateur a le droit de voir les données des organisations listées ici. Si aucune organisation n\'est spécifiée, alors aucune restriction ne s\'applique.',
|
||||
'Class:User/Attribute:status' => 'Etat',
|
||||
'Class:User/Attribute:status+' => 'Est-ce que ce compte utilisateur est actif, ou non ?',
|
||||
'Class:User/Attribute:status+' => 'Est-ce que ce compte utilisateur est actif, ou non?',
|
||||
'Class:User/Attribute:status/Value:enabled' => 'Actif',
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Désactivé',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Le login doit être unique - "%1s" est déjà utilisé.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'L\'utilisateur doit avoir au moins un profil.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Le profil "%1$s" ne peut pas être donné aux Administrateurs, SuperUsers et REST Services Users',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Le profil "%1$s" ne peux pas être ajouté à son propre utilisateur, il interdit l\'accès à la console',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Impossible de changer l\'état de son propre utilisateur',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Les organisations permises doivent contenir l\'organisation de l\'utilisateur',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'Vous ne pouvez pas supprimer votre propre profil Administrateur. Demandez à un autre Administrateur de le faire pour vous',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Vous ne pouvez pas supprimer vos propres droits de modification des utilisateurs.',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Les profils existants ne permettent pas de modifier les utilisateurs',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Le profil Portal power user ne donne pas suffisamment de droits à l\'utilisateur (un autre profil doit être ajouté)',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'L\'utilisateur doit avoir au moins une organisation.',
|
||||
'Class:User/Error:OrganizationNotAllowed' => 'Organisation non autorisée.',
|
||||
|
||||
@@ -40,8 +40,6 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'Class:AuditRule/Attribute:name+' => '',
|
||||
'Class:AuditRule/Attribute:description' => 'Leírás',
|
||||
'Class:AuditRule/Attribute:description+' => '',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Lekérdezés',
|
||||
'Class:AuditRule/Attribute:query+' => '',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Érvényes objektum?',
|
||||
@@ -54,8 +52,6 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'Class:AuditRule/Attribute:category_id+' => '',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Kategórianév',
|
||||
'Class:AuditRule/Attribute:category_name+' => '',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -168,11 +164,9 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Letiltott',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'A felhasználónévnek egyedinek kell lennie - "%1s" már létezik.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Legalább egy profilt a felhasználóhoz kell rendelni.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'A "%1$s" profil nem adható hozzá, le lesz tiltva',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'A saját felhasználó státuszának cseréje nem engedélyezett',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Az engedélyezett szervezeteknek tartalmazniuk kell a felhasználói szervezetet',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'A profilok jelenlegi listája nem ad elegendő hozzáférési jogot (a felhasználók már nem módosíthatók)',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'A felhasználóhoz legalább egy szervezeti egységet hozzá kell rendelni',
|
||||
|
||||
@@ -40,8 +40,6 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
'Class:AuditRule/Attribute:name+' => '',
|
||||
'Class:AuditRule/Attribute:description' => 'Descrizione della regola di Audit',
|
||||
'Class:AuditRule/Attribute:description+' => 'Descrizione dettagliata per questa regola di audit ',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Query da eseguire',
|
||||
'Class:AuditRule/Attribute:query+' => 'Espressio OQL da eseguire',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Oggetti validi?',
|
||||
@@ -54,8 +52,6 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'Categoria per questa regola',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Categoria',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Nome della categoria per questa regola',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -168,11 +164,9 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Disabilitato',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Il Login deve essere unico - "%1s" già in uso',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'È necessario almeno un profilo assegnato all\'utente.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Il profilo "%1$s" non può essere aggiunto poiché nega l\'accesso al back office.',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'La modifica dello stato non è consentita per il proprio utente.',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Le organizzazioni consentite devono includere l\'organizzazione dell\'utente.',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'L\'elenco attuale dei profili non conferisce diritti di accesso sufficienti (gli utenti non sono più modificabili).',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Il profilo utente con poteri del portale non concede diritti di accesso sufficienti (deve essere aggiunto un altro profilo)',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'È necessario assegnare almeno un\'organizzazione a questo utente.',
|
||||
|
||||
@@ -40,8 +40,6 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
'Class:AuditRule/Attribute:name+' => 'ルールの短縮名',
|
||||
'Class:AuditRule/Attribute:description' => '監査ルール説明',
|
||||
'Class:AuditRule/Attribute:description+' => 'この監査ルールの長い説明',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => '実行するクエリ',
|
||||
'Class:AuditRule/Attribute:query+' => '実行するOQL式',
|
||||
'Class:AuditRule/Attribute:valid_flag' => '有効なオブジェクト',
|
||||
@@ -54,8 +52,6 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'このルールのカテゴリ',
|
||||
'Class:AuditRule/Attribute:category_name' => 'カテゴリ',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'このルールのカテゴリ名',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -168,12 +164,10 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Disabled~~',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'ログイン名は一意でないといけません。- "%1s" はすでに使われています。',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => '少なくとも1件のプロフィールがこのユーザに指定されなければなりません。',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.~~',
|
||||
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.~~',
|
||||
|
||||
@@ -40,8 +40,6 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
|
||||
'Class:AuditRule/Attribute:name+' => 'Naam van de regel',
|
||||
'Class:AuditRule/Attribute:description' => 'Beschrijving',
|
||||
'Class:AuditRule/Attribute:description+' => 'Uitgebreide beschrijving van deze Auditregel',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Query om uit te voeren',
|
||||
'Class:AuditRule/Attribute:query+' => 'De OQL-expressie voor het uitvoeren',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Geldige objecten?',
|
||||
@@ -54,8 +52,6 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'De categorie voor deze regel',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Categorie',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Naam van de categorie voor deze regel',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -168,11 +164,9 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Uitgeschakeld',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Login moet uniek zijn - "%1s" is al in gebruik',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Minstens één profiel moet toegewezen zijn aan deze gebruiker',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Profiel "%1$s" kan niet toegevoegd worden omdat het de toegang tot de backoffice zou ontzeggen.',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Je kan de status voor je eigen gebruikersaccount niet wijzigen.',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'De toegestande organisaties moeten minstens de organisatie bevatten waartoe de gebruikersaccount behoort.',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'De huidige lijst van profielen heeft niet voldoende toegangsrechten (gebruikersaccount zijn niet meer wijzigbaar).',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Minstens één organisatie moet toegewezen zijn aan deze gebruiker',
|
||||
|
||||
@@ -40,8 +40,6 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
'Class:AuditRule/Attribute:name+' => 'Krótka nazwa reguły',
|
||||
'Class:AuditRule/Attribute:description' => 'Opis reguły audytu',
|
||||
'Class:AuditRule/Attribute:description+' => 'Długi opis reguły inspekcji',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Zapytanie do wykonania',
|
||||
'Class:AuditRule/Attribute:query+' => 'Wyrażenie OQL do wykonania',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Prawidłowe obiekty?',
|
||||
@@ -54,8 +52,6 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'Kategoria dla reguły',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Kategoria',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Nazwa kategorii dla reguły',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -168,11 +164,9 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Wyłączone',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Login musi być unikatowy - "%1s" jest już używany.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Do użytkownika musi być przypisany co najmniej jeden profil.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Nie można dodać profilu "%1$s" nie ma on dostępu do zaplecza',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Zmiana statusu nie jest dozwolona dla własnego użytkownika',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Dozwolone organizacje muszą zawierać organizację użytkownika',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Aktualna lista profili nie daje wystarczających praw dostępu (Użytkowników nie można już modyfikować)',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Profil użytkownika zaawansowanego Portalu nie zapewnia wystarczających praw dostępu (należy dodać kolejny profil)',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Do użytkownika musi być przypisana co najmniej jedna organizacja.',
|
||||
|
||||
@@ -40,8 +40,6 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
'Class:AuditRule/Attribute:name+' => 'Nome curto para esta regra',
|
||||
'Class:AuditRule/Attribute:description' => 'Descrição',
|
||||
'Class:AuditRule/Attribute:description+' => 'Descrição longa para essa regra',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Executar consulta',
|
||||
'Class:AuditRule/Attribute:query+' => 'Executar a expressão OQL',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Objetos válidos?',
|
||||
@@ -54,8 +52,6 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'A categoria para esta regra',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Categoria',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Nome da categoria para essa regra',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -168,11 +164,9 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Inativa',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Login deve ser único - "%1s" já existe',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Pelo menos um perfil deve ser atribuído a esse usuário',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'O perfil "%1$s" não pôde ser adicionado, ele negará o acesso ao backoffice',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Alterar o status da conta não é permitido para o seu próprio usuário',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'As organizações permitidas devem conter apenas usuários pertencentes a organização',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'A lista atual de perfis não fornece permissões de acesso suficientes (os usuários não são mais modificáveis)',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Pelo menos uma organização deve ser atribuída a esse usuário',
|
||||
|
||||
@@ -41,8 +41,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:AuditRule/Attribute:name+' => 'Краткое название этого правила',
|
||||
'Class:AuditRule/Attribute:description' => 'Описание правила аудита',
|
||||
'Class:AuditRule/Attribute:description+' => 'Полное описание этого правила аудита',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Запрос для выполнения',
|
||||
'Class:AuditRule/Attribute:query+' => 'OQL выражение, выполняющее проверку набора объектов категории аудита',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Валидные объекты?',
|
||||
@@ -55,8 +53,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'Категория для этого правила',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Категория',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Категория для этого правила',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -169,12 +165,10 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Отключен',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Логин должен быть уникальным - "%1s" уже используется.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Как минимум один профиль должен быть назначен данному пользователю.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Этому пользователю должна быть назначена хотя бы одна организация.',
|
||||
'Class:User/Error:OrganizationNotAllowed' => 'Организация не разрешена.',
|
||||
|
||||
@@ -41,9 +41,7 @@ It is applied on the scope of objects defined by the audit category~~',
|
||||
'Class:AuditRule/Attribute:name' => 'Názov pravidla',
|
||||
'Class:AuditRule/Attribute:name+' => 'Short name for this rule~~',
|
||||
'Class:AuditRule/Attribute:description' => 'Popis pravidla auditu',
|
||||
'Class:AuditRule/Attribute:description+' => 'What is checked?~~',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:description+' => 'What is checked? How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Spustenie dopytu',
|
||||
'Class:AuditRule/Attribute:query+' => 'The OQL expression to run. Returned classes must be aligned with those of the category\'s scope~~',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Platný objekt?',
|
||||
@@ -58,8 +56,6 @@ It is applied on the scope of objects defined by the audit category~~',
|
||||
'Class:AuditRule/Attribute:category_id+' => 'The category of this rule~~',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Kategória',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Name of the category of this rule~~',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -172,12 +168,10 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Disabled~~',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Prihlasovacie meno musí byť jedinečné - "%1s" sa už používa.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Aspoň jeden profil musí byť priradený k profilu.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.~~',
|
||||
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.~~',
|
||||
|
||||
@@ -40,8 +40,6 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
|
||||
'Class:AuditRule/Attribute:name+' => 'Kural Adı',
|
||||
'Class:AuditRule/Attribute:description' => 'Kural tanımlaması',
|
||||
'Class:AuditRule/Attribute:description+' => 'Kural tanımlaması',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => 'Çalıştırılacak Sorgu',
|
||||
'Class:AuditRule/Attribute:query+' => 'Çalıştırılcak OQL ifadesi',
|
||||
'Class:AuditRule/Attribute:valid_flag' => 'Geçerli nesneler?',
|
||||
@@ -54,8 +52,6 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
|
||||
'Class:AuditRule/Attribute:category_id+' => 'Kuralın kategorisi',
|
||||
'Class:AuditRule/Attribute:category_name' => 'Kategori',
|
||||
'Class:AuditRule/Attribute:category_name+' => 'Kural için kategori adı',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -168,12 +164,10 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
|
||||
'Class:User/Attribute:status/Value:disabled' => 'Disabled~~',
|
||||
'Class:User/Error:LoginMustBeUnique' => 'Kullanıcı adı tekil olmalı - "%1s" mevcut bir kullanıcıya ait.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'En az bir profil kullanıcıya atanmalı',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.~~',
|
||||
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.~~',
|
||||
|
||||
@@ -42,8 +42,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:AuditRule/Attribute:name+' => '规则名称',
|
||||
'Class:AuditRule/Attribute:description' => '描述',
|
||||
'Class:AuditRule/Attribute:description+' => '检查什么? 如何修复? 谁去做? ...',
|
||||
'Class:AuditRule/Attribute:process' => 'Correction process~~',
|
||||
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
|
||||
'Class:AuditRule/Attribute:query' => '要运行的查询',
|
||||
'Class:AuditRule/Attribute:query+' => '要运行的OQL表达式',
|
||||
'Class:AuditRule/Attribute:valid_flag' => '是否有效?',
|
||||
@@ -56,8 +54,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:AuditRule/Attribute:category_id+' => '该规则对应的类别',
|
||||
'Class:AuditRule/Attribute:category_name' => '类别',
|
||||
'Class:AuditRule/Attribute:category_name+' => '该规则对应的类名称',
|
||||
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
|
||||
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
|
||||
]);
|
||||
|
||||
//
|
||||
@@ -170,11 +166,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'Class:User/Attribute:status/Value:disabled' => '停用',
|
||||
'Class:User/Error:LoginMustBeUnique' => '登录名必须唯一 - "%1s" 已经被使用.',
|
||||
'Class:User/Error:AtLeastOneProfileIsNeeded' => '必须指定至少一个角色给此用户.',
|
||||
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
|
||||
'Class:User/Error:ProfileNotAllowed' => '无法添加角色 "%1$s" 因为这将导致禁止访问后台',
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => '不允许更改您自己用户的状态',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => '允许访问组织必须包含用户组织',
|
||||
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => '当前指定的角色列表没有提供足够的访问权限 (用户将无法被修改)',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => '必须为此用户指定一个组织.',
|
||||
|
||||
@@ -14,10 +14,7 @@ if (PHP_VERSION_ID < 50600) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
throw new RuntimeException($err);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
@@ -61,7 +61,7 @@ return array(
|
||||
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
||||
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
|
||||
'Pelago\\Emogrifier\\' => array($vendorDir . '/pelago/emogrifier/src'),
|
||||
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-google/src'),
|
||||
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-google/src', $vendorDir . '/league/oauth2-client/src'),
|
||||
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
|
||||
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
|
||||
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
|
||||
|
||||
@@ -340,8 +340,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
),
|
||||
'League\\OAuth2\\Client\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/league/oauth2-client/src',
|
||||
1 => __DIR__ . '/..' . '/league/oauth2-google/src',
|
||||
0 => __DIR__ . '/..' . '/league/oauth2-google/src',
|
||||
1 => __DIR__ . '/..' . '/league/oauth2-client/src',
|
||||
),
|
||||
'GuzzleHttp\\Psr7\\' =>
|
||||
array (
|
||||
|
||||
@@ -36,8 +36,7 @@ if ($issues) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
throw new \RuntimeException(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,11 +60,6 @@ class iTopExtension
|
||||
* @var bool
|
||||
*/
|
||||
public $bMarkedAsChosen;
|
||||
/**
|
||||
* If null, check if at least one module cannot be uninstalled
|
||||
* @var bool|null
|
||||
*/
|
||||
public ?bool $bCanBeUninstalled = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
@@ -96,14 +91,6 @@ class iTopExtension
|
||||
* @var string[]
|
||||
*/
|
||||
public $aMissingDependencies;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public bool $bInstalled = false;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public bool $bRemovedFromDisk = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -128,14 +115,13 @@ class iTopExtension
|
||||
* @since 3.3.0
|
||||
* @return bool
|
||||
*/
|
||||
public function CanBeUninstalled(): bool
|
||||
public function CanBeUninstalled()
|
||||
{
|
||||
if (!is_null($this->bCanBeUninstalled)) {
|
||||
return $this->bCanBeUninstalled;
|
||||
}
|
||||
foreach ($this->aModuleInfo as $sModuleCode => $aModuleInfo) {
|
||||
$this->bCanBeUninstalled = $aModuleInfo['uninstallable'] === 'yes';
|
||||
return $this->bCanBeUninstalled;
|
||||
$bUninstallable = $aModuleInfo['uninstallable'] === 'yes';
|
||||
if (!$bUninstallable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -153,11 +139,6 @@ class iTopExtensionsMap
|
||||
* @return void
|
||||
*/
|
||||
protected $aExtensions;
|
||||
/**
|
||||
* The list of all currently installed extensions
|
||||
* @var array|null
|
||||
*/
|
||||
protected ?array $aInstalledExtensions = null;
|
||||
|
||||
/**
|
||||
* The list of directories browsed using the ReadDir method when building the map
|
||||
@@ -165,7 +146,7 @@ class iTopExtensionsMap
|
||||
*/
|
||||
protected $aScannedDirs;
|
||||
|
||||
public function __construct($sFromEnvironment = 'production', $aExtraDirs = [])
|
||||
public function __construct($sFromEnvironment = 'production', $bNormalizeOldExtensions = true, $aExtraDirs = [])
|
||||
{
|
||||
$this->aExtensions = [];
|
||||
$this->aScannedDirs = [];
|
||||
@@ -174,6 +155,9 @@ class iTopExtensionsMap
|
||||
$this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE);
|
||||
}
|
||||
$this->CheckDependencies($sFromEnvironment);
|
||||
if ($bNormalizeOldExtensions) {
|
||||
$this->NormalizeOldExtensions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,7 +213,6 @@ class iTopExtensionsMap
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->sCode = $aChoiceInfo['extension_code'];
|
||||
$oExtension->sLabel = $aChoiceInfo['title'];
|
||||
$oExtension->sDescription = $aChoiceInfo['description'];
|
||||
if (array_key_exists('modules', $aChoiceInfo)) {
|
||||
// Some wizard choices are not associated with any module
|
||||
$oExtension->aModules = $aChoiceInfo['modules'];
|
||||
@@ -278,7 +261,7 @@ class iTopExtensionsMap
|
||||
*
|
||||
* @return \iTopExtension|null
|
||||
*/
|
||||
public function GetFromExtensionCode(string $sExtensionCode): ?iTopExtension
|
||||
public function Get(string $sExtensionCode): ?iTopExtension
|
||||
{
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->sCode === $sExtensionCode) {
|
||||
@@ -358,7 +341,7 @@ class iTopExtensionsMap
|
||||
$this->aExtensions[$sParentExtensionId]->aModuleVersion[$sModuleName] = $sModuleVersion;
|
||||
$this->aExtensions[$sParentExtensionId]->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
|
||||
} else {
|
||||
// Not already inside a folder containing an 'extension.xml' file
|
||||
// Not already inside an folder containing an 'extension.xml' file
|
||||
|
||||
// Ignore non-visible modules and auto-select ones, since these are never prompted
|
||||
// as a choice to the end-user
|
||||
@@ -449,19 +432,10 @@ class iTopExtensionsMap
|
||||
return $this->aExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array All available extensions and extensions currently installed but not available due to files removal
|
||||
*/
|
||||
public function GetAllExtensionsWithPreviouslyInstalled(): array
|
||||
{
|
||||
//Mind the order, local extensions data must overwrite installed extensions data since installed extensions does not have the associated modules.
|
||||
return array_merge($this->aInstalledExtensions ?? [], $this->aExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given extension as chosen
|
||||
* @param string $sExtensionCode The code of the extension (code without version number)
|
||||
* @param bool $bMark The value to set for the bMarkAsChosen flag
|
||||
* @param string $sExtensionCode The code of the extension (code without verison number)
|
||||
* @param bool $bMark The value to set for the bmarkAschosen flag
|
||||
* @return void
|
||||
*/
|
||||
public function MarkAsChosen($sExtensionCode, $bMark = true)
|
||||
@@ -526,55 +500,124 @@ class iTopExtensionsMap
|
||||
* @return bool
|
||||
*/
|
||||
public function LoadChoicesFromDatabase(Config $oConfig)
|
||||
{
|
||||
foreach ($this->LoadInstalledExtensionsFromDatabase($oConfig) as $oExtension) {
|
||||
$this->MarkAsChosen($oExtension->sCode);
|
||||
$this->SetInstalledVersion($oExtension->sCode, $oExtension->sVersion);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function LoadInstalledExtensionsFromDatabase(Config $oConfig): array|false
|
||||
{
|
||||
try {
|
||||
$aInstalledExtensions = [];
|
||||
if (CMDBSource::DBName() === null) {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
}
|
||||
$sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->Get('db_subname')."priv_extension_install");
|
||||
$aDBInfo = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_extension_install WHERE installed = '".$sLatestInstallationDate."'");
|
||||
|
||||
$this->aInstalledExtensions = [];
|
||||
foreach ($aDBInfo as $aExtensionInfo) {
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->sCode = $aExtensionInfo['code'];
|
||||
$oExtension->sLabel = $aExtensionInfo['label'];
|
||||
$oExtension->sDescription = $aExtensionInfo['description'] ?? '';
|
||||
$oExtension->sVersion = $aExtensionInfo['version'];
|
||||
$oExtension->sSource = $aExtensionInfo['source'];
|
||||
$oExtension->bMandatory = false;
|
||||
$oExtension->sMoreInfoUrl = '';
|
||||
$oExtension->aModules = [];
|
||||
$oExtension->aModuleVersion = [];
|
||||
$oExtension->aModuleInfo = [];
|
||||
$oExtension->sSourceDir = '';
|
||||
$oExtension->bVisible = true;
|
||||
$oExtension->bInstalled = true;
|
||||
$oExtension->bCanBeUninstalled = !isset($aExtensionInfo['uninstallable']) || $aExtensionInfo['uninstallable'] === 'yes';
|
||||
$oChoice = $this->GetFromExtensionCode($oExtension->sCode);
|
||||
if ($oChoice) {
|
||||
$oChoice->bInstalled = true;
|
||||
} else {
|
||||
$oExtension->bRemovedFromDisk = true;
|
||||
}
|
||||
|
||||
$this->aInstalledExtensions[$oExtension->sCode.'/'.$oExtension->sVersion] = $oExtension;
|
||||
}
|
||||
|
||||
return $this->aInstalledExtensions;
|
||||
$aInstalledExtensions = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_extension_install WHERE installed = '".$sLatestInstallationDate."'");
|
||||
} catch (MySQLException $e) {
|
||||
// No database or erroneous information
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($aInstalledExtensions as $aDBInfo) {
|
||||
$this->MarkAsChosen($aDBInfo['code']);
|
||||
$this->SetInstalledVersion($aDBInfo['code'], $aDBInfo['version']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find is a single-module extension is contained within another extension
|
||||
* @param iTopExtension $oExtension
|
||||
* @return NULL|iTopExtension
|
||||
*/
|
||||
public function IsExtensionObsoletedByAnother(iTopExtension $oExtension)
|
||||
{
|
||||
// Complex extensions (more than 1 module) are never considered as obsolete
|
||||
if (count($oExtension->aModules) != 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->GetAllExtensions() as $oOtherExtension) {
|
||||
if (($oOtherExtension->sSourceDir != $oExtension->sSourceDir) && ($oOtherExtension->sSource != iTopExtension::SOURCE_WIZARD)) {
|
||||
if (array_key_exists($oExtension->sCode, $oOtherExtension->aModuleVersion) &&
|
||||
(version_compare($oOtherExtension->aModuleVersion[$oExtension->sCode], $oExtension->sVersion, '>='))) {
|
||||
// Found another extension containing a more recent version of the extension/module
|
||||
return $oOtherExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No match at all
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for multi-module extensions that are NOT deployed as an extension (i.e. shipped with an extension.xml file)
|
||||
* but as a bunch of un-related modules based on the signature of some well-known extensions. If such an extension is found,
|
||||
* replace the stand-alone modules by an "extension" with the appropriate label/description/version containing the same modules.
|
||||
* @param string $sInSourceOnly The source directory to scan (datamodel|extensions|data)
|
||||
*/
|
||||
public function NormalizeOldExtensions($sInSourceOnly = iTopExtension::SOURCE_MANUAL)
|
||||
{
|
||||
$aSignatures = $this->GetOldExtensionsSignatures();
|
||||
foreach ($aSignatures as $sExtensionCode => $aExtensionSignatures) {
|
||||
$bFound = false;
|
||||
foreach ($aExtensionSignatures['versions'] as $sVersion => $aModules) {
|
||||
$bInstalled = true;
|
||||
foreach ($aModules as $sModuleId) {
|
||||
if (!$this->ModuleIsPresent($sModuleId, $sInSourceOnly)) {
|
||||
$bFound = false;
|
||||
break; // One missing module is enough to determine that the extension/version is not present
|
||||
} else {
|
||||
$bInstalled = $bInstalled && $this->ModuleIsInstalled($sModuleId, $sInSourceOnly);
|
||||
$bFound = true;
|
||||
}
|
||||
}
|
||||
if ($bFound) {
|
||||
break;
|
||||
} // The current version matches the signature
|
||||
}
|
||||
|
||||
if ($bFound) {
|
||||
$oExtension = new iTopExtension();
|
||||
$oExtension->sCode = $sExtensionCode;
|
||||
$oExtension->sLabel = $aExtensionSignatures['label'];
|
||||
$oExtension->sSource = $sInSourceOnly;
|
||||
$oExtension->sDescription = $aExtensionSignatures['description'];
|
||||
$oExtension->sVersion = $sVersion;
|
||||
$oExtension->aModules = [];
|
||||
if ($bInstalled) {
|
||||
$oExtension->sInstalledVersion = $sVersion;
|
||||
$oExtension->bMarkedAsChosen = true;
|
||||
}
|
||||
foreach ($aModules as $sModuleId) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
$oExtension->aModules[] = $sModuleName;
|
||||
$oExtension->aModuleInfo[$sModuleName] = $this->aExtensions[$sModuleId]->aModuleInfo[$sModuleName];
|
||||
}
|
||||
$this->ReplaceModulesByNormalizedExtension($aExtensionSignatures['versions'][$sVersion], $oExtension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given module-code/version is present on the disk
|
||||
* @param string $sModuleIdToFind The module ID (code/version) to search for
|
||||
* @param string $sInSourceOnly The origin (=source) to search in (datamodel|extensions|data)
|
||||
* @return boolean
|
||||
*/
|
||||
protected function ModuleIsPresent($sModuleIdToFind, $sInSourceOnly)
|
||||
{
|
||||
return (array_key_exists($sModuleIdToFind, $this->aExtensions) && ($this->aExtensions[$sModuleIdToFind]->sSource == $sInSourceOnly));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given module-code/version is currently installed
|
||||
* @param string $sModuleIdToFind The module ID (code/version) to search for
|
||||
* @param string $sInSourceOnly The origin (=source) to search in (datamodel|extensions|data)
|
||||
* @return boolean
|
||||
*/
|
||||
protected function ModuleIsInstalled($sModuleIdToFind, $sInSourceOnly)
|
||||
{
|
||||
return (array_key_exists($sModuleIdToFind, $this->aExtensions) &&
|
||||
($this->aExtensions[$sModuleIdToFind]->sSource == $sInSourceOnly) &&
|
||||
($this->aExtensions[$sModuleIdToFind]->sInstalledVersion !== ''));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -597,4 +640,657 @@ class iTopExtensionsMap
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a given set of stand-alone modules by one single "extension"
|
||||
* @param string[] $aModules
|
||||
* @param iTopExtension $oNewExtension
|
||||
*/
|
||||
protected function ReplaceModulesByNormalizedExtension($aModules, iTopExtension $oNewExtension)
|
||||
{
|
||||
foreach ($aModules as $sModuleId) {
|
||||
unset($this->aExtensions[$sModuleId]);
|
||||
}
|
||||
$this->AddExtension($oNewExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of signatures of some well-known multi-module extensions without extension.xml file (should not exist anymore)
|
||||
*
|
||||
* @return string[][]|string[][][][]
|
||||
*/
|
||||
protected function GetOldExtensionsSignatures()
|
||||
{
|
||||
// Generated by the Factory using the page export_component_versions_for_normalisation.php
|
||||
return [
|
||||
'combodo-approval-process-light' =>
|
||||
[
|
||||
'label' => 'Approval process light',
|
||||
'description' => 'Approve a request via a simple email',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.0',
|
||||
1 => 'combodo-approval-light/1.0.1',
|
||||
],
|
||||
'1.0.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.1',
|
||||
1 => 'combodo-approval-light/1.0.2',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.2',
|
||||
1 => 'combodo-approval-light/1.0.2',
|
||||
],
|
||||
'1.1.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.2',
|
||||
1 => 'combodo-approval-light/1.0.2',
|
||||
],
|
||||
'1.1.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.3',
|
||||
1 => 'combodo-approval-light/1.0.2',
|
||||
],
|
||||
'1.1.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.6',
|
||||
1 => 'combodo-approval-light/1.0.2',
|
||||
],
|
||||
'1.1.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.6',
|
||||
1 => 'combodo-approval-light/1.0.3',
|
||||
],
|
||||
'1.2.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.3.0',
|
||||
1 => 'combodo-approval-light/1.0.3',
|
||||
],
|
||||
'1.2.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.0',
|
||||
1 => 'combodo-approval-light/1.0.4',
|
||||
],
|
||||
'1.3.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.2',
|
||||
1 => 'combodo-approval-light/1.1.1',
|
||||
],
|
||||
'1.3.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.0',
|
||||
1 => 'combodo-approval-light/1.1.1',
|
||||
],
|
||||
'1.3.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.0',
|
||||
1 => 'combodo-approval-light/1.1.2',
|
||||
],
|
||||
'1.2.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.2',
|
||||
1 => 'combodo-approval-light/1.0.5',
|
||||
],
|
||||
'1.3.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.1',
|
||||
1 => 'combodo-approval-light/1.1.2',
|
||||
],
|
||||
'1.3.4' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.2',
|
||||
1 => 'combodo-approval-light/1.1.2',
|
||||
],
|
||||
'1.3.5' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.3',
|
||||
1 => 'combodo-approval-light/1.1.2',
|
||||
],
|
||||
'1.4.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.3',
|
||||
1 => 'combodo-approval-light/1.1.2',
|
||||
2 => 'itop-approval-portal/1.0.0',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-approval-process-automation' =>
|
||||
[
|
||||
'label' => 'Approval process automation',
|
||||
'description' => 'Control your approval process with predefined rules based on service catalog',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.0',
|
||||
1 => 'combodo-approval-extended/1.0.2',
|
||||
],
|
||||
'1.0.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.1',
|
||||
1 => 'combodo-approval-extended/1.0.4',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.1.2',
|
||||
1 => 'combodo-approval-extended/1.0.4',
|
||||
],
|
||||
'1.1.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.2',
|
||||
1 => 'combodo-approval-extended/1.0.4',
|
||||
],
|
||||
'1.1.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.3',
|
||||
1 => 'combodo-approval-extended/1.0.4',
|
||||
],
|
||||
'1.1.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.6',
|
||||
1 => 'combodo-approval-extended/1.0.5',
|
||||
],
|
||||
'1.1.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.2.6',
|
||||
1 => 'combodo-approval-extended/1.0.6',
|
||||
],
|
||||
'1.2.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.3.0',
|
||||
1 => 'combodo-approval-extended/1.0.7',
|
||||
],
|
||||
'1.2.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.0',
|
||||
1 => 'combodo-approval-extended/1.0.8',
|
||||
],
|
||||
'1.3.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.2',
|
||||
1 => 'combodo-approval-extended/1.2.1',
|
||||
],
|
||||
'1.3.1' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.0',
|
||||
1 => 'combodo-approval-extended/1.2.1',
|
||||
],
|
||||
'1.3.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.0',
|
||||
1 => 'combodo-approval-extended/1.2.2',
|
||||
],
|
||||
'1.2.2' =>
|
||||
[
|
||||
0 => 'approval-base/2.4.2',
|
||||
1 => 'combodo-approval-extended/1.0.9',
|
||||
],
|
||||
'1.3.3' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.1',
|
||||
1 => 'combodo-approval-extended/1.2.3',
|
||||
],
|
||||
'1.3.4' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.2',
|
||||
1 => 'combodo-approval-extended/1.2.3',
|
||||
],
|
||||
'1.3.5' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.3',
|
||||
1 => 'combodo-approval-extended/1.2.3',
|
||||
],
|
||||
'1.4.0' =>
|
||||
[
|
||||
0 => 'approval-base/2.5.3',
|
||||
1 => 'combodo-approval-extended/1.2.3',
|
||||
3 => 'itop-approval-portal/1.0.0',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-predefined-response-models' =>
|
||||
[
|
||||
'label' => 'Predefined response models',
|
||||
'description' => 'Pick common answers from a list of predefined replies grouped by categories to update tickets log',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.0' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.0',
|
||||
1 => 'precanned-replies-pro/1.0.0',
|
||||
],
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.1',
|
||||
1 => 'precanned-replies-pro/1.0.1',
|
||||
],
|
||||
'1.0.2' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.2',
|
||||
1 => 'precanned-replies-pro/1.0.1',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.3',
|
||||
1 => 'precanned-replies-pro/1.0.1',
|
||||
],
|
||||
'1.0.4' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.3',
|
||||
1 => 'precanned-replies-pro/1.0.2',
|
||||
],
|
||||
'1.0.5' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.0.4',
|
||||
1 => 'precanned-replies-pro/1.0.2',
|
||||
],
|
||||
'1.1.0' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.1.0',
|
||||
1 => 'precanned-replies-pro/1.0.2',
|
||||
],
|
||||
'1.1.1' =>
|
||||
[
|
||||
0 => 'precanned-replies/1.1.1',
|
||||
1 => 'precanned-replies-pro/1.0.2',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-customized-request-forms' =>
|
||||
[
|
||||
'label' => 'Customized request forms',
|
||||
'description' => 'Define personalized request forms based on the service catalog. Add extra fields for a given type of request.',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'templates-base/2.1.1',
|
||||
1 => 'itop-request-template/1.0.0',
|
||||
],
|
||||
'1.0.2' =>
|
||||
[
|
||||
0 => 'templates-base/2.1.2',
|
||||
1 => 'itop-request-template/1.0.0',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'templates-base/2.1.2',
|
||||
1 => 'itop-request-template/1.0.1',
|
||||
],
|
||||
'1.0.4' =>
|
||||
[
|
||||
0 => 'templates-base/2.1.3',
|
||||
1 => 'itop-request-template/1.0.1',
|
||||
],
|
||||
'1.0.5' =>
|
||||
[
|
||||
0 => 'templates-base/2.1.4',
|
||||
1 => 'itop-request-template/1.0.1',
|
||||
],
|
||||
'2.0.0' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.0',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.1' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.1',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.2' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.2',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.3' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.4',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.4' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.5',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.5' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.6',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.6' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.8',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.7' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.9',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
'2.0.8' =>
|
||||
[
|
||||
0 => 'templates-base/3.0.12',
|
||||
1 => 'itop-request-template/2.0.0',
|
||||
2 => 'itop-request-template-portal/1.0.0',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-sla-considering-business-hours' =>
|
||||
[
|
||||
'label' => 'SLA considering business hours',
|
||||
'description' => 'Compute SLAs taking into account service coverage window and holidays',
|
||||
'versions' =>
|
||||
[
|
||||
'2.0.1' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.0.1',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.0',
|
||||
],
|
||||
'2.1.0' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.0',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.0',
|
||||
],
|
||||
'2.1.1' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.1',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.0',
|
||||
],
|
||||
'2.1.2' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.2',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.0',
|
||||
],
|
||||
'2.1.3' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.2',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.1',
|
||||
],
|
||||
'2.0.2' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.0.1',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.1',
|
||||
],
|
||||
'2.1.4' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.3',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.1',
|
||||
],
|
||||
'2.1.5' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.5',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.1',
|
||||
],
|
||||
'2.1.6' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.5',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.2',
|
||||
],
|
||||
'2.1.7' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.6',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.2',
|
||||
],
|
||||
'2.1.8' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.7',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.2',
|
||||
],
|
||||
'2.1.9' =>
|
||||
[
|
||||
0 => 'combodo-sla-computation/2.1.8',
|
||||
1 => 'combodo-coverage-windows-computation/2.0.2',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-mail-to-ticket-automation' =>
|
||||
[
|
||||
'label' => 'Mail to ticket automation',
|
||||
'description' => 'Scan several mailboxes to create or update tickets.',
|
||||
'versions' =>
|
||||
[
|
||||
'2.6.0' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.0',
|
||||
1 => 'itop-standard-email-synchro/2.6.0',
|
||||
],
|
||||
'2.6.1' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.1',
|
||||
1 => 'itop-standard-email-synchro/2.6.0',
|
||||
],
|
||||
'2.6.2' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.2',
|
||||
1 => 'itop-standard-email-synchro/2.6.0',
|
||||
],
|
||||
'2.6.3' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.2',
|
||||
1 => 'itop-standard-email-synchro/2.6.1',
|
||||
],
|
||||
'2.6.4' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.3',
|
||||
1 => 'itop-standard-email-synchro/2.6.2',
|
||||
],
|
||||
'2.6.5' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.4',
|
||||
1 => 'itop-standard-email-synchro/2.6.2',
|
||||
],
|
||||
'2.6.6' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.5',
|
||||
1 => 'itop-standard-email-synchro/2.6.3',
|
||||
],
|
||||
'2.6.7' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.6',
|
||||
1 => 'itop-standard-email-synchro/2.6.4',
|
||||
],
|
||||
'2.6.8' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.7',
|
||||
1 => 'itop-standard-email-synchro/2.6.4',
|
||||
],
|
||||
'2.6.9' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.8',
|
||||
1 => 'itop-standard-email-synchro/2.6.5',
|
||||
],
|
||||
'2.6.10' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.9',
|
||||
1 => 'itop-standard-email-synchro/2.6.6',
|
||||
],
|
||||
'2.6.11' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.10',
|
||||
1 => 'itop-standard-email-synchro/2.6.6',
|
||||
],
|
||||
'2.6.12' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/2.6.11',
|
||||
1 => 'itop-standard-email-synchro/2.6.6',
|
||||
],
|
||||
'3.0.0' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.0',
|
||||
1 => 'itop-standard-email-synchro/3.0.0',
|
||||
],
|
||||
'3.0.1' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.1',
|
||||
1 => 'itop-standard-email-synchro/3.0.1',
|
||||
],
|
||||
'3.0.2' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.2',
|
||||
1 => 'itop-standard-email-synchro/3.0.1',
|
||||
],
|
||||
'3.0.3' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.3',
|
||||
1 => 'itop-standard-email-synchro/3.0.3',
|
||||
],
|
||||
'3.0.4' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.3',
|
||||
1 => 'itop-standard-email-synchro/3.0.4',
|
||||
],
|
||||
'3.0.5' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.4',
|
||||
1 => 'itop-standard-email-synchro/3.0.4',
|
||||
],
|
||||
'3.0.6' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.5',
|
||||
1 => 'itop-standard-email-synchro/3.0.4',
|
||||
],
|
||||
'3.0.7' =>
|
||||
[
|
||||
0 => 'combodo-email-synchro/3.0.5',
|
||||
1 => 'itop-standard-email-synchro/3.0.5',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-configurator-for-automatic-object-creation' =>
|
||||
[
|
||||
'label' => 'Configurator for automatic object creation',
|
||||
'description' => 'Templating based on existing objects.',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.13' =>
|
||||
[
|
||||
1 => 'itop-stencils/1.0.6',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-user-actions-configurator' =>
|
||||
[
|
||||
'label' => 'User actions configurator',
|
||||
'description' => 'Configure user actions to simplify and automate processes (e.g. create an incident from a CI).',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.0' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.0.0',
|
||||
],
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.0.1',
|
||||
],
|
||||
'1.0.2' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.0.2',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.0.3',
|
||||
],
|
||||
'1.1.0' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.0',
|
||||
],
|
||||
'1.1.1' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.1',
|
||||
],
|
||||
'1.1.2' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.2',
|
||||
],
|
||||
'1.1.3' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.3',
|
||||
],
|
||||
'1.1.4' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.4',
|
||||
],
|
||||
'1.1.5' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.5',
|
||||
],
|
||||
'1.1.6' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.6',
|
||||
],
|
||||
'1.1.7' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.7',
|
||||
],
|
||||
'1.1.8' =>
|
||||
[
|
||||
0 => 'itop-object-copier/1.1.8',
|
||||
],
|
||||
],
|
||||
],
|
||||
'combodo-send-updates-by-email' =>
|
||||
[
|
||||
'label' => 'Send updates by email',
|
||||
'description' => 'Send an email to pre-configured contacts when a ticket log is updated.',
|
||||
'versions' =>
|
||||
[
|
||||
'1.0.1' =>
|
||||
[
|
||||
0 => 'email-reply/1.0.1',
|
||||
],
|
||||
'1.0.3' =>
|
||||
[
|
||||
0 => 'email-reply/1.0.3',
|
||||
],
|
||||
'1.1.1' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.1',
|
||||
],
|
||||
'1.1.2' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.2',
|
||||
],
|
||||
'1.1.3' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.3',
|
||||
],
|
||||
'1.1.4' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.4',
|
||||
],
|
||||
'1.1.5' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.5',
|
||||
],
|
||||
'1.1.6' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.6',
|
||||
],
|
||||
'1.1.7' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.7',
|
||||
],
|
||||
// 1.1.8 was never released
|
||||
'1.1.9' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.9',
|
||||
],
|
||||
'1.1.10' =>
|
||||
[
|
||||
0 => 'email-reply/1.1.10',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency;
|
||||
|
||||
require_once(APPROOT.'/setup/runtimeenv.class.inc.php');
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
use ModuleFileReaderException;
|
||||
use RunTimeEnvironment;
|
||||
|
||||
/**
|
||||
* Class that handles a module dependency
|
||||
* Dependency expression example : (moduleA/123 || moduleB>456)
|
||||
*/
|
||||
class DependencyExpression
|
||||
{
|
||||
private static PhpExpressionEvaluator $oPhpExpressionEvaluator;
|
||||
|
||||
private string $sDependencyExpression;
|
||||
private bool $bValid = true;
|
||||
private bool $bResolved = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool> $aRemainingModuleNamesToResolve
|
||||
*/
|
||||
private array $aRemainingModuleNamesToResolve;
|
||||
|
||||
/**
|
||||
* @var array<string, array> $aParamsPerModuleId
|
||||
*/
|
||||
private array $aParamsPerModuleId;
|
||||
|
||||
public function __construct(string $sDependencyExpression)
|
||||
{
|
||||
$this->sDependencyExpression = $sDependencyExpression;
|
||||
$this->aParamsPerModuleId = [];
|
||||
$this->aRemainingModuleNamesToResolve = [];
|
||||
|
||||
if (preg_match_all('/([^\(\)&| ]+)/', $sDependencyExpression, $aMatches)) {
|
||||
foreach ($aMatches as $aMatch) {
|
||||
foreach ($aMatch as $sModuleId) {
|
||||
if (!array_key_exists($sModuleId, $this->aParamsPerModuleId)) {
|
||||
// $sModuleId in the dependency string is made of a <name>/<optional_operator><version>
|
||||
// where the operator is < <= = > >= (by default >=)
|
||||
$aModuleMatches = [];
|
||||
if (preg_match('|^([^/]+)/(<?>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches)) {
|
||||
$sModuleName = $aModuleMatches[1];
|
||||
$this->aRemainingModuleNamesToResolve[$sModuleName] = true;
|
||||
$sOperator = $aModuleMatches[2];
|
||||
if ($sOperator == '') {
|
||||
$sOperator = '>=';
|
||||
}
|
||||
$sExpectedVersion = $aModuleMatches[3];
|
||||
$this->aParamsPerModuleId[$sModuleId] = [$sModuleName, $sOperator, $sExpectedVersion];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->bValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
{
|
||||
if (!isset(static::$oPhpExpressionEvaluator)) {
|
||||
static::$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
|
||||
}
|
||||
|
||||
return static::$oPhpExpressionEvaluator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return module names potentially required by current dependency
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetRemainingModuleNamesToResolve(): array
|
||||
{
|
||||
return array_keys($this->aRemainingModuleNamesToResolve);
|
||||
}
|
||||
|
||||
public function IsResolved(): bool
|
||||
{
|
||||
return $this->bResolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if dependency is resolved with current list of module versions
|
||||
*
|
||||
* @param array $aResolvedModuleVersions : versions by module names dict
|
||||
* @param array $aAllModuleNames : modules names dict
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function UpdateModuleResolutionState(array $aResolvedModuleVersions, array $aAllModuleNames): void
|
||||
{
|
||||
if (!$this->bValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aReplacements = [];
|
||||
|
||||
$bDelayEvaluation = false;
|
||||
foreach ($this->aParamsPerModuleId as $sModuleId => list($sModuleName, $sOperator, $sExpectedVersion)) {
|
||||
if (array_key_exists($sModuleName, $aResolvedModuleVersions)) {
|
||||
// module is resolved, check the version
|
||||
$sCurrentVersion = $aResolvedModuleVersions[$sModuleName];
|
||||
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) {
|
||||
if (array_key_exists($sModuleName, $this->aRemainingModuleNamesToResolve)) {
|
||||
unset($this->aRemainingModuleNamesToResolve[$sModuleName]);
|
||||
}
|
||||
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
} else {
|
||||
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
}
|
||||
} else {
|
||||
// module is not resolved yet
|
||||
|
||||
if (array_key_exists($sModuleName, $aAllModuleNames)) {
|
||||
//Weird piece of code that covers below usecase:
|
||||
//module B dependency: 'moduleA || true'
|
||||
// if moduleA not present on disk, whole expression can be evaluated and may be resolved
|
||||
// if moduleA present on disk, we need to sort moduleB after moduleA. expression cannot be resolved yet
|
||||
$bDelayEvaluation = true;
|
||||
} else {
|
||||
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ($bDelayEvaluation) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bResult = false;
|
||||
$sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $this->sDependencyExpression);
|
||||
try {
|
||||
$bResult = self::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($sBooleanExpr);
|
||||
} catch (ModuleFileReaderException $e) {
|
||||
//logged already
|
||||
echo "Failed to parse the boolean Expression = '$sBooleanExpr'<br/>";
|
||||
}
|
||||
|
||||
$this->bResolved = $bResult;
|
||||
}
|
||||
|
||||
public function IsValid(): bool
|
||||
{
|
||||
return $this->bValid;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency;
|
||||
|
||||
require_once(__DIR__.'/dependencyexpression.class.inc.php');
|
||||
use ModuleDiscovery;
|
||||
|
||||
/**
|
||||
* Class that handles a modules and all its dependencies
|
||||
*/
|
||||
class Module
|
||||
{
|
||||
private string $sModuleId;
|
||||
private string $sModuleName;
|
||||
private string $sVersion;
|
||||
|
||||
/**
|
||||
* @var array<string> $aInitialDependencyExpressions
|
||||
*/
|
||||
private array $aInitialDependencyExpressions;
|
||||
|
||||
/**
|
||||
* @var array<string, DependencyExpression> $aRemainingDependenciesToResolve
|
||||
*/
|
||||
public array $aRemainingDependenciesToResolve;
|
||||
|
||||
public function __construct(string $sModuleId)
|
||||
{
|
||||
$this->sModuleId = $sModuleId;
|
||||
list($this->sModuleName, $this->sVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
}
|
||||
|
||||
public function IsDependencyExpressionResolved(string $sDependencyExpression): bool
|
||||
{
|
||||
return ! array_key_exists($sDependencyExpression, $this->aRemainingDependenciesToResolve);
|
||||
}
|
||||
|
||||
public function GetDependencyResolutionFeedback(): array
|
||||
{
|
||||
$aDepsWithIcons = [];
|
||||
|
||||
foreach ($this->aInitialDependencyExpressions as $sDependencyExpression) {
|
||||
if (! $this->IsDependencyExpressionResolved($sDependencyExpression)) {
|
||||
$aDepsWithIcons[] = '❌ '.$sDependencyExpression;
|
||||
}
|
||||
}
|
||||
return $aDepsWithIcons;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetModuleName()
|
||||
{
|
||||
return $this->sModuleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetVersion()
|
||||
{
|
||||
return $this->sVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetModuleId()
|
||||
{
|
||||
return $this->sModuleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aAllDependencyExpressions: list of dependencies (string)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function SetDependencies(array $aAllDependencyExpressions): void
|
||||
{
|
||||
$this->aInitialDependencyExpressions = $aAllDependencyExpressions;
|
||||
$this->aRemainingDependenciesToResolve = [];
|
||||
|
||||
foreach ($aAllDependencyExpressions as $sDependencyExpression) {
|
||||
$this->aRemainingDependenciesToResolve[$sDependencyExpression] = new DependencyExpression($sDependencyExpression);
|
||||
}
|
||||
}
|
||||
|
||||
public function IsResolved(): bool
|
||||
{
|
||||
return (0 === count($this->aRemainingDependenciesToResolve));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if module dependencies are resolved with current list of module versions
|
||||
* @param array<string, string> $aResolvedModuleVersions : versions by module names dict
|
||||
* @param array<string> $aAllModuleNames : resolved modules names
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function UpdateModuleResolutionState(array $aResolvedModuleVersions, array $aAllModuleNames): void
|
||||
{
|
||||
$aNextDependencies = [];
|
||||
|
||||
foreach ($this->aRemainingDependenciesToResolve as $sDependencyExpression => $oModuleDependency) {
|
||||
/** @var DependencyExpression $oModuleDependency*/
|
||||
$oModuleDependency->UpdateModuleResolutionState($aResolvedModuleVersions, $aAllModuleNames);
|
||||
if (!$oModuleDependency->IsResolved()) {
|
||||
$aNextDependencies[$sDependencyExpression] = $oModuleDependency;
|
||||
}
|
||||
}
|
||||
|
||||
$this->aRemainingDependenciesToResolve = $aNextDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array: list of unique module names
|
||||
*/
|
||||
public function GetUnresolvedDependencyModuleNames(): array
|
||||
{
|
||||
$aRes = [];
|
||||
foreach ($this->aRemainingDependenciesToResolve as $sDependencyExpression => $oModuleDependency) {
|
||||
/** @var DependencyExpression $oModuleDependency */
|
||||
$aRes = array_merge($aRes, $oModuleDependency->GetRemainingModuleNamesToResolve());
|
||||
}
|
||||
|
||||
return array_unique($aRes);
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\ModuleDependency;
|
||||
|
||||
require_once(__DIR__.'/module.class.inc.php');
|
||||
|
||||
use MissingDependencyException;
|
||||
|
||||
/**
|
||||
* Class that sorts module dependencies
|
||||
*/
|
||||
class ModuleDependencySort
|
||||
{
|
||||
private static ModuleDependencySort $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): ModuleDependencySort
|
||||
{
|
||||
if (!isset(static::$oInstance)) {
|
||||
static::$oInstance = new static();
|
||||
}
|
||||
|
||||
return static::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?ModuleDependencySort $oInstance): void
|
||||
{
|
||||
static::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of modules, based on their (inter) dependencies
|
||||
*
|
||||
* @param array $aModules The list of modules to process: 'id' => $aModuleInfo
|
||||
* @param bool $bAbortOnMissingDependency ...
|
||||
*
|
||||
* @return array
|
||||
* @throws \MissingDependencyException
|
||||
*/
|
||||
public function GetModulesOrderedForInstallation($aModules, $bAbortOnMissingDependency = false)
|
||||
{
|
||||
// Filter modules to compute
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$aAllModuleNames = [];
|
||||
foreach ($aModules as $sModuleId => $aModule) {
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
$oModule->SetDependencies($aModule['dependencies']);
|
||||
$aUnresolvedDependencyModules[$sModuleId] = $oModule;
|
||||
$aAllModuleNames[$sModuleName] = true;
|
||||
}
|
||||
|
||||
// Make sure order is deterministic (alphabtical order)
|
||||
ksort($aUnresolvedDependencyModules);
|
||||
|
||||
//Attempt to resolve module dependencies
|
||||
$aOrderedModules = [];
|
||||
$aResolvedModuleVersions = [];
|
||||
$iPreviousUnresolvedCount = -1;
|
||||
//loop until no dependency is resolved
|
||||
while ($iPreviousUnresolvedCount !== count($aUnresolvedDependencyModules)) {
|
||||
$iPreviousUnresolvedCount = count($aUnresolvedDependencyModules);
|
||||
if ($iPreviousUnresolvedCount === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
|
||||
/** @var Module $oModule */
|
||||
$oModule->UpdateModuleResolutionState($aResolvedModuleVersions, $aAllModuleNames);
|
||||
if ($oModule->IsResolved()) {
|
||||
$aOrderedModules[] = $sModuleId;
|
||||
$aResolvedModuleVersions[$oModule->GetModuleName()] = $oModule->GetVersion();
|
||||
unset($aUnresolvedDependencyModules[$sModuleId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report unresolved dependencies
|
||||
if ($bAbortOnMissingDependency && count($aUnresolvedDependencyModules) > 0) {
|
||||
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
|
||||
$aUnresolvedModulesInfo = [];
|
||||
$aModuleDeps = [];
|
||||
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
|
||||
$aModule = $aModules[$sModuleId];
|
||||
$aDepsWithIcons = $oModule->GetDependencyResolutionFeedback();
|
||||
|
||||
$aModuleDeps[] = "{$aModule['label']} (id: $sModuleId) depends on: ".implode(' + ', $aDepsWithIcons);
|
||||
$aUnresolvedModulesInfo[$sModuleId] = ['module' => $aModule, 'dependencies' => $aDepsWithIcons];
|
||||
}
|
||||
$sMessage = "The following modules have unmet dependencies:\n".implode(",\n", $aModuleDeps);
|
||||
$oException = new MissingDependencyException($sMessage);
|
||||
$oException->aModulesInfo = $aUnresolvedModulesInfo;
|
||||
throw $oException;
|
||||
}
|
||||
|
||||
// Return the ordered list, so that the dependencies are met...
|
||||
$aResult = [];
|
||||
foreach ($aOrderedModules as $sId) {
|
||||
$aResult[$sId] = $aModules[$sId];
|
||||
}
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is key as it sorts modules by their dependencies (topological sort).
|
||||
* Modules with less dependencies are first.
|
||||
* When module A depends from module B with same amount of dependencies, moduleB is first.
|
||||
* This order can deal with
|
||||
* - cyclic dependencies
|
||||
* - further versions of same module (name)
|
||||
*
|
||||
* @param array $aUnresolvedDependencyModules : dict of Module objects by moduleId key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function SortModulesByCountOfDepencenciesDescending(array &$aUnresolvedDependencyModules): void
|
||||
{
|
||||
$aCountDepsByModuleId = [];
|
||||
$aDependsOnModuleName = [];
|
||||
|
||||
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
|
||||
/** @var Module $oModule */
|
||||
$aDependsOnModuleName[$oModule->GetModuleName()] = [];
|
||||
}
|
||||
|
||||
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
|
||||
$iInDegreeCounter = 0;
|
||||
/** @var Module $oModule */
|
||||
$aUnresolvedDependencyModuleNames = $oModule->GetUnresolvedDependencyModuleNames();
|
||||
foreach ($aUnresolvedDependencyModuleNames as $sModuleName) {
|
||||
if (array_key_exists($sModuleName, $aDependsOnModuleName)) {
|
||||
$aDependsOnModuleName[$sModuleName][] = $sModuleId;
|
||||
$iInDegreeCounter++;
|
||||
}
|
||||
}
|
||||
//include all modules
|
||||
$iInDegreeCounterIncludingOutsideModules = count($oModule->GetUnresolvedDependencyModuleNames());
|
||||
$aCountDepsByModuleId[$sModuleId] = [$iInDegreeCounter, $iInDegreeCounterIncludingOutsideModules, $sModuleId];
|
||||
}
|
||||
|
||||
$aRes = [];
|
||||
while (count($aUnresolvedDependencyModules) > 0) {
|
||||
asort($aCountDepsByModuleId);
|
||||
|
||||
uasort($aCountDepsByModuleId, function (array $aDeps1, array $aDeps2) {
|
||||
//compare $iInDegreeCounter
|
||||
$res = $aDeps1[0] - $aDeps2[0];
|
||||
if ($res != 0) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
//compare $iInDegreeCounterIncludingOutsideModules
|
||||
$res = $aDeps1[1] - $aDeps2[1];
|
||||
if ($res != 0) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
//alphabetical order at least
|
||||
return strcmp($aDeps1[2], $aDeps2[2]);
|
||||
});
|
||||
|
||||
$bOneLoopAtLeast = false;
|
||||
foreach ($aCountDepsByModuleId as $sModuleId => $iInDegreeCounter) {
|
||||
$oModule = $aUnresolvedDependencyModules[$sModuleId];
|
||||
|
||||
if ($bOneLoopAtLeast && $iInDegreeCounter > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
unset($aUnresolvedDependencyModules[$sModuleId]);
|
||||
unset($aCountDepsByModuleId[$sModuleId]);
|
||||
|
||||
$aRes[$sModuleId] = $oModule;
|
||||
|
||||
//when 2 versions of the same module (name) below array has been removed already
|
||||
if (array_key_exists($oModule->GetModuleName(), $aDependsOnModuleName)) {
|
||||
foreach ($aDependsOnModuleName[$oModule->GetModuleName()] as $sModuleId2) {
|
||||
if (!array_key_exists($sModuleId2, $aCountDepsByModuleId)) {
|
||||
continue;
|
||||
}
|
||||
$aDepCount = $aCountDepsByModuleId[$sModuleId2];
|
||||
$iInDegreeCounter = $aDepCount[0] - 1;
|
||||
$iInDegreeCounterIncludingOutsideModules = $aDepCount[1];
|
||||
$aCountDepsByModuleId[$sModuleId2] = [$iInDegreeCounter, $iInDegreeCounterIncludingOutsideModules, $sModuleId2];
|
||||
}
|
||||
|
||||
unset($aDependsOnModuleName[$oModule->GetModuleName()]);
|
||||
}
|
||||
|
||||
$bOneLoopAtLeast = true;
|
||||
}
|
||||
}
|
||||
|
||||
$aUnresolvedDependencyModules = $aRes;
|
||||
}
|
||||
}
|
||||
148
setup/modulediscovery.class.inc.php
Executable file → Normal file
148
setup/modulediscovery.class.inc.php
Executable file → Normal file
@@ -21,14 +21,10 @@
|
||||
*/
|
||||
|
||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
|
||||
require_once(APPROOT.'setup/modulediscovery/ModuleFileReader.php');
|
||||
require_once(__DIR__.'/moduledependency/moduledependencysort.class.inc.php');
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependencySort;
|
||||
|
||||
class MissingDependencyException extends CoreException
|
||||
{
|
||||
@@ -215,23 +211,76 @@ class ModuleDiscovery
|
||||
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
|
||||
* @return array
|
||||
* @throws \MissingDependencyException
|
||||
*/
|
||||
*/
|
||||
public static function OrderModulesByDependencies($aModules, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
if (is_null($aModulesToLoad)) {
|
||||
$aFilteredModules = $aModules;
|
||||
} else {
|
||||
$aFilteredModules = [];
|
||||
foreach ($aModules as $sModuleId => $aModule) {
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
if (in_array($sModuleName, $aModulesToLoad)) {
|
||||
$aFilteredModules[$sModuleId] = $aModule;
|
||||
}
|
||||
// Order the modules to take into account their inter-dependencies
|
||||
$aDependencies = [];
|
||||
$aSelectedModules = [];
|
||||
foreach ($aModules as $sId => $aModule) {
|
||||
list($sModuleName, ) = self::GetModuleName($sId);
|
||||
if (is_null($aModulesToLoad) || in_array($sModuleName, $aModulesToLoad)) {
|
||||
$aDependencies[$sId] = $aModule['dependencies'];
|
||||
$aSelectedModules[$sModuleName] = true;
|
||||
}
|
||||
}
|
||||
ksort($aDependencies);
|
||||
$aOrderedModules = [];
|
||||
$iLoopCount = 0;
|
||||
while (($iLoopCount < count($aModules)) && (count($aDependencies) > 0)) {
|
||||
foreach ($aDependencies as $sId => $aRemainingDeps) {
|
||||
$bDependenciesSolved = true;
|
||||
foreach ($aRemainingDeps as $sDepId) {
|
||||
if (!self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) {
|
||||
$bDependenciesSolved = false;
|
||||
}
|
||||
}
|
||||
if ($bDependenciesSolved) {
|
||||
$aOrderedModules[] = $sId;
|
||||
unset($aDependencies[$sId]);
|
||||
}
|
||||
}
|
||||
$iLoopCount++;
|
||||
}
|
||||
if ($bAbortOnMissingDependency && count($aDependencies) > 0) {
|
||||
$aModulesInfo = [];
|
||||
$aModuleDeps = [];
|
||||
foreach ($aDependencies as $sId => $aDeps) {
|
||||
$aModule = $aModules[$sId];
|
||||
$aDepsWithIcons = [];
|
||||
foreach ($aDeps as $sIndex => $sDepId) {
|
||||
if (self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) {
|
||||
$aDepsWithIcons[$sIndex] = '✅ '.$sDepId;
|
||||
} else {
|
||||
$aDepsWithIcons[$sIndex] = '❌ '.$sDepId;
|
||||
}
|
||||
}
|
||||
$aModuleDeps[] = "{$aModule['label']} (id: $sId) depends on: ".implode(' + ', $aDepsWithIcons);
|
||||
$aModulesInfo[$sId] = ['module' => $aModule, 'dependencies' => $aDepsWithIcons];
|
||||
}
|
||||
$sMessage = "The following modules have unmet dependencies:\n".implode(",\n", $aModuleDeps);
|
||||
$oException = new MissingDependencyException($sMessage);
|
||||
$oException->aModulesInfo = $aModulesInfo;
|
||||
throw $oException;
|
||||
}
|
||||
// Return the ordered list, so that the dependencies are met...
|
||||
$aResult = [];
|
||||
foreach ($aOrderedModules as $sId) {
|
||||
$aResult[$sId] = $aModules[$sId];
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
return ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aFilteredModules, $bAbortOnMissingDependency);
|
||||
/**
|
||||
* Remove the duplicate modules (i.e. modules with the same name but with a different version) from the supplied list of modules
|
||||
* @param array $aModules
|
||||
* @return array The ordered modules as a duplicate-free list of modules
|
||||
*/
|
||||
public static function RemoveDuplicateModules($aModules)
|
||||
{
|
||||
// No longer needed, kept only for compatibility
|
||||
// The de-duplication is now done directly by the AddModule method
|
||||
return $aModules;
|
||||
}
|
||||
|
||||
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
|
||||
@@ -243,6 +292,73 @@ class ModuleDiscovery
|
||||
return static::$oPhpExpressionEvaluator;
|
||||
}
|
||||
|
||||
protected static function DependencyIsResolved($sDepString, $aOrderedModules, $aSelectedModules)
|
||||
{
|
||||
$bResult = false;
|
||||
$aModuleVersions = [];
|
||||
// Separate the module names from their version for an easier comparison later
|
||||
foreach ($aOrderedModules as $sModuleId) {
|
||||
list($sModuleName, $sVersion) = self::GetModuleName($sModuleId);
|
||||
$aModuleVersions[$sModuleName] = $sVersion;
|
||||
}
|
||||
if (preg_match_all('/([^\(\)&| ]+)/', $sDepString, $aMatches)) {
|
||||
$aReplacements = [];
|
||||
$aPotentialPrerequisites = [];
|
||||
foreach ($aMatches as $aMatch) {
|
||||
foreach ($aMatch as $sModuleId) {
|
||||
// $sModuleId in the dependency string is made of a <name>/<optional_operator><version>
|
||||
// where the operator is < <= = > >= (by default >=)
|
||||
$aModuleMatches = [];
|
||||
if (preg_match('|^([^/]+)/(<?>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches)) {
|
||||
$sModuleName = $aModuleMatches[1];
|
||||
$aPotentialPrerequisites[$sModuleName] = true;
|
||||
$sOperator = $aModuleMatches[2];
|
||||
if ($sOperator == '') {
|
||||
$sOperator = '>=';
|
||||
}
|
||||
$sExpectedVersion = $aModuleMatches[3];
|
||||
if (array_key_exists($sModuleName, $aModuleVersions)) {
|
||||
// module is present, check the version
|
||||
$sCurrentVersion = $aModuleVersions[$sModuleName];
|
||||
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) {
|
||||
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
} else {
|
||||
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
}
|
||||
} else {
|
||||
// module is not present
|
||||
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$bMissingPrerequisite = false;
|
||||
foreach (array_keys($aPotentialPrerequisites) as $sModuleName) {
|
||||
if (array_key_exists($sModuleName, $aSelectedModules)) {
|
||||
// This module is actually a prerequisite
|
||||
if (!array_key_exists($sModuleName, $aModuleVersions)) {
|
||||
$bMissingPrerequisite = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($bMissingPrerequisite) {
|
||||
$bResult = false;
|
||||
} else {
|
||||
$sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $sDepString);
|
||||
try {
|
||||
$bResult = self::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($sBooleanExpr);
|
||||
} catch (ModuleFileReaderException $e) {
|
||||
//logged already
|
||||
echo "Failed to parse the boolean Expression = '$sBooleanExpr'<br/>";
|
||||
}
|
||||
}
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search (on the disk) for all defined iTop modules, load them and returns the list (as an array)
|
||||
* of the possible iTop modules to install
|
||||
|
||||
@@ -89,7 +89,6 @@ class ExtensionInstallation extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeString("source", ["allowed_values" => null, "sql" => "source", "default_value" => null, "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("uninstallable", ["allowed_values" => new ValueSetEnum('yes,no,maybe'), "sql" => "uninstallable", "default_value" => 'yes', "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", ["allowed_values" => null, "sql" => "installed", "default_value" => 'NOW()', "is_null_allowed" => false, "depends_on" => []]));
|
||||
MetaModel::Init_AddAttribute(new AttributeText("description", ["allowed_values" => null, "sql" => "description", "default_value" => null, "is_null_allowed" => true, "depends_on" => []]));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', ['code', 'label', 'version', 'installed', 'source']); // Attributes to be displayed for the complete details
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024 Combodo SAS
|
||||
//
|
||||
// This file is part of iTop.
|
||||
@@ -17,6 +16,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Manage a runtime environment
|
||||
*
|
||||
@@ -33,16 +33,18 @@ require_once APPROOT.'setup/modelfactory.class.inc.php';
|
||||
require_once APPROOT.'setup/compiler.class.inc.php';
|
||||
require_once APPROOT.'setup/extensionsmap.class.inc.php';
|
||||
|
||||
define('MODULE_ACTION_OPTIONAL', 1);
|
||||
define('MODULE_ACTION_MANDATORY', 2);
|
||||
define('MODULE_ACTION_IMPOSSIBLE', 3);
|
||||
define('ROOT_MODULE', '_Root_'); // Convention to store IN MEMORY the name/version of the root module i.e. application
|
||||
define('DATAMODEL_MODULE', 'datamodel'); // Convention to store the version of the datamodel
|
||||
define ('MODULE_ACTION_OPTIONAL', 1);
|
||||
define ('MODULE_ACTION_MANDATORY', 2);
|
||||
define ('MODULE_ACTION_IMPOSSIBLE', 3);
|
||||
define ('ROOT_MODULE', '_Root_'); // Convention to store IN MEMORY the name/version of the root module i.e. application
|
||||
define ('DATAMODEL_MODULE', 'datamodel'); // Convention to store the version of the datamodel
|
||||
|
||||
|
||||
|
||||
class RunTimeEnvironment
|
||||
{
|
||||
public const STATIC_CALL_AUTOSELECT_WHITELIST = [
|
||||
"SetupInfo::ModuleIsSelected",
|
||||
const STATIC_CALL_AUTOSELECT_WHITELIST=[
|
||||
"SetupInfo::ModuleIsSelected"
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -72,10 +74,13 @@ class RunTimeEnvironment
|
||||
public function __construct($sEnvironment = 'production', $bAutoCommit = true)
|
||||
{
|
||||
$this->sFinalEnv = $sEnvironment;
|
||||
if ($bAutoCommit) {
|
||||
if ($bAutoCommit)
|
||||
{
|
||||
// Build directly onto the requested environment
|
||||
$this->sTargetEnv = $sEnvironment;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Build into a temporary target
|
||||
$this->sTargetEnv = $sEnvironment.'-build';
|
||||
}
|
||||
@@ -116,20 +121,25 @@ class RunTimeEnvironment
|
||||
require_once APPROOT.'/setup/moduleinstallation.class.inc.php';
|
||||
|
||||
$sConfigFile = $oConfig->GetLoadedFile();
|
||||
if (strlen($sConfigFile) > 0) {
|
||||
if (strlen($sConfigFile) > 0)
|
||||
{
|
||||
$this->log_info("MetaModel::Startup from $sConfigFile (ModelOnly = $bModelOnly)");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->log_info("MetaModel::Startup (ModelOnly = $bModelOnly)");
|
||||
}
|
||||
|
||||
if (!$bUseCache) {
|
||||
if (!$bUseCache)
|
||||
{
|
||||
// Reset the cache for the first use !
|
||||
MetaModel::ResetAllCaches($this->sTargetEnv);
|
||||
}
|
||||
|
||||
MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false /* $bTraceSourceFiles */, $this->sTargetEnv);
|
||||
|
||||
if ($this->oExtensionsMap === null) {
|
||||
if ($this->oExtensionsMap === null)
|
||||
{
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv);
|
||||
}
|
||||
}
|
||||
@@ -168,23 +178,26 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
$aRes = [
|
||||
ROOT_MODULE => [
|
||||
$aRes = array(
|
||||
ROOT_MODULE => array(
|
||||
'version_db' => '',
|
||||
'name_db' => '',
|
||||
'version_code' => ITOP_VERSION_FULL,
|
||||
'name_code' => ITOP_APPLICATION,
|
||||
],
|
||||
];
|
||||
)
|
||||
);
|
||||
|
||||
$aDirs = is_array($modulesPath) ? $modulesPath : [$modulesPath];
|
||||
$aDirs = is_array($modulesPath) ? $modulesPath : array($modulesPath);
|
||||
$aModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo) {
|
||||
foreach($aModules as $sModuleId => $aModuleInfo)
|
||||
{
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if ($sModuleName == '') {
|
||||
if ($sModuleName == '')
|
||||
{
|
||||
throw new Exception("Missing name for the module: '$sModuleId'");
|
||||
}
|
||||
if ($sModuleVersion == '') {
|
||||
if ($sModuleVersion == '')
|
||||
{
|
||||
// The version must not be empty (it will be used as a criteria to determine wether a module has been installed or not)
|
||||
//throw new Exception("Missing version for the module: '$sModuleId'");
|
||||
$sModuleVersion = '1.0.0';
|
||||
@@ -194,76 +207,95 @@ class RunTimeEnvironment
|
||||
$aModuleInfo['version_db'] = '';
|
||||
$aModuleInfo['version_code'] = $sModuleVersion;
|
||||
|
||||
if (!in_array($sModuleAppVersion, ['1.0.0', '1.0.1', '1.0.2'])) {
|
||||
if (!in_array($sModuleAppVersion, array('1.0.0', '1.0.1', '1.0.2')))
|
||||
{
|
||||
// This module is NOT compatible with the current version
|
||||
$aModuleInfo['install'] = [
|
||||
$aModuleInfo['install'] = array(
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is not compatible with the current version of the application',
|
||||
];
|
||||
} elseif ($aModuleInfo['mandatory']) {
|
||||
$aModuleInfo['install'] = [
|
||||
'message' => 'the module is not compatible with the current version of the application'
|
||||
);
|
||||
}
|
||||
elseif ($aModuleInfo['mandatory'])
|
||||
{
|
||||
$aModuleInfo['install'] = array(
|
||||
'flag' => MODULE_ACTION_MANDATORY,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aModuleInfo['install'] = [
|
||||
'message' => 'the module is part of the application'
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aModuleInfo['install'] = array(
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
'message' => ''
|
||||
);
|
||||
}
|
||||
$aRes[$sModuleName] = $aModuleInfo;
|
||||
}
|
||||
|
||||
try {
|
||||
$aSelectInstall = [];
|
||||
try
|
||||
{
|
||||
$aSelectInstall = array();
|
||||
if (! is_null($oConfig)) {
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install");
|
||||
}
|
||||
} catch (MySQLException $e) {
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
// No database or erroneous information
|
||||
}
|
||||
|
||||
// Build the list of installed module (get the latest installation)
|
||||
//
|
||||
$aInstallByModule = []; // array of <module> => array ('installed' => timestamp, 'version' => <version>)
|
||||
$aInstallByModule = array(); // array of <module> => array ('installed' => timestamp, 'version' => <version>)
|
||||
$iRootId = 0;
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
if (($aInstall['parent_id'] == 0) && ($aInstall['name'] != 'datamodel')) {
|
||||
foreach ($aSelectInstall as $aInstall)
|
||||
{
|
||||
if (($aInstall['parent_id'] == 0) && ($aInstall['name'] != 'datamodel'))
|
||||
{
|
||||
// Root module, what is its ID ?
|
||||
$iId = (int) $aInstall['id'];
|
||||
if ($iId > $iRootId) {
|
||||
if ($iId > $iRootId)
|
||||
{
|
||||
$iRootId = $iId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
foreach ($aSelectInstall as $aInstall)
|
||||
{
|
||||
//$aInstall['comment']; // unsused
|
||||
$iInstalled = strtotime($aInstall['installed']);
|
||||
$sModuleName = $aInstall['name'];
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '') {
|
||||
if ($sModuleVersion == '')
|
||||
{
|
||||
// Though the version cannot be empty in iTop 2.0, it used to be possible
|
||||
// therefore we have to put something here or the module will not be considered
|
||||
// as being installed
|
||||
$sModuleVersion = '0.0.0';
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
if ($aInstall['parent_id'] == 0)
|
||||
{
|
||||
$sModuleName = ROOT_MODULE;
|
||||
} elseif ($aInstall['parent_id'] != $iRootId) {
|
||||
}
|
||||
else if($aInstall['parent_id'] != $iRootId)
|
||||
{
|
||||
// Skip all modules belonging to previous installations
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_key_exists($sModuleName, $aInstallByModule)) {
|
||||
if ($iInstalled < $aInstallByModule[$sModuleName]['installed']) {
|
||||
if (array_key_exists($sModuleName, $aInstallByModule))
|
||||
{
|
||||
if ($iInstalled < $aInstallByModule[$sModuleName]['installed'])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
if ($aInstall['parent_id'] == 0)
|
||||
{
|
||||
$aRes[$sModuleName]['version_db'] = $sModuleVersion;
|
||||
$aRes[$sModuleName]['name_db'] = $aInstall['name'];
|
||||
}
|
||||
@@ -274,33 +306,37 @@ class RunTimeEnvironment
|
||||
|
||||
// Adjust the list of proposed modules
|
||||
//
|
||||
foreach ($aInstallByModule as $sModuleName => $aModuleDB) {
|
||||
if ($sModuleName == ROOT_MODULE) {
|
||||
continue;
|
||||
} // Skip the main module
|
||||
foreach ($aInstallByModule as $sModuleName => $aModuleDB)
|
||||
{
|
||||
if ($sModuleName == ROOT_MODULE) continue; // Skip the main module
|
||||
|
||||
if (!array_key_exists($sModuleName, $aRes)) {
|
||||
if (!array_key_exists($sModuleName, $aRes))
|
||||
{
|
||||
// A module was installed, it is not proposed in the new build... skip
|
||||
continue;
|
||||
}
|
||||
$aRes[$sModuleName]['version_db'] = $aModuleDB['version'];
|
||||
|
||||
if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY) {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY)
|
||||
{
|
||||
$aRes[$sModuleName]['uninstall'] = array(
|
||||
'flag' => MODULE_ACTION_IMPOSSIBLE,
|
||||
'message' => 'the module is part of the application',
|
||||
];
|
||||
} else {
|
||||
$aRes[$sModuleName]['uninstall'] = [
|
||||
'message' => 'the module is part of the application'
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRes[$sModuleName]['uninstall'] = array(
|
||||
'flag' => MODULE_ACTION_OPTIONAL,
|
||||
'message' => '',
|
||||
];
|
||||
'message' => ''
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Config $oConfig
|
||||
*
|
||||
@@ -323,10 +359,10 @@ class RunTimeEnvironment
|
||||
* Return an array with extra directories to scan for extensions/modules to install
|
||||
* @return string[]
|
||||
*/
|
||||
protected function GetExtraDirsToScan($aDirs = [])
|
||||
protected function GetExtraDirsToScan($aDirs = array())
|
||||
{
|
||||
// Do nothing, overload this method if needed
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,22 +381,25 @@ class RunTimeEnvironment
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
|
||||
{
|
||||
$sSourceDirFull = APPROOT.$sSourceDir;
|
||||
if (!is_dir($sSourceDirFull)) {
|
||||
if (!is_dir($sSourceDirFull))
|
||||
{
|
||||
throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
|
||||
}
|
||||
$aDirsToCompile = [$sSourceDirFull];
|
||||
if (is_dir(APPROOT.'extensions')) {
|
||||
$aDirsToCompile = array($sSourceDirFull);
|
||||
if (is_dir(APPROOT.'extensions'))
|
||||
{
|
||||
$aDirsToCompile[] = APPROOT.'extensions';
|
||||
}
|
||||
$sExtraDir = utils::GetDataPath().$this->sTargetEnv.'-modules/';
|
||||
if (is_dir($sExtraDir)) {
|
||||
if (is_dir($sExtraDir))
|
||||
{
|
||||
$aDirsToCompile[] = $sExtraDir;
|
||||
}
|
||||
|
||||
$aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile);
|
||||
$aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs);
|
||||
|
||||
$aRet = [];
|
||||
$aRet = array();
|
||||
|
||||
// Determine the installed modules and extensions
|
||||
//
|
||||
@@ -373,10 +412,12 @@ class RunTimeEnvironment
|
||||
// mark as (automatically) chosen alll the "remote" modules present in the
|
||||
// target environment (data/<target-env>-modules)
|
||||
// The actual choices will be recorded by RecordInstallation below
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv, $aExtraDirs);
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv, true, $aExtraDirs);
|
||||
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
if ($this->IsExtensionSelected($oExtension)) {
|
||||
foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
|
||||
{
|
||||
if($this->IsExtensionSelected($oExtension))
|
||||
{
|
||||
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
@@ -388,23 +429,28 @@ class RunTimeEnvironment
|
||||
|
||||
$oFactory = new ModelFactory($aDirsToCompile);
|
||||
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
|
||||
if (file_exists($sDeltaFile)) {
|
||||
if (file_exists($sDeltaFile))
|
||||
{
|
||||
$oCoreModule = new MFCoreModule('core', 'Core Module', $sDeltaFile);
|
||||
$aRet[$oCoreModule->GetName()] = $oCoreModule;
|
||||
}
|
||||
$sDeltaFile = APPROOT.'application/datamodel.application.xml';
|
||||
if (file_exists($sDeltaFile)) {
|
||||
if (file_exists($sDeltaFile))
|
||||
{
|
||||
$oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile);
|
||||
$aRet[$oApplicationModule->GetName()] = $oApplicationModule;
|
||||
}
|
||||
|
||||
$aModules = $oFactory->FindModules();
|
||||
foreach ($aModules as $oModule) {
|
||||
foreach($aModules as $oModule)
|
||||
{
|
||||
$sModule = $oModule->GetName();
|
||||
$sModuleRootDir = $oModule->GetRootDir();
|
||||
$bIsExtra = $this->oExtensionsMap->ModuleIsChosenAsPartOfAnExtension($sModule, iTopExtension::SOURCE_REMOTE);
|
||||
if (array_key_exists($sModule, $aAvailableModules)) {
|
||||
if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) { //Extra modules are always unless they are 'AutoSelect'
|
||||
if (array_key_exists($sModule, $aAvailableModules))
|
||||
{
|
||||
if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) //Extra modules are always unless they are 'AutoSelect'
|
||||
{
|
||||
$aRet[$oModule->GetName()] = $oModule;
|
||||
}
|
||||
}
|
||||
@@ -413,27 +459,33 @@ class RunTimeEnvironment
|
||||
$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
|
||||
|
||||
// Now process the 'AutoSelect' modules
|
||||
do {
|
||||
do
|
||||
{
|
||||
// Loop while new modules are added...
|
||||
$bModuleAdded = false;
|
||||
foreach ($aModules as $oModule) {
|
||||
if (!array_key_exists($oModule->GetName(), $aRet) && $oModule->IsAutoSelect()) {
|
||||
foreach($aModules as $oModule)
|
||||
{
|
||||
if (!array_key_exists($oModule->GetName(), $aRet) && $oModule->IsAutoSelect())
|
||||
{
|
||||
SetupInfo::SetSelectedModules($aRet);
|
||||
try {
|
||||
try{
|
||||
$bSelected = $oPhpExpressionEvaluator->ParseAndEvaluateBooleanExpression($oModule->GetAutoSelect());
|
||||
if ($bSelected) {
|
||||
if ($bSelected)
|
||||
{
|
||||
$aRet[$oModule->GetName()] = $oModule; // store the Id of the selected module
|
||||
$bModuleAdded = true;
|
||||
}
|
||||
} catch (ModuleFileReaderException $e) {
|
||||
} catch(ModuleFileReaderException $e){
|
||||
//do nothing. logged already
|
||||
}
|
||||
}
|
||||
}
|
||||
} while ($bModuleAdded);
|
||||
}
|
||||
while($bModuleAdded);
|
||||
|
||||
$sDeltaFile = utils::GetDataPath().$this->sTargetEnv.'.delta.xml';
|
||||
if (file_exists($sDeltaFile)) {
|
||||
if (file_exists($sDeltaFile))
|
||||
{
|
||||
$oDelta = new MFDeltaModule($sDeltaFile);
|
||||
$aRet[$oDelta->GetName()] = $oDelta;
|
||||
}
|
||||
@@ -462,8 +514,10 @@ class RunTimeEnvironment
|
||||
//
|
||||
$oFactory = new ModelFactory($sSourceDirFull);
|
||||
$aModulesToCompile = $this->GetMFModulesToCompile($sSourceEnv, $sSourceDir);
|
||||
foreach ($aModulesToCompile as $oModule) {
|
||||
if ($oModule instanceof MFDeltaModule) {
|
||||
foreach ($aModulesToCompile as $oModule)
|
||||
{
|
||||
if ($oModule instanceof MFDeltaModule)
|
||||
{
|
||||
// Just before loading the delta, let's save an image of the datamodel
|
||||
// in case there is no delta the operation will be done after the end of the loop
|
||||
$oFactory->SaveToFile(utils::GetDataPath().'datamodel-'.$this->sTargetEnv.'.xml');
|
||||
@@ -471,6 +525,7 @@ class RunTimeEnvironment
|
||||
$oFactory->LoadModule($oModule);
|
||||
}
|
||||
|
||||
|
||||
if ($oModule instanceof MFDeltaModule) {
|
||||
// A delta was loaded, let's save a second copy of the datamodel
|
||||
$oFactory->SaveToFile(utils::GetDataPath().'datamodel-'.$this->sTargetEnv.'-with-delta.xml');
|
||||
@@ -505,32 +560,45 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function CreateDatabaseStructure(Config $oConfig, $sMode)
|
||||
{
|
||||
if (strlen($oConfig->Get('db_subname')) > 0) {
|
||||
if (strlen($oConfig->Get('db_subname')) > 0)
|
||||
{
|
||||
$this->log_info("Creating the structure in '".$oConfig->Get('db_name')."' (table names prefixed by '".$oConfig->Get('db_subname')."').");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->log_info("Creating the structure in '".$oConfig->Get('db_name')."'.");
|
||||
}
|
||||
|
||||
//MetaModel::CheckDefinitions();
|
||||
if ($sMode == 'install') {
|
||||
if (!MetaModel::DBExists(/* bMustBeComplete */ false)) {
|
||||
MetaModel::DBCreate([$this, 'LogQueryCallback']);
|
||||
if ($sMode == 'install')
|
||||
{
|
||||
if (!MetaModel::DBExists(/* bMustBeComplete */ false))
|
||||
{
|
||||
MetaModel::DBCreate(array($this, 'LogQueryCallback'));
|
||||
$this->log_ok("Database structure successfully created.");
|
||||
} else {
|
||||
if (strlen($oConfig->Get('db_subname')) > 0) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strlen($oConfig->Get('db_subname')) > 0)
|
||||
{
|
||||
throw new Exception("Error: found iTop tables into the database '".$oConfig->Get('db_name')."' (prefix: '".$oConfig->Get('db_subname')."'). Please, try selecting another database instance or specify another prefix to prevent conflicting table names.");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Error: found iTop tables into the database '".$oConfig->Get('db_name')."'. Please, try selecting another database instance or specify a prefix to prevent conflicting table names.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (MetaModel::DBExists(/* bMustBeComplete */ false)) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MetaModel::DBExists(/* bMustBeComplete */ false))
|
||||
{
|
||||
// Have it work fine even if the DB has been set in read-only mode for the users
|
||||
// (fix copied from RunTimeEnvironment::RecordInstallation)
|
||||
$iPrevAccessMode = $oConfig->Get('access_mode');
|
||||
$oConfig->Set('access_mode', ACCESS_FULL);
|
||||
|
||||
MetaModel::DBCreate([$this, 'LogQueryCallback']);
|
||||
MetaModel::DBCreate(array($this, 'LogQueryCallback'));
|
||||
$this->log_ok("Database structure successfully updated.");
|
||||
|
||||
// Check (and update only if it seems needed) the hierarchical keys
|
||||
@@ -558,10 +626,15 @@ class RunTimeEnvironment
|
||||
|
||||
// Restore the previous access mode
|
||||
$oConfig->Set('access_mode', $iPrevAccessMode);
|
||||
} else {
|
||||
if (strlen($oConfig->Get('db_subname')) > 0) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strlen($oConfig->Get('db_subname')) > 0)
|
||||
{
|
||||
throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->Get('db_name')."' (prefix: '".$oConfig->Get('db_subname')."'). Please, try selecting another database instance.");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->Get('db_name')."'. Please, try selecting another database instance.");
|
||||
}
|
||||
}
|
||||
@@ -578,36 +651,46 @@ class RunTimeEnvironment
|
||||
|
||||
// Constant classes (e.g. User profiles)
|
||||
//
|
||||
foreach (MetaModel::GetClasses() as $sClass) {
|
||||
$aPredefinedObjects = call_user_func([
|
||||
foreach (MetaModel::GetClasses() as $sClass)
|
||||
{
|
||||
$aPredefinedObjects = call_user_func(array(
|
||||
$sClass,
|
||||
'GetPredefinedObjects',
|
||||
]);
|
||||
if ($aPredefinedObjects != null) {
|
||||
$this->log_info("$sClass::GetPredefinedObjects() returned ".count($aPredefinedObjects)." elements.");
|
||||
'GetPredefinedObjects'
|
||||
));
|
||||
if ($aPredefinedObjects != null)
|
||||
{
|
||||
$this->log_info("$sClass::GetPredefinedObjects() returned " . count($aPredefinedObjects) . " elements.");
|
||||
|
||||
// Create/Delete/Update objects of this class,
|
||||
// according to the given constant values
|
||||
//
|
||||
$aDBIds = [];
|
||||
$aDBIds = array();
|
||||
$oAll = new DBObjectSet(new DBObjectSearch($sClass));
|
||||
while ($oObj = $oAll->Fetch()) {
|
||||
if (array_key_exists($oObj->GetKey(), $aPredefinedObjects)) {
|
||||
while ($oObj = $oAll->Fetch())
|
||||
{
|
||||
if (array_key_exists($oObj->GetKey(), $aPredefinedObjects))
|
||||
{
|
||||
$aObjValues = $aPredefinedObjects[$oObj->GetKey()];
|
||||
foreach ($aObjValues as $sAttCode => $value) {
|
||||
foreach ($aObjValues as $sAttCode => $value)
|
||||
{
|
||||
$oObj->Set($sAttCode, $value);
|
||||
}
|
||||
$oObj->DBUpdate();
|
||||
$aDBIds[$oObj->GetKey()] = true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
$oObj->DBDelete();
|
||||
}
|
||||
}
|
||||
foreach ($aPredefinedObjects as $iRefId => $aObjValues) {
|
||||
if (! array_key_exists($iRefId, $aDBIds)) {
|
||||
foreach ($aPredefinedObjects as $iRefId => $aObjValues)
|
||||
{
|
||||
if (! array_key_exists($iRefId, $aDBIds))
|
||||
{
|
||||
$oNewObj = MetaModel::NewObject($sClass);
|
||||
$oNewObj->SetKey($iRefId);
|
||||
foreach ($aObjValues as $sAttCode => $value) {
|
||||
foreach ($aObjValues as $sAttCode => $value)
|
||||
{
|
||||
$oNewObj->Set($sAttCode, $value);
|
||||
}
|
||||
$oNewObj->DBInsert();
|
||||
@@ -627,20 +710,22 @@ class RunTimeEnvironment
|
||||
MetaModel::GetConfig()->Set('access_mode', ACCESS_FULL);
|
||||
//$oConfig->Set('access_mode', ACCESS_FULL);
|
||||
|
||||
if (CMDBSource::DBName() == '') {
|
||||
if (CMDBSource::DBName() == '')
|
||||
{
|
||||
// In case this has not yet been done
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
}
|
||||
|
||||
if ($sShortComment === null) {
|
||||
if ($sShortComment === null)
|
||||
{
|
||||
$sShortComment = 'Done by the setup program';
|
||||
}
|
||||
$sMainComment = $sShortComment."\nBuilt on ".ITOP_BUILD_DATE;
|
||||
|
||||
// Record datamodel version
|
||||
$aData = [
|
||||
$aData = array(
|
||||
'source_dir' => $oConfig->Get('source_dir'),
|
||||
];
|
||||
);
|
||||
$iInstallationTime = time(); // Make sure that all modules record the same installation time
|
||||
$oInstallRec = new ModuleInstallation();
|
||||
$oInstallRec->Set('name', DATAMODEL_MODULE);
|
||||
@@ -659,9 +744,10 @@ class RunTimeEnvironment
|
||||
$oInstallRec->Set('installed', $iInstallationTime);
|
||||
$iMainItopRecord = $oInstallRec->DBInsertNoReload();
|
||||
|
||||
|
||||
// Record installed modules and extensions
|
||||
//
|
||||
$aAvailableExtensions = [];
|
||||
$aAvailableExtensions = array();
|
||||
$aAvailableModules = $this->AnalyzeInstallation($oConfig, $this->GetBuildDir());
|
||||
foreach ($aSelectedModuleCodes as $sModuleId) {
|
||||
if (!array_key_exists($sModuleId, $aAvailableModules)) {
|
||||
@@ -671,7 +757,7 @@ class RunTimeEnvironment
|
||||
$sName = $sModuleId;
|
||||
$sVersion = $aModuleData['version_code'];
|
||||
$sUninstallable = $aModuleData['uninstallable'] ?? 'yes';
|
||||
$aComments = [];
|
||||
$aComments = array();
|
||||
$aComments[] = $sShortComment;
|
||||
if ($aModuleData['mandatory']) {
|
||||
$aComments[] = 'Mandatory';
|
||||
@@ -702,24 +788,27 @@ class RunTimeEnvironment
|
||||
$oInstallRec->DBInsertNoReload();
|
||||
}
|
||||
|
||||
if ($this->oExtensionsMap) {
|
||||
if ($this->oExtensionsMap)
|
||||
{
|
||||
// Mark as chosen the selected extensions code passed to us
|
||||
// Note: some other extensions may already be marked as chosen
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
if (in_array($oExtension->sCode, $aSelectedExtensionCodes)) {
|
||||
foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
|
||||
{
|
||||
if (in_array($oExtension->sCode, $aSelectedExtensionCodes))
|
||||
{
|
||||
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->oExtensionsMap->GetChoices() as $oExtension) {
|
||||
foreach($this->oExtensionsMap->GetChoices() as $oExtension)
|
||||
{
|
||||
$oInstallRec = new ExtensionInstallation();
|
||||
$oInstallRec->Set('code', $oExtension->sCode);
|
||||
$oInstallRec->Set('label', $oExtension->sLabel);
|
||||
$oInstallRec->Set('version', $oExtension->sVersion);
|
||||
$oInstallRec->Set('source', $oExtension->sSource);
|
||||
$oInstallRec->Set('source', $oExtension->sSource);
|
||||
$oInstallRec->Set('uninstallable', $oExtension->CanBeUninstalled() ? 'yes' : 'no');
|
||||
$oInstallRec->Set('installed', $iInstallationTime);
|
||||
$oInstallRec->Set('description', $oExtension->sDescription);
|
||||
$oInstallRec->DBInsertNoReload();
|
||||
}
|
||||
}
|
||||
@@ -738,11 +827,14 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function GetApplicationVersion(Config $oConfig)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
CMDBSource::InitFromConfig($oConfig);
|
||||
$sSQLQuery = "SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install";
|
||||
$aSelectInstall = CMDBSource::QueryToArray($sSQLQuery);
|
||||
} catch (MySQLException $e) {
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
// No database or erroneous information
|
||||
$this->log_error('Can not connect to the database: host: '.$oConfig->Get('db_host').', user:'.$oConfig->Get('db_user').', pwd:'.$oConfig->Get('db_pwd').', db name:'.$oConfig->Get('db_name'));
|
||||
$this->log_error('Exception '.$e->getMessage());
|
||||
@@ -751,29 +843,37 @@ class RunTimeEnvironment
|
||||
|
||||
$aResult = [];
|
||||
// Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version
|
||||
foreach ($aSelectInstall as $aInstall) {
|
||||
foreach ($aSelectInstall as $aInstall)
|
||||
{
|
||||
$sModuleVersion = $aInstall['version'];
|
||||
if ($sModuleVersion == '') {
|
||||
if ($sModuleVersion == '')
|
||||
{
|
||||
// Though the version cannot be empty in iTop 2.0, it used to be possible
|
||||
// therefore we have to put something here or the module will not be considered
|
||||
// as being installed
|
||||
$sModuleVersion = '0.0.0';
|
||||
}
|
||||
|
||||
if ($aInstall['parent_id'] == 0) {
|
||||
if ($aInstall['name'] == DATAMODEL_MODULE) {
|
||||
if ($aInstall['parent_id'] == 0)
|
||||
{
|
||||
if ($aInstall['name'] == DATAMODEL_MODULE)
|
||||
{
|
||||
$aResult['datamodel_version'] = $sModuleVersion;
|
||||
$aComments = json_decode($aInstall['comment'], true);
|
||||
if (is_array($aComments)) {
|
||||
if (is_array($aComments))
|
||||
{
|
||||
$aResult = array_merge($aResult, $aComments);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResult['product_name'] = $aInstall['name'];
|
||||
$aResult['product_version'] = $sModuleVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('datamodel_version', $aResult)) {
|
||||
if (!array_key_exists('datamodel_version', $aResult))
|
||||
{
|
||||
// Versions prior to 2.0 did not record the version of the datamodel
|
||||
// so assume that the datamodel version is equal to the application version
|
||||
$aResult['datamodel_version'] = $aResult['product_version'];
|
||||
@@ -784,8 +884,10 @@ class RunTimeEnvironment
|
||||
|
||||
public static function MakeDirSafe($sDir)
|
||||
{
|
||||
if (!is_dir($sDir)) {
|
||||
if (!@mkdir($sDir)) {
|
||||
if (!is_dir($sDir))
|
||||
{
|
||||
if (!@mkdir($sDir))
|
||||
{
|
||||
throw new Exception("Failed to create directory '$sDir', please check that the web server process has enough rights to create the directory.");
|
||||
}
|
||||
@chmod($sDir, 0770); // RWX for owner and group, nothing for others
|
||||
@@ -824,7 +926,8 @@ class RunTimeEnvironment
|
||||
{
|
||||
$sSetupQueriesFilePath = SetupUtils::GetSetupQueriesFilePath();
|
||||
$hSetupQueriesFile = @fopen($sSetupQueriesFilePath, 'a');
|
||||
if ($hSetupQueriesFile !== false) {
|
||||
if ($hSetupQueriesFile !== false)
|
||||
{
|
||||
fwrite($hSetupQueriesFile, "$sQuery\n");
|
||||
fclose($hSetupQueriesFile);
|
||||
}
|
||||
@@ -833,9 +936,10 @@ class RunTimeEnvironment
|
||||
public function GetCurrentDataModelVersion()
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT ModuleInstallation WHERE name='".DATAMODEL_MODULE."'");
|
||||
$oSet = new DBObjectSet($oSearch, ['installed' => false]);
|
||||
$oSet = new DBObjectSet($oSearch, array('installed' => false));
|
||||
$oLatestDM = $oSet->Fetch();
|
||||
if ($oLatestDM == null) {
|
||||
if ($oLatestDM == null)
|
||||
{
|
||||
return '0.0.0';
|
||||
}
|
||||
return $oLatestDM->Get('version');
|
||||
@@ -843,9 +947,12 @@ class RunTimeEnvironment
|
||||
|
||||
public function Commit()
|
||||
{
|
||||
if ($this->sFinalEnv != $this->sTargetEnv) {
|
||||
if (file_exists(utils::GetDataPath().$this->sTargetEnv.'.delta.xml')) {
|
||||
if (file_exists(utils::GetDataPath().$this->sFinalEnv.'.delta.xml')) {
|
||||
if ($this->sFinalEnv != $this->sTargetEnv)
|
||||
{
|
||||
if (file_exists(utils::GetDataPath().$this->sTargetEnv.'.delta.xml'))
|
||||
{
|
||||
if (file_exists(utils::GetDataPath().$this->sFinalEnv.'.delta.xml'))
|
||||
{
|
||||
// Make a "previous" file
|
||||
copy(
|
||||
utils::GetDataPath().$this->sFinalEnv.'.delta.xml',
|
||||
@@ -906,24 +1013,34 @@ class RunTimeEnvironment
|
||||
*/
|
||||
protected function CommitFile($sSource, $sDest, $bSourceMustExist = true)
|
||||
{
|
||||
if (file_exists($sSource)) {
|
||||
if (file_exists($sSource))
|
||||
{
|
||||
SetupUtils::builddir(dirname($sDest));
|
||||
if (file_exists($sDest)) {
|
||||
if (file_exists($sDest))
|
||||
{
|
||||
$bRes = @unlink($sDest);
|
||||
if (!$bRes) {
|
||||
if (!$bRes)
|
||||
{
|
||||
throw new Exception('Commit - Failed to cleanup destination file: '.$sDest);
|
||||
}
|
||||
}
|
||||
rename($sSource, $sDest);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// The file does not exist
|
||||
if ($bSourceMustExist) {
|
||||
if ($bSourceMustExist)
|
||||
{
|
||||
throw new Exception('Commit - Missing file: '.$sSource);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Align the destination with the source... make sure there is NO file
|
||||
if (file_exists($sDest)) {
|
||||
if (file_exists($sDest))
|
||||
{
|
||||
$bRes = @unlink($sDest);
|
||||
if (!$bRes) {
|
||||
if (!$bRes)
|
||||
{
|
||||
throw new Exception('Commit - Failed to cleanup destination file: '.$sDest);
|
||||
}
|
||||
}
|
||||
@@ -942,15 +1059,22 @@ class RunTimeEnvironment
|
||||
*/
|
||||
protected function CommitDir($sSource, $sDest, $bSourceMustExist = true, $bRemoveSource = true)
|
||||
{
|
||||
if (file_exists($sSource)) {
|
||||
if (file_exists($sSource))
|
||||
{
|
||||
SetupUtils::movedir($sSource, $sDest, $bRemoveSource);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// The file does not exist
|
||||
if ($bSourceMustExist) {
|
||||
if ($bSourceMustExist)
|
||||
{
|
||||
throw new Exception('Commit - Missing directory: '.$sSource);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Align the destination with the source... make sure there is NO file
|
||||
if (file_exists($sDest)) {
|
||||
if (file_exists($sDest))
|
||||
{
|
||||
SetupUtils::rrmdir($sDest);
|
||||
}
|
||||
}
|
||||
@@ -959,7 +1083,8 @@ class RunTimeEnvironment
|
||||
|
||||
public function Rollback()
|
||||
{
|
||||
if ($this->sFinalEnv != $this->sTargetEnv) {
|
||||
if ($this->sFinalEnv != $this->sTargetEnv)
|
||||
{
|
||||
SetupUtils::tidydir(APPROOT.'env-'.$this->sTargetEnv);
|
||||
}
|
||||
}
|
||||
@@ -973,8 +1098,10 @@ class RunTimeEnvironment
|
||||
*/
|
||||
public function CallInstallerHandlers($aAvailableModules, $aSelectedModules, $sHandlerName)
|
||||
{
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules)) {
|
||||
foreach($aAvailableModules as $sModuleId => $aModule)
|
||||
{
|
||||
if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules))
|
||||
{
|
||||
$aArgs = [MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code']];
|
||||
RunTimeEnvironment::CallInstallerHandler($aAvailableModules[$sModuleId], $sHandlerName, $aArgs);
|
||||
}
|
||||
@@ -993,13 +1120,14 @@ class RunTimeEnvironment
|
||||
public static function CallInstallerHandler(array $aModuleInfo, $sHandlerName, array $aArgs)
|
||||
{
|
||||
$sModuleInstallerClass = ModuleFileReader::GetInstance()->GetAndCheckModuleInstallerClass($aModuleInfo);
|
||||
if (is_null($sModuleInstallerClass)) {
|
||||
if (is_null($sModuleInstallerClass)){
|
||||
return;
|
||||
}
|
||||
|
||||
SetupLog::Debug("Calling Module Handler: $sModuleInstallerClass::$sHandlerName");
|
||||
SetupLog::Info("Calling Module Handler: $sModuleInstallerClass::$sHandlerName", null, $aArgs);
|
||||
$aCallSpec = [$sModuleInstallerClass, $sHandlerName];
|
||||
if (is_callable($aCallSpec)) {
|
||||
if (is_callable($aCallSpec))
|
||||
{
|
||||
try {
|
||||
call_user_func_array($aCallSpec, $aArgs);
|
||||
} catch (Exception $e) {
|
||||
@@ -1032,8 +1160,8 @@ class RunTimeEnvironment
|
||||
SetupLog::Info("starting data load session");
|
||||
$oDataLoader->StartSession($oMyChange);
|
||||
|
||||
$aFiles = [];
|
||||
$aPreviouslyLoadedFiles = [];
|
||||
$aFiles = array();
|
||||
$aPreviouslyLoadedFiles = array();
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId != ROOT_MODULE)) {
|
||||
$sRelativePath = 'env-'.$this->sTargetEnv.'/'.basename($aModule['root_dir']);
|
||||
@@ -1044,15 +1172,22 @@ class RunTimeEnvironment
|
||||
if ($bSampleData) {
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load only structural data
|
||||
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
}
|
||||
} else {
|
||||
if ($bSampleData) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($bSampleData)
|
||||
{
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load only structural data
|
||||
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
|
||||
}
|
||||
@@ -1064,10 +1199,12 @@ class RunTimeEnvironment
|
||||
// Simulate the load of the previously loaded files, in order to initialize
|
||||
// the mapping between the identifiers in the XML and the actual identifiers
|
||||
// in the current database
|
||||
foreach ($aPreviouslyLoadedFiles as $sFileRelativePath) {
|
||||
foreach($aPreviouslyLoadedFiles as $sFileRelativePath)
|
||||
{
|
||||
$sFileName = APPROOT.$sFileRelativePath;
|
||||
SetupLog::Info("Loading file: $sFileName (just to get the keys mapping)");
|
||||
if (empty($sFileName) || !file_exists($sFileName)) {
|
||||
if (empty($sFileName) || !file_exists($sFileName))
|
||||
{
|
||||
throw(new Exception("File $sFileName does not exist"));
|
||||
}
|
||||
|
||||
@@ -1076,10 +1213,12 @@ class RunTimeEnvironment
|
||||
SetupLog::Info($sResult);
|
||||
}
|
||||
|
||||
foreach ($aFiles as $sFileRelativePath) {
|
||||
foreach($aFiles as $sFileRelativePath)
|
||||
{
|
||||
$sFileName = APPROOT.$sFileRelativePath;
|
||||
SetupLog::Info("Loading file: $sFileName");
|
||||
if (empty($sFileName) || !file_exists($sFileName)) {
|
||||
if (empty($sFileName) || !file_exists($sFileName))
|
||||
{
|
||||
throw(new Exception("File $sFileName does not exist"));
|
||||
}
|
||||
|
||||
@@ -1101,8 +1240,9 @@ class RunTimeEnvironment
|
||||
*/
|
||||
protected static function MergeWithRelativeDir($aSourceArray, $sBaseDir, $aFilesToMerge)
|
||||
{
|
||||
$aToMerge = [];
|
||||
foreach ($aFilesToMerge as $sFile) {
|
||||
$aToMerge = array();
|
||||
foreach($aFilesToMerge as $sFile)
|
||||
{
|
||||
$aToMerge[] = $sBaseDir.'/'.$sFile;
|
||||
}
|
||||
return array_merge($aSourceArray, $aToMerge);
|
||||
@@ -1118,8 +1258,10 @@ class RunTimeEnvironment
|
||||
{
|
||||
$iCount = 0;
|
||||
$fStart = microtime(true);
|
||||
foreach (MetaModel::GetClasses() as $sClass) {
|
||||
if (false == MetaModel::HasTable($sClass) && MetaModel::IsAbstract($sClass)) {
|
||||
foreach(MetaModel::GetClasses() as $sClass)
|
||||
{
|
||||
if (false == MetaModel::HasTable($sClass) && MetaModel::IsAbstract($sClass))
|
||||
{
|
||||
//if a class is not persisted and is abstract, the code below would crash. Needed by the class AbstractRessource. This is tolerable to skip this because we check the setup process integrity, not the datamodel integrity.
|
||||
continue;
|
||||
}
|
||||
@@ -1128,21 +1270,24 @@ class RunTimeEnvironment
|
||||
$oSearch->SetShowObsoleteData(false);
|
||||
$oSQLQuery = $oSearch->GetSQLQueryStructure(null, false);
|
||||
$sViewName = MetaModel::DBGetView($sClass);
|
||||
if (strlen($sViewName) > 64) {
|
||||
if (strlen($sViewName) > 64)
|
||||
{
|
||||
throw new Exception("Class name too long for class: '$sClass'. The name of the corresponding view ($sViewName) would exceed MySQL's limit for the name of a table (64 characters).");
|
||||
}
|
||||
$sTableName = MetaModel::DBGetTable($sClass);
|
||||
if (strlen($sTableName) > 64) {
|
||||
if (strlen($sTableName) > 64)
|
||||
{
|
||||
throw new Exception("Table name too long for class: '$sClass'. The name of the corresponding MySQL table ($sTableName) would exceed MySQL's limit for the name of a table (64 characters).");
|
||||
}
|
||||
$iTableCount = $oSQLQuery->CountTables();
|
||||
if ($iTableCount > 61) {
|
||||
if ($iTableCount > 61)
|
||||
{
|
||||
throw new Exception("Class requiring too many tables: '$sClass'. The structure of the class ($sClass) would require a query with more than 61 JOINS (MySQL's limitation).");
|
||||
}
|
||||
$iCount++;
|
||||
}
|
||||
$fDuration = microtime(true) - $fStart;
|
||||
|
||||
return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration * 1000.0);
|
||||
return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration*1000.0);
|
||||
}
|
||||
} // End of class
|
||||
|
||||
@@ -79,7 +79,7 @@ class InstallationFileService
|
||||
public function GetItopExtensionsMap(): ItopExtensionsMap
|
||||
{
|
||||
if (is_null($this->oItopExtensionsMap)) {
|
||||
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment);
|
||||
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment, true);
|
||||
}
|
||||
return $this->oItopExtensionsMap;
|
||||
}
|
||||
|
||||
@@ -210,12 +210,12 @@ HTML;
|
||||
}
|
||||
}
|
||||
$oPage->LinkScriptFromAppRoot('setup/setup.js');
|
||||
$oPage->add_script("function CanMoveForward()\n{\n".$oStep->JSCanMoveForward()."\n}\n");
|
||||
$oPage->add_script("function CanMoveBackward()\n{\n".$oStep->JSCanMoveBackward()."\n}\n");
|
||||
$oPage->add('<form id="wiz_form" class="ibo-setup--wizard" method="post">');
|
||||
$oPage->add('<div class="ibo-setup--wizard--content">');
|
||||
$oStep->Display($oPage);
|
||||
$oPage->add('</div>');
|
||||
$oPage->add_script("function CanMoveForward()\n{\n".$oStep->JSCanMoveForward()."\n}\n");
|
||||
$oPage->add_script("function CanMoveBackward()\n{\n".$oStep->JSCanMoveBackward()."\n}\n");
|
||||
|
||||
// Add the back / next buttons and the hidden form
|
||||
// to store the parameters
|
||||
|
||||
@@ -1310,16 +1310,14 @@ EOF
|
||||
*/
|
||||
class WizStepModulesChoice extends WizardStep
|
||||
{
|
||||
protected static string $SEP = '_';
|
||||
protected bool $bUpgrade = false;
|
||||
protected bool $bCanMoveForward = true;
|
||||
protected ?Config $oConfig = null;
|
||||
protected static $SEP = '_';
|
||||
protected $bUpgrade = false;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var iTopExtensionsMap
|
||||
*/
|
||||
protected iTopExtensionsMap $oExtensionsMap;
|
||||
protected $oExtensionsMap;
|
||||
|
||||
protected PhpExpressionEvaluator $oPhpExpressionEvaluator;
|
||||
|
||||
@@ -1327,7 +1325,7 @@ class WizStepModulesChoice extends WizardStep
|
||||
* Whether we were able to load the choices from the database or not
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $bChoicesFromDatabase;
|
||||
protected $bChoicesFromDatabase;
|
||||
|
||||
public function __construct(WizardController $oWizard, $sCurrentState)
|
||||
{
|
||||
@@ -1345,13 +1343,12 @@ class WizStepModulesChoice extends WizardStep
|
||||
// only called if the config file exists : we are updating a previous installation !
|
||||
// WARNING : we can't load this config directly, as it might be from another directory with a different approot_url (N°2684)
|
||||
if ($sConfigPath !== null) {
|
||||
$this->oConfig = new Config($sConfigPath);
|
||||
$oConfig = new Config($sConfigPath);
|
||||
|
||||
$aParamValues = $oWizard->GetParamForConfigArray();
|
||||
$this->oConfig->UpdateFromParams($aParamValues);
|
||||
$oConfig->UpdateFromParams($aParamValues);
|
||||
|
||||
$this->oExtensionsMap->LoadChoicesFromDatabase($this->oConfig);
|
||||
$this->bChoicesFromDatabase = true;
|
||||
$this->bChoicesFromDatabase = $this->oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1455,7 +1452,7 @@ class WizStepModulesChoice extends WizardStep
|
||||
}
|
||||
$oPage->add('<img src="'.$sBannerUrl.'"/>');
|
||||
}
|
||||
$sDescription = $aStepInfo['description'] ?? '';
|
||||
$sDescription = isset($aStepInfo['description']) ? $aStepInfo['description'] : '';
|
||||
$oPage->add('<span>'.$sDescription.'</span>');
|
||||
$oPage->add('</div>');
|
||||
|
||||
@@ -1849,7 +1846,6 @@ EOF
|
||||
}
|
||||
return $index;
|
||||
}
|
||||
|
||||
protected function GetStepInfo($idx = null)
|
||||
{
|
||||
$aStepInfo = null;
|
||||
@@ -1870,12 +1866,12 @@ EOF
|
||||
// Additional step for the "extensions"
|
||||
$aStepDefinition = [
|
||||
'title' => 'Extensions',
|
||||
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions or remove installed ones.</h2>',
|
||||
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions, but you cannot remove already installed extensions.</h2>',
|
||||
'banner' => '/images/icons/icons8-puzzle.svg',
|
||||
'options' => [],
|
||||
];
|
||||
|
||||
foreach ($this->oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) {
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
if (($oExtension->sSource !== iTopExtension::SOURCE_WIZARD) && ($oExtension->bVisible) && (count($oExtension->aMissingDependencies) == 0)) {
|
||||
$aStepDefinition['options'][] = [
|
||||
'extension_code' => $oExtension->sCode,
|
||||
@@ -1886,19 +1882,16 @@ EOF
|
||||
'modules' => $oExtension->aModules,
|
||||
'mandatory' => $oExtension->bMandatory || ($oExtension->sSource === iTopExtension::SOURCE_REMOTE),
|
||||
'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource),
|
||||
'uninstallable' => $oExtension->CanBeUninstalled(),
|
||||
'missing' => $oExtension->bRemovedFromDisk,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Display this step of the wizard only if there is something to display
|
||||
if (count($aStepDefinition['options']) !== 0) {
|
||||
$aSteps[] = $aStepDefinition;
|
||||
$this->oWizard->SetParameter('additional_extensions_modules', json_encode($aStepDefinition['options']));
|
||||
}
|
||||
} else {
|
||||
// No wizard configuration provided, build a standard one with just one big list. All items are mandatory, only works when there are no conflicted modules.
|
||||
// No wizard configuration provided, build a standard one with just one big list
|
||||
$aStepDefinition = [
|
||||
'title' => 'Modules Selection',
|
||||
'description' => '<h2>Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.</h2>',
|
||||
@@ -1965,41 +1958,18 @@ EOF
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
$bIsDefault = array_key_exists($sChoiceId, $aDefaults);
|
||||
|
||||
$oITopExtension = $this->oExtensionsMap->GetFromExtensionCode($aChoice['extension_code']);
|
||||
$bCanBeUninstalled = isset($aChoice['uninstallable']) ? $aChoice['uninstallable'] : $oITopExtension->CanBeUninstalled();
|
||||
$bCanBeUninstalled = $this->oExtensionsMap->Get($aChoice['extension_code'])->CanBeUninstalled();
|
||||
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
|
||||
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || $this->bUpgrade && $bIsDefault && !$bCanBeUninstalled && !$bDisableUninstallCheck;
|
||||
;
|
||||
$bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true;
|
||||
$bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled;
|
||||
$bDisabled = $bMandatory || $bAllDisabled || $bMissingFromDisk;
|
||||
$bDisabled = $bMandatory || $bAllDisabled;
|
||||
$bChecked = $bMandatory || $bSelected;
|
||||
|
||||
$sTooltip = '';
|
||||
$sUnremovable = '';
|
||||
if ($bMissingFromDisk) {
|
||||
$sTooltip .= '<span class="setup-extension-tag removed">source removed</span>';
|
||||
}
|
||||
if ($bInstalled) {
|
||||
$sTooltip .= '<span class="setup-extension-tag checked installed">installed</span>';
|
||||
$sTooltip .= '<span class="setup-extension-tag unchecked tobeuninstalled">to be uninstalled</span>';
|
||||
} else {
|
||||
$sTooltip .= '<span class="setup-extension-tag checked tobeinstalled">to be installed</span>';
|
||||
$sTooltip .= '<span class="setup-extension-tag unchecked notinstalled">not installed</span>';
|
||||
}
|
||||
if (!$bCanBeUninstalled) {
|
||||
$sTooltip .= '<span class="setup-extension-tag notuninstallable">cannot be uninstalled</span>';
|
||||
}
|
||||
if ($bDisabled && !$bChecked && !$bCanBeUninstalled && !$bDisableUninstallCheck) {
|
||||
$this->bCanMoveForward = false;//Disable "Next"
|
||||
}
|
||||
$sChecked = $bChecked ? ' checked ' : '';
|
||||
$sDisabled = $bDisabled ? ' disabled data-disabled="disabled" ' : '';
|
||||
$sMissingModule = $bMissingFromDisk ? 'setup-extension--missing' : '';
|
||||
|
||||
$sUnremovable = !$bCanBeUninstalled ? ' unremovable ' : '';
|
||||
$sHiddenInput = $bDisabled && $bChecked ? '<input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'"/>' : '';
|
||||
$oPage->add('<div class="choice '.$sMissingModule.'" '.$sDataId.'><input class="wiz-choice '.$sUnremovable.'" id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'" '.$sDisabled.$sChecked.'/>'.$sHiddenInput.' ');
|
||||
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled, $sTooltip);
|
||||
$oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice '.$sUnremovable.'" id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'" '.$sDisabled.$sChecked.'/>'.$sHiddenInput.' ');
|
||||
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled, $bCanBeUninstalled);
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
$sChoiceName = null;
|
||||
@@ -2060,13 +2030,14 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false, $sTooltip = '')
|
||||
protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false, $bUninstallable = true)
|
||||
{
|
||||
$sMoreInfo = (isset($aChoice['more_info']) && ($aChoice['more_info'] != '')) ? '<a class="setup--wizard-choice--more-info" target="_blank" href="'.$aChoice['more_info'].'">More information</a>' : '';
|
||||
$sSourceLabel = $aChoice['source_label'] ?? '';
|
||||
$sSourceLabel = isset($aChoice['source_label']) ? $aChoice['source_label'] : '';
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
$sUninstallationWarning = $bUninstallable ? '' : '<span style="color:orangered" title="Once this extension has been installed, it cannot be removed">(!)</span>';
|
||||
|
||||
$oPage->add('<label class="setup--wizard-choice--label" for="'.$sId.'">'.$sSourceLabel.'<b>'.utils::EscapeHtml($aChoice['title']).'</b>'.' '.$sTooltip.'</label> '.$sMoreInfo.'');
|
||||
$oPage->add('<label class="setup--wizard-choice--label" for="'.$sId.'">'.$sSourceLabel.'<b>'.utils::EscapeHtml($aChoice['title']).'</b>'.'</label> '.$sUninstallationWarning.' '.$sMoreInfo.'');
|
||||
$sDescription = isset($aChoice['description']) ? utils::EscapeHtml($aChoice['description']) : '';
|
||||
$oPage->add('<div class="setup--wizard-choice--description description">'.$sDescription.'<span id="sub_choices'.$sId.'">');
|
||||
if (isset($aChoice['sub_options'])) {
|
||||
@@ -2081,22 +2052,6 @@ EOF
|
||||
return $sSourceDir.'/installation.xml';
|
||||
}
|
||||
|
||||
public function CanMoveForward()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function JSCanMoveForward()
|
||||
{
|
||||
|
||||
return $this->bCanMoveForward ? 'return true;' : 'return false;';
|
||||
}
|
||||
|
||||
public function GetNextButtonLabel()
|
||||
{
|
||||
return $this->bCanMoveForward ? 'Next' : 'Non-uninstallable extension missing';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2747,7 +2747,7 @@ class SynchroReplica extends DBObject implements iDisplay
|
||||
$aRows = [];
|
||||
foreach ($aData as $sKey => $value) {
|
||||
if (strpos(CMDBSource::GetFieldType($sSQLTable, $sKey), 'blob') !== false) {
|
||||
$aRows[] = ['attcode' => $sKey, 'data' => sprintf('<i>%s (%s)</i>', Dict::S('Core:AttributeBlob'), utils::BytesToFriendlyFormat(utils::StrLen($value)))];
|
||||
$aRows[] = ['attcode' => $sKey, 'data' => sprintf('<i>%s (%s)</i>', Dict::S('Core:AttributeBlob'), utils::BytesToFriendlyFormat(strlen($value)))];
|
||||
} else {
|
||||
$aRows[] = ['attcode' => $sKey, 'data' => utils::EscapeHtml($value)];
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
<testsuite name="ModuleIntegration">
|
||||
<file>integration-tests/DictionariesConsistencyAfterSetupTest.php</file>
|
||||
<file>integration-tests/DictionariesConsistencyTest.php</file>
|
||||
<file>integration-tests/iTopModulesDependencyValidationServiceTest.php</file>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
|
||||
@@ -650,29 +650,6 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBObject $oUser
|
||||
* @param int $iProfileId
|
||||
*
|
||||
* @return \DBObject
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function RemoveProfileFromUser($oUser, $iProfileId)
|
||||
{
|
||||
/** @var \ormLinkSet $oSet */
|
||||
$oSet = $oUser->Get('profile_list');
|
||||
foreach ($oSet as $oUserProfile) {
|
||||
if ($oUserProfile->Get('profileid') == $iProfileId) {
|
||||
$oSet->RemoveItem($oUserProfile->GetKey());
|
||||
break;
|
||||
}
|
||||
}
|
||||
$oUser = $this->updateObject(User::class, $oUser->GetKey(), [
|
||||
'profile_list' => $oSet,
|
||||
]);
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Hypervisor in database
|
||||
*
|
||||
|
||||
@@ -35,7 +35,6 @@ use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use DeleteException;
|
||||
use MetaModel;
|
||||
use UserLocal;
|
||||
use UserRights;
|
||||
use utils;
|
||||
|
||||
@@ -81,21 +80,6 @@ class UserRightsTest extends ItopDataTestCase
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
protected function GivenUserWithProfiles(string $sLogin, array $aProfileIds): DBObject
|
||||
{
|
||||
$oProfiles = new \ormLinkSet(\UserLocal::class, 'profile_list', \DBObjectSet::FromScratch(\URP_UserProfile::class));
|
||||
foreach ($aProfileIds as $iProfileId) {
|
||||
$oProfiles->AddItem(MetaModel::NewObject('URP_UserProfile', ['profileid' => $iProfileId, 'reason' => 'UNIT Tests']));
|
||||
}
|
||||
$oUser = MetaModel::NewObject('UserLocal', [
|
||||
'login' => $sLogin,
|
||||
'password' => 'Password1!',
|
||||
'expiration' => UserLocal::EXPIRE_NEVER,
|
||||
'profile_list' => $oProfiles,
|
||||
]);
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
public function testIsLoggedIn()
|
||||
{
|
||||
$this->assertFalse(UserRights::IsLoggedIn());
|
||||
@@ -295,79 +279,87 @@ class UserRightsTest extends ItopDataTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider UserCannotLoseConsoleAccessProvider
|
||||
* @dataProvider ProfileDenyingConsoleProvider
|
||||
* @doesNotPerformAssertions
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUserCannotLoseConsoleAccess(int $iProfileId)
|
||||
public function testProfileDenyingConsole(int $iProfileId)
|
||||
{
|
||||
$oUser = $this->CreateUniqueUserAndLogin('test1', $iProfileId);
|
||||
|
||||
$this->expectException(CoreCannotSaveObjectException::class);
|
||||
$this->expectExceptionMessage('Profile "Portal user" cannot be added it will deny the access to backoffice');
|
||||
$this->AddProfileToUser($oUser, 2);
|
||||
try {
|
||||
$this->AddProfileToUser($oUser, 2);
|
||||
$this->fail('Profile should not be added');
|
||||
} catch (CoreCannotSaveObjectException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function UserCannotLoseConsoleAccessProvider(): array
|
||||
public function ProfileDenyingConsoleProvider(): array
|
||||
{
|
||||
return [
|
||||
'Administrator' => [1],
|
||||
'SuperUser' => [117],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider UserCannotElevateTheirOwnRightsProvider
|
||||
* @dataProvider ProfileCannotModifySelfProvider
|
||||
* @doesNotPerformAssertions
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUserCannotElevateTheirOwnRights(int $iCurrentProfileId, int $iElevatedProfileId)
|
||||
{
|
||||
$oUser = $this->CreateUniqueUserAndLogin('test1', $iCurrentProfileId);
|
||||
|
||||
$this->expectException(CoreCannotSaveObjectException::class);
|
||||
$this->AddProfileToUser($oUser, $iElevatedProfileId);
|
||||
}
|
||||
|
||||
public function UserCannotElevateTheirOwnRightsProvider(): array
|
||||
{
|
||||
return [
|
||||
'Configuration manager to SuperUser' => ['current' => 3, 'added' => 117],
|
||||
'Configuration manager to Administrator' => ['current' => 3, 'added' => 1],
|
||||
'SuperUser to Administrator' => ['current' => 117, 'added' => 1],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider UserCannotDeleteOwnUserProvider
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUserCannotDeleteOwnUser(int $iProfileId)
|
||||
public function testProfileCannotModifySelf(int $iProfileId)
|
||||
{
|
||||
$oUser = $this->CreateUniqueUserAndLogin('test1', $iProfileId);
|
||||
|
||||
$this->expectException(DeleteException::class);
|
||||
$oUser->DBDelete();
|
||||
try {
|
||||
$this->AddProfileToUser($oUser, 1); // trying to become an admin
|
||||
$this->fail('User should not modify self');
|
||||
} catch (CoreException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function UserCannotDeleteOwnUserProvider(): array
|
||||
public function ProfileCannotModifySelfProvider(): array
|
||||
{
|
||||
return [
|
||||
'Configuration manager' => [3],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider DeletingSelfUserProvider
|
||||
* @doesNotPerformAssertions
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testDeletingSelfUser(int $iProfileId)
|
||||
{
|
||||
$oUser = $this->CreateUniqueUserAndLogin('test1', $iProfileId);
|
||||
|
||||
try {
|
||||
$oUser->DBDelete();
|
||||
$this->fail('Current User cannot be deleted');
|
||||
} catch (DeleteException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function DeletingSelfUserProvider(): array
|
||||
{
|
||||
return [
|
||||
'Administrator' => [1],
|
||||
'Configuration manager' => [3],
|
||||
'SuperUser' => [117],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider UserCannotRemoveOwnContactProvider
|
||||
* @dataProvider RemovingOwnContactProvider
|
||||
* @doesNotPerformAssertions
|
||||
*
|
||||
* @param int $iProfileId
|
||||
*
|
||||
@@ -375,83 +367,68 @@ class UserRightsTest extends ItopDataTestCase
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUserCannotRemoveOwnContact(int $iProfileId)
|
||||
public function testRemovingOwnContact(int $iProfileId)
|
||||
{
|
||||
$oUser = $this->CreateUniqueUserAndLogin('test1', $iProfileId);
|
||||
|
||||
$oUser->Set('contactid', 0);
|
||||
$this->expectException(CoreCannotSaveObjectException::class);
|
||||
$oUser->DBWrite();
|
||||
|
||||
try {
|
||||
$oUser->DBWrite();
|
||||
$this->fail('Current User cannot remove his own contact');
|
||||
} catch (CoreCannotSaveObjectException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function UserCannotRemoveOwnContactProvider(): array
|
||||
public function RemovingOwnContactProvider(): array
|
||||
{
|
||||
return [
|
||||
'Administrator' => [1],
|
||||
'Configuration manager' => [3],
|
||||
'SuperUser' => [117],
|
||||
];
|
||||
}
|
||||
|
||||
public function testAdminCannotRemoveOwnAdminProfile()
|
||||
/**
|
||||
* @doesNotPerformAssertions
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUpgradingToAdmin()
|
||||
{
|
||||
$oUser = $this->CreateUniqueUserAndLogin('admin111', 1); // Administrator
|
||||
// Keep only the SuperUser profile (remove Administrator profile)
|
||||
$this->AddProfileToUser($oUser, 117); // SuperUser profile for the test
|
||||
$oUser = $this->CreateUniqueUserAndLogin('test1', 3);
|
||||
|
||||
$this->expectException(CoreCannotSaveObjectException::class);
|
||||
$this->expectExceptionMessage('You cannot remove your own Administrator profile. Ask another Administrator to do it for you');
|
||||
$this->RemoveProfileFromUser($oUser, 1); // Remove admin profile
|
||||
try {
|
||||
$this->AddProfileToUser($oUser, 1);
|
||||
$this->fail('Should not be able to upgrade to Administrator');
|
||||
} catch (CoreCannotSaveObjectException $e) {
|
||||
} catch (CoreException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider UserCannotLoseUserEditionRightsProvider
|
||||
* @doesNotPerformAssertions
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUserCannotLoseUserEditionRights(int $iProfileId)
|
||||
public function testDenyingUserModification()
|
||||
{
|
||||
$oUser = $this->CreateUniqueUserAndLogin('configmgr111', $iProfileId); // SuperUser
|
||||
$oUser = $this->CreateUniqueUserAndLogin('test1', 1);
|
||||
$this->AddProfileToUser($oUser, 3);
|
||||
|
||||
$this->expectException(CoreCannotSaveObjectException::class);
|
||||
$this->expectExceptionMessage('You cannot remove your own rights to edit Users');
|
||||
$this->RemoveProfileFromUser($oUser, $iProfileId);
|
||||
}
|
||||
// Keep only the profile 3 (remove profile 1)
|
||||
$oSet = new \ormLinkSet(\UserLocal::class, 'profile_list', \DBObjectSet::FromScratch(\URP_UserProfile::class));
|
||||
$oSet->AddItem(MetaModel::NewObject('URP_UserProfile', ['profileid' => 3, 'reason' => 'UNIT Tests']));
|
||||
$oUser->Set('profile_list', $oSet);
|
||||
|
||||
public function UserCannotLoseUserEditionRightsProvider(): array
|
||||
{
|
||||
return [
|
||||
'Administrator' => [1],
|
||||
'SuperUser' => [117],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider PrivilegedUsersMustHaveBackofficeAccessProvider
|
||||
*/
|
||||
public function testPrivilegedUsersMustHaveBackofficeAccess(int $iProfileId)
|
||||
{
|
||||
$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)');
|
||||
$oUser->DBInsert();
|
||||
|
||||
}
|
||||
public function PrivilegedUsersMustHaveBackofficeAccessProvider(): array
|
||||
{
|
||||
return [
|
||||
'killing another administrator' => [1],
|
||||
'killing superuser ' => [117],
|
||||
'killing Rest User' => [1024],
|
||||
|
||||
];
|
||||
}
|
||||
public function testNonPrivilegedUsersCanBeDeniedFromBackoffice()
|
||||
{
|
||||
$oUser = $this->GivenUserWithProfiles('test1', [5, 2]);
|
||||
// No exception expected
|
||||
$oUser->DBInsert();
|
||||
$this->expectNotToPerformAssertions();
|
||||
try {
|
||||
$oUser->DBWrite();
|
||||
$this->fail('Should not be able to deny User modifications');
|
||||
} catch (CoreCannotSaveObjectException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,18 +471,18 @@ class UserRightsTest extends ItopDataTestCase
|
||||
$oSearch = new DBObjectSearch('URP_UserProfile');
|
||||
$oSearch->AddCondition('userid', $oUserAdmin->GetKey());
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$this->assertEquals($iExpectedCount, $oSet->Count(), 'Visibility on Link between User and Administrator Profiles should be controlled by hide_administrators setting');
|
||||
$this->assertEquals($iExpectedCount, $oSet->Count());
|
||||
// Get the Profiles as well
|
||||
$oSearch = DBObjectSearch::FromOQL('SELECT URP_Profiles JOIN URP_UserProfile ON URP_UserProfile.profileid = URP_Profiles.id WHERE URP_UserProfile.userid='.$oUserAdmin->GetKey());
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$this->assertEquals($iExpectedCount, $oSet->Count(), 'Visibility on Administrator Profiles should be controlled by hide_administrators setting');
|
||||
$this->assertEquals($iExpectedCount, $oSet->Count());
|
||||
}
|
||||
|
||||
public function NonAdminCannotListAdminProfilesProvider(): array
|
||||
{
|
||||
return [
|
||||
'with Admins visible' => ['hide_administrators' => false, 'visible_objects' => 1],
|
||||
'with Admins hidden' => ['hide_administrators' => true, 'visible_objects' => 0],
|
||||
'with Admins visible' => [false, 1],
|
||||
'with Admins hidden' => [true, 0],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use ItopExtensionsMap;
|
||||
use ModuleDiscovery;
|
||||
|
||||
class ExtensionsMapTest extends ItopTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp(); // TODO: Change the autogenerated stub
|
||||
$this->RequireOnceItopFile('/setup/unattended-install/InstallationFileService.php');
|
||||
ModuleDiscovery::ResetCache();
|
||||
|
||||
}
|
||||
|
||||
public function testGetAllExtensionsWithPreviouslyInstalledDoesNotCrash()
|
||||
{
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
$aExtensions = $oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled();
|
||||
$this->assertGreaterThan(0, count($aExtensions));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use MissingDependencyException;
|
||||
use ModuleDiscovery;
|
||||
|
||||
class ModuleDiscoveryTest extends ItopTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/runtimeenv.class.inc.php');
|
||||
$this->RequireOnceItopFile('setup/modulediscovery.class.inc.php');
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_RealExample()
|
||||
{
|
||||
$aModules = json_decode(file_get_contents(__DIR__.'/ressources/reallife_discovered_modules.json'), true);
|
||||
|
||||
$aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true);
|
||||
|
||||
$aExpected = json_decode(file_get_contents(__DIR__.'/ressources/reallife_expected_ordered_modules.json'), true);
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_LoadOnlyChoosenModules()
|
||||
{
|
||||
$aChoices = ['id1', 'id2'];
|
||||
|
||||
$aModules = [
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label3',
|
||||
],
|
||||
];
|
||||
|
||||
$aResult = ModuleDiscovery::OrderModulesByDependencies($aModules, true, $aChoices);
|
||||
|
||||
$aExpected = [
|
||||
"id2/2",
|
||||
"id1/1",
|
||||
];
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_FailWhenChoosenModuleDependsOnUnchoosenModule()
|
||||
{
|
||||
$aChoices = ['id1'];
|
||||
|
||||
$aModules = [
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
];
|
||||
|
||||
$sExpectedMessage = <<<TXT
|
||||
The following modules have unmet dependencies:
|
||||
label1 (id: id1/1) depends on: ❌ id2/2
|
||||
TXT;
|
||||
$this->expectException(MissingDependencyException::class);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
|
||||
ModuleDiscovery::OrderModulesByDependencies($aModules, true, $aChoices);
|
||||
}
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\DependencyExpression;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
class DependencyExpressionTest extends ItopTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/moduledependency/dependencyexpression.class.inc.php');
|
||||
}
|
||||
|
||||
public function testModuleDependencyInit_Invalid()
|
||||
{
|
||||
$oModuleDependency = new DependencyExpression('||');
|
||||
$this->assertFalse($oModuleDependency->IsValid());
|
||||
$this->assertFalse($oModuleDependency->IsResolved());
|
||||
}
|
||||
|
||||
public static function WithOperatorProvider()
|
||||
{
|
||||
return [
|
||||
"nominal case" => [
|
||||
"dep" => "itop-config-mgmt/2.4.0",
|
||||
'expected_operator' => '>=',
|
||||
],
|
||||
">" => [
|
||||
"dep" => "itop-config-mgmt/>2.4.0",
|
||||
'expected_operator' => '>',
|
||||
],
|
||||
">=" => [
|
||||
"dep" => "itop-config-mgmt/>=2.4.0",
|
||||
'expected_operator' => '>=',
|
||||
],
|
||||
"<" => [
|
||||
"dep" => "itop-config-mgmt/<2.4.0",
|
||||
'expected_operator' => '<',
|
||||
],
|
||||
"<=" => [
|
||||
"dep" => "itop-config-mgmt/<=2.4.0",
|
||||
'expected_operator' => '<=',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider WithOperatorProvider
|
||||
*/
|
||||
public function testModuleDependencyInit_WithOperator($sDepId, $sExpectedOperator)
|
||||
{
|
||||
$oModuleDependency = new DependencyExpression($sDepId);
|
||||
$this->assertEquals([$sDepId => ['itop-config-mgmt', $sExpectedOperator, '2.4.0']], $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertTrue($oModuleDependency->IsValid());
|
||||
$this->assertFalse($oModuleDependency->IsResolved());
|
||||
;
|
||||
$this->assertEquals(['itop-config-mgmt'], $oModuleDependency->GetRemainingModuleNamesToResolve());
|
||||
}
|
||||
|
||||
public static function WithVariousOperatorProvider()
|
||||
{
|
||||
$aInternalStructure = ['itop-structure/3.0.0' => ['itop-structure', ">=", '3.0.0'], 'itop-portal/<3.2.1' => ['itop-portal', "<", '3.2.1']];
|
||||
|
||||
return [
|
||||
'&&' => [
|
||||
'sDepId' => 'itop-structure/3.0.0 && itop-portal/<3.2.1',
|
||||
'expected_structure' => $aInternalStructure,
|
||||
],
|
||||
'&& with parenthesis' => [
|
||||
'sDepId' => '(itop-structure/3.0.0) && (itop-portal/<3.2.1)',
|
||||
'expected_structure' => $aInternalStructure,
|
||||
],
|
||||
'||' => [
|
||||
'sDepId' => 'itop-structure/3.0.0 || itop-portal/<3.2.1',
|
||||
'expected_structure' => $aInternalStructure,
|
||||
],
|
||||
'|| with parenthesis' => [
|
||||
'sDepId' => '(itop-structure/3.0.0) || (itop-portal/<3.2.1)',
|
||||
'expected_structure' => $aInternalStructure,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider WithVariousOperatorProvider
|
||||
*/
|
||||
public function testModuleDependencyInit_WithOperand($sDepId, $sExpected)
|
||||
{
|
||||
$oModuleDependency = new DependencyExpression($sDepId);
|
||||
$this->assertEquals($sExpected, $this->GetNonPublicProperty($oModuleDependency, 'aParamsPerModuleId'));
|
||||
$this->assertTrue($oModuleDependency->IsValid());
|
||||
;
|
||||
$this->assertEquals(['itop-structure', 'itop-portal'], $oModuleDependency->GetRemainingModuleNamesToResolve());
|
||||
}
|
||||
|
||||
public static function SimpleDependencyExpressionIsResolvedProvider()
|
||||
{
|
||||
return [
|
||||
'unresolved with major version' => [
|
||||
'expr' => 'itop-config-mgmt/2.4.0',
|
||||
'resolved_module_versions' => ['itop-config-mgmt' => '1.2.3'],
|
||||
'expected_is_resolved' => false,
|
||||
],
|
||||
'unresolved with minor version' => [
|
||||
'expr' => 'itop-config-mgmt/2.4.1',
|
||||
'resolved_module_versions' => ['itop-config-mgmt' => '2.4.0-1'],
|
||||
'expected_is_resolved' => false,
|
||||
],
|
||||
'resolution OK with major version' => [
|
||||
'expr' => 'itop-config-mgmt/2.4.0',
|
||||
'resolved_module_versions' => ['itop-config-mgmt' => '2.4.2'],
|
||||
'expected_is_resolved' => true,
|
||||
],
|
||||
'resolution OK with minor version' => [
|
||||
'expr' => 'itop-config-mgmt/2.4.0',
|
||||
'resolved_module_versions' => ['itop-config-mgmt' => '2.4.0-1'],
|
||||
'expected_is_resolved' => true,
|
||||
],
|
||||
'unproper use of api' => [
|
||||
'expr' => 'itop-config-mgmt/2.4.0',
|
||||
'resolved_module_versions' => [],
|
||||
'expected_is_resolved' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider SimpleDependencyExpressionIsResolvedProvider
|
||||
*/
|
||||
public function testSimpleDependencyExpressionIsResolved($sExpression, $aModuleVersions, $bExpectedResolved)
|
||||
{
|
||||
$oModuleDependency = new DependencyExpression($sExpression);
|
||||
$oModuleDependency->UpdateModuleResolutionState($aModuleVersions, ['itop-config-mgmt' => true]);
|
||||
$this->assertEquals($bExpectedResolved, $oModuleDependency->IsResolved());
|
||||
if ($bExpectedResolved) {
|
||||
$this->assertEquals([], $oModuleDependency->GetRemainingModuleNamesToResolve());
|
||||
}
|
||||
}
|
||||
|
||||
public static function ComplexDependencyExpressionIsResolvedProvider()
|
||||
{
|
||||
$aAllModules = ['itop-structure' => true, 'itop-portal' => true];
|
||||
|
||||
return [
|
||||
'and + unresolved due to missing itop-portal' => [
|
||||
'expr' => 'itop-structure/3.0.0 && itop-portal/3.2.1',
|
||||
'resolved_module_versions' => ['itop-structure' => '3.0.0'],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => false,
|
||||
'remaining_module_names' => ['itop-portal'],
|
||||
],
|
||||
'and + unresolved due to unsifficient itop-portal version' => [
|
||||
'expr' => 'itop-structure/3.0.0 && itop-portal/3.2.1',
|
||||
'resolved_module_versions' => ['itop-structure' => '3.0.0', 'itop-portal' => '1.0.0'],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => false,
|
||||
'remaining_module_names' => ['itop-portal'],
|
||||
],
|
||||
'and + resolved' => [
|
||||
'expr' => 'itop-structure/3.0.0 && itop-portal/3.2.1',
|
||||
'resolved_module_versions' => ['itop-structure' => '3.0.0', 'itop-portal' => '3.3.3'],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => true,
|
||||
'remaining_module_names' => [],
|
||||
],
|
||||
'or||true (step1) + dependency expression evaluation is delayed for sorting purpose' => [
|
||||
'expr' => 'itop-structure/3.0.0||true',
|
||||
'resolved_module_versions' => [],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => false,
|
||||
'remaining_module_names' => ['itop-structure'],
|
||||
],
|
||||
'or||true (step2) + expression is evaluated because itop-structure has been resolved' => [
|
||||
'expr' => 'itop-structure/3.0.0||true',
|
||||
'resolved_module_versions' => ['itop-structure' => '3.0.0'],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => true,
|
||||
'remaining_module_names' => [],
|
||||
],
|
||||
'or||true + resolved DIRECTLY as itop-structure is not on disk (all_modules)' => [
|
||||
'expr' => 'itop-structure/3.0.0||true',
|
||||
'resolved_module_versions' => [],
|
||||
'all_modules' => [],
|
||||
'expected_is_resolved' => true,
|
||||
'remaining_module_names' => ['itop-structure'],
|
||||
],
|
||||
'or + unresolved because dependency trick used to sort as well' => [
|
||||
'expr' => 'itop-structure/3.0.0 || itop-portal/3.2.1',
|
||||
'resolved_module_versions' => ['itop-structure' => '3.0.0'],
|
||||
'all_modules' => $aAllModules,
|
||||
'expected_is_resolved' => false,
|
||||
'remaining_module_names' => ['itop-portal'],
|
||||
],
|
||||
'1 can be used as a boolean' => [
|
||||
'expr' => 'true||1',
|
||||
'resolved_module_versions' => [],
|
||||
'all_modules' => [],
|
||||
'expected_is_resolved' => true,
|
||||
'remaining_module_names' => [],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ComplexDependencyExpressionIsResolvedProvider
|
||||
*/
|
||||
public function testComplexDependencyExpressionIsResolved($sExpression, $aModuleVersions, $aAllModules, $bExpectedResolved, $aRemainingModuleNames)
|
||||
{
|
||||
$oModuleDependency = new DependencyExpression($sExpression);
|
||||
|
||||
$oModuleDependency->UpdateModuleResolutionState($aModuleVersions, $aAllModules);
|
||||
$this->assertEquals($aRemainingModuleNames, $oModuleDependency->GetRemainingModuleNamesToResolve());
|
||||
$this->assertEquals($bExpectedResolved, $oModuleDependency->IsResolved());
|
||||
}
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\Setup\ModuleDependency;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDependency\ModuleDependencySort;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use MissingDependencyException;
|
||||
|
||||
class ModuleDependencySortTest extends ItopTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->RequireOnceItopFile('setup/modulediscovery.class.inc.php');
|
||||
$this->RequireOnceItopFile('setup/moduledependency/moduledependencysort.class.inc.php');
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_CheckExceptionWhenAllModuleUnresolved()
|
||||
{
|
||||
$aModules = [
|
||||
"id1/123" => [
|
||||
'dependencies' => [ 'id3/666', 'id4/666'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/456" => [
|
||||
'dependencies' => ['id3/666'],
|
||||
'label' => 'label2',
|
||||
],
|
||||
];
|
||||
|
||||
$sExpectedMessage = <<<MSG
|
||||
The following modules have unmet dependencies:
|
||||
label2 (id: id2/456) depends on: ❌ id3/666,
|
||||
label1 (id: id1/123) depends on: ❌ id3/666 + ❌ id4/666
|
||||
MSG;
|
||||
$this->expectException(MissingDependencyException::class);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
|
||||
ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_CheckExceptionWhenSomeModuleUnresolved()
|
||||
{
|
||||
$aModules = [
|
||||
"id1/123" => [
|
||||
'dependencies' => [ 'id2/456', 'id4/666', 'id3/789'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/456" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/789" => [
|
||||
'dependencies' => [ 'id2/456', 'id4/666'],
|
||||
'label' => 'label3',
|
||||
],
|
||||
];
|
||||
|
||||
$sExpectedMessage = <<<MSG
|
||||
The following modules have unmet dependencies:
|
||||
label3 (id: id3/789) depends on: ❌ id4/666,
|
||||
label1 (id: id1/123) depends on: ❌ id4/666 + ❌ id3/789
|
||||
MSG;
|
||||
$this->expectException(MissingDependencyException::class);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
|
||||
ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_CheckExceptionWhenCircularDependencies()
|
||||
{
|
||||
$aModules = [
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => ['id3/3'],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => ['id4/4'],
|
||||
'label' => 'label3',
|
||||
],
|
||||
"id4/4" => [
|
||||
'dependencies' => ['id1/1'],
|
||||
'label' => 'label4',
|
||||
],
|
||||
];
|
||||
|
||||
$sExpectedMessage = <<<MSG
|
||||
The following modules have unmet dependencies:
|
||||
label1 (id: id1/1) depends on: ❌ id2/2,
|
||||
label4 (id: id4/4) depends on: ❌ id1/1,
|
||||
label3 (id: id3/3) depends on: ❌ id4/4,
|
||||
label2 (id: id2/2) depends on: ❌ id3/3
|
||||
MSG;
|
||||
$this->expectException(MissingDependencyException::class);
|
||||
$this->expectExceptionMessage($sExpectedMessage);
|
||||
|
||||
ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_KeepGoingEvenWithFailure()
|
||||
{
|
||||
$aModules = [
|
||||
"id1/123" => [
|
||||
'dependencies' => [ 'id2/456', 'id4/666', 'id3/789'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/456" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/789" => [
|
||||
'dependencies' => [ 'id2/456', 'id4/666'],
|
||||
'label' => 'label3',
|
||||
],
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, false);
|
||||
|
||||
$aExpected = [
|
||||
'id2/456',
|
||||
];
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_Nominalcase()
|
||||
{
|
||||
$aModules = [
|
||||
"id0/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => ['id3/3'],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => ['id4/4'],
|
||||
'label' => 'label3',
|
||||
],
|
||||
"id4/4" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label4',
|
||||
],
|
||||
];
|
||||
|
||||
$aExpected = [
|
||||
"id4/4",
|
||||
"id3/3",
|
||||
"id2/2",
|
||||
"id0/1",
|
||||
"id1/1",
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
//warning : tricky usecase
|
||||
public function testOrderModulesByDependencies_AllTermsOfOrExpressionWillImpactTheOrder()
|
||||
{
|
||||
$aModules = [
|
||||
"id0/1" => [
|
||||
'dependencies' => [ 'id2/2 || id1/1'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id1/1" => [
|
||||
'dependencies' => [ 'id2/2'],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id2/2" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
];
|
||||
|
||||
$aExpected = [
|
||||
"id2/2",
|
||||
"id1/1",
|
||||
"id0/1",
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
//WARNING: alphabetical order make setup are determinititic
|
||||
public function testOrderModulesByDependencies_ResolveNoDependendenciesOrderByAlphabeticalOrder()
|
||||
{
|
||||
$aModules = [
|
||||
"id2/2" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id1/1" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label3',
|
||||
],
|
||||
"id4/4" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label4',
|
||||
],
|
||||
"id0/1" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label0',
|
||||
],
|
||||
];
|
||||
|
||||
$aExpected = [
|
||||
"id0/1",
|
||||
"id1/1",
|
||||
"id2/2",
|
||||
"id3/3",
|
||||
"id4/4",
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_AlphabeticalOrderWithDependencies()
|
||||
{
|
||||
$aModules = [
|
||||
"id2/2" => [
|
||||
'dependencies' => ["id1/1"],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id1/1" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => ["id1/1"],
|
||||
'label' => 'label3',
|
||||
],
|
||||
];
|
||||
|
||||
$aExpected = [
|
||||
"id1/1",
|
||||
"id2/2",
|
||||
"id3/3",
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testOrderModulesByDependencies_AlphabeticalOrderWithDependencies2()
|
||||
{
|
||||
$aModules = [
|
||||
"z_id2/2" => [ //difference here
|
||||
'dependencies' => ["id1/1"],
|
||||
'label' => 'label2',
|
||||
],
|
||||
"id1/1" => [
|
||||
'dependencies' => [],
|
||||
'label' => 'label1',
|
||||
],
|
||||
"id3/3" => [
|
||||
'dependencies' => ["id1/1"],
|
||||
'label' => 'label3',
|
||||
],
|
||||
];
|
||||
|
||||
$aExpected = [
|
||||
"id1/1",
|
||||
"id3/3",
|
||||
"z_id2/2",
|
||||
];
|
||||
|
||||
$aResult = ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aModules, true);
|
||||
|
||||
$this->assertEquals($aExpected, array_keys($aResult));
|
||||
}
|
||||
|
||||
public function testSortModulesByCountOfDepencenciesDescending_NoDependencies()
|
||||
{
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'c', []);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'b', []);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'a', []);
|
||||
|
||||
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
$this->assertEquals(['a', 'b', 'c'], array_keys($aUnresolvedDependencyModules));
|
||||
}
|
||||
|
||||
public function testSortModulesByCountOfDepencenciesDescending_NominalUseCase()
|
||||
{
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-change-mgmt/456', ['itop-config-mgmt/2.2.0', 'itop-tickets/2.0.0']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-tickets/2.0.0', ['itop-structure/2.7.1']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-config-mgmt/123', ['itop-structure/2.7.1']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-structure/2.7.1', []);
|
||||
|
||||
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'itop-structure/2.7.1',
|
||||
'itop-config-mgmt/123',
|
||||
'itop-tickets/2.0.0',
|
||||
'itop-change-mgmt/456',
|
||||
],
|
||||
array_keys($aUnresolvedDependencyModules)
|
||||
);
|
||||
}
|
||||
|
||||
public function testSortModulesByCountOfDepencenciesDescending_NominalUseCaseWithMissingDependency()
|
||||
{
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-change-mgmt/456', ['itop-config-mgmt/2.2.0', 'itop-tickets/2.0.0']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-tickets/2.0.0', ['itop-structure/2.7.1']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'itop-config-mgmt/123', ['itop-structure/2.7.1']);
|
||||
|
||||
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'itop-config-mgmt/123',
|
||||
'itop-tickets/2.0.0',
|
||||
'itop-change-mgmt/456',
|
||||
],
|
||||
array_keys($aUnresolvedDependencyModules)
|
||||
);
|
||||
}
|
||||
|
||||
public function testSortModulesByCountOfDepencenciesDescending_FurtherVersionsOfSameModule()
|
||||
{
|
||||
$aUnresolvedDependencyModules = [];
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'moduleA/1', []);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'moduleA/2', ['moduleC/1']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'moduleB/1', ['moduleA/1']);
|
||||
$this->AddModule($aUnresolvedDependencyModules, 'moduleC/1', []);
|
||||
|
||||
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'moduleA/1',
|
||||
'moduleC/1',
|
||||
'moduleA/2',
|
||||
'moduleB/1',
|
||||
],
|
||||
array_keys($aUnresolvedDependencyModules)
|
||||
);
|
||||
}
|
||||
|
||||
private function AddModule(array &$aUnresolvedDependencyModules, string $sModuleId, array $aDeps)
|
||||
{
|
||||
$oModule = new Module($sModuleId);
|
||||
$oModule->SetDependencies($aDeps);
|
||||
$aUnresolvedDependencyModules[$sModuleId] = $oModule;
|
||||
}
|
||||
|
||||
private function SortModulesByCountOfDepencenciesDescending(array &$aUnresolvedDependencyModules)
|
||||
{
|
||||
$this->InvokeNonPublicMethod(ModuleDependencySort::class, 'SortModulesByCountOfDepencenciesDescending', ModuleDependencySort::GetInstance(), [&$aUnresolvedDependencyModules]);
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
class ModuleTest extends ItopTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('setup/moduledependency/module.class.inc.php');
|
||||
}
|
||||
|
||||
public function testModuleInit()
|
||||
{
|
||||
$oModule = new Module("itop-config-mgmt/2.4.0");
|
||||
$this->assertEquals("itop-config-mgmt", $oModule->GetModuleName());
|
||||
$this->assertEquals("2.4.0", $oModule->GetVersion());
|
||||
$this->assertEquals("itop-config-mgmt/2.4.0", $oModule->GetModuleId());
|
||||
}
|
||||
|
||||
public function testModuleInit_NoVersion()
|
||||
{
|
||||
$oModule = new Module("itop-config-mgmt");
|
||||
$this->assertEquals("itop-config-mgmt", $oModule->GetModuleName());
|
||||
$this->assertEquals("1.0.0", $oModule->GetVersion());
|
||||
$this->assertEquals("itop-config-mgmt", $oModule->GetModuleId());
|
||||
}
|
||||
|
||||
public function testSetDependencies_ComplexExpressionsParsing()
|
||||
{
|
||||
$oModule = new Module("itop-bridge-datacenter-mgmt-services");
|
||||
$oModule->SetDependencies([
|
||||
'itop-config-mgmt/>2.7.1',
|
||||
'itop-service-mgmt/=2.7.1 || itop-service-mgmt-provider/<=2.7.1',
|
||||
'itop-datacenter-mgmt/3.1.0 || true && false',
|
||||
]);
|
||||
$this->assertEquals(
|
||||
['itop-config-mgmt', 'itop-service-mgmt', 'itop-service-mgmt-provider', 'itop-datacenter-mgmt' ],
|
||||
$oModule->GetUnresolvedDependencyModuleNames()
|
||||
);
|
||||
}
|
||||
|
||||
public function testIsResolved_Unresolved()
|
||||
{
|
||||
$oModule = new Module("itop-bridge-cmdb-ticket");
|
||||
$oModule->SetDependencies(['itop-config-mgmt/2.7.1', 'itop-tickets/2.7.0']);
|
||||
$this->assertEquals(['itop-config-mgmt', 'itop-tickets'], $oModule->GetUnresolvedDependencyModuleNames(), "all dependencies are unresolved");
|
||||
$this->assertFalse($oModule->IsResolved());
|
||||
|
||||
$oModule->UpdateModuleResolutionState([], []);
|
||||
$this->assertFalse($oModule->IsResolved(), "all dependencies are still unresolved");
|
||||
}
|
||||
|
||||
public function testIsResolved_PartialResolution()
|
||||
{
|
||||
$oModule = new Module("itop-bridge-cmdb-ticket");
|
||||
$oModule->SetDependencies(['itop-config-mgmt/2.7.1', 'itop-tickets/2.7.0']);
|
||||
|
||||
$oModule->UpdateModuleResolutionState(['itop-config-mgmt' => '2.7.1'], ['itop-config-mgmt' => true]);
|
||||
$this->assertFalse($oModule->IsResolved(), "some dependencies are still unresolved");
|
||||
$this->assertEquals(['itop-tickets'], $oModule->GetUnresolvedDependencyModuleNames(), 'one dependency is remaining');
|
||||
}
|
||||
|
||||
public function testIsResolved_OK()
|
||||
{
|
||||
$oModule = new Module("itop-bridge-cmdb-ticket");
|
||||
$oModule->SetDependencies(['itop-config-mgmt/2.7.1', 'itop-tickets/2.7.0']);
|
||||
|
||||
$oModule->UpdateModuleResolutionState(['itop-config-mgmt' => '2.7.1', 'itop-tickets' => '2.7.0'], ['itop-config-mgmt' => true, 'itop-tickets' => true]);
|
||||
$this->assertTrue($oModule->IsResolved());
|
||||
$this->assertEquals([], $oModule->GetUnresolvedDependencyModuleNames());
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
{
|
||||
"authent-cas\/3.2.1": {
|
||||
"label": "CAS SSO",
|
||||
"dependencies": []
|
||||
},
|
||||
"authent-external\/3.2.1": {
|
||||
"label": "External user authentication",
|
||||
"dependencies": []
|
||||
},
|
||||
"authent-ldap\/3.2.1": {
|
||||
"label": "User authentication based on LDAP",
|
||||
"dependencies": []
|
||||
},
|
||||
"authent-local\/3.2.1": {
|
||||
"label": "User authentication based on the local DB",
|
||||
"dependencies": []
|
||||
},
|
||||
"combodo-backoffice-darkmoon-theme\/3.2.1": {
|
||||
"label": "Backoffice: Darkmoon theme",
|
||||
"dependencies": []
|
||||
},
|
||||
"combodo-backoffice-fullmoon-high-contrast-theme\/3.2.1": {
|
||||
"label": "Backoffice: Fullmoon with high contrast accessibility theme",
|
||||
"dependencies": []
|
||||
},
|
||||
"combodo-backoffice-fullmoon-protanopia-deuteranopia-theme\/3.2.1": {
|
||||
"label": "Backoffice: Fullmoon with protonopia & deuteranopia accessibility theme",
|
||||
"dependencies": []
|
||||
},
|
||||
"combodo-backoffice-fullmoon-tritanopia-theme\/3.2.1": {
|
||||
"label": "Backoffice: Fullmoon with tritanopia accessibility theme",
|
||||
"dependencies": []
|
||||
},
|
||||
"combodo-db-tools\/3.2.1": {
|
||||
"label": "Database maintenance tools",
|
||||
"dependencies": [
|
||||
"itop-structure\/3.0.0"
|
||||
]
|
||||
},
|
||||
"itop-attachments\/3.2.1": {
|
||||
"label": "Tickets Attachments",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-backup\/3.2.1": {
|
||||
"label": "Backup utilities",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-bridge-cmdb-services\/3.2.1": {
|
||||
"label": "Bridge for CMDB and Services",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1"
|
||||
]
|
||||
},
|
||||
"itop-bridge-cmdb-ticket\/3.2.1": {
|
||||
"label": "Bridge for CMDB and Ticket",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-tickets\/2.7.0"
|
||||
]
|
||||
},
|
||||
"itop-bridge-datacenter-mgmt-services\/3.2.1": {
|
||||
"label": "Bridge for CMDB Virtualization objects and Services",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"itop-datacenter-mgmt\/3.1.0"
|
||||
]
|
||||
},
|
||||
"itop-bridge-endusers-devices-services\/3.2.1": {
|
||||
"label": "Bridge for CMDB endusers objects and Services",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"itop-endusers-devices\/3.1.0"
|
||||
]
|
||||
},
|
||||
"itop-bridge-storage-mgmt-services\/3.2.1": {
|
||||
"label": "Bridge for CMDB Virtualization objects and Services",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"itop-storage-mgmt\/3.1.0"
|
||||
]
|
||||
},
|
||||
"itop-bridge-virtualization-mgmt-services\/3.2.1": {
|
||||
"label": "Bridge for CMDB Virtualization objects and Services",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.7.1",
|
||||
"itop-service-mgmt\/2.7.1 || itop-service-mgmt-provider\/2.7.1",
|
||||
"itop-virtualization-mgmt\/3.1.0"
|
||||
]
|
||||
},
|
||||
"itop-bridge-virtualization-storage\/3.2.1": {
|
||||
"label": "Links between virtualization and storage",
|
||||
"dependencies": [
|
||||
"itop-storage-mgmt\/2.2.0",
|
||||
"itop-virtualization-mgmt\/2.2.0"
|
||||
]
|
||||
},
|
||||
"itop-change-mgmt-itil\/3.2.1": {
|
||||
"label": "Change Management ITIL",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.2.0",
|
||||
"itop-tickets\/2.0.0"
|
||||
]
|
||||
},
|
||||
"itop-change-mgmt\/3.2.1": {
|
||||
"label": "Change Management",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.2.0",
|
||||
"itop-tickets\/2.0.0"
|
||||
]
|
||||
},
|
||||
"itop-config-mgmt\/3.2.1": {
|
||||
"label": "Configuration Management (CMDB)",
|
||||
"dependencies": [
|
||||
"itop-structure\/2.7.1"
|
||||
]
|
||||
},
|
||||
"itop-config\/3.2.1": {
|
||||
"label": "Configuration editor",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-core-update\/3.2.1": {
|
||||
"label": "iTop Core Update",
|
||||
"dependencies": [
|
||||
"itop-files-information\/2.7.0",
|
||||
"combodo-db-tools\/2.7.0"
|
||||
]
|
||||
},
|
||||
"itop-datacenter-mgmt\/3.2.1": {
|
||||
"label": "Datacenter Management",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.2.0"
|
||||
]
|
||||
},
|
||||
"itop-endusers-devices\/3.2.1": {
|
||||
"label": "End-user Devices Management",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.2.0"
|
||||
]
|
||||
},
|
||||
"itop-faq-light\/3.2.1": {
|
||||
"label": "Frequently Asked Questions Database",
|
||||
"dependencies": [
|
||||
"itop-structure\/3.0.0 || itop-portal\/3.0.0"
|
||||
]
|
||||
},
|
||||
"itop-files-information\/3.2.1": {
|
||||
"label": "iTop files information",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-full-itil\/3.2.1": {
|
||||
"label": "Bridge - Request management ITIL + Incident management ITIL",
|
||||
"dependencies": [
|
||||
"itop-request-mgmt-itil\/2.3.0",
|
||||
"itop-incident-mgmt-itil\/2.3.0"
|
||||
]
|
||||
},
|
||||
"itop-hub-connector\/3.2.1": {
|
||||
"label": "iTop Hub Connector",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.4.0"
|
||||
]
|
||||
},
|
||||
"itop-incident-mgmt-itil\/3.2.1": {
|
||||
"label": "Incident Management ITIL",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.4.0",
|
||||
"itop-tickets\/2.4.0",
|
||||
"itop-profiles-itil\/2.3.0"
|
||||
]
|
||||
},
|
||||
"itop-knownerror-mgmt\/3.2.1": {
|
||||
"label": "Known Errors Database",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.2.0"
|
||||
]
|
||||
},
|
||||
"itop-oauth-client\/3.2.1": {
|
||||
"label": "OAuth 2.0 client",
|
||||
"dependencies": [
|
||||
"itop-welcome-itil\/3.1.0,"
|
||||
]
|
||||
},
|
||||
"itop-portal-base\/3.2.1": {
|
||||
"label": "Portal Development Library",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-portal\/3.2.1": {
|
||||
"label": "Enhanced Customer Portal",
|
||||
"dependencies": [
|
||||
"itop-portal-base\/2.7.0"
|
||||
]
|
||||
},
|
||||
"itop-problem-mgmt\/3.2.1": {
|
||||
"label": "Problem Management",
|
||||
"dependencies": [
|
||||
"itop-tickets\/2.0.0"
|
||||
]
|
||||
},
|
||||
"itop-profiles-itil\/3.2.1": {
|
||||
"label": "Create standard ITIL profiles",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-request-mgmt-itil\/3.2.1": {
|
||||
"label": "User request Management ITIL",
|
||||
"dependencies": [
|
||||
"itop-tickets\/2.4.0"
|
||||
]
|
||||
},
|
||||
"itop-request-mgmt\/3.2.1": {
|
||||
"label": "Simple Ticket Management",
|
||||
"dependencies": [
|
||||
"itop-tickets\/2.4.0"
|
||||
]
|
||||
},
|
||||
"itop-service-mgmt-provider\/3.2.1": {
|
||||
"label": "Service Management for Service Providers",
|
||||
"dependencies": [
|
||||
"itop-tickets\/2.0.0"
|
||||
]
|
||||
},
|
||||
"itop-service-mgmt\/3.2.1": {
|
||||
"label": "Service Management",
|
||||
"dependencies": [
|
||||
"itop-tickets\/2.0.0"
|
||||
]
|
||||
},
|
||||
"itop-sla-computation\/3.2.1": {
|
||||
"label": "SLA Computation",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-storage-mgmt\/3.2.1": {
|
||||
"label": "Advanced Storage Management",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.4.0"
|
||||
]
|
||||
},
|
||||
"itop-structure\/3.2.1": {
|
||||
"label": "Core iTop Structure",
|
||||
"dependencies": []
|
||||
},
|
||||
"itop-themes-compat\/3.2.1": {
|
||||
"label": "Light grey and Test red themes compatibility",
|
||||
"dependencies": [
|
||||
"itop-structure\/3.1.0"
|
||||
]
|
||||
},
|
||||
"itop-tickets\/3.2.1": {
|
||||
"label": "Tickets Management",
|
||||
"dependencies": [
|
||||
"itop-structure\/2.7.1"
|
||||
]
|
||||
},
|
||||
"itop-virtualization-mgmt\/3.2.1": {
|
||||
"label": "Virtualization Management",
|
||||
"dependencies": [
|
||||
"itop-config-mgmt\/2.4.0"
|
||||
]
|
||||
},
|
||||
"itop-welcome-itil\/3.2.1": {
|
||||
"label": "ITIL skin",
|
||||
"dependencies": []
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
["authent-cas\/3.2.1","authent-external\/3.2.1","authent-ldap\/3.2.1","authent-local\/3.2.1","combodo-backoffice-darkmoon-theme\/3.2.1","combodo-backoffice-fullmoon-high-contrast-theme\/3.2.1","combodo-backoffice-fullmoon-protanopia-deuteranopia-theme\/3.2.1","combodo-backoffice-fullmoon-tritanopia-theme\/3.2.1","itop-attachments\/3.2.1","itop-backup\/3.2.1","itop-config\/3.2.1","itop-files-information\/3.2.1","itop-portal-base\/3.2.1","itop-portal\/3.2.1","itop-profiles-itil\/3.2.1","itop-sla-computation\/3.2.1","itop-structure\/3.2.1","itop-themes-compat\/3.2.1","itop-tickets\/3.2.1","itop-welcome-itil\/3.2.1","combodo-db-tools\/3.2.1","itop-config-mgmt\/3.2.1","itop-core-update\/3.2.1","itop-datacenter-mgmt\/3.2.1","itop-endusers-devices\/3.2.1","itop-faq-light\/3.2.1","itop-hub-connector\/3.2.1","itop-incident-mgmt-itil\/3.2.1","itop-knownerror-mgmt\/3.2.1","itop-oauth-client\/3.2.1","itop-problem-mgmt\/3.2.1","itop-request-mgmt-itil\/3.2.1","itop-request-mgmt\/3.2.1","itop-service-mgmt-provider\/3.2.1","itop-service-mgmt\/3.2.1","itop-storage-mgmt\/3.2.1","itop-virtualization-mgmt\/3.2.1","itop-bridge-cmdb-services\/3.2.1","itop-bridge-cmdb-ticket\/3.2.1","itop-bridge-datacenter-mgmt-services\/3.2.1","itop-bridge-endusers-devices-services\/3.2.1","itop-bridge-storage-mgmt-services\/3.2.1","itop-bridge-virtualization-mgmt-services\/3.2.1","itop-bridge-virtualization-storage\/3.2.1","itop-change-mgmt-itil\/3.2.1","itop-change-mgmt\/3.2.1","itop-full-itil\/3.2.1"]
|
||||
Reference in New Issue
Block a user