mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-06 17:44:12 +01:00
Compare commits
4 Commits
feature/89
...
feature/87
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be3ef66f03 | ||
|
|
d336090fbd | ||
|
|
69725ee1b4 | ||
|
|
1437eff77d |
@@ -3,14 +3,12 @@ $ibo-extension-details--information--metadata--delimiter: "-" !default;
|
||||
$ibo-extension-details--information--metadata--color: $ibo-color-grey-700 !default;
|
||||
$ibo-extension-details--actions--button--padding-y: 3px !default;
|
||||
$ibo-extension-details--actions--button--padding-x: $ibo-button--padding-x !default;
|
||||
$ibo-extension-details--padding-top: 5px !default;
|
||||
|
||||
.ibo-extension-details {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: $ibo-extension-details--padding-top;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -43,20 +41,9 @@ $ibo-extension-details--padding-top: 5px !default;
|
||||
padding-right: $ibo-extension-details--information--metadata--padding;
|
||||
}
|
||||
|
||||
//ibo-extension-details can have other ibo-extension-details inside its ibo-extension-details--information--description in the setup. We need to only affect direct children
|
||||
.ibo-extension-details:has(>.ibo-extension-details--actions input:is([type="checkbox"], [type="radio"]):checked){
|
||||
&>.ibo-extension-details--information>.ibo-extension-details--information--label .ibo-badge.unchecked {
|
||||
display: none;
|
||||
}
|
||||
.ibo-extension-details:has(input:checked) .ibo-badge.unchecked, .ibo-extension-details:has(input:not(:checked)) .ibo-badge.checked {
|
||||
display: none;
|
||||
}
|
||||
//Merging the two lines below with :is([type="checkbox"], [type="radio"]) will generate a warning in scss compiler
|
||||
.ibo-extension-details:has(>.ibo-extension-details--actions input[type="checkbox"]:not(:checked)),
|
||||
.ibo-extension-details:has(>.ibo-extension-details--actions input[type="radio"]:not(:checked)) {
|
||||
&>.ibo-extension-details--information>.ibo-extension-details--information--label .ibo-badge.checked {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ibo-extension-details--actions > button {
|
||||
padding: $ibo-extension-details--actions--button--padding-y $ibo-extension-details--actions--button--padding-x;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -316,26 +316,27 @@ fieldset {
|
||||
background-color: #F7FAFC;
|
||||
padding: 10px;
|
||||
.wiz-choice{
|
||||
&:not(:checked) ~ label .checked{
|
||||
&: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 .unchecked{
|
||||
&:checked ~ label .setup-extension-tag.unchecked{
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-extension-details:has(>.ibo-extension-details--actions>input:checked) {
|
||||
.ibo-extension-details:has(#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked), #itop-ticket-mgmt-itil-enhanced-portal:not(:checked)) {
|
||||
.ibo-extension-details--information--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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -632,21 +633,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-extension-details {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.ibo-extension-details--actions input{
|
||||
margin:0.2em 0.5em;
|
||||
width: 12px;
|
||||
}
|
||||
:not(.ibo-badge) ~ .ibo-badge{
|
||||
margin-left:0.5em;
|
||||
}
|
||||
.ibo-extension-details--information--label i{
|
||||
font-size : 0.9em;
|
||||
margin-left:0.3em;
|
||||
}
|
||||
|
||||
.setup--wizard-choice--label + .setup--wizard-choice--more-info {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -12,13 +12,16 @@ require_once APPROOT.'setup/feature_removal/DryRemovalRuntimeEnvironment.php';
|
||||
|
||||
use Combodo\iTop\Application\TwigBase\Controller\Controller;
|
||||
use Combodo\iTop\AuthentToken\Helper\TokenAuthLog;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use Combodo\iTop\DataFeatureRemoval\Model\DataFeatureRemoverAuditRuleService;
|
||||
use Combodo\iTop\DataFeatureRemoval\Model\DataFeatureRemoverExtensionService;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\DeletionPlanService;
|
||||
use Combodo\iTop\Setup\FeatureRemoval\DryRemovalRuntimeEnvironment;
|
||||
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
|
||||
use Dict;
|
||||
use Exception;
|
||||
use IssueLog;
|
||||
use MetaModel;
|
||||
use utils;
|
||||
|
||||
@@ -51,8 +54,10 @@ class DataFeatureRemovalController extends Controller
|
||||
$iTotalCount = 0;
|
||||
$aData = [];
|
||||
$aColumns = [];
|
||||
$aClasses = [];
|
||||
foreach (DataFeatureRemoverAuditRuleService::GetInstance()->ReadCheckRules() as $oRule) {
|
||||
$sContent = $oRule->Get('class_name');
|
||||
$aClasses[] = $sContent;
|
||||
$sModuleName = MetaModel::GetModuleName($sContent);
|
||||
$aExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetIncludingExtensions($sModuleName);
|
||||
$sExtensions = implode(' ', $aExtensions);
|
||||
@@ -79,10 +84,12 @@ HTML,
|
||||
|
||||
$aParams['aCheckRules'] = $this->GetTableData('Analysis', $aColumns, $aData);
|
||||
$aParams['rule_count'] = $iTotalCount;
|
||||
$aParams['aClasses'] = $aClasses;
|
||||
}
|
||||
|
||||
public function OperationAnalyze()
|
||||
{
|
||||
$this->ValidateTransactionId();
|
||||
$aSelectedExtensionsFromUI = utils::ReadPostedParam('aExtensions', []);
|
||||
$this->aSelectedExtensionsForCheck = [];
|
||||
foreach ($aSelectedExtensionsFromUI as $sCode => $aData) {
|
||||
@@ -98,7 +105,7 @@ HTML,
|
||||
$this->Analyze();
|
||||
$this->OperationMain();
|
||||
} catch (Exception $e) {
|
||||
\IssueLog::Error(__METHOD__, null, ['stack' => $e->getTraceAsString(), 'exception' => $e->getMessage()]);
|
||||
IssueLog::Error(__METHOD__, null, ['stack' => $e->getTraceAsString(), 'exception' => $e->getMessage()]);
|
||||
$this->OperationMain($e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -150,7 +157,7 @@ HTML,
|
||||
return [
|
||||
'Type' => 'Table',
|
||||
'Columns' => [['label' => '']],
|
||||
'Data' => [[ Dict::S('DbCleaner:Table:Empty')]],
|
||||
'Data' => [[ Dict::S('DataFeatureRemoval:Table:Empty')]],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -182,8 +189,74 @@ HTML,
|
||||
|
||||
private function Save(array $aGetRemovedClasses)
|
||||
{
|
||||
\IssueLog::Debug(__METHOD__, null, ['aGetRemovedClasses' => $aGetRemovedClasses]);
|
||||
IssueLog::Debug(__METHOD__, null, ['aGetRemovedClasses' => $aGetRemovedClasses]);
|
||||
|
||||
DataFeatureRemoverAuditRuleService::GetInstance()->SaveChecks($aGetRemovedClasses);
|
||||
}
|
||||
|
||||
public function OperationDeletionPlan()
|
||||
{
|
||||
$aParams = [];
|
||||
$this->ValidateTransactionId();
|
||||
|
||||
$aClasses = utils::ReadPostedParam('classes', null, utils::ENUM_SANITIZATION_FILTER_CLASS);
|
||||
|
||||
$aDeletionPlanSummaryEntities = DeletionPlanService::GetInstance()->GetDeletionPlanSummary($aClasses);
|
||||
$aColumns = ['Class', 'DeleteCount' , 'UpdateCount', 'Issue'];
|
||||
$aRows = [];
|
||||
foreach ($aDeletionPlanSummaryEntities as $oDeletionPlanSummaryEntity) {
|
||||
$aRows[] = [
|
||||
$oDeletionPlanSummaryEntity->sClass,
|
||||
$oDeletionPlanSummaryEntity->iDeleteCount,
|
||||
$oDeletionPlanSummaryEntity->iUpdateCount,
|
||||
$oDeletionPlanSummaryEntity->sIssue ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
$aParams['sTransactionId'] = utils::GetNewTransactionId();
|
||||
$aParams['aDeletionPlanSummary'] = $this->GetTableData('Extensions', $aColumns, $aRows);
|
||||
$aParams['aClasses'] = $aClasses;
|
||||
|
||||
$this->DisplayPage($aParams);
|
||||
}
|
||||
|
||||
public function OperationDoDeletion()
|
||||
{
|
||||
$aParams = [];
|
||||
$this->ValidateTransactionId();
|
||||
|
||||
$aClasses = utils::ReadPostedParam('classes', null, utils::ENUM_SANITIZATION_FILTER_CLASS);
|
||||
|
||||
$aDeletionExecutionSummary = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
|
||||
$aColumns = ['Class', 'DeleteCount' , 'UpdateCount'];
|
||||
$aRows = [];
|
||||
foreach ($aDeletionExecutionSummary as $oDeletionExecutionSummaryEntity) {
|
||||
$aRows[] = [
|
||||
$oDeletionExecutionSummaryEntity->sClass,
|
||||
$oDeletionExecutionSummaryEntity->iDeleteCount,
|
||||
$oDeletionExecutionSummaryEntity->iUpdateCount,
|
||||
];
|
||||
}
|
||||
|
||||
$aParams['sTransactionId'] = utils::GetNewTransactionId();
|
||||
$aParams['aDeletionExecutionSummary'] = $this->GetTableData('Extensions', $aColumns, $aRows);
|
||||
$this->DisplayPage($aParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \Combodo\iTop\MFABase\Helper\MFABaseException
|
||||
*/
|
||||
public function ValidateTransactionId(): void
|
||||
{
|
||||
if (empty($_POST)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sTransactionId = utils::ReadPostedParam('transaction_id', null, utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID);
|
||||
IssueLog::Debug(__FUNCTION__.": Transaction [$sTransactionId]");
|
||||
if (empty($sTransactionId) || !utils::IsTransactionValid($sTransactionId, false)) {
|
||||
throw new DataFeatureRemovalException(Dict::S("iTopUpdate:Error:InvalidToken"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Entity;
|
||||
|
||||
class DeletionPlanSummaryEntity
|
||||
{
|
||||
public string $sClass;
|
||||
|
||||
/**
|
||||
* @var int : DEL_MANUAL|DEL_AUTO|DEL_SILENT|DEL_MOVEUP|DEL_NONE
|
||||
* @see \AttributeDefinition DEL_xxx
|
||||
*/
|
||||
public int $iMode = 0;
|
||||
public ?string $sIssue = null;
|
||||
public int $iUpdateCount = 0;
|
||||
public int $iDeleteCount = 0;
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*/
|
||||
public function __construct(string $sClass)
|
||||
{
|
||||
$this->sClass = $sClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||
use DeletionPlan;
|
||||
use Hoa\Math\Test\Unit\Issue;
|
||||
|
||||
class DeletionPlanService
|
||||
{
|
||||
private static DeletionPlanService $oInstance;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): DeletionPlanService
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new DeletionPlanService();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?DeletionPlanService $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $sClasses
|
||||
*
|
||||
* @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity>
|
||||
*/
|
||||
public function GetDeletionPlanSummary(array $aClasses): array
|
||||
{
|
||||
$aSummary = [];
|
||||
|
||||
$oDeletionPlan = new DeletionPlan();
|
||||
foreach ($aClasses as $sClass) {
|
||||
$aObjects = $this->GetAllObjects($sClass);
|
||||
foreach ($aObjects as $oObject) {
|
||||
$oObject->CheckToDelete($oDeletionPlan);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($oDeletionPlan->ListUpdates() as $sClass => $aUpdates) {
|
||||
$oDeletionPlanSummaryEntity = new DeletionPlanSummaryEntity($sClass);
|
||||
$oDeletionPlanSummaryEntity->iUpdateCount = count($aUpdates);
|
||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
}
|
||||
|
||||
foreach ($oDeletionPlan->ListDeletes() as $sClass => $aDeletes) {
|
||||
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
|
||||
$oDeletionPlanSummaryEntity->iDeleteCount = count($aDeletes);
|
||||
|
||||
$aDelete = array_shift($aDeletes);
|
||||
$oDeletionPlanSummaryEntity->iMode = $aDelete['mode'];
|
||||
$oDeletionPlanSummaryEntity->sIssue = $aDelete['issue'] ?? null;
|
||||
|
||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
}
|
||||
|
||||
return $aSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DBObject[]
|
||||
*/
|
||||
private function GetAllObjects(string $sClass): array
|
||||
{
|
||||
$oFilter = new \DBObjectSearch($sClass);
|
||||
$oFilter->AllowAllData();
|
||||
$oSet = new \DBObjectSet($oFilter);
|
||||
return $oSet->ToArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $sClasses
|
||||
*
|
||||
* @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity>
|
||||
*/
|
||||
public function ExecuteDeletionPlan(array $aClasses)
|
||||
{
|
||||
$oDeletionPlan = new DeletionPlan();
|
||||
foreach ($aClasses as $sClass) {
|
||||
$aObjects = $this->GetAllObjects($sClass);
|
||||
foreach ($aObjects as $oObject) {
|
||||
$oObject->CheckToDelete($oDeletionPlan);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->DoDelete($oDeletionPlan);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DeletionPlan $oDeletionPlan
|
||||
*
|
||||
* @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity>
|
||||
*/
|
||||
private function DoDelete(DeletionPlan $oDeletionPlan)
|
||||
{
|
||||
if (count($oDeletionPlan->GetIssues()) > 0) {
|
||||
throw new DataFeatureRemovalException("Deletion Plan cannot be executed due to issues");
|
||||
}
|
||||
|
||||
$aSummary = [];
|
||||
foreach ($oDeletionPlan->ListUpdates() as $sClass => $aToUpdate) {
|
||||
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
|
||||
|
||||
foreach ($aToUpdate as $aData) {
|
||||
$oToUpdate = $aData['to_reset'];
|
||||
/** @var \DBObject $oToUpdate */
|
||||
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) {
|
||||
$oToUpdate->Set($sRemoteExtKey, 0);
|
||||
$oToUpdate->DBUpdate();
|
||||
|
||||
$oDeletionPlanSummaryEntity->iUpdateCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
}
|
||||
|
||||
foreach ($oDeletionPlan->ListDeletes() as $sClass => $aDeletes) {
|
||||
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
|
||||
|
||||
foreach ($aDeletes as $sId => $aDelete) {
|
||||
$oFilter = \DBObjectSearch::FromOQL_AllData("SELECT $sClass WHERE id=:id");
|
||||
$sQuery = $oFilter->MakeDeleteQuery(['id' => $sId]);
|
||||
\CMDBSource::DeleteFrom($sQuery);
|
||||
\IssueLog::Info(__METHOD__, null, [$sQuery]);
|
||||
|
||||
$oDeletionPlanSummaryEntity->iDeleteCount++;
|
||||
}
|
||||
|
||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
}
|
||||
|
||||
return $aSummary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{# @copyright Copyright (C) 2010-2026 Combodo SARL #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
{% UIPanel ForInformation { sTitle:'DataFeatureRemoval:DeletionPlan:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:DeletionPlan:SubTitle'|dict_s } %}
|
||||
{% UIDataTable ForForm { sRef:'aDeletionPlanSummary', aColumns:aDeletionPlanSummary.Columns, aData:aDeletionPlanSummary.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIPanel %}
|
||||
|
||||
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
|
||||
{% UIInput ForHidden { sName:'operation', sValue:'DoDeletion'} %}
|
||||
{% for sKey, sClass in aClasses %}
|
||||
{% UIInput ForHidden { sName:"classes[" ~ sKey ~ "]", sValue:sClass } %}
|
||||
{% endfor %}
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:DoDeletion'|dict_s, sName:'btn_deletion', sId:'btn_deletion', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
|
||||
{% UIInput ForHidden { sName:'operation', sValue:'Main'} %}
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:BackToMain'|dict_s, sName:'btn_deletion', sId:'btn_deletion', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
@@ -0,0 +1,14 @@
|
||||
{# @copyright Copyright (C) 2010-2026 Combodo SARL #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
{% UIPanel ForInformation { sTitle:'DataFeatureRemoval:DoDeletion:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:DoDeletion:SubTitle'|dict_s } %}
|
||||
{% UIDataTable ForForm { sRef:'aDeletionExecutionSummary', aColumns:aDeletionExecutionSummary.Columns, aData:aDeletionExecutionSummary.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIPanel %}
|
||||
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
|
||||
{% UIInput ForHidden { sName:'operation', sValue:'Main'} %}
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:BackToMain'|dict_s, sName:'btn_deletion', sId:'btn_deletion', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
@@ -0,0 +1,17 @@
|
||||
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Analysis:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:Analysis:SubTitle'|dict_format(rule_count) } %}
|
||||
{% UIDataTable ForForm { sRef:'aCheckRules', aColumns:aCheckRules.Columns, aData:aCheckRules.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIPanel %}
|
||||
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
|
||||
{% UIInput ForHidden { sName:'operation', sValue:'DeletionPlan'} %}
|
||||
{% for sKey, sClass in aClasses %}
|
||||
{% UIInput ForHidden { sName:"classes[" ~ sKey ~ "]", sValue:sClass } %}
|
||||
{% endfor %}
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:PlanDeletion'|dict_s, sName:'btn_plandeletion', sId:'btn_plandeletion', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
@@ -1,8 +0,0 @@
|
||||
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
{% UIForm Standard {} %}
|
||||
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Analysis:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:Analysis:SubTitle'|dict_format(rule_count) } %}
|
||||
{% UIDataTable ForForm { sRef:'aCheckRules', aColumns:aCheckRules.Columns, aData:aCheckRules.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIPanel %}
|
||||
{% EndUIForm %}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
{# DataFeatureRemoval #}
|
||||
|
||||
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Main:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:Main:SubTitle'|dict_s } %}
|
||||
{% UIPanel ForInformation { sTitle:'DataFeatureRemoval:Main:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:Main:SubTitle'|dict_s } %}
|
||||
|
||||
{% UIAlert ForInformation { sTitle:'DataFeatureRemoval:Helper:Title'|dict_s } %}
|
||||
{{ 'DataFeatureRemoval:Helper:Desc1'|dict_s }}<BR>
|
||||
@@ -23,6 +23,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include 'FeaturesTab.html.twig' %}
|
||||
{% include 'ExtensionRemovalDataTab.html.twig' %}
|
||||
{% include 'Features.html.twig' %}
|
||||
{% include 'ExtensionRemovalData.html.twig' %}
|
||||
{% EndUIPanel %}
|
||||
|
||||
@@ -7,11 +7,12 @@ $baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => $baseDir . '/src/Controller/DataFeatureRemovalController.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanSummaryEntity' => $baseDir . '/src/Entity/DeletionPlanSummaryEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalException' => $baseDir . '/src/Helper/DataFeatureRemovalException.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => $baseDir . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => $baseDir . '/src/Helper/DataFeatureRemovalLog.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverAuditRuleService' => $baseDir . '/src/Model/DataFeatureRemoverAuditRuleService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverExtensionService' => $baseDir . '/src/Model/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\SetupAudit' => $baseDir . '/src/Service/SetupAudit.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DeletionPlanService' => $baseDir . '/src/Service/DeletionPlanService.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
@@ -26,12 +26,13 @@ class ComposerStaticInit4f96a7199e2c0d90e547333758b26464
|
||||
|
||||
public static $classMap = array (
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => __DIR__ . '/../..' . '/src/Controller/DataFeatureRemovalController.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanSummaryEntity' => __DIR__ . '/../..' . '/src/Entity/DeletionPlanSummaryEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalException' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalException.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalLog.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverAuditRuleService' => __DIR__ . '/../..' . '/src/Model/DataFeatureRemoverAuditRuleService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverExtensionService' => __DIR__ . '/../..' . '/src/Model/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\SetupAudit' => __DIR__ . '/../..' . '/src/Service/SetupAudit.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DeletionPlanService' => __DIR__ . '/../..' . '/src/Service/DeletionPlanService.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@
|
||||
<choice>
|
||||
<extension_code>itop-problem-mgmt</extension_code>
|
||||
<title>Problem Management</title>
|
||||
<description>Select this option to track "Problems" in iTop.</description>
|
||||
<description>Select this option track "Problems" in iTop.</description>
|
||||
<modules type="array">
|
||||
<module>itop-problem-mgmt</module>
|
||||
</modules>
|
||||
|
||||
@@ -408,7 +408,6 @@ class iTopExtensionsMap
|
||||
'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource),
|
||||
'uninstallable' => $oExtension->CanBeUninstalled(),
|
||||
'missing' => $oExtension->bRemovedFromDisk,
|
||||
'version' => $oExtension->sVersion,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -417,18 +416,26 @@ class iTopExtensionsMap
|
||||
|
||||
protected function GetExtensionSourceLabel($sSource)
|
||||
{
|
||||
$sResult = '';
|
||||
$sDecorationClass = '';
|
||||
switch ($sSource) {
|
||||
case iTopExtension::SOURCE_MANUAL:
|
||||
$sResult = 'Local extensions folder';
|
||||
$sDecorationClass = 'fas fa-folder';
|
||||
break;
|
||||
|
||||
case iTopExtension::SOURCE_REMOTE:
|
||||
$sResult = (ITOP_APPLICATION == 'iTop') ? 'iTop Hub' : 'ITSM Designer';
|
||||
$sDecorationClass = (ITOP_APPLICATION == 'iTop') ? 'fc fc-chameleon-icon' : 'fa pencil-ruler';
|
||||
break;
|
||||
|
||||
default:
|
||||
$sResult = '';
|
||||
}
|
||||
return $sResult;
|
||||
if ($sResult == '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<i class="setup-extension--icon '.$sDecorationClass.'" data-tooltip-content="'.$sResult.'"></i>';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -638,6 +638,7 @@ EOF
|
||||
if ($index + 1 >= count($this->aSteps)) {
|
||||
//make sure we also cache next step as well
|
||||
$aOptions = $this->oExtensionsMap->GetAllExtensionsOptionInfo($bRemoteExtensionsShouldBeMandatory);
|
||||
|
||||
// Display this step of the wizard only if there is something to display
|
||||
if (count($aOptions) > 0) {
|
||||
$this->aSteps[] = [
|
||||
@@ -671,7 +672,7 @@ EOF
|
||||
$oITopExtension = $this->oExtensionsMap->GetFromExtensionCode($aChoice['extension_code']);
|
||||
//If the extension is missing from disk, it won't exist in the ExtensionsMap, thus returning null
|
||||
$bCanBeUninstalled = isset($aChoice['uninstallable']) ? $aChoice['uninstallable'] === true || $aChoice['uninstallable'] === 'yes' : $oITopExtension->CanBeUninstalled();
|
||||
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] === $sChoiceId);
|
||||
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
|
||||
$bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true;
|
||||
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']);
|
||||
$bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled;
|
||||
@@ -727,16 +728,39 @@ EOF
|
||||
|
||||
foreach ($aOptions as $index => $aChoice) {
|
||||
$sChoiceId = $sParentId.self::$SEP.$index;
|
||||
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
$aFlags = $this->ComputeChoiceFlags($aChoice, $sChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $this->bUpgrade);
|
||||
|
||||
$sTooltip = '';
|
||||
$sUnremovable = '';
|
||||
if ($aFlags['missing']) {
|
||||
$sTooltip .= '<div class="setup-extension-tag removed">source removed</div>';
|
||||
}
|
||||
if ($aFlags['installed']) {
|
||||
$sTooltip .= '<div class="setup-extension-tag checked installed">installed</div>';
|
||||
$sTooltip .= '<div class="setup-extension-tag unchecked tobeuninstalled">to be uninstalled</div>';
|
||||
} else {
|
||||
$sTooltip .= '<div class="setup-extension-tag checked tobeinstalled">to be installed</div>';
|
||||
$sTooltip .= '<div class="setup-extension-tag unchecked notinstalled">not installed</div>';
|
||||
}
|
||||
if (!$aFlags['uninstallable']) {
|
||||
$sTooltip .= '<div class="setup-extension-tag notuninstallable">cannot be uninstalled</div>';
|
||||
}
|
||||
if ($aFlags['disabled'] && !$aFlags['checked'] && !$aFlags['uninstallable'] && !$bDisableUninstallCheck) {
|
||||
$this->bCanMoveForward = false;//Disable "Next"
|
||||
}
|
||||
$sChecked = $aFlags['checked'] ? ' checked ' : '';
|
||||
$sDisabled = $aFlags['disabled'] ? ' disabled data-disabled="disabled" ' : '';
|
||||
$sMissingModule = $aFlags['missing'] ? 'setup-extension--missing' : '';
|
||||
|
||||
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $sChoiceId, $aFlags);
|
||||
$sHiddenInput = $aFlags['disabled'] && $aFlags['checked'] ? '<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, $aFlags['disabled'], $sTooltip);
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
|
||||
$sChoiceName = null;
|
||||
$sDisabled = '';
|
||||
$bDisabled = false;
|
||||
$sChoiceIdNone = null;
|
||||
foreach ($aAlternatives as $index => $aChoice) {
|
||||
@@ -744,108 +768,67 @@ EOF
|
||||
if ($sChoiceName == null) {
|
||||
$sChoiceName = $sChoiceId; // All radios share the same name
|
||||
}
|
||||
//Defaults contains previous installation choices during upgrade
|
||||
$bIsDefault = array_key_exists($sChoiceName, $aDefaults) && ($aDefaults[$sChoiceName] === $sChoiceId);
|
||||
$bIsDefault = array_key_exists($sChoiceName, $aDefaults) && ($aDefaults[$sChoiceName] == $sChoiceId);
|
||||
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault);
|
||||
if ($bMandatory || $bAllDisabled) {
|
||||
// One choice is mandatory, all alternatives are disabled
|
||||
$sDisabled = ' disabled data-disabled="disabled"';
|
||||
$bDisabled = true;
|
||||
}
|
||||
if ((!isset($aChoice['sub_options']) || (count($aChoice['sub_options']) === 0)) && (!isset($aChoice['modules']) || (count($aChoice['modules']) === 0))) {
|
||||
//If there is no modules in the choice AND it has no sub choices, it is an empty choice.
|
||||
if ((!isset($aChoice['sub_options']) || (count($aChoice['sub_options']) == 0)) && (!isset($aChoice['modules']) || (count($aChoice['modules']) == 0))) {
|
||||
$sChoiceIdNone = $sChoiceId; // the "None" / empty choice
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists($sChoiceName, $aDefaults) || ($aDefaults[$sChoiceName] === $sChoiceIdNone)) {
|
||||
if (!array_key_exists($sChoiceName, $aDefaults) || ($aDefaults[$sChoiceName] == $sChoiceIdNone)) {
|
||||
// The "none" choice does not disable the selection !!
|
||||
$sDisabled = '';
|
||||
$bDisabled = false;
|
||||
}
|
||||
|
||||
foreach ($aAlternatives as $index => $aChoice) {
|
||||
$sAttributes = '';
|
||||
$sChoiceId = $sParentId.self::$SEP.$index;
|
||||
if ($sChoiceName === null) {
|
||||
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
if ($sChoiceName == null) {
|
||||
$sChoiceName = $sChoiceId; // All radios share the same name
|
||||
}
|
||||
$bSelected = isset($aSelectedComponents[$sChoiceName]) && ($aSelectedComponents[$sChoiceName] === $sChoiceId);
|
||||
if (!isset($aSelectedComponents[$sChoiceName]) && ($sChoiceIdNone !== null)) {
|
||||
$bIsDefault = array_key_exists($sChoiceName, $aDefaults) && ($aDefaults[$sChoiceName] == $sChoiceId);
|
||||
$bSelected = isset($aSelectedComponents[$sChoiceName]) && ($aSelectedComponents[$sChoiceName] == $sChoiceId);
|
||||
if (!isset($aSelectedComponents[$sChoiceName]) && ($sChoiceIdNone != null)) {
|
||||
// No choice selected, select the "None" option
|
||||
$bSelected = ($sChoiceId === $sChoiceIdNone);
|
||||
$bSelected = ($sChoiceId == $sChoiceIdNone);
|
||||
}
|
||||
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault);
|
||||
|
||||
$aFlags = $this->ComputeChoiceFlags($aChoice, $sChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $this->bUpgrade);
|
||||
//ComputeChoiceFlags does not completely compute alternative flags
|
||||
$aFlags['disabled'] = $bDisabled;
|
||||
$aFlags['checked'] = $bSelected;
|
||||
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceName, $sChoiceId, $aFlags, 'radio');
|
||||
if ($bSelected) {
|
||||
$sAttributes = ' checked ';
|
||||
}
|
||||
$sHidden = '';
|
||||
if ($bMandatory && $bDisabled) {
|
||||
$sAttributes = ' checked ';
|
||||
$sHidden = '<input type="hidden" name="choice['.$sChoiceName.']" value="'.$sChoiceId.'"/>';
|
||||
}
|
||||
$oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice" id="'.$sId.'" name="choice['.$sChoiceName.']" type="radio"'.$sAttributes.' value="'.$sChoiceId.'"'.$sDisabled.'/>'.$sHidden.' ');
|
||||
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled && !$bSelected);
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
}
|
||||
|
||||
protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceName, $sChoiceId, $aFlags, $sInputType = 'checkbox')
|
||||
protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false, $sTooltip = '')
|
||||
{
|
||||
$sMoreInfo = (isset($aChoice['more_info']) && ($aChoice['more_info'] != '')) ? '
|
||||
<a class="setup--wizard-choice--more-info" target="_blank" href="'.$aChoice['more_info'].'">
|
||||
<i class="setup-extension--icon fas fa-external-link-alt" title="More information"></i>
|
||||
</a>' : '';
|
||||
$sDescription = isset($aChoice['description']) ? utils::EscapeHtml($aChoice['description']) : '';
|
||||
$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'] ?? '';
|
||||
$sId = utils::EscapeHtml($aChoice['extension_code']);
|
||||
$sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"';
|
||||
$sDisabled = $aFlags['disabled'] ? ' disabled data-disabled="disabled"' : '';
|
||||
$sChecked = $aFlags['checked'] ? ' checked ' : '';
|
||||
$sHiddenInput = $aFlags['disabled'] && $aFlags['checked'] ? '<input type="hidden" name="choice['.$sChoiceName.']" value="'.$sChoiceId.'"/>' : '';
|
||||
|
||||
$sTooltip = '';
|
||||
if ($aFlags['missing']) {
|
||||
$sTooltip .= '<span class="ibo-badge ibo-block ibo-is-red" title="The local extension folder has been removed from the disk. This will force the uninstallation of this extension." >source removed</span>';
|
||||
}
|
||||
if ($aFlags['installed']) {
|
||||
$sTooltip .= '<span class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</span>';
|
||||
|
||||
$sTooltip .= '<span class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</span>';
|
||||
} else {
|
||||
$sTooltip .= '<span class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</span>';
|
||||
$sTooltip .= '<span class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</span>';
|
||||
}
|
||||
if (!$aFlags['uninstallable']) {
|
||||
$sTooltip .= '<span class="ibo-badge ibo-block ibo-is-orange" title="Once this extension has been installed, it should not be uninstalled." >cannot be uninstalled</span>';
|
||||
}
|
||||
|
||||
$sMetadata = '';
|
||||
if (isset($aChoice['version']) && isset($aChoice['source_label'])) {
|
||||
$sMetadata = '<span>v'.$aChoice['version'].'</span><span>'.$aChoice['source_label'].'</span>';
|
||||
}
|
||||
$sChoiceDisabled = $aFlags['disabled'] && !$aFlags['checked'] ? 'choice-disabled' : '';
|
||||
|
||||
$oPage->add('
|
||||
<div class="ibo-extension-details ibo-content-block ibo-block '.$sChoiceDisabled.'" '.$sDataId.'>
|
||||
<div class="ibo-extension-details--actions">
|
||||
<input class="wiz-choice" id="'.$sId.'" name="choice['.$sChoiceName.']" type="'.$sInputType.'" value="'.$sChoiceId.'" '.$sDisabled.$sChecked.'/>
|
||||
'.$sHiddenInput.'
|
||||
</div>
|
||||
<div class="ibo-extension-details--information">
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="'.$sId.'"><b>'.utils::EscapeHtml($aChoice['title']).'</b></label>
|
||||
'.$sMoreInfo.'
|
||||
'.$sTooltip.'
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
'.$sMetadata.'
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--description">
|
||||
'.$sDescription.'
|
||||
<div id="sub_choices'.$sId.'">
|
||||
|
||||
');
|
||||
$bSubOptionsDisabled = $aFlags['disabled'] && (!$aFlags['installed'] || $sInputType === 'checkbox');
|
||||
$oPage->add('<label class="setup--wizard-choice--label" for="'.$sId.'">'.$sSourceLabel.'<b>'.utils::EscapeHtml($aChoice['title']).'</b>'.' '.$sTooltip.'</label> '.$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'])) {
|
||||
$this->DisplayOptions($oPage, $aChoice['sub_options'], $aSelectedComponents, $aDefaults, $sChoiceId, $bSubOptionsDisabled);
|
||||
$this->DisplayOptions($oPage, $aChoice['sub_options'], $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled);
|
||||
}
|
||||
$oPage->add('
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
');
|
||||
$oPage->add('</span></div>');
|
||||
}
|
||||
|
||||
protected function GetSourceFilePath()
|
||||
|
||||
@@ -219,7 +219,7 @@
|
||||
<choice>
|
||||
<extension_code>itop-problem-mgmt</extension_code>
|
||||
<title>Problem Management</title>
|
||||
<description>Select this option to track "Problems" in iTop.</description>
|
||||
<description>Select this option track "Problems" in iTop.</description>
|
||||
<modules type="array">
|
||||
<module>itop-problem-mgmt</module>
|
||||
</modules>
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
<choice>
|
||||
<extension_code>itop-problem-mgmt</extension_code>
|
||||
<title>Problem Management</title>
|
||||
<description>Select this option to track "Problems" in iTop.</description>
|
||||
<description>Select this option track "Problems" in iTop.</description>
|
||||
<modules type="array">
|
||||
<module>itop-problem-mgmt</module>
|
||||
</modules>
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
<choice>
|
||||
<extension_code>itop-problem-mgmt</extension_code>
|
||||
<title>Problem Management</title>
|
||||
<description>Select this option to track "Problems" in iTop.</description>
|
||||
<description>Select this option track "Problems" in iTop.</description>
|
||||
<modules type="array">
|
||||
<module>itop-problem-mgmt</module>
|
||||
</modules>
|
||||
|
||||
Reference in New Issue
Block a user