mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-13 13:04:13 +01:00
Compare commits
192 Commits
feature/91
...
feature/87
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37ed4b1294 | ||
|
|
35d3194e6c | ||
|
|
a627f8a471 | ||
|
|
7713a113cc | ||
|
|
1a93b303e3 | ||
|
|
0dc32b3b13 | ||
|
|
048b1a94b1 | ||
|
|
d21b1eb151 | ||
|
|
d17c3aabbb | ||
|
|
f010476923 | ||
|
|
49e58692e3 | ||
|
|
fb3cbcd779 | ||
|
|
c898df461f | ||
|
|
e50109a898 | ||
|
|
c6589b07f1 | ||
|
|
bd20818b66 | ||
|
|
b653236e86 | ||
|
|
75afd3ba46 | ||
|
|
2530d59e08 | ||
|
|
a77e54c8cb | ||
|
|
eff59a93fd | ||
|
|
fdc958a734 | ||
|
|
2b8de85af8 | ||
|
|
41d15f1b33 | ||
|
|
94fed54529 | ||
|
|
5352047ce4 | ||
|
|
2f36846d87 | ||
|
|
49ff9378aa | ||
|
|
4d7183e067 | ||
|
|
c40e7ab10e | ||
|
|
090925e28b | ||
|
|
9543624a0d | ||
|
|
0d3f7c5f07 | ||
|
|
8374d12869 | ||
|
|
9c00ea2bbc | ||
|
|
be54bd5602 | ||
|
|
b315b97e9e | ||
|
|
1fe6103d4f | ||
|
|
3503805fe1 | ||
|
|
e092f87ae9 | ||
|
|
af8fb733fb | ||
|
|
b9591b05d8 | ||
|
|
ee762b91c1 | ||
|
|
ba1d62a147 | ||
|
|
4349303a78 | ||
|
|
4e6c12b903 | ||
|
|
099b996c9a | ||
|
|
5295dec868 | ||
|
|
9c540b6227 | ||
|
|
842bbcee9d | ||
|
|
4620710f5a | ||
|
|
b1129f19d7 | ||
|
|
d8c0cd7f21 | ||
|
|
8f8a0e2672 | ||
|
|
3d8e429c2b | ||
|
|
54aaf55a92 | ||
|
|
a88f33575d | ||
|
|
e790929cbe | ||
|
|
81f056a91c | ||
|
|
06e5c80786 | ||
|
|
e67fcbcfdc | ||
|
|
8401aa1aec | ||
|
|
8f39e69002 | ||
|
|
1e859ef890 | ||
|
|
b8a093e625 | ||
|
|
d0a2af44ac | ||
|
|
827250357e | ||
|
|
6aabed3063 | ||
|
|
041ee2af95 | ||
|
|
72c5cbfedf | ||
|
|
a0f28f725c | ||
|
|
02650f8cf7 | ||
|
|
91958646d2 | ||
|
|
87ca11b771 | ||
|
|
852238a110 | ||
|
|
8e3d132714 | ||
|
|
7a515d04dd | ||
|
|
7a67267c7d | ||
|
|
10ac497a18 | ||
|
|
283ecb6e67 | ||
|
|
33e8f9c3ae | ||
|
|
039beb4c07 | ||
|
|
f2a5559eea | ||
|
|
ce53487093 | ||
|
|
dd4eb9b9f0 | ||
|
|
a19472cfd1 | ||
|
|
7b29d562c8 | ||
|
|
3e471edefd | ||
|
|
bd44f971e4 | ||
|
|
65bd6d9fd0 | ||
|
|
6678689b77 | ||
|
|
8f05c2bcba | ||
|
|
4169e10eec | ||
|
|
e95ce21188 | ||
|
|
24d78df21d | ||
|
|
546912ad77 | ||
|
|
f93926528f | ||
|
|
0cb7c7359f | ||
|
|
7c0f454936 | ||
|
|
11973d61ec | ||
|
|
e08835c1a2 | ||
|
|
8ecb36b1fe | ||
|
|
6a4ed98299 | ||
|
|
2a8ed931e9 | ||
|
|
07a61d5f0d | ||
|
|
047c820466 | ||
|
|
753d0acce4 | ||
|
|
51ebbc4274 | ||
|
|
3085023267 | ||
|
|
e02fc9cb44 | ||
|
|
ea95ae60a6 | ||
|
|
b9d905ad1b | ||
|
|
5b114285cc | ||
|
|
b34566076e | ||
|
|
750b2b9cfa | ||
|
|
bdde63a39c | ||
|
|
891df6ab1c | ||
|
|
dc1ce2dc64 | ||
|
|
e5058fb8f7 | ||
|
|
f68082da96 | ||
|
|
7733f13d14 | ||
|
|
cef8fbc859 | ||
|
|
36d49d3550 | ||
|
|
c2ec3a9f02 | ||
|
|
4d159ea3f1 | ||
|
|
5cacfcb754 | ||
|
|
101b1b217e | ||
|
|
5af8adf7ce | ||
|
|
3d2485a004 | ||
|
|
cbb0e2ef7e | ||
|
|
7095e5e959 | ||
|
|
7fd27913f4 | ||
|
|
cfdf4121d3 | ||
|
|
a448f668bc | ||
|
|
b6ec29c6ec | ||
|
|
03c37f2021 | ||
|
|
5b7a1ee44f | ||
|
|
9ea546ebf6 | ||
|
|
8fdad7997e | ||
|
|
b3ed7f4f5b | ||
|
|
4f4ba7167d | ||
|
|
a092b65be7 | ||
|
|
5a1f6ffde9 | ||
|
|
0909ddfb3a | ||
|
|
20da9664c2 | ||
|
|
68d2038488 | ||
|
|
4c9373d034 | ||
|
|
0d7ccd7d82 | ||
|
|
077c48870f | ||
|
|
5df44b8a18 | ||
|
|
d4415d6c3a | ||
|
|
0196765eb6 | ||
|
|
45e4d9239c | ||
|
|
0df54413b3 | ||
|
|
601cde0e2d | ||
|
|
4c10cfee60 | ||
|
|
7a6f36b395 | ||
|
|
06dbdcb5cd | ||
|
|
c00bcbcd81 | ||
|
|
1394edc111 | ||
|
|
8582e89b8c | ||
|
|
6952bfa978 | ||
|
|
98048219e7 | ||
|
|
8134d9a592 | ||
|
|
2a3de68652 | ||
|
|
5d335b39d2 | ||
|
|
c2fcf4144b | ||
|
|
675db85131 | ||
|
|
212309e938 | ||
|
|
a4fbe90579 | ||
|
|
eb3c5e4eee | ||
|
|
33c03f9493 | ||
|
|
243a525e15 | ||
|
|
a1025ac837 | ||
|
|
7708f8e00e | ||
|
|
a51272f107 | ||
|
|
5582afe5ae | ||
|
|
21b786e0fa | ||
|
|
fd449d195d | ||
|
|
98c0b11db7 | ||
|
|
8c3543363e | ||
|
|
5dea3f5299 | ||
|
|
239814a38c | ||
|
|
c4e2cc6c1c | ||
|
|
ee745f8be9 | ||
|
|
1a4a64cec1 | ||
|
|
834d4d461b | ||
|
|
5df73f5820 | ||
|
|
c83d998924 | ||
|
|
ccdfbbe0bf | ||
|
|
0dae7346d1 | ||
|
|
cdfded766f |
16
.phpstorm.meta.php
Normal file
16
.phpstorm.meta.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace PHPSTORM_META
|
||||
{
|
||||
override(\MetaModel::NewObject(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
override(\MetaModel::GetObject(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
}
|
||||
@@ -16,5 +16,5 @@ require_once(APPROOT.'/application/audit.category.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.domain.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.rule.class.inc.php');
|
||||
require_once(APPROOT.'/application/query.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstallation/moduleinstallation.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstallation.class.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
|
||||
@@ -849,5 +849,222 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
|
||||
</methods>
|
||||
</class>
|
||||
</classes>
|
||||
<property_trees _delta="define" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://www.combodo.com/itop-schema/3.3">
|
||||
<property_tree id="DashletGroupBy" xsi:type="Combodo-PropertyTree">
|
||||
<extends>Dashlet</extends>
|
||||
<nodes>
|
||||
<node id="title" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletGroupBy:Prop-Title</label>
|
||||
<value-type xsi:type="Combodo-ValueType-Label"/><!-- LabelFormBlock -->
|
||||
</node>
|
||||
<node id="query" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletGroupBy:Prop-Query</label>
|
||||
<data-transform>
|
||||
<xml-node-empty-not-allowed/> <!-- mandatory quoi ! -->
|
||||
</data-transform>
|
||||
<value-type xsi:type="Combodo-ValueType-OQL"/><!-- OqlFormBlock -->
|
||||
</node>
|
||||
<node id="group_by" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletGroupBy:Prop-GroupBy</label>
|
||||
<data-transform>
|
||||
<xml-node-empty-not-allowed/>
|
||||
</data-transform>
|
||||
<value-type xsi:type="Combodo-ValueType-ClassAttributeGroupBy"> <!-- par défaut, il proposera le state attribute de la classe -->
|
||||
<class>{{query.selected_class}}</class>
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="style" xsi:type="Combodo-Property"> <!-- possible de le cacher, etc.... celui-ci nous met dedans -->
|
||||
<label>UI:DashletGroupBy:Prop-Style</label>
|
||||
<data-transform>
|
||||
<xml-node-empty-defaults-to>bars</xml-node-empty-defaults-to>
|
||||
</data-transform>
|
||||
<value-type xsi:type="Combodo-ValueType-Choice">
|
||||
<values>
|
||||
<value id="bars">
|
||||
<label>UI:DashletGroupByBars:Label</label>
|
||||
</value>
|
||||
<value id="pie">
|
||||
<label>UI:DashletGroupByPie:Label</label>
|
||||
</value>
|
||||
<value id="table">
|
||||
<label>UI:DashletGroupByTable:Label</label>
|
||||
</value>
|
||||
</values>
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="aggregation_function" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletGroupBy:Prop-Function</label>
|
||||
<data-transform>
|
||||
<xml-node-empty-not-allowed/> <!-- mandatory quoi ! -->
|
||||
</data-transform>
|
||||
<value-type xsi:type="Combodo-ValueType-AggregateFunction"> <!-- par défaut : count -->
|
||||
<class>{{query.selected_class}}</class> <!-- pour savoir si il y a des attributs additionnables -->
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="aggregation_attribute" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletGroupBy:Prop-FunctionAttribute</label>
|
||||
<relevance-condition>{{aggregation_function.value != 'count'}}</relevance-condition>
|
||||
<data-transform>
|
||||
<xml-node-empty-not-allowed/> <!-- mandatory quoi ! -->
|
||||
</data-transform>
|
||||
<value-type xsi:type="Combodo-ValueType-ClassAttribute">
|
||||
<class>{{query.selected_class}}</class>
|
||||
<category>numeric</category>
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="order_by" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletGroupBy:Prop-OrderField</label>
|
||||
<value-type xsi:type="Combodo-ValueType-ChoiceFromInput">
|
||||
<values>
|
||||
<value id="attribute">
|
||||
<label>{{aggregation_attribute.label}}</label>
|
||||
</value>
|
||||
<value id="function">
|
||||
<label>{{aggregation_function.label}}</label>
|
||||
</value>
|
||||
</values>
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="limit" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletGroupBy:Prop-Limit</label>
|
||||
<relevance-condition>{{order_by.value = 'function'}}</relevance-condition>
|
||||
<value-type xsi:type="Combodo-ValueType-Integer"/>
|
||||
</node>
|
||||
<node id="order_direction" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletGroupBy:Prop-OrderDirection</label>
|
||||
<data-transform>
|
||||
<xml-node-empty-not-allowed/>
|
||||
</data-transform>
|
||||
<value-type xsi:type="Combodo-ValueType-Choice">
|
||||
<values>
|
||||
<value id="asc">
|
||||
<label>UI:DashletGroupBy:Order:asc</label>
|
||||
</value>
|
||||
<value id="desc">
|
||||
<label>UI:DashletGroupBy:Order:desc</label>
|
||||
</value>
|
||||
</values>
|
||||
</value-type>
|
||||
<display-default>desc</display-default>
|
||||
</node>
|
||||
</nodes>
|
||||
</property_tree>
|
||||
<property_tree id="DashletBadge" xsi:type="Combodo-PropertyTree">
|
||||
<extends>Dashlet</extends>
|
||||
<nodes>
|
||||
<node id="class" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletBadge:Prop-Class</label>
|
||||
<value-type xsi:type="Combodo-ValueType-Class">
|
||||
<categories-csv>bizmodel</categories-csv>
|
||||
</value-type>
|
||||
</node>
|
||||
</nodes>
|
||||
</property_tree>
|
||||
<property_tree id="DashletHeaderDynamic" xsi:type="Combodo-PropertyTree">
|
||||
<extends>Dashlet</extends>
|
||||
<nodes>
|
||||
<node id="title" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletHeaderDynamic:Prop-Title</label>
|
||||
<value-type xsi:type="Combodo-ValueType-Label">
|
||||
<not-blank/>
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="icon" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletHeaderDynamic:Prop-Icon</label>
|
||||
<value-type xsi:type="Combodo-ValueType-Choice">
|
||||
<values>
|
||||
<value id="todo">
|
||||
<label>ToDo</label>
|
||||
</value>
|
||||
</values>
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="subtitle" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletHeaderDynamic:Prop-Subtitle</label>
|
||||
<value-type xsi:type="Combodo-ValueType-Label"/>
|
||||
</node>
|
||||
<node id="query" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletHeaderDynamic:Prop-Query</label>
|
||||
<data-transform>
|
||||
<xml-node-empty-not-allowed/>
|
||||
</data-transform>
|
||||
<value-type xsi:type="Combodo-ValueType-OQL"/>
|
||||
</node>
|
||||
<node id="group_by" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletHeaderDynamic:Prop-GroupBy</label>
|
||||
<data-transform>
|
||||
<xml-node-empty-not-allowed/>
|
||||
</data-transform>
|
||||
<value-type xsi:type="Combodo-ValueType-ClassAttribute">
|
||||
<class>{{query.selected_class}}</class>
|
||||
<category>enum</category>
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="values" xsi:type="Combodo-CollectionOfValues">
|
||||
<label>UI:DashletHeaderDynamic:Prop-Values</label>
|
||||
<data-transform>
|
||||
<xml-format xsi:type="xml-format-csv"/>
|
||||
</data-transform>
|
||||
<value-type xsi:type="Combodo-ValueType-ClassAttributeValue">
|
||||
<class>{{query.selected_class}}</class>
|
||||
<attribute>{{group_by.value}}</attribute>
|
||||
</value-type>
|
||||
</node>
|
||||
</nodes>
|
||||
</property_tree>
|
||||
<property_tree id="DashletHeaderStatic" xsi:type="Combodo-PropertyTree">
|
||||
<extends>Dashlet</extends>
|
||||
<nodes>
|
||||
<node id="title" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletHeaderStatic:Prop-Title</label>
|
||||
<value-type xsi:type="Combodo-ValueType-Label"/>
|
||||
</node>
|
||||
<node id="icon" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletHeaderStatic:Prop-Icon</label>
|
||||
<value-type xsi:type="Combodo-ValueType-Icon"/>
|
||||
</node>
|
||||
</nodes>
|
||||
</property_tree>
|
||||
<property_tree id="DashletObjectList" xsi:type="Combodo-PropertyTree">
|
||||
<extends>Dashlet</extends>
|
||||
<nodes>
|
||||
<node id="title" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletObjectList:Prop-Title</label>
|
||||
<value-type xsi:type="Combodo-ValueType-Label">
|
||||
</value-type>
|
||||
</node>
|
||||
<node id="query" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletObjectList:Prop-Query</label>
|
||||
<value-type xsi:type="Combodo-ValueType-OQL"/>
|
||||
</node>
|
||||
<node id="menu" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletObjectList:Prop-Menu</label>
|
||||
<value-type xsi:type="Combodo-ValueType-Boolean">
|
||||
<on>
|
||||
<!-- not so cute, but matches exactly 3.2 implementation of boolean fields -->
|
||||
<label>UI:UserManagement:ActionAllowed:Yes</label>
|
||||
<value>true</value>
|
||||
</on>
|
||||
<off>
|
||||
<label>UI:UserManagement:ActionAllowed:No</label>
|
||||
<value>false</value>
|
||||
</off>
|
||||
</value-type>
|
||||
</node>
|
||||
</nodes>
|
||||
</property_tree>
|
||||
<property_tree id="DashletPlainText" xsi:type="Combodo-PropertyTree">
|
||||
<extends>Dashlet</extends>
|
||||
<nodes>
|
||||
<node id="text" xsi:type="Combodo-Property">
|
||||
<label>UI:DashletPlainText:Prop-Text</label>
|
||||
<constraints>
|
||||
<not_blank/>
|
||||
</constraints>
|
||||
<value-type xsi:type="Combodo-ValueType-Text"/>
|
||||
</node>
|
||||
</nodes>
|
||||
</property_tree>
|
||||
</property_trees>
|
||||
</meta>
|
||||
</itop_design>
|
||||
|
||||
@@ -1900,6 +1900,12 @@ SQL;
|
||||
return $response;
|
||||
}
|
||||
|
||||
public static function QuoteForPHP(string $sValue): string
|
||||
{
|
||||
$sEscaped = str_replace(['\\', "'"], ['\\\\', "\\'"], $sValue);
|
||||
return "'$sEscaped'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a standard list of character sets
|
||||
*
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"symfony/mailer": "^6.4",
|
||||
"symfony/security-csrf": "^6.4",
|
||||
"symfony/twig-bundle": "~6.4.0",
|
||||
"symfony/validator" : "^6.4",
|
||||
"symfony/yaml": "~6.4.0",
|
||||
"tecnickcom/tcpdf": "^6.6.0",
|
||||
"thenetworg/oauth2-azure": "^2.0"
|
||||
|
||||
103
composer.lock
generated
103
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "26fa7aa920057d080bcc0948bf052fda",
|
||||
"content-hash" : "3e627286597661542dd598499c2bcc36",
|
||||
"packages": [
|
||||
{
|
||||
"name": "apereo/phpcas",
|
||||
@@ -4984,6 +4984,107 @@
|
||||
"time": "2025-07-10T08:14:14+00:00"
|
||||
},
|
||||
{
|
||||
"name" : "symfony/validator",
|
||||
"version" : "v6.4.29",
|
||||
"source" : {
|
||||
"type" : "git",
|
||||
"url" : "https://github.com/symfony/validator.git",
|
||||
"reference" : "99df8a769e64e399f510166141ea74f450e8dd1d"
|
||||
},
|
||||
"dist" : {
|
||||
"type" : "zip",
|
||||
"url" : "https://api.github.com/repos/symfony/validator/zipball/99df8a769e64e399f510166141ea74f450e8dd1d",
|
||||
"reference" : "99df8a769e64e399f510166141ea74f450e8dd1d",
|
||||
"shasum" : ""
|
||||
},
|
||||
"require" : {
|
||||
"php" : ">=8.1",
|
||||
"symfony/deprecation-contracts" : "^2.5|^3",
|
||||
"symfony/polyfill-ctype" : "~1.8",
|
||||
"symfony/polyfill-mbstring" : "~1.0",
|
||||
"symfony/polyfill-php83" : "^1.27",
|
||||
"symfony/translation-contracts" : "^2.5|^3"
|
||||
},
|
||||
"conflict" : {
|
||||
"doctrine/annotations" : "<1.13",
|
||||
"doctrine/lexer" : "<1.1",
|
||||
"symfony/dependency-injection" : "<5.4",
|
||||
"symfony/expression-language" : "<5.4",
|
||||
"symfony/http-kernel" : "<5.4",
|
||||
"symfony/intl" : "<5.4",
|
||||
"symfony/property-info" : "<5.4",
|
||||
"symfony/translation" : "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3",
|
||||
"symfony/yaml" : "<5.4"
|
||||
},
|
||||
"require-dev" : {
|
||||
"doctrine/annotations" : "^1.13|^2",
|
||||
"egulias/email-validator" : "^2.1.10|^3|^4",
|
||||
"symfony/cache" : "^5.4|^6.0|^7.0",
|
||||
"symfony/config" : "^5.4|^6.0|^7.0",
|
||||
"symfony/console" : "^5.4|^6.0|^7.0",
|
||||
"symfony/dependency-injection" : "^5.4|^6.0|^7.0",
|
||||
"symfony/expression-language" : "^5.4|^6.0|^7.0",
|
||||
"symfony/finder" : "^5.4|^6.0|^7.0",
|
||||
"symfony/http-client" : "^5.4|^6.0|^7.0",
|
||||
"symfony/http-foundation" : "^5.4|^6.0|^7.0",
|
||||
"symfony/http-kernel" : "^5.4|^6.0|^7.0",
|
||||
"symfony/intl" : "^5.4|^6.0|^7.0",
|
||||
"symfony/mime" : "^5.4|^6.0|^7.0",
|
||||
"symfony/property-access" : "^5.4|^6.0|^7.0",
|
||||
"symfony/property-info" : "^5.4|^6.0|^7.0",
|
||||
"symfony/translation" : "^5.4.35|~6.3.12|^6.4.3|^7.0.3",
|
||||
"symfony/yaml" : "^5.4|^6.0|^7.0"
|
||||
},
|
||||
"type" : "library",
|
||||
"autoload" : {
|
||||
"psr-4" : {
|
||||
"Symfony\\Component\\Validator\\" : ""
|
||||
},
|
||||
"exclude-from-classmap" : [
|
||||
"/Tests/",
|
||||
"/Resources/bin/"
|
||||
]
|
||||
},
|
||||
"notification-url" : "https://packagist.org/downloads/",
|
||||
"license" : [
|
||||
"MIT"
|
||||
],
|
||||
"authors" : [
|
||||
{
|
||||
"name" : "Fabien Potencier",
|
||||
"email" : "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name" : "Symfony Community",
|
||||
"homepage" : "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description" : "Provides tools to validate values",
|
||||
"homepage" : "https://symfony.com",
|
||||
"support" : {
|
||||
"source" : "https://github.com/symfony/validator/tree/v6.4.29"
|
||||
},
|
||||
"funding" : [
|
||||
{
|
||||
"url" : "https://symfony.com/sponsor",
|
||||
"type" : "custom"
|
||||
},
|
||||
{
|
||||
"url" : "https://github.com/fabpot",
|
||||
"type" : "github"
|
||||
},
|
||||
{
|
||||
"url" : "https://github.com/nicolas-grekas",
|
||||
"type" : "github"
|
||||
},
|
||||
{
|
||||
"url" : "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type" : "tidelift"
|
||||
}
|
||||
],
|
||||
"time" : "2025-11-06T20:26:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v6.4.26",
|
||||
"source": {
|
||||
|
||||
@@ -24,7 +24,7 @@ MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.domain.class.inc.php');
|
||||
MetaModel::IncludeModule('application/query.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation/moduleinstallation.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
|
||||
|
||||
MetaModel::IncludeModule('core/event.class.inc.php');
|
||||
MetaModel::IncludeModule('core/action.class.inc.php');
|
||||
|
||||
@@ -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)',
|
||||
@@ -2418,7 +2410,6 @@ class Config
|
||||
public function SetAllowedLoginTypes($aAllowedLoginTypes)
|
||||
{
|
||||
$this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes);
|
||||
$this->Set('allowed_login_types', implode('|', $aAllowedLoginTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2685,13 +2676,14 @@ class Config
|
||||
*
|
||||
* @param array $aParamValues
|
||||
* @param ?string $sModulesDir
|
||||
* @param bool $bPreserveModuleSettings
|
||||
*
|
||||
* @return void The current object is modified directly
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function UpdateFromParams($aParamValues, $sModulesDir = null)
|
||||
public function UpdateFromParams($aParamValues, $sModulesDir = null, $bPreserveModuleSettings = false)
|
||||
{
|
||||
if (isset($aParamValues['application_path'])) {
|
||||
$this->Set('app_root_url', $aParamValues['application_path']);
|
||||
@@ -2739,10 +2731,7 @@ class Config
|
||||
} else {
|
||||
$aSelectedModules = null;
|
||||
}
|
||||
|
||||
if (! is_null($sModulesDir)) {
|
||||
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
|
||||
}
|
||||
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
|
||||
|
||||
if (isset($aParamValues['source_dir'])) {
|
||||
$this->Set('source_dir', $aParamValues['source_dir']);
|
||||
@@ -2760,13 +2749,17 @@ class Config
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function UpdateIncludes(string $sModulesDir, $aSelectedModules = null)
|
||||
public function UpdateIncludes($sModulesDir, $aSelectedModules = null)
|
||||
{
|
||||
if ($sModulesDir === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the arrays below with default values for the application...
|
||||
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
|
||||
$aAddOns = $oEmptyConfig->GetAddOns();
|
||||
|
||||
$aModules = ModuleDiscovery::GetModulesOrderedByDependencies([APPROOT.$sModulesDir]);
|
||||
$aModules = ModuleDiscovery::GetAvailableModules([APPROOT.$sModulesDir]);
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo) {
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules)) {
|
||||
|
||||
@@ -5108,8 +5108,8 @@ abstract class DBObject implements iDisplay
|
||||
protected function GetReferencingObjectsForDeletion($bAllowAllData = false)
|
||||
{
|
||||
$aDependentObjects = [];
|
||||
$aReferencingMe = MetaModel::EnumReferencingClasses(get_class($this));
|
||||
foreach ($aReferencingMe as $sRemoteClass => $aExtKeys) {
|
||||
$aRererencingMe = MetaModel::EnumReferencingClasses(get_class($this));
|
||||
foreach ($aRererencingMe as $sRemoteClass => $aExtKeys) {
|
||||
/** @var \AttributeExternalKey $oExtKeyAttDef */
|
||||
foreach ($aExtKeys as $sExtKeyAttCode => $oExtKeyAttDef) {
|
||||
// skip if external key doesn't require the deletion cascading
|
||||
|
||||
@@ -41,7 +41,7 @@ use utils;
|
||||
/**
|
||||
* Class \Combodo\iTop\DesignDocument
|
||||
*
|
||||
* A design document is the DOM tree that modelize behaviors. One of its
|
||||
* A design document is the DOM tree that models behaviors. One of its
|
||||
* characteristics is that it can be altered by the mean of the same kind of document.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -22,8 +22,6 @@ use Combodo\iTop\Application\EventRegister\ApplicationEvents;
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Setup\ModuleDependency\Module;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
|
||||
require_once APPROOT.'core/modulehandler.class.inc.php';
|
||||
require_once APPROOT.'core/querymodifier.class.inc.php';
|
||||
@@ -464,43 +462,6 @@ abstract class MetaModel
|
||||
return call_user_func([$sClass, 'GetClassDescription'], $sClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final public static function GetModuleName($sClass)
|
||||
{
|
||||
try {
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
$sDir = realpath(dirname($oReflectionClass->getFileName()));
|
||||
$sApproot = realpath(APPROOT);
|
||||
while (($sDir !== $sApproot) && (str_contains($sDir, $sApproot))) {
|
||||
$aFiles = glob("$sDir/module.*.php");
|
||||
if (count($aFiles) > 1) {
|
||||
return 'core';
|
||||
}
|
||||
|
||||
if (count($aFiles) == 0) {
|
||||
$sDir = realpath(dirname($sDir));
|
||||
continue;
|
||||
}
|
||||
|
||||
$sModuleFilePath = $aFiles[0];
|
||||
$aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);
|
||||
$sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
|
||||
list($sModuleName, ) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
|
||||
return $sModuleName;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new CoreException("Cannot find class module", ['class' => $sClass], '', $e);
|
||||
}
|
||||
|
||||
return 'core';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
|
||||
@@ -55,6 +55,11 @@ abstract class ModelReflection
|
||||
abstract public function GetFiltersList($sClass);
|
||||
abstract public function IsValidFilterCode($sClass, $sFilterCode);
|
||||
|
||||
/**
|
||||
* @since 3.3.0
|
||||
*/
|
||||
abstract public function IsAbstract($sClass): bool;
|
||||
|
||||
/**
|
||||
* @param string $sOQL
|
||||
*
|
||||
@@ -148,7 +153,7 @@ class ModelReflectionRuntime extends ModelReflection
|
||||
$sAttributeClass = get_class($oAttDef);
|
||||
if ($aScope != null) {
|
||||
foreach ($aScope as $sScopeClass) {
|
||||
if (($sAttributeClass == $sScopeClass) || is_subclass_of($sAttributeClass, $sScopeClass)) {
|
||||
if (is_a($sAttributeClass, $sScopeClass, true)) {
|
||||
$aAttributes[$sAttCode] = $sAttributeClass;
|
||||
break;
|
||||
}
|
||||
@@ -230,6 +235,11 @@ class ModelReflectionRuntime extends ModelReflection
|
||||
return MetaModel::IsValidFilterCode($sClass, $sFilterCode);
|
||||
}
|
||||
|
||||
public function IsAbstract($sClass): bool
|
||||
{
|
||||
return MetaModel::IsAbstract($sClass);
|
||||
}
|
||||
|
||||
public function GetQuery($sOQL)
|
||||
{
|
||||
return new QueryReflectionRuntime($sOQL, $this);
|
||||
|
||||
@@ -2103,12 +2103,18 @@ class VariableExpression extends UnaryExpression
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
*
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \MissingQueryArgument
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
throw new Exception('not implemented yet');
|
||||
if (!isset($aArgs[$this->m_sName])) {
|
||||
throw new MissingQueryArgument('Missing variable expression argument', array('expecting'=>$this->m_sName));
|
||||
}
|
||||
return $aArgs[$this->m_sName];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,9 +72,15 @@ class OQLException extends CoreException
|
||||
}
|
||||
else
|
||||
{
|
||||
$sExpectations = '{'.implode(', ', $this->m_aExpecting).'}';
|
||||
$sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput'";
|
||||
if (count($this->m_aExpecting) < 30) {
|
||||
$sExpectations = '{'.implode(', ', $this->m_aExpecting).'}';
|
||||
$sMessage .= ', expecting '.json_encode($sExpectations);
|
||||
}
|
||||
$sSuggest = self::FindClosestString($this->m_sUnexpected, $this->m_aExpecting);
|
||||
$sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput', expecting $sExpectations, I would suggest to use '$sSuggest'";
|
||||
if (strlen($sSuggest) > 0) {
|
||||
$sMessage .= ", I would suggest to use ".json_encode($sSuggest);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure everything is assigned properly
|
||||
@@ -155,5 +161,3 @@ class OQLException extends CoreException
|
||||
return $sRet;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -57,15 +57,15 @@ class OqlName
|
||||
{
|
||||
return $this->m_iPos;
|
||||
}
|
||||
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->m_sValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Store hexadecimal values as strings so that we can support 64-bit values
|
||||
*
|
||||
*/
|
||||
@@ -77,12 +77,12 @@ class OqlHexValue
|
||||
{
|
||||
$this->m_sValue = $sValue;
|
||||
}
|
||||
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->m_sValue;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class OqlJoinSpec
|
||||
@@ -109,6 +109,7 @@ class OqlJoinSpec
|
||||
{
|
||||
return $this->m_oClass->GetValue();
|
||||
}
|
||||
|
||||
public function GetClassAlias()
|
||||
{
|
||||
return $this->m_oClassAlias->GetValue();
|
||||
@@ -118,6 +119,7 @@ class OqlJoinSpec
|
||||
{
|
||||
return $this->m_oClass;
|
||||
}
|
||||
|
||||
public function GetClassAliasDetails()
|
||||
{
|
||||
return $this->m_oClassAlias;
|
||||
@@ -127,10 +129,12 @@ class OqlJoinSpec
|
||||
{
|
||||
return $this->m_oLeftField;
|
||||
}
|
||||
|
||||
public function GetRightField()
|
||||
{
|
||||
return $this->m_oRightField;
|
||||
}
|
||||
|
||||
public function GetOperator()
|
||||
{
|
||||
return $this->m_sOperator;
|
||||
@@ -146,8 +150,9 @@ interface CheckableExpression
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
* @param array $aAliases Aliases to class names (for the current query)
|
||||
* @param string $sSourceQuery For the reporting
|
||||
*
|
||||
* @throws OqlNormalizeException
|
||||
*/
|
||||
*/
|
||||
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery);
|
||||
}
|
||||
|
||||
@@ -168,13 +173,11 @@ class MatchOqlExpression extends MatchExpression implements CheckableExpression
|
||||
$this->m_oRightExpr->Check($oModelReflection, $aAliases, $sSourceQuery);
|
||||
|
||||
// Only field MATCHES scalar is allowed
|
||||
if (!$this->m_oLeftExpr instanceof FieldExpression)
|
||||
{
|
||||
if (!$this->m_oLeftExpr instanceof FieldExpression) {
|
||||
throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oLeftExpr->RenderExpression(true), 0));
|
||||
}
|
||||
// Only field MATCHES scalar is allowed
|
||||
if (!$this->m_oRightExpr instanceof ScalarExpression && !$this->m_oRightExpr instanceof VariableOqlExpression)
|
||||
{
|
||||
if (!$this->m_oRightExpr instanceof ScalarExpression && !$this->m_oRightExpr instanceof VariableOqlExpression) {
|
||||
throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oRightExpr->RenderExpression(true), 0));
|
||||
}
|
||||
}
|
||||
@@ -198,7 +201,7 @@ class NestedQueryOqlExpression extends NestedQueryExpression implements Checkabl
|
||||
*
|
||||
* @param OQLObjectQuery $oOQLObjectQuery
|
||||
*/
|
||||
public function __construct($oOQLObjectQuery )
|
||||
public function __construct($oOQLObjectQuery)
|
||||
{
|
||||
parent::__construct($oOQLObjectQuery->ToDBSearch(""));
|
||||
$this->m_oOQLObjectQuery = $oOQLObjectQuery;
|
||||
@@ -232,8 +235,7 @@ class FieldOqlExpression extends FieldExpression implements CheckableExpression
|
||||
|
||||
public function __construct($oName, $oParent = null)
|
||||
{
|
||||
if (is_null($oParent))
|
||||
{
|
||||
if (is_null($oParent)) {
|
||||
$oParent = new OqlName('', 0);
|
||||
}
|
||||
$this->m_oParent = $oParent;
|
||||
@@ -256,37 +258,28 @@ class FieldOqlExpression extends FieldExpression implements CheckableExpression
|
||||
{
|
||||
$sClassAlias = $this->GetParent();
|
||||
$sFltCode = $this->GetName();
|
||||
if (empty($sClassAlias))
|
||||
{
|
||||
if (empty($sClassAlias)) {
|
||||
// Try to find an alias
|
||||
// Build an array of field => array of aliases
|
||||
$aFieldClasses = array();
|
||||
foreach($aAliases as $sAlias => $sReal)
|
||||
{
|
||||
foreach($oModelReflection->GetFiltersList($sReal) as $sAnFltCode)
|
||||
{
|
||||
foreach ($aAliases as $sAlias => $sReal) {
|
||||
foreach ($oModelReflection->GetFiltersList($sReal) as $sAnFltCode) {
|
||||
$aFieldClasses[$sAnFltCode][] = $sAlias;
|
||||
}
|
||||
}
|
||||
if (!array_key_exists($sFltCode, $aFieldClasses))
|
||||
{
|
||||
if (!array_key_exists($sFltCode, $aFieldClasses)) {
|
||||
throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
|
||||
}
|
||||
if (count($aFieldClasses[$sFltCode]) > 1)
|
||||
{
|
||||
if (count($aFieldClasses[$sFltCode]) > 1) {
|
||||
throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails());
|
||||
}
|
||||
$sClassAlias = $aFieldClasses[$sFltCode][0];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!array_key_exists($sClassAlias, $aAliases))
|
||||
{
|
||||
} else {
|
||||
if (!array_key_exists($sClassAlias, $aAliases)) {
|
||||
throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $this->GetParentDetails(), array_keys($aAliases));
|
||||
}
|
||||
$sClass = $aAliases[$sClassAlias];
|
||||
if (!$oModelReflection->IsValidFilterCode($sClass, $sFltCode))
|
||||
{
|
||||
if (!$oModelReflection->IsValidFilterCode($sClass, $sFltCode)) {
|
||||
throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), $oModelReflection->GetFiltersList($sClass));
|
||||
}
|
||||
}
|
||||
@@ -305,8 +298,7 @@ class ListOqlExpression extends ListExpression implements CheckableExpression
|
||||
{
|
||||
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
|
||||
{
|
||||
foreach ($this->GetItems() as $oItemExpression)
|
||||
{
|
||||
foreach ($this->GetItems() as $oItemExpression) {
|
||||
$oItemExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
|
||||
}
|
||||
}
|
||||
@@ -316,8 +308,7 @@ class FunctionOqlExpression extends FunctionExpression implements CheckableExpre
|
||||
{
|
||||
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
|
||||
{
|
||||
foreach ($this->GetArgs() as $oArgExpression)
|
||||
{
|
||||
foreach ($this->GetArgs() as $oArgExpression) {
|
||||
$oArgExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
|
||||
}
|
||||
}
|
||||
@@ -350,6 +341,7 @@ abstract class OqlQuery
|
||||
* Determine the class
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -392,6 +384,7 @@ class OqlObjectQuery extends OqlQuery
|
||||
* Determine the class
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -415,6 +408,7 @@ class OqlObjectQuery extends OqlQuery
|
||||
{
|
||||
return $this->m_oClass;
|
||||
}
|
||||
|
||||
public function GetClassAliasDetails()
|
||||
{
|
||||
return $this->m_oClassAlias;
|
||||
@@ -424,6 +418,7 @@ class OqlObjectQuery extends OqlQuery
|
||||
{
|
||||
return $this->m_aJoins;
|
||||
}
|
||||
|
||||
public function GetCondition()
|
||||
{
|
||||
return $this->m_oCondition;
|
||||
@@ -432,44 +427,37 @@ class OqlObjectQuery extends OqlQuery
|
||||
/**
|
||||
* Recursively check the validity of the expression with regard to the data model
|
||||
* and the query in which it is used
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
*
|
||||
* @throws OqlNormalizeException
|
||||
*/
|
||||
*/
|
||||
public function Check(ModelReflection $oModelReflection, $sSourceQuery, $aParentAliases = array())
|
||||
{
|
||||
$sClass = $this->GetClass($oModelReflection);
|
||||
$sClassAlias = $this->GetClassAlias();
|
||||
|
||||
if (!$oModelReflection->IsValidClass($sClass))
|
||||
{
|
||||
if (!$oModelReflection->IsValidClass($sClass)) {
|
||||
throw new UnknownClassOqlException($sSourceQuery, $this->GetClassDetails(), $oModelReflection->GetClasses());
|
||||
}
|
||||
|
||||
$aAliases = array_merge(array($sClassAlias => $sClass),$aParentAliases);
|
||||
$aAliases = array_merge(array($sClassAlias => $sClass), $aParentAliases);
|
||||
|
||||
$aJoinSpecs = $this->GetJoins();
|
||||
if (is_array($aJoinSpecs))
|
||||
{
|
||||
foreach ($aJoinSpecs as $oJoinSpec)
|
||||
{
|
||||
if (is_array($aJoinSpecs)) {
|
||||
foreach ($aJoinSpecs as $oJoinSpec) {
|
||||
$sJoinClass = $oJoinSpec->GetClass();
|
||||
$sJoinClassAlias = $oJoinSpec->GetClassAlias();
|
||||
if (!$oModelReflection->IsValidClass($sJoinClass))
|
||||
{
|
||||
if (!$oModelReflection->IsValidClass($sJoinClass)) {
|
||||
throw new UnknownClassOqlException($sSourceQuery, $oJoinSpec->GetClassDetails(), $oModelReflection->GetClasses());
|
||||
}
|
||||
if (array_key_exists($sJoinClassAlias, $aAliases))
|
||||
{
|
||||
if ($sJoinClassAlias != $sJoinClass)
|
||||
{
|
||||
if (array_key_exists($sJoinClassAlias, $aAliases)) {
|
||||
if ($sJoinClassAlias != $sJoinClass) {
|
||||
throw new OqlNormalizeException('Duplicate class alias', $sSourceQuery, $oJoinSpec->GetClassAliasDetails());
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
throw new OqlNormalizeException('Duplicate class name', $sSourceQuery, $oJoinSpec->GetClassDetails());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assumption: ext key on the left only !!!
|
||||
// normalization should take care of this
|
||||
@@ -480,85 +468,74 @@ class OqlObjectQuery extends OqlQuery
|
||||
$oRightField = $oJoinSpec->GetRightField();
|
||||
$sToClass = $oRightField->GetParent();
|
||||
$sPKeyDescriptor = $oRightField->GetName();
|
||||
if ($sPKeyDescriptor != 'id')
|
||||
{
|
||||
if ($sPKeyDescriptor != 'id') {
|
||||
throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sSourceQuery, $oRightField->GetNameDetails(), array('id'));
|
||||
}
|
||||
|
||||
$aAliases[$sJoinClassAlias] = $sJoinClass;
|
||||
|
||||
if (!array_key_exists($sFromClass, $aAliases))
|
||||
{
|
||||
if (!array_key_exists($sFromClass, $aAliases)) {
|
||||
throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sSourceQuery, $oLeftField->GetParentDetails(), array_keys($aAliases));
|
||||
}
|
||||
if (!array_key_exists($sToClass, $aAliases))
|
||||
{
|
||||
if (!array_key_exists($sToClass, $aAliases)) {
|
||||
throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sSourceQuery, $oRightField->GetParentDetails(), array_keys($aAliases));
|
||||
}
|
||||
$aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], \Combodo\iTop\Core\AttributeDefinition\AttributeExternalKey::class);
|
||||
$aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], \Combodo\iTop\Core\AttributeDefinition\AttributeObjectKey::class);
|
||||
$aAllKeys = array_merge($aExtKeys, $aObjKeys);
|
||||
if (!array_key_exists($sExtKeyAttCode, $aAllKeys))
|
||||
{
|
||||
if (!array_key_exists($sExtKeyAttCode, $aAllKeys)) {
|
||||
throw new OqlNormalizeException('Unknown key in join condition (left expression)', $sSourceQuery, $oLeftField->GetNameDetails(), array_keys($aAllKeys));
|
||||
}
|
||||
|
||||
if ($sFromClass == $sJoinClassAlias)
|
||||
{
|
||||
if ($sFromClass == $sJoinClassAlias) {
|
||||
if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
|
||||
{
|
||||
$sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
|
||||
if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
|
||||
{
|
||||
if (!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass)) {
|
||||
throw new OqlNormalizeException("The joined class ($aAliases[$sFromClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sOperator = $oJoinSpec->GetOperator();
|
||||
switch($sOperator)
|
||||
{
|
||||
switch ($sOperator) {
|
||||
case '=':
|
||||
$iOperatorCode = TREE_OPERATOR_EQUALS;
|
||||
break;
|
||||
$iOperatorCode = TREE_OPERATOR_EQUALS;
|
||||
break;
|
||||
case 'BELOW':
|
||||
$iOperatorCode = TREE_OPERATOR_BELOW;
|
||||
break;
|
||||
$iOperatorCode = TREE_OPERATOR_BELOW;
|
||||
break;
|
||||
case 'BELOW_STRICT':
|
||||
$iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
|
||||
break;
|
||||
$iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
|
||||
break;
|
||||
case 'NOT_BELOW':
|
||||
$iOperatorCode = TREE_OPERATOR_NOT_BELOW;
|
||||
break;
|
||||
$iOperatorCode = TREE_OPERATOR_NOT_BELOW;
|
||||
break;
|
||||
case 'NOT_BELOW_STRICT':
|
||||
$iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
|
||||
break;
|
||||
$iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
|
||||
break;
|
||||
case 'ABOVE':
|
||||
$iOperatorCode = TREE_OPERATOR_ABOVE;
|
||||
break;
|
||||
$iOperatorCode = TREE_OPERATOR_ABOVE;
|
||||
break;
|
||||
case 'ABOVE_STRICT':
|
||||
$iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
|
||||
break;
|
||||
$iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
|
||||
break;
|
||||
case 'NOT_ABOVE':
|
||||
$iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
|
||||
break;
|
||||
$iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
|
||||
break;
|
||||
case 'NOT_ABOVE_STRICT':
|
||||
$iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
|
||||
break;
|
||||
$iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
|
||||
break;
|
||||
}
|
||||
if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
|
||||
{
|
||||
$sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
|
||||
if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
|
||||
{
|
||||
if (!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass)) {
|
||||
throw new OqlNormalizeException("The joined class ($aAliases[$sToClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
|
||||
}
|
||||
}
|
||||
$aAttList = $oModelReflection->ListAttributes($aAliases[$sFromClass]);
|
||||
$sAttType = $aAttList[$sExtKeyAttCode];
|
||||
if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class) && ($sAttType != \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class))
|
||||
{
|
||||
if (($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_a($sAttType, \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class, true)) {
|
||||
throw new OqlNormalizeException("The specified tree operator $sOperator is not applicable to the key", $sSourceQuery, $oLeftField->GetNameDetails());
|
||||
}
|
||||
}
|
||||
@@ -567,26 +544,23 @@ class OqlObjectQuery extends OqlQuery
|
||||
|
||||
// Check the select information
|
||||
//
|
||||
foreach ($this->GetSelectedClasses() as $oClassDetails)
|
||||
{
|
||||
foreach ($this->GetSelectedClasses() as $oClassDetails) {
|
||||
$sClassToSelect = $oClassDetails->GetValue();
|
||||
if (!array_key_exists($sClassToSelect, $aAliases))
|
||||
{
|
||||
if (!array_key_exists($sClassToSelect, $aAliases)) {
|
||||
throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $oClassDetails, array_keys($aAliases));
|
||||
}
|
||||
}
|
||||
|
||||
// Check the condition tree
|
||||
//
|
||||
if ($this->m_oCondition instanceof Expression)
|
||||
{
|
||||
if ($this->m_oCondition instanceof Expression) {
|
||||
$this->m_oCondition->Check($oModelReflection, $aAliases, $sSourceQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the relevant DBSearch instance (FromOQL)
|
||||
*/
|
||||
*/
|
||||
public function ToDBSearch($sQuery)
|
||||
{
|
||||
$sClass = $this->GetClass(new ModelReflectionRuntime());
|
||||
@@ -594,6 +568,7 @@ class OqlObjectQuery extends OqlQuery
|
||||
|
||||
$oSearch = new DBObjectSearch($sClass, $sClassAlias);
|
||||
$oSearch->InitFromOqlQuery($this, $sQuery);
|
||||
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
@@ -606,19 +581,15 @@ class OqlUnionQuery extends OqlQuery
|
||||
{
|
||||
parent::__construct();
|
||||
$this->aQueries[] = $oLeftQuery;
|
||||
if ($oRightQueryOrUnion instanceof OqlUnionQuery)
|
||||
{
|
||||
foreach ($oRightQueryOrUnion->GetQueries() as $oSingleQuery)
|
||||
{
|
||||
if ($oRightQueryOrUnion instanceof OqlUnionQuery) {
|
||||
foreach ($oRightQueryOrUnion->GetQueries() as $oSingleQuery) {
|
||||
$this->aQueries[] = $oSingleQuery;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$this->aQueries[] = $oRightQueryOrUnion;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function GetQueries()
|
||||
{
|
||||
return $this->aQueries;
|
||||
@@ -627,66 +598,54 @@ class OqlUnionQuery extends OqlQuery
|
||||
/**
|
||||
* Check the validity of the expression with regard to the data model
|
||||
* and the query in which it is used
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
*
|
||||
* @throws OqlNormalizeException
|
||||
*/
|
||||
*/
|
||||
public function Check(ModelReflection $oModelReflection, $sSourceQuery)
|
||||
{
|
||||
$aColumnToClasses = array();
|
||||
foreach ($this->aQueries as $iQuery => $oQuery)
|
||||
{
|
||||
foreach ($this->aQueries as $iQuery => $oQuery) {
|
||||
$oQuery->Check($oModelReflection, $sSourceQuery);
|
||||
|
||||
$aAliasToClass = array($oQuery->GetClassAlias() => $oQuery->GetClass($oModelReflection));
|
||||
$aJoinSpecs = $oQuery->GetJoins();
|
||||
if (is_array($aJoinSpecs))
|
||||
{
|
||||
foreach ($aJoinSpecs as $oJoinSpec)
|
||||
{
|
||||
if (is_array($aJoinSpecs)) {
|
||||
foreach ($aJoinSpecs as $oJoinSpec) {
|
||||
$aAliasToClass[$oJoinSpec->GetClassAlias()] = $oJoinSpec->GetClass();
|
||||
}
|
||||
}
|
||||
|
||||
$aSelectedClasses = $oQuery->GetSelectedClasses();
|
||||
if ($iQuery != 0)
|
||||
{
|
||||
if (count($aSelectedClasses) < count($aColumnToClasses))
|
||||
{
|
||||
if ($iQuery != 0) {
|
||||
if (count($aSelectedClasses) < count($aColumnToClasses)) {
|
||||
$oLastClass = end($aSelectedClasses);
|
||||
throw new OqlNormalizeException('Too few selected classes in the subquery', $sSourceQuery, $oLastClass);
|
||||
}
|
||||
if (count($aSelectedClasses) > count($aColumnToClasses))
|
||||
{
|
||||
if (count($aSelectedClasses) > count($aColumnToClasses)) {
|
||||
$oLastClass = end($aSelectedClasses);
|
||||
throw new OqlNormalizeException('Too many selected classes in the subquery', $sSourceQuery, $oLastClass);
|
||||
}
|
||||
}
|
||||
foreach ($aSelectedClasses as $iColumn => $oClassDetails)
|
||||
{
|
||||
foreach ($aSelectedClasses as $iColumn => $oClassDetails) {
|
||||
$sAlias = $oClassDetails->GetValue();
|
||||
$sClass = $aAliasToClass[$sAlias];
|
||||
$aColumnToClasses[$iColumn][] = array(
|
||||
'alias' => $sAlias,
|
||||
'class' => $sClass,
|
||||
'alias' => $sAlias,
|
||||
'class' => $sClass,
|
||||
'class_name' => $oClassDetails,
|
||||
);
|
||||
}
|
||||
}
|
||||
foreach ($aColumnToClasses as $iColumn => $aClasses)
|
||||
{
|
||||
foreach ($aColumnToClasses as $iColumn => $aClasses) {
|
||||
$sRootClass = null;
|
||||
foreach ($aClasses as $iQuery => $aData)
|
||||
{
|
||||
if ($iQuery == 0)
|
||||
{
|
||||
foreach ($aClasses as $iQuery => $aData) {
|
||||
if ($iQuery == 0) {
|
||||
// Establish the reference
|
||||
$sRootClass = $oModelReflection->GetRootClass($aData['class']);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($oModelReflection->GetRootClass($aData['class']) != $sRootClass)
|
||||
{
|
||||
} else {
|
||||
if ($oModelReflection->GetRootClass($aData['class']) != $sRootClass) {
|
||||
$aSubclasses = $oModelReflection->EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL);
|
||||
throw new OqlNormalizeException('Incompatible classes: could not find a common ancestor', $sSourceQuery, $aData['class_name'], $aSubclasses);
|
||||
}
|
||||
@@ -699,21 +658,21 @@ class OqlUnionQuery extends OqlQuery
|
||||
* Determine the class
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetClass(ModelReflection $oModelReflection)
|
||||
{
|
||||
$aFirstColClasses = array();
|
||||
foreach ($this->aQueries as $iQuery => $oQuery)
|
||||
{
|
||||
foreach ($this->aQueries as $iQuery => $oQuery) {
|
||||
$aFirstColClasses[] = $oQuery->GetClass($oModelReflection);
|
||||
}
|
||||
$sClass = self::GetLowestCommonAncestor($oModelReflection, $aFirstColClasses);
|
||||
if (is_null($sClass))
|
||||
{
|
||||
if (is_null($sClass)) {
|
||||
throw new Exception('Could not determine the class of the union query. This issue should have been detected earlier by calling OqlQuery::Check()');
|
||||
}
|
||||
|
||||
return $sClass;
|
||||
}
|
||||
|
||||
@@ -726,6 +685,7 @@ class OqlUnionQuery extends OqlQuery
|
||||
public function GetClassAlias()
|
||||
{
|
||||
$sAlias = $this->aQueries[0]->GetClassAlias();
|
||||
|
||||
return $sAlias;
|
||||
}
|
||||
|
||||
@@ -735,29 +695,25 @@ class OqlUnionQuery extends OqlQuery
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
* @param array $aClasses Flat list of classes
|
||||
*
|
||||
* @return string the lowest common ancestor amongst classes, null if none has been found
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function GetLowestCommonAncestor(ModelReflection $oModelReflection, $aClasses)
|
||||
{
|
||||
$sAncestor = null;
|
||||
foreach($aClasses as $sClass)
|
||||
{
|
||||
if (is_null($sAncestor))
|
||||
{
|
||||
foreach ($aClasses as $sClass) {
|
||||
if (is_null($sAncestor)) {
|
||||
// first loop
|
||||
$sAncestor = $sClass;
|
||||
}
|
||||
elseif ($oModelReflection->GetRootClass($sClass) != $oModelReflection->GetRootClass($sAncestor))
|
||||
{
|
||||
} elseif ($oModelReflection->GetRootClass($sClass) != $oModelReflection->GetRootClass($sAncestor)) {
|
||||
$sAncestor = null;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sAncestor = self::LowestCommonAncestor($oModelReflection, $sAncestor, $sClass);
|
||||
}
|
||||
}
|
||||
|
||||
return $sAncestor;
|
||||
}
|
||||
|
||||
@@ -766,37 +722,32 @@ class OqlUnionQuery extends OqlQuery
|
||||
*/
|
||||
protected static function LowestCommonAncestor(ModelReflection $oModelReflection, $sClassA, $sClassB)
|
||||
{
|
||||
if ($sClassA == $sClassB)
|
||||
{
|
||||
if ($sClassA == $sClassB) {
|
||||
$sRet = $sClassA;
|
||||
}
|
||||
elseif (in_array($sClassA, $oModelReflection->EnumChildClasses($sClassB)))
|
||||
{
|
||||
} elseif (in_array($sClassA, $oModelReflection->EnumChildClasses($sClassB))) {
|
||||
$sRet = $sClassB;
|
||||
}
|
||||
elseif (in_array($sClassB, $oModelReflection->EnumChildClasses($sClassA)))
|
||||
{
|
||||
} elseif (in_array($sClassB, $oModelReflection->EnumChildClasses($sClassA))) {
|
||||
$sRet = $sClassA;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Recurse
|
||||
$sRet = self::LowestCommonAncestor($oModelReflection, $sClassA, $oModelReflection->GetParentClass($sClassB));
|
||||
}
|
||||
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the relevant DBSearch instance (FromOQL)
|
||||
*/
|
||||
*/
|
||||
public function ToDBSearch($sQuery)
|
||||
{
|
||||
$aSearches = array();
|
||||
foreach ($this->aQueries as $oQuery)
|
||||
{
|
||||
foreach ($this->aQueries as $oQuery) {
|
||||
$aSearches[] = $oQuery->ToDBSearch($sQuery);
|
||||
}
|
||||
|
||||
$oSearch = new DBUnionSearch($aSearches);
|
||||
|
||||
return $oSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,4 @@
|
||||
@import "medallion-with-blocklist";
|
||||
@import "field-badge-within-datatable";
|
||||
@import "jquery-blockui-within-dialog";
|
||||
@import "jquery-blockui-within-datatable";
|
||||
@import "badge-with-badge";
|
||||
@import "extension-details-with-extension-details";
|
||||
@import "jquery-blockui-within-datatable";
|
||||
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-badge--spacing-left--with-same-block: $ibo-spacing-200 !default;
|
||||
|
||||
.ibo-badge + .ibo-badge {
|
||||
margin-left: $ibo-badge--spacing-left--with-same-block;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-extension-details--margin-top: $ibo-spacing-300 !default;
|
||||
|
||||
.ibo-extension-details + .ibo-extension-details,
|
||||
.ibo-extension-details--information--description .ibo-extension-details {
|
||||
margin-top: $ibo-extension-details--margin-top;
|
||||
}
|
||||
@@ -33,5 +33,4 @@
|
||||
@import "field-badge";
|
||||
@import "file-select";
|
||||
@import "medallion-icon";
|
||||
@import "toast";
|
||||
@import "badge";
|
||||
@import "toast";
|
||||
@@ -1,41 +0,0 @@
|
||||
$ibo-badge--padding-x : $ibo-spacing-200 !default;
|
||||
$ibo-badge--padding-y : $ibo-spacing-100 !default;
|
||||
$ibo-badge--border-radius : $ibo-border-radius-400 !default;
|
||||
|
||||
$ibo-badge-colors: (
|
||||
'primary': ($ibo-color-primary-100, $ibo-color-primary-900),
|
||||
'secondary': ($ibo-color-secondary-100, $ibo-color-secondary-900),
|
||||
'neutral': ($ibo-color-secondary-100, $ibo-color-secondary-900),
|
||||
'information': ($ibo-color-information-100, $ibo-color-information-900),
|
||||
'success': ($ibo-color-success-100, $ibo-color-success-900),
|
||||
'failure': ($ibo-color-danger-100, $ibo-color-danger-900),
|
||||
'warning': ($ibo-color-warning-100,$ibo-color-warning-900),
|
||||
'danger': ($ibo-color-danger-100,$ibo-color-danger-900),
|
||||
'grey' : ($ibo-color-grey-100, $ibo-color-grey-900),
|
||||
'blue-grey': ($ibo-color-blue-grey-100, $ibo-color-blue-grey-900),
|
||||
'blue': ($ibo-color-blue-100, $ibo-color-blue-900),
|
||||
'cyan': ($ibo-color-cyan-100, $ibo-color-cyan-900),
|
||||
'green': ($ibo-color-green-100, $ibo-color-green-900),
|
||||
'orange' : ($ibo-color-orange-100, $ibo-color-orange-900),
|
||||
'red': ($ibo-color-red-100, $ibo-color-red-900),
|
||||
'pink': ($ibo-color-pink-100, $ibo-color-pink-900),
|
||||
) !default;
|
||||
|
||||
|
||||
|
||||
.ibo-badge {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
padding : $ibo-badge--padding-y $ibo-badge--padding-x;
|
||||
border-radius : $ibo-badge--border-radius;
|
||||
@extend %ibo-font-ral-med-50;
|
||||
|
||||
@each $sColor, $aColorValues in $ibo-badge-colors {
|
||||
$bg-color: nth($aColorValues, 1);
|
||||
$text-color: nth($aColorValues, 2);
|
||||
&.ibo-is-#{$sColor} {
|
||||
background-color: $bg-color;
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,59 @@
|
||||
.ibo-prop-header {
|
||||
@extend %ibo-font-size-150;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
.help-text{
|
||||
padding: 1px 5px;
|
||||
background-color: #d7e3f8;
|
||||
border: 1px solid #c6e7f5;
|
||||
border-radius: 5px;
|
||||
margin: 5px 0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.form-error ul{
|
||||
padding: 1px 5px;
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: 5px;
|
||||
margin: 5px 0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.subform{
|
||||
background-color: #efefef;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.form-buttons{
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.form select{
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.form select option{
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.turbo-refreshing{
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.ibo-field legend{
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
collection-entry-element {
|
||||
margin-top: 8px;
|
||||
display: block;
|
||||
padding: 10px 10px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
}
|
||||
@@ -8,7 +8,6 @@ $ibo-toggler--wrapper--height: 20px !default;
|
||||
|
||||
$ibo-toggler--slider--border-radius: $ibo-border-radius-900 !default;
|
||||
$ibo-toggler--slider--background-color: $ibo-color-secondary-600 !default;
|
||||
$ibo-toggler--slider--disabled--background-color: $ibo-color-secondary-200 !default;
|
||||
|
||||
$ibo-toggler--slider--before--left: 3px !default;
|
||||
$ibo-toggler--slider--before--bottom: 3px !default;
|
||||
@@ -18,7 +17,6 @@ $ibo-toggler--slider--before--border-radius: $ibo-border-radius-full !default;
|
||||
$ibo-toggler--slider--before--background-color: $ibo-color-grey-100 !default;
|
||||
|
||||
$ibo-toggler--slider--checked--background-color: $ibo-color-primary-600 !default;
|
||||
$ibo-toggler--slider--checked-disabled--background-color: $ibo-color-primary-200 !default;
|
||||
$ibo-toggler--slider--focus--box-shadow: 0 0 1px $ibo-color-primary-600 !default;
|
||||
|
||||
$ibo-toggler--label--margin-left: 4px !default;
|
||||
@@ -63,13 +61,6 @@ $ibo-toggler--label--margin-left: 4px !default;
|
||||
background-color: $ibo-toggler--slider--checked--background-color;
|
||||
}
|
||||
|
||||
.ibo-toggler--wrapper input:disabled + .ibo-toggler--slider {
|
||||
background-color: $ibo-toggler--slider--disabled--background-color;
|
||||
}
|
||||
.ibo-toggler--wrapper input:checked:disabled + .ibo-toggler--slider {
|
||||
background-color: $ibo-toggler--slider--checked-disabled--background-color;
|
||||
}
|
||||
|
||||
input:focus + .ibo-toggler--slider {
|
||||
box-shadow: $ibo-toggler--slider--focus--box-shadow;
|
||||
}
|
||||
|
||||
@@ -15,4 +15,3 @@
|
||||
@import "wizard-container/wizard-container";
|
||||
@import "object/all";
|
||||
@import "activity-panel/all";
|
||||
@import "extension/all";
|
||||
@@ -1 +0,0 @@
|
||||
@import "extension-details";
|
||||
@@ -1,65 +0,0 @@
|
||||
$ibo-extension-details--information--metadata--padding: $ibo-spacing-200 !default;
|
||||
$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 {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ibo-extension-details--information {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ibo-extension-details--actions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ibo-extension-details--information--label {
|
||||
@extend %ibo-font-ral-med-150;
|
||||
}
|
||||
|
||||
.ibo-extension-details--information--metadata {
|
||||
@extend %ibo-font-ral-med-100;
|
||||
color: $ibo-extension-details--information--metadata--color;
|
||||
}
|
||||
|
||||
.ibo-extension-details--information--description {
|
||||
@extend %ibo-font-ral-med-100;
|
||||
}
|
||||
|
||||
.ibo-extension-details--information--metadata span + span:before {
|
||||
content: $ibo-extension-details--information--metadata--delimiter;
|
||||
padding-left: $ibo-extension-details--information--metadata--padding;
|
||||
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;
|
||||
}
|
||||
}
|
||||
//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;
|
||||
}
|
||||
|
||||
.ibo-extension-details--actions:has(.toggler-install:not(:disabled)) .ibo-popover-menu--section a[data-resource-id="force_uninstall"] {
|
||||
display: none;
|
||||
}
|
||||
3
css/backoffice/vendors/_tomselect.scss
vendored
Normal file
3
css/backoffice/vendors/_tomselect.scss
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
@import "../../../node_modules/tom-select/dist/scss/tom-select.scss";
|
||||
|
||||
$select-color-item-active-border: $ibo-input--focus--border-color;
|
||||
File diff suppressed because one or more lines are too long
101
css/setup.scss
101
css/setup.scss
@@ -311,39 +311,28 @@ 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{
|
||||
&:not(:checked) ~ label .checked{
|
||||
display:none;
|
||||
}
|
||||
&:checked ~ label .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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-extension-details--information--metadata{
|
||||
color: $ibo-color-grey-800;
|
||||
}
|
||||
|
||||
.choice-disabled {
|
||||
color: $ibo-color-grey-700;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
font-size: 1.17rem;
|
||||
@@ -527,12 +516,10 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.ibo-setup-summary-title, .ibo-setup-summary-title:visited, .ibo-setup-summary-title:hover {
|
||||
font-size: $ibo-font-size-150;
|
||||
color: inherit;
|
||||
.ibo-setup-summary-title {
|
||||
font-size: $ibo-font-size-150;
|
||||
}
|
||||
|
||||
|
||||
#ibo-setup-licenses--components-list {
|
||||
background-color: $ibo-color-white-200;
|
||||
padding: 12px;
|
||||
@@ -608,51 +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 {
|
||||
display: inline-flex;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -704,6 +646,9 @@ body {
|
||||
overflow: auto;
|
||||
text-align: center;
|
||||
}
|
||||
#installation_progress {
|
||||
display: none;
|
||||
}
|
||||
#fresh_content{
|
||||
border: 0;
|
||||
min-height: 300px;
|
||||
|
||||
0
data/.compilation-symlinks
Normal file
0
data/.compilation-symlinks
Normal file
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* CSS of the template page
|
||||
*/
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Javascript file loaded in template page
|
||||
*/
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"classmap-authoritative": true
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Combodo\\iTop\\DataFeatureRemoval\\": "src",
|
||||
"": "src/NoNamespace"
|
||||
}
|
||||
},
|
||||
"name": "combodo/combodo-data-feature-removal",
|
||||
"type": "itop-extension",
|
||||
"description": "iTop Data Feature Removal",
|
||||
"require": {
|
||||
"composer-runtime-api": "^2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b862a55cbf5448fb99f0905a4db6529b",
|
||||
"packages": [],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"composer-runtime-api": "^2.0"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
|
||||
<menus>
|
||||
<menu id="DataFeatureRemovalMenu" xsi:type="WebPageMenuNode" _delta="define">
|
||||
<rank>30</rank>
|
||||
<parent>SystemTools</parent>
|
||||
<url>$pages/exec.php?exec_module=combodo-data-feature-removal&exec_page=index.php&c[menu]=DataFeatureRemovalMenu</url>
|
||||
<enable_admin_only>1</enable_admin_only>
|
||||
</menu>
|
||||
</menus>
|
||||
</itop_design>
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*/
|
||||
|
||||
Dict::Add('EN US', 'English', 'English', [
|
||||
'Menu:DataFeatureRemovalMenu' => 'Features Removal',
|
||||
'combodo-data-feature-removal/Operation:Main/Title' => 'Features Removal',
|
||||
|
||||
'DataFeatureRemoval:Main:Title' => 'Features Removal',
|
||||
'DataFeatureRemoval:Main:SubTitle' => 'Prepare features you want to enable/disable in a future setup',
|
||||
'DataFeatureRemoval:Failure:Title' => 'Feature dry removal errors',
|
||||
'DataFeatureRemoval:Helper:Title' => 'Enable or disable features that are installed in your iTop.',
|
||||
'DataFeatureRemoval:Helper:Desc1' => 'It will prepare the setup step that proceeds to feature enabling or disabling.',
|
||||
'DataFeatureRemoval:Helper:Desc2' => 'Analyze if there are any data or dependency preventing you from enabling/disabling a feature.',
|
||||
|
||||
'DataFeatureRemoval:Features:Title' => 'Features',
|
||||
'DataFeatureRemoval:Analysis:Title' => 'Analysis result',
|
||||
'DataFeatureRemoval:Analysis:SubTitle' => '%1$s element(s) to clean before continuing',
|
||||
|
||||
'DataFeatureRemoval:DeletionPlan:Title' => 'Deletion plan',
|
||||
'DataFeatureRemoval:DeletionPlan:SubTitle' => 'Database tables to clean before continuing',
|
||||
'DataFeatureRemoval:DoDeletion:Title' => 'Do deletion',
|
||||
'DataFeatureRemoval:DoDeletion:SubTitle' => 'Remove all the entries from the database',
|
||||
|
||||
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Element to remove',
|
||||
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Feature name',
|
||||
'DataFeatureRemoval:Table:Analysis:Module' => 'Module name',
|
||||
'DataFeatureRemoval:Table:Analysis:Occurrence' => 'Occurrence',
|
||||
|
||||
'UI:Button:Analyze' => 'Analyze',
|
||||
'UI:Button:ModifyChoices' => 'Modify Choices',
|
||||
'UI:Button:AnalyzeAndSetup' => 'Analyze and go to setup',
|
||||
'UI:Button:PlanDeletion' => 'Prepare deletion plan',
|
||||
'UI:Button:DoDeletion' => 'Delete data',
|
||||
'UI:Button:BackToMain' => 'Back to Feature Removal',
|
||||
'UI:Button:Setup' => 'Back to setup',
|
||||
|
||||
'UI:Action:ForceUninstall' => 'Force uninstall',
|
||||
'UI:Action:MoreInfo' => 'More information',
|
||||
|
||||
'DataFeatureRemoval:Table:Empty' => 'No data to remove',
|
||||
|
||||
'DataFeatureRemoval:Column:Class' => 'Class',
|
||||
'DataFeatureRemoval:Column:DeleteCount' => 'Entries to delete',
|
||||
'DataFeatureRemoval:Column:UpdateCount' => 'Entries to update',
|
||||
'DataFeatureRemoval:Column:Issue' => 'Issue',
|
||||
|
||||
'DataFeatureRemoval:Column:DeletedCount' => 'Deleted entries',
|
||||
'DataFeatureRemoval:Column:UpdatedCount' => 'Updated entries',
|
||||
]);
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*/
|
||||
|
||||
Dict::Add('FR FR', 'French', 'Français', [
|
||||
'Menu:DataFeatureRemovalMenu' => 'Features Removal',
|
||||
'combodo-data-feature-removal/Operation:Main/Title' => 'Features Removal',
|
||||
|
||||
'DataFeatureRemoval:Main:Title' => 'Features Removal',
|
||||
'DataFeatureRemoval:Main:SubTitle' => 'Prepare features you want to enable/disable in a future setup',
|
||||
'DataFeatureRemoval:Failure:Title' => 'Feature dry removal errors',
|
||||
'DataFeatureRemoval:Helper:Title' => 'Enable or disable features that are installed in your iTop.',
|
||||
'DataFeatureRemoval:Helper:Desc1' => 'It will prepare the setup step that proceeds to feature enabling or disabling.',
|
||||
'DataFeatureRemoval:Helper:Desc2' => 'Analyze if there are any data or dependency preventing you from enabling/disabling a feature.',
|
||||
|
||||
'DataFeatureRemoval:Features:Title' => 'Features',
|
||||
'DataFeatureRemoval:Analysis:Title' => 'Analysis result',
|
||||
'DataFeatureRemoval:Analysis:SubTitle' => '%1$s element(s) to clean before continuing',
|
||||
|
||||
'DataFeatureRemoval:DeletionPlan:Title' => 'Deletion plan',
|
||||
'DataFeatureRemoval:DeletionPlan:SubTitle' => 'Database tables to clean before continuing',
|
||||
'DataFeatureRemoval:DoDeletion:Title' => 'Do deletion',
|
||||
'DataFeatureRemoval:DoDeletion:SubTitle' => 'Remove all the entries from the database',
|
||||
|
||||
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Element to remove',
|
||||
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Feature name',
|
||||
'DataFeatureRemoval:Table:Analysis:Module' => 'Module name',
|
||||
'DataFeatureRemoval:Table:Analysis:Occurrence' => 'Occurrence',
|
||||
|
||||
'UI:Button:Analyze' => 'Analyze',
|
||||
'UI:Button:ModifyChoices' => 'Modify Choices',
|
||||
'UI:Button:AnalyzeAndSetup' => 'Analyze and go to setup',
|
||||
'UI:Button:PlanDeletion' => 'Prepare deletion plan',
|
||||
'UI:Button:DoDeletion' => 'Delete data',
|
||||
'UI:Button:BackToMain' => 'Back to Feature Removal',
|
||||
'UI:Button:Setup' => 'Back to setup',
|
||||
|
||||
'UI:Action:ForceUninstall' => 'Force uninstall',
|
||||
'UI:Action:MoreInfo' => 'More information',
|
||||
|
||||
'DataFeatureRemoval:Table:Empty' => 'No data to remove',
|
||||
|
||||
'DataFeatureRemoval:Column:Class' => 'Class',
|
||||
'DataFeatureRemoval:Column:DeleteCount' => 'Entries to delete',
|
||||
'DataFeatureRemoval:Column:UpdateCount' => 'Entries to update',
|
||||
'DataFeatureRemoval:Column:Issue' => 'Issue',
|
||||
|
||||
'DataFeatureRemoval:Column:DeletedCount' => 'Deleted entries',
|
||||
'DataFeatureRemoval:Column:UpdatedCount' => 'Updated entries',
|
||||
]);
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval;
|
||||
|
||||
use Combodo\iTop\DataFeatureRemoval\Controller\DataFeatureRemovalController;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalLog;
|
||||
|
||||
require_once(APPROOT.'application/startup.inc.php');
|
||||
|
||||
DataFeatureRemovalLog::Enable();
|
||||
|
||||
$oController = new DataFeatureRemovalController(MODULESROOT.DataFeatureRemovalHelper::MODULE_NAME.'/templates', DataFeatureRemovalHelper::MODULE_NAME);
|
||||
$oController->SetDefaultOperation('Main');
|
||||
$oController->HandleOperation();
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
// PHP Data Model definition file
|
||||
|
||||
// WARNING - WARNING - WARNING
|
||||
// DO NOT EDIT THIS FILE (unless you know what you are doing)
|
||||
//
|
||||
// If you provide a datamodel.xxxx.xml file with your module,
|
||||
// this file WILL BE overwritten by the compilation of the
|
||||
// module (during the setup) if the datamodel.xxxx.xml file
|
||||
// contains the definition of new classes or menus.
|
||||
//
|
||||
// The recommended way to define new classes (for iTop 2.0 and later) is via the XML definition.
|
||||
// This file remains in the module's template only for the cases where there is:
|
||||
// - either no new class or menu defined in the XML file
|
||||
// - or no XML file at all supplied by the module
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
//
|
||||
// iTop module definition file
|
||||
//
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'combodo-data-feature-removal/3.3.0',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
'label' => 'iTop Data Feature Removal',
|
||||
'category' => 'business',
|
||||
|
||||
// Setup
|
||||
//
|
||||
'dependencies' => [
|
||||
|
||||
],
|
||||
'mandatory' => true,
|
||||
'visible' => false,
|
||||
|
||||
// Components
|
||||
//
|
||||
'datamodel' => [
|
||||
'vendor/autoload.php',
|
||||
'model.combodo-data-feature-removal.php', // Contains the PHP code generated by the "compilation" of datamodel.combodo-data-feature-removal.xml
|
||||
],
|
||||
'webservice' => [],
|
||||
'data.struct' => [
|
||||
// add your 'structure' definition XML files here,
|
||||
],
|
||||
'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,252 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Controller;
|
||||
|
||||
require_once APPROOT.'setup/feature_removal/SetupAudit.php';
|
||||
require_once APPROOT.'setup/feature_removal/DryRemovalRuntimeEnvironment.php';
|
||||
|
||||
use Combodo\iTop\Application\TwigBase\Controller\Controller;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\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;
|
||||
|
||||
class DataFeatureRemovalController extends Controller
|
||||
{
|
||||
private array $aSelectedExtensionsForCheck = [];
|
||||
private array $aCountClassesToCleanup = [];
|
||||
private array $aAnalysisDataTable = [];
|
||||
private int $iCount = 0;
|
||||
|
||||
public function OperationMain($sErrorMessage = null): void
|
||||
{
|
||||
$aParams = [];
|
||||
|
||||
$this->ReadRemovedExtensions();
|
||||
$this->AddAnalyzeParams();
|
||||
$aParams['sTransactionId'] = utils::GetNewTransactionId();
|
||||
$aParams['aExtensions'] = $this->GetExtensionsTable();
|
||||
$aParams['aAnalysisDataTable'] = $this->aAnalysisDataTable;
|
||||
$aParams['aClasses'] = array_keys($this->aCountClassesToCleanup);
|
||||
$aParams['DataFeatureRemovalErrorMessage'] = $sErrorMessage;
|
||||
$aParams['bHasData'] = $this->iCount > 0;
|
||||
$aParams['sSetupUrl'] = utils::GetAbsoluteUrlAppRoot().'setup';
|
||||
$aParams['iCount'] = $this->iCount;
|
||||
|
||||
$this->AddLinkedStylesheet(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/css/DataFeatureRemoval.css');
|
||||
$this->AddLinkedScript(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/js/DataFeatureRemoval.js');
|
||||
$this->DisplayPage($aParams);
|
||||
}
|
||||
|
||||
public function AddAnalyzeParams(): void
|
||||
{
|
||||
$aData = [];
|
||||
$aColumns = [];
|
||||
$this->iCount = 0;
|
||||
foreach ($this->aCountClassesToCleanup as $sClass => $iCount) {
|
||||
$sModuleName = MetaModel::GetModuleName($sClass);
|
||||
$aExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetIncludingExtensions($sModuleName);
|
||||
$sExtensions = implode(' ', $aExtensions);
|
||||
$aColumns = ['ClassName','FeatureName','Module','Occurrence'];
|
||||
$aData[] = [$sClass,$sExtensions,$sModuleName,$iCount];
|
||||
$this->iCount += $iCount;
|
||||
}
|
||||
|
||||
$this->aAnalysisDataTable = $this->GetTableData('Analysis', $aColumns, $aData);
|
||||
}
|
||||
|
||||
public function OperationAnalyze(): void
|
||||
{
|
||||
$this->ReadRemovedExtensions();
|
||||
|
||||
$this->m_sOperation = 'Main';
|
||||
|
||||
try {
|
||||
if (count($this->aSelectedExtensionsForCheck) > 0) {
|
||||
$this->Analyze();
|
||||
}
|
||||
$this->OperationMain();
|
||||
} catch (Exception $e) {
|
||||
IssueLog::Error(__METHOD__, null, ['stack' => $e->getTraceAsString(), 'exception' => $e->getMessage()]);
|
||||
$this->OperationMain($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function Analyze(): void
|
||||
{
|
||||
$sSourceEnv = MetaModel::GetEnvironment();
|
||||
$oDryRemovalRuntimeEnvironment = new DryRemovalRuntimeEnvironment();
|
||||
$oDryRemovalRuntimeEnvironment->Prepare($sSourceEnv, $this->aSelectedExtensionsForCheck);
|
||||
$oDryRemovalRuntimeEnvironment->CompileFrom($sSourceEnv);
|
||||
|
||||
$oSetupAudit = new SetupAudit($sSourceEnv, DryRemovalRuntimeEnvironment::DRY_REMOVAL_AUDIT_ENV);
|
||||
$aGetRemovedClasses = $oSetupAudit->GetIssues();
|
||||
IssueLog::Debug(__METHOD__, null, ['aGetRemovedClasses' => $aGetRemovedClasses]);
|
||||
$this->aCountClassesToCleanup = $aGetRemovedClasses;
|
||||
}
|
||||
|
||||
public function OperationDeletionPlan(): void
|
||||
{
|
||||
$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(): void
|
||||
{
|
||||
$aParams = [];
|
||||
$this->ValidateTransactionId();
|
||||
|
||||
$aClasses = utils::ReadPostedParam('classes', null, utils::ENUM_SANITIZATION_FILTER_CLASS);
|
||||
|
||||
$aDeletionExecutionSummary = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
|
||||
$aColumns = ['Class', 'DeletedCount' , 'UpdatedCount'];
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get installed extensions from disk
|
||||
*
|
||||
* @return array structure for twig datatable
|
||||
*/
|
||||
private function GetExtensionsTable(): array
|
||||
{
|
||||
$aExtensions = [];
|
||||
$aColumns = ['', 'Version', 'Name', 'Code'];
|
||||
|
||||
foreach (DataFeatureRemoverExtensionService::GetInstance()->ReadItopExtensions() as $sCode => $oExtension) {
|
||||
/** @var \iTopExtension $oExtension */
|
||||
|
||||
$sChecked = '';
|
||||
$sDisabledHtml = '';
|
||||
if ($oExtension->bRemovedFromDisk) {
|
||||
$sDisabledHtml = 'disabled=""';
|
||||
$sChecked = 'checked';
|
||||
} elseif (in_array($sCode, $this->aSelectedExtensionsForCheck)) {
|
||||
$sChecked = 'checked';
|
||||
}
|
||||
|
||||
$sLabel = $oExtension->sLabel;
|
||||
$sVersion = $oExtension->sVersion;
|
||||
$sIdEnable = "aExtensions[$sCode][enable]";
|
||||
|
||||
$aExtensions[] = [
|
||||
<<<HTML
|
||||
<input type="checkbox" $sDisabledHtml class="extension_check" $sChecked id="$sIdEnable" name="$sIdEnable"/>
|
||||
HTML,
|
||||
$sVersion,
|
||||
$sLabel,
|
||||
$sCode,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->GetTableData('Extensions', $aColumns, $aExtensions);
|
||||
}
|
||||
|
||||
private function GetTableData(string $sTableName, array $aColumns, array $aData): array
|
||||
{
|
||||
if (empty($aData)) {
|
||||
return [
|
||||
'Type' => 'Table',
|
||||
'Columns' => [['label' => '']],
|
||||
'Data' => [[ Dict::S('DataFeatureRemoval:Table:Empty')]],
|
||||
];
|
||||
}
|
||||
|
||||
$aNewColumns = [];
|
||||
foreach ($aColumns as $sColumn) {
|
||||
$aNewColumns[] = ['label' => Dict::S("DataFeatureRemoval:Table:$sTableName:$sColumn", Dict::S("DataFeatureRemoval:Column:$sColumn", $sColumn))];
|
||||
}
|
||||
$aColumns = $aNewColumns;
|
||||
|
||||
return [
|
||||
'Type' => 'Table',
|
||||
'Columns' => $aColumns,
|
||||
'Data' => $aData,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
|
||||
*/
|
||||
private 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"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function ReadRemovedExtensions(): void
|
||||
{
|
||||
if (count($this->aSelectedExtensionsForCheck) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aSelectedExtensionsFromUI = utils::ReadPostedParam('aExtensions', []);
|
||||
foreach ($aSelectedExtensionsFromUI as $sCode => $aData) {
|
||||
$sValue = $aData['enable'] ?? 'off';
|
||||
if (($sValue) === 'on') {
|
||||
$this->aSelectedExtensionsForCheck[] = $sCode;
|
||||
}
|
||||
}
|
||||
|
||||
// Add source removed to check
|
||||
foreach (DataFeatureRemoverExtensionService::GetInstance()->ReadItopExtensions() as $sCode => $oExtension) {
|
||||
if ($oExtension->bRemovedFromDisk) {
|
||||
$this->aSelectedExtensionsForCheck[] = $sCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Helper;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class DataFeatureRemovalException extends Exception
|
||||
{
|
||||
public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null, array $aContext = [])
|
||||
{
|
||||
if (!is_null($previous)) {
|
||||
$sStack = $previous->getTraceAsString();
|
||||
$sError = $previous->getMessage();
|
||||
} else {
|
||||
$sStack = $this->getTraceAsString();
|
||||
$sError = '';
|
||||
}
|
||||
|
||||
$aContext['error'] = $sError;
|
||||
$aContext['stack'] = $sStack;
|
||||
DataFeatureRemovalLog::Error($message, null, $aContext);
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Helper;
|
||||
|
||||
class DataFeatureRemovalHelper
|
||||
{
|
||||
public const MODULE_NAME = 'combodo-data-feature-removal';
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Helper;
|
||||
|
||||
use LogAPI;
|
||||
|
||||
class DataFeatureRemovalLog extends LogAPI
|
||||
{
|
||||
public const CHANNEL_DEFAULT = 'DataFeatureRemoval';
|
||||
|
||||
protected static $m_oFileLog = null;
|
||||
|
||||
public static function Enable($sTargetFile = null)
|
||||
{
|
||||
if (empty($sTargetFile)) {
|
||||
$sTargetFile = APPROOT.'log/error.log';
|
||||
}
|
||||
parent::Enable($sTargetFile);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||
|
||||
use iTopExtension;
|
||||
use iTopExtensionsMap;
|
||||
use MetaModel;
|
||||
|
||||
class DataFeatureRemoverExtensionService
|
||||
{
|
||||
private static DataFeatureRemoverExtensionService $oInstance;
|
||||
|
||||
private array $aItopExtensions = [];
|
||||
private array $aIncludingExtensionsByModuleName = [];
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): DataFeatureRemoverExtensionService
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new DataFeatureRemoverExtensionService();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?DataFeatureRemoverExtensionService $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sModuleName
|
||||
*
|
||||
* @return array
|
||||
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
|
||||
*/
|
||||
public function GetIncludingExtensions(string $sModuleName): array
|
||||
{
|
||||
if (count($this->aIncludingExtensionsByModuleName) === 0) {
|
||||
foreach ($this->ReadItopExtensions() as $oExtension) {
|
||||
$aModuleNames = $oExtension->aModules;
|
||||
if (is_array($aModuleNames) && count($aModuleNames) > 0) {
|
||||
foreach ($aModuleNames as $sModule) {
|
||||
$aExtensions = $this->aIncludingExtensionsByModuleName[$sModule] ?? [];
|
||||
$aExtensions[] = $oExtension->sLabel.'/'.$oExtension->sVersion;
|
||||
$this->aIncludingExtensionsByModuleName[$sModule] = $aExtensions;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aIncludingExtensionsByModuleName[$sModuleName] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iTopExtension[]
|
||||
*/
|
||||
public function ReadItopExtensions(): array
|
||||
{
|
||||
if (count($this->aItopExtensions) === 0) {
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
$oExtensionsMap->LoadInstalledExtensionsFromDatabase(MetaModel::GetConfig());
|
||||
$this->aItopExtensions = $oExtensionsMap->GetAllExtensionsToDisplayInSetup(true);
|
||||
|
||||
uasort($this->aItopExtensions, function (iTopExtension $oiTopExtension1, iTopExtension $oiTopExtension2) {
|
||||
return strcmp($oiTopExtension1->sLabel, $oiTopExtension2->sLabel);
|
||||
});
|
||||
}
|
||||
|
||||
return $this->aItopExtensions;
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||
use DBObjectSearch;
|
||||
use DeletionPlan;
|
||||
use MetaModel;
|
||||
|
||||
class DeletionPlanService
|
||||
{
|
||||
private static DeletionPlanService $oInstance;
|
||||
|
||||
public int $iExecutionCount = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of the deletion plan computed for the classes.
|
||||
* The result is used for display
|
||||
*
|
||||
* @param array|null $aClasses
|
||||
*
|
||||
* @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity>
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function GetDeletionPlanSummary(?array $aClasses): array
|
||||
{
|
||||
$aSummary = [];
|
||||
if (is_null($aClasses)) {
|
||||
return $aSummary;
|
||||
}
|
||||
|
||||
$oDeletionPlan = $this->GetDeletionPlan($aClasses);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return \DBObject[]
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function GetAllObjects(string $sClass): array
|
||||
{
|
||||
$oFilter = new DBObjectSearch($sClass);
|
||||
$oFilter->AllowAllData();
|
||||
$oSet = new \DBObjectSet($oFilter);
|
||||
return $oSet->ToArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.3.0
|
||||
* @param array $aClasses
|
||||
* @param int $iUnixTimeLimit : max execution time in seconds since Epoch before stopping deletion. by default: no limit (ie remove all without stop)
|
||||
* @param int $iMaxExecutionCount : max execution count before stopping deletion. by default: no limit (ie remove all without stop)
|
||||
*
|
||||
* @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity>
|
||||
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function ExecuteDeletionPlan(array $aClasses, int $iUnixTimeLimit = 0, int $iMaxExecutionCount = -1): array
|
||||
{
|
||||
$oDeletionPlan = $this->GetDeletionPlan($aClasses);
|
||||
|
||||
if (count($oDeletionPlan->GetIssues()) > 0) {
|
||||
throw new DataFeatureRemovalException("Deletion Plan cannot be executed due to issues");
|
||||
}
|
||||
|
||||
$this->iExecutionCount = 0;
|
||||
|
||||
$aSummary = [];
|
||||
foreach ($oDeletionPlan->ListUpdates() as $sClass => $aToUpdate) {
|
||||
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
|
||||
|
||||
foreach ($aToUpdate as $aData) {
|
||||
if ($this->IsTimeLimitExceeded($iUnixTimeLimit, $iMaxExecutionCount)) {
|
||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
return $aSummary;
|
||||
}
|
||||
|
||||
$oToUpdate = $aData['to_reset'];
|
||||
/** @var \DBObject $oToUpdate */
|
||||
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) {
|
||||
$oToUpdate->Set($sRemoteExtKey, $aData['values'][$sRemoteExtKey]);
|
||||
}
|
||||
$oToUpdate->DBUpdate();
|
||||
$oDeletionPlanSummaryEntity->iUpdateCount++;
|
||||
}
|
||||
|
||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
}
|
||||
|
||||
foreach ($oDeletionPlan->ListDeletes() as $sClass => $aDeletes) {
|
||||
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
|
||||
|
||||
foreach ($aDeletes as $sId => $aDelete) {
|
||||
if ($this->IsTimeLimitExceeded($iUnixTimeLimit, $iMaxExecutionCount)) {
|
||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
return $aSummary;
|
||||
}
|
||||
|
||||
try {
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
// Delete any existing change tracking about the current object
|
||||
$oFilter = new DBObjectSearch('CMDBChangeOp');
|
||||
$oFilter->AddCondition('objclass', $sClass, '=');
|
||||
$oFilter->AddCondition('objkey', $sId, '=');
|
||||
MetaModel::PurgeData($oFilter);
|
||||
|
||||
// Delete the entry
|
||||
$aClassesToRemove = array_merge(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL), MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false));
|
||||
foreach ($aClassesToRemove as $sParentClass) {
|
||||
$oFilter = DBObjectSearch::FromOQL_AllData("SELECT $sParentClass WHERE id=:id");
|
||||
$sQuery = $oFilter->MakeDeleteQuery(['id' => $sId]);
|
||||
CMDBSource::DeleteFrom($sQuery);
|
||||
}
|
||||
|
||||
CMDBSource::Query('COMMIT');
|
||||
} catch (\Exception $e) {
|
||||
\IssueLog::Exception(__METHOD__.": Cleanup failed", $e);
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
throw $e;
|
||||
}
|
||||
$oDeletionPlanSummaryEntity->iDeleteCount++;
|
||||
}
|
||||
|
||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
||||
}
|
||||
|
||||
return $aSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a deletion plan for all the objects of the classes
|
||||
*
|
||||
* @param array $aClasses array of class names to clean
|
||||
*
|
||||
* @return \DeletionPlan
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function GetDeletionPlan(array $aClasses): DeletionPlan
|
||||
{
|
||||
$oDeletionPlan = new DeletionPlan();
|
||||
foreach ($aClasses as $sClass) {
|
||||
$aObjects = $this->GetAllObjects($sClass);
|
||||
foreach ($aObjects as $oObject) {
|
||||
$oObject->CheckToDelete($oDeletionPlan);
|
||||
}
|
||||
}
|
||||
|
||||
return $oDeletionPlan;
|
||||
}
|
||||
|
||||
public function IsTimeLimitExceeded(int $iUnixTimeLimit, int $iMaxExecutionCount): bool
|
||||
{
|
||||
if (($iMaxExecutionCount !== -1) && ($iMaxExecutionCount <= $this->iExecutionCount)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->iExecutionCount++;
|
||||
|
||||
if ($iUnixTimeLimit === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (time() <= $iUnixTimeLimit);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{# @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_back', sId:'btn_back', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
@@ -1,14 +0,0 @@
|
||||
{# @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_back_to_main', sId:'btn_back_to_main', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
@@ -1,19 +0,0 @@
|
||||
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
{% if bHasData %}
|
||||
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Analysis:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:Analysis:SubTitle'|dict_format(iCount) } %}
|
||||
{% UIDataTable ForForm { sRef:'aAnalysisDataTable', aColumns:aAnalysisDataTable.Columns, aData:aAnalysisDataTable.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 %}
|
||||
{% endif %}
|
||||
@@ -1,16 +0,0 @@
|
||||
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
|
||||
{% UIForm Standard {} %}
|
||||
{% UIInput ForHidden {sName:'operation', sValue:'Analyze'} %}
|
||||
{% UIInput ForHidden {sName:'transaction_id', sValue:sTransactionId} %}
|
||||
|
||||
{% UIFieldSet Standard {sLegend:'DataFeatureRemoval:Features:Title'|dict_s} %}
|
||||
{% UIDataTable ForForm { sRef:'aExtensions', aColumns:aExtensions.Columns, aData:aExtensions.Data} %}{% EndUIDataTable %}
|
||||
{% EndUIFieldSet %}
|
||||
|
||||
{% UIToolbar ForButton {} %}
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:Analyze'|dict_s, sName:'btn_apply', sId:'btn_apply', bIsSubmit:true} %}
|
||||
{% EndUIToolbar %}
|
||||
{% EndUIForm %}
|
||||
@@ -1,35 +0,0 @@
|
||||
{# @copyright Copyright (C) 2010-2025 Combodo SARL #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
{# Usable variables: #}
|
||||
{# * sTitle => page title #}
|
||||
{# * sMessage => success message #}
|
||||
{# * sError => error message #}
|
||||
|
||||
{# DataFeatureRemoval #}
|
||||
|
||||
{% 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>
|
||||
{{ 'DataFeatureRemoval:Helper:Desc2'|dict_s }}
|
||||
{% EndUIAlert %}
|
||||
|
||||
{% if null != DataFeatureRemovalErrorMessage %}
|
||||
<div id="feature_removal_error_msg_div" style="display:block">
|
||||
{% UIAlert ForFailure { sTitle:'DataFeatureRemoval:Failure:Title'|dict_s, sId: 'feature_removal_error_msg', sContent:DataFeatureRemovalErrorMessage } %}
|
||||
{% EndUIAlert %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include 'Features.html.twig' %}
|
||||
{% include 'ExtensionRemovalData.html.twig' %}
|
||||
|
||||
{% if not bHasData %}
|
||||
{% UIToolbar ForButton {} %}
|
||||
<a href="{{ sSetupUrl }}">
|
||||
{% UIButton ForPrimaryAction {sLabel:'UI:Button:Setup'|dict_s, sName:'btn_setup', sId:'btn_setup', bIsSubmit:false} %}
|
||||
</a>
|
||||
{% EndUIToolbar %}
|
||||
{% endif %}
|
||||
{% EndUIPanel %}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException($err);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit4f96a7199e2c0d90e547333758b26464::getLoader();
|
||||
@@ -1,579 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see https://www.php-fig.org/psr/psr-0/
|
||||
* @see https://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/** @var \Closure(string):void */
|
||||
private static $includeFile;
|
||||
|
||||
/** @var string|null */
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
/**
|
||||
* List of PSR-0 prefixes
|
||||
*
|
||||
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
|
||||
*
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $classMap Class to filename map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 base directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return true|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently registered loaders keyed by their corresponding vendor directories.
|
||||
*
|
||||
* @return array<string, self>
|
||||
*/
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $ext
|
||||
* @return string|false
|
||||
*/
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$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\\Service\\DataFeatureRemoverExtensionService' => $baseDir . '/src/Service/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DeletionPlanService' => $baseDir . '/src/Service/DeletionPlanService.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\' => array($baseDir . '/src'),
|
||||
'' => array($baseDir . '/src/NoNamespace'),
|
||||
);
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit4f96a7199e2c0d90e547333758b26464
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit4f96a7199e2c0d90e547333758b26464', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit4f96a7199e2c0d90e547333758b26464', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit4f96a7199e2c0d90e547333758b26464::getInitializer($loader));
|
||||
|
||||
$loader->setClassMapAuthoritative(true);
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit4f96a7199e2c0d90e547333758b26464
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'C' =>
|
||||
array (
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\' => 32,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $fallbackDirsPsr4 = array (
|
||||
0 => __DIR__ . '/../..' . '/src/NoNamespace',
|
||||
);
|
||||
|
||||
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\\Service\\DataFeatureRemoverExtensionService' => __DIR__ . '/../..' . '/src/Service/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DeletionPlanService' => __DIR__ . '/../..' . '/src/Service/DeletionPlanService.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$prefixDirsPsr4;
|
||||
$loader->fallbackDirsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$fallbackDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ SetupWebPage::AddModule(
|
||||
// Identification
|
||||
//
|
||||
'label' => 'Database maintenance tools',
|
||||
'category' => 'business',
|
||||
'category' => 'Application management',
|
||||
|
||||
// Setup
|
||||
//
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace Combodo\iTop\DBTools\Service;
|
||||
use CMDBSource;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use IssueLog;
|
||||
|
||||
class DBToolsUtils
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -323,38 +323,18 @@
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>30</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="implementation">
|
||||
<code>implementation</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>
|
||||
<value id="stock">
|
||||
<code>stock</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-neutral-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-neutral-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>40</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
@@ -6917,29 +6897,14 @@
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</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="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</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>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
|
||||
@@ -28,6 +28,7 @@ class ConfigEditorController extends Controller
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(MODULESROOT.static::MODULE_NAME.'/templates', static::MODULE_NAME);
|
||||
$this->SetDebugAllowed(false);
|
||||
}
|
||||
|
||||
public function OperationEdit(): void
|
||||
|
||||
@@ -30,7 +30,7 @@ SetupWebPage::AddModule(
|
||||
// Identification
|
||||
//
|
||||
'label' => 'iTop Core Update',
|
||||
'category' => 'business',
|
||||
'category' => 'Application management',
|
||||
|
||||
// Setup
|
||||
//
|
||||
|
||||
@@ -92,7 +92,7 @@ final class CoreUpdater
|
||||
$sFinalEnv = 'production';
|
||||
$oRuntimeEnv = new RunTimeEnvironmentCoreUpdater($sFinalEnv, false);
|
||||
$oRuntimeEnv->CheckDirectories($sFinalEnv);
|
||||
$oRuntimeEnv->CompileFrom($sFinalEnv);
|
||||
$oRuntimeEnv->CompileFrom('production');
|
||||
|
||||
$oRuntimeEnv->Rollback();
|
||||
|
||||
@@ -155,13 +155,21 @@ final class CoreUpdater
|
||||
APPROOT.'extensions',
|
||||
];
|
||||
$aAvailableModules = $oRuntimeEnv->AnalyzeInstallation($oConfig, $aDirsToScanForModules);
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'BeforeDatabaseCreation');
|
||||
$aSelectedModules = [];
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId == ROOT_MODULE) || ($sModuleId == DATAMODEL_MODULE)) {
|
||||
continue;
|
||||
} else {
|
||||
$aSelectedModules[] = $sModuleId;
|
||||
}
|
||||
}
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'BeforeDatabaseCreation');
|
||||
$oRuntimeEnv->CreateDatabaseStructure($oConfig, 'upgrade');
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseCreation');
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseCreation');
|
||||
$oRuntimeEnv->UpdatePredefinedObjects();
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseSetup');
|
||||
$oRuntimeEnv->LoadData($aAvailableModules, false /* no sample data*/);
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDataLoad');
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseSetup');
|
||||
$oRuntimeEnv->LoadData($aAvailableModules, $aSelectedModules, false /* no sample data*/);
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDataLoad');
|
||||
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
// Default choices = as before
|
||||
@@ -179,7 +187,7 @@ final class CoreUpdater
|
||||
$oRuntimeEnv->RecordInstallation(
|
||||
$oConfig,
|
||||
$sDataModelVersion,
|
||||
array_keys($aAvailableModules),
|
||||
$aSelectedModules,
|
||||
$aSelectedExtensionCodes,
|
||||
'Done by the iTop Core Updater'
|
||||
);
|
||||
|
||||
@@ -135,7 +135,7 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment
|
||||
$aAvailableModules[$oModule->GetName()] = $oModule;
|
||||
}
|
||||
// TODO check the auto-selected modules here
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
if ($oExtension->bMarkedAsChosen) {
|
||||
foreach ($oExtension->aModules as $sModuleName) {
|
||||
if (!isset($aRet[$sModuleName]) && isset($aAvailableModules[$sModuleName])) {
|
||||
|
||||
@@ -24,13 +24,129 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\HubConnector\Controller\HubController;
|
||||
use Combodo\iTop\Application\WebPage\JsonPage;
|
||||
|
||||
require_once(APPROOT.'application/utils.inc.php');
|
||||
require_once(APPROOT.'core/log.class.inc.php');
|
||||
IssueLog::Enable(APPROOT.'log/error.log');
|
||||
|
||||
require_once(__DIR__.'/src/Controller/HubController.php');
|
||||
require_once(APPROOT.'setup/runtimeenv.class.inc.php');
|
||||
require_once(APPROOT.'setup/backup.class.inc.php');
|
||||
require_once(APPROOT.'core/mutex.class.inc.php');
|
||||
require_once(APPROOT.'core/dict.class.inc.php');
|
||||
require_once(APPROOT.'setup/xmldataloader.class.inc.php');
|
||||
require_once(__DIR__.'/hubruntimeenvironment.class.inc.php');
|
||||
|
||||
/**
|
||||
* Overload of DBBackup to handle logging
|
||||
*/
|
||||
class DBBackupWithErrorReporting extends DBBackup
|
||||
{
|
||||
protected $aInfos = [];
|
||||
|
||||
protected $aErrors = [];
|
||||
|
||||
protected function LogInfo($sMsg)
|
||||
{
|
||||
$aInfos[] = $sMsg;
|
||||
}
|
||||
|
||||
protected function LogError($sMsg)
|
||||
{
|
||||
IssueLog::Error($sMsg);
|
||||
$aErrors[] = $sMsg;
|
||||
}
|
||||
|
||||
public function GetInfos()
|
||||
{
|
||||
return $this->aInfos;
|
||||
}
|
||||
|
||||
public function GetErrors()
|
||||
{
|
||||
return $this->aErrors;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $sTargetFile
|
||||
* @throws Exception
|
||||
* @return DBBackupWithErrorReporting
|
||||
*/
|
||||
function DoBackup($sTargetFile)
|
||||
{
|
||||
// Make sure the target directory exists
|
||||
$sBackupDir = dirname($sTargetFile);
|
||||
SetupUtils::builddir($sBackupDir);
|
||||
|
||||
$oBackup = new DBBackupWithErrorReporting();
|
||||
$oBackup->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
|
||||
$sSourceConfigFile = APPCONF.utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
|
||||
|
||||
$oMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
|
||||
$oMutex->Lock();
|
||||
try {
|
||||
$oBackup->CreateCompressedBackup($sTargetFile, $sSourceConfigFile);
|
||||
} catch (Exception $e) {
|
||||
$oMutex->Unlock();
|
||||
throw $e;
|
||||
}
|
||||
$oMutex->Unlock();
|
||||
return $oBackup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the status of the current ajax execution (as a JSON structure)
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param bool $bSuccess
|
||||
* @param number $iErrorCode
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
function ReportStatus($sMessage, $bSuccess, $iErrorCode = 0, $aMoreFields = [])
|
||||
{
|
||||
// Do not use AjaxPage during setup phases, because it uses InterfaceDiscovery in Twig compilation
|
||||
$oPage = new JsonPage();
|
||||
$aResult = [
|
||||
'code' => $iErrorCode,
|
||||
'message' => $sMessage,
|
||||
'fields' => $aMoreFields,
|
||||
];
|
||||
$oPage->SetData($aResult);
|
||||
$oPage->SetOutputDataOnly(true);
|
||||
$oPage->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to output the status of a successful execution
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
function ReportSuccess($sMessage, $aMoreFields = [])
|
||||
{
|
||||
ReportStatus($sMessage, true, 0, $aMoreFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to output the status of a failed execution
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param number $iErrorCode
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
function ReportError($sMessage, $iErrorCode, $aMoreFields = [])
|
||||
{
|
||||
if ($iErrorCode == 0) {
|
||||
// 0 means no error, so change it if no meaningful error code is supplied
|
||||
$iErrorCode = -1;
|
||||
}
|
||||
ReportStatus($sMessage, false, $iErrorCode, $aMoreFields);
|
||||
}
|
||||
|
||||
try {
|
||||
SetupUtils::ExitMaintenanceMode(false); // Reset maintenance mode in case of problem
|
||||
@@ -67,7 +183,7 @@ try {
|
||||
foreach ($aChecks as $oCheckResult) {
|
||||
if ($oCheckResult->iSeverity == CheckResult::ERROR) {
|
||||
$bFailed = true;
|
||||
HubController::GetInstance()->ReportError($oCheckResult->sLabel, -2);
|
||||
ReportError($oCheckResult->sLabel, -2);
|
||||
}
|
||||
}
|
||||
if (!$bFailed) {
|
||||
@@ -75,27 +191,169 @@ try {
|
||||
$fFreeSpace = SetupUtils::CheckDiskSpace($sDBBackupPath);
|
||||
if ($fFreeSpace !== false) {
|
||||
$sMessage = Dict::Format('iTopHub:BackupFreeDiskSpaceIn', SetupUtils::HumanReadableSize($fFreeSpace), dirname($sDBBackupPath));
|
||||
HubController::GetInstance()->ReportSuccess($sMessage);
|
||||
ReportSuccess($sMessage);
|
||||
} else {
|
||||
HubController::GetInstance()->ReportError(Dict::S('iTopHub:FailedToCheckFreeDiskSpace'), -1);
|
||||
ReportError(Dict::S('iTopHub:FailedToCheckFreeDiskSpace'), -1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'do_backup':
|
||||
HubController::GetInstance()->LaunchBackup();
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
|
||||
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
|
||||
|
||||
try {
|
||||
if (MetaModel::GetConfig()->Get('demo_mode')) {
|
||||
throw new Exception('Sorry the installation of extensions is not allowed in demo mode');
|
||||
}
|
||||
SetupLog::Info('Backup starts...');
|
||||
set_time_limit(0);
|
||||
$sBackupPath = APPROOT.'/data/backups/manual/backup-';
|
||||
$iSuffix = 1;
|
||||
$sSuffix = '';
|
||||
// Generate a unique name...
|
||||
do {
|
||||
$sBackupFile = $sBackupPath.date('Y-m-d-His').$sSuffix;
|
||||
$sSuffix = '-'.$iSuffix;
|
||||
$iSuffix++ ;
|
||||
} while (file_exists($sBackupFile));
|
||||
|
||||
$oBackup = DoBackup($sBackupFile);
|
||||
$aErrors = $oBackup->GetErrors();
|
||||
if (count($aErrors) > 0) {
|
||||
SetupLog::Error('Backup failed.');
|
||||
SetupLog::Error(implode("\n", $aErrors));
|
||||
ReportError(Dict::S('iTopHub:BackupFailed'), -1, $aErrors);
|
||||
} else {
|
||||
SetupLog::Info('Backup successfully completed.');
|
||||
ReportSuccess(Dict::S('iTopHub:BackupOk'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
SetupLog::Error($e->getMessage());
|
||||
ReportError($e->getMessage(), $e->getCode());
|
||||
}
|
||||
break;
|
||||
|
||||
case 'compile':
|
||||
HubController::GetInstance()->LaunchCompile();
|
||||
SetupLog::Info('Deployment starts...');
|
||||
$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
|
||||
if (!file_exists(utils::GetDataPath().'hub/compile_authent') || $sAuthent !== file_get_contents(utils::GetDataPath().'hub/compile_authent')) {
|
||||
throw new SecurityException(Dict::S('iTopHub:FailAuthent'));
|
||||
}
|
||||
// First step: prepare the datamodel, if it fails, roll-back
|
||||
$aSelectedExtensionCodes = utils::ReadParam('extension_codes', []);
|
||||
$aSelectedExtensionDirs = utils::ReadParam('extension_dirs', []);
|
||||
|
||||
$oRuntimeEnv = new HubRunTimeEnvironment('production', false); // use a temp environment: production-build
|
||||
$oRuntimeEnv->MoveSelectedExtensions(APPROOT.'/data/downloaded-extensions/', $aSelectedExtensionDirs);
|
||||
|
||||
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
|
||||
if ($oConfig->Get('demo_mode')) {
|
||||
throw new Exception('Sorry the installation of extensions is not allowed in demo mode');
|
||||
}
|
||||
|
||||
$aSelectModules = $oRuntimeEnv->CompileFrom('production', false); // WARNING symlinks does not seem to be compatible with manual Commit
|
||||
|
||||
$oRuntimeEnv->UpdateIncludes($oConfig);
|
||||
|
||||
$oRuntimeEnv->InitDataModel($oConfig, true /* model only */);
|
||||
|
||||
// Safety check: check the inter dependencies, will throw an exception in case of inconsistency
|
||||
$oRuntimeEnv->AnalyzeInstallation($oConfig, $oRuntimeEnv->GetBuildDir(), true);
|
||||
|
||||
$oRuntimeEnv->CheckMetaModel(); // Will throw an exception if a problem is detected
|
||||
|
||||
// Everything seems Ok so far, commit in env-production!
|
||||
$oRuntimeEnv->WriteConfigFileSafe($oConfig);
|
||||
$oRuntimeEnv->Commit();
|
||||
|
||||
// Report the success in a way that will be detected by the ajax caller
|
||||
SetupLog::Info('Compilation completed...');
|
||||
ReportSuccess('Ok'); // No access to Dict::S here
|
||||
break;
|
||||
|
||||
case 'move_to_production':
|
||||
HubController::GetInstance()->LaunchDeploy();
|
||||
// Second step: update the schema and the data
|
||||
// Everything happening below is based on env-production
|
||||
$oRuntimeEnv = new RunTimeEnvironment('production', true);
|
||||
|
||||
try {
|
||||
SetupLog::Info('Move to production starts...');
|
||||
$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
|
||||
if (!file_exists(utils::GetDataPath().'hub/compile_authent') || $sAuthent !== file_get_contents(utils::GetDataPath().'hub/compile_authent')) {
|
||||
throw new SecurityException(Dict::S('iTopHub:FailAuthent'));
|
||||
}
|
||||
unlink(utils::GetDataPath().'hub/compile_authent');
|
||||
// Load the "production" config file to clone & update it
|
||||
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
|
||||
SetupUtils::EnterReadOnlyMode($oConfig);
|
||||
|
||||
$oRuntimeEnv->InitDataModel($oConfig, true /* model only */);
|
||||
|
||||
$aAvailableModules = $oRuntimeEnv->AnalyzeInstallation($oConfig, $oRuntimeEnv->GetBuildDir(), true);
|
||||
|
||||
$aSelectedModules = [];
|
||||
foreach ($aAvailableModules as $sModuleId => $aModule) {
|
||||
if (($sModuleId == ROOT_MODULE) || ($sModuleId == DATAMODEL_MODULE)) {
|
||||
continue;
|
||||
} else {
|
||||
$aSelectedModules[] = $sModuleId;
|
||||
}
|
||||
}
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'BeforeDatabaseCreation');
|
||||
|
||||
$oRuntimeEnv->CreateDatabaseStructure($oConfig, 'upgrade');
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseCreation');
|
||||
|
||||
$oRuntimeEnv->UpdatePredefinedObjects();
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseSetup');
|
||||
|
||||
$oRuntimeEnv->LoadData($aAvailableModules, $aSelectedModules, false /* no sample data*/);
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDataLoad');
|
||||
|
||||
// Record the installation so that the "about box" knows about the installed modules
|
||||
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
|
||||
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
|
||||
// Default choices = as before
|
||||
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
// Plus all "remote" extensions
|
||||
if ($oExtension->sSource == iTopExtension::SOURCE_REMOTE) {
|
||||
$oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
$aSelectedExtensionCodes = [];
|
||||
foreach ($oExtensionsMap->GetChoices() as $oExtension) {
|
||||
$aSelectedExtensionCodes[] = $oExtension->sCode;
|
||||
}
|
||||
$aSelectedExtensions = $oExtensionsMap->GetChoices();
|
||||
$oRuntimeEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModules, $aSelectedExtensionCodes, 'Done by the iTop Hub Connector');
|
||||
|
||||
// Report the success in a way that will be detected by the ajax caller
|
||||
SetupLog::Info('Deployment successfully completed.');
|
||||
ReportSuccess(Dict::S('iTopHub:CompiledOK'));
|
||||
} catch (Exception $e) {
|
||||
if (file_exists(utils::GetDataPath().'hub/compile_authent')) {
|
||||
unlink(utils::GetDataPath().'hub/compile_authent');
|
||||
}
|
||||
// Note: at this point, the dictionnary is not necessarily loaded
|
||||
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted')."\n".$e->getMessage());
|
||||
SetupLog::Error('Debug trace: '.$e->getTraceAsString());
|
||||
ReportError($e->getMessage(), $e->getCode());
|
||||
} finally {
|
||||
SetupUtils::ExitReadOnlyMode();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
HubController::GetInstance()->ReportError("Invalid operation: '$sOperation'", -1);
|
||||
ReportError("Invalid operation: '$sOperation'", -1);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted')."\n".$e->getMessage());
|
||||
@@ -103,5 +361,5 @@ try {
|
||||
|
||||
utils::PopArchiveMode();
|
||||
|
||||
HubController::GetInstance()->ReportError($e->getMessage(), $e->getCode());
|
||||
ReportError($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
@@ -1,83 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\HubConnector\setup;
|
||||
|
||||
use Config;
|
||||
use Exception;
|
||||
use RunTimeEnvironment;
|
||||
use SetupUtils;
|
||||
|
||||
class HubRunTimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $sEnvironment
|
||||
* @param string $bAutoCommit
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public function UpdateIncludes(Config $oConfig)
|
||||
{
|
||||
$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
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
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
|
||||
* @param string[] $aSelectedExtensionDirs The list of folders to move
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
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) {
|
||||
|
||||
@@ -186,7 +186,9 @@ function collect_configuration()
|
||||
|
||||
// iTop modules
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$aInstalledModules = ModuleInstallationRepository::GetInstance()->ReadFromDB($oConfig);
|
||||
$sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->Get('db_subname')."priv_module_install");
|
||||
// Get the latest installed modules, without the "root" ones (iTop version and datamodel version)
|
||||
$aInstalledModules = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install WHERE installed = '".$sLatestInstallationDate."' AND parent_id != 0");
|
||||
|
||||
foreach ($aInstalledModules as $aDBInfo) {
|
||||
$aConfiguration['itop_modules'][$aDBInfo['name']] = $aDBInfo['version'];
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\HubConnector\Controller;
|
||||
|
||||
use Combodo\iTop\Application\WebPage\JsonPage;
|
||||
use Combodo\iTop\HubConnector\Model\DBBackupWithErrorReporting;
|
||||
use Combodo\iTop\HubConnector\setup\HubRunTimeEnvironment;
|
||||
use Config;
|
||||
use Dict;
|
||||
use Exception;
|
||||
use iTopExtension;
|
||||
use iTopExtensionsMap;
|
||||
use iTopMutex;
|
||||
use LoginWebPage;
|
||||
use MetaModel;
|
||||
use MFCompiler;
|
||||
use RunTimeEnvironment;
|
||||
use SecurityException;
|
||||
use SetupLog;
|
||||
use SetupUtils;
|
||||
use utils;
|
||||
|
||||
require_once(APPROOT.'setup/runtimeenv.class.inc.php');
|
||||
require_once(APPROOT.'setup/backup.class.inc.php');
|
||||
require_once(APPROOT.'core/mutex.class.inc.php');
|
||||
require_once(APPROOT.'core/dict.class.inc.php');
|
||||
require_once(APPROOT.'setup/xmldataloader.class.inc.php');
|
||||
require_once(__DIR__.'/../setup/hubruntimeenvironment.class.inc.php');
|
||||
|
||||
class HubController
|
||||
{
|
||||
private static HubController $oInstance;
|
||||
protected $bOutputHeaders = false;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
final public static function GetInstance(): HubController
|
||||
{
|
||||
if (!isset(self::$oInstance)) {
|
||||
self::$oInstance = new HubController();
|
||||
}
|
||||
|
||||
return self::$oInstance;
|
||||
}
|
||||
|
||||
final public static function SetInstance(?HubController $oInstance): void
|
||||
{
|
||||
self::$oInstance = $oInstance;
|
||||
}
|
||||
|
||||
public function LaunchBackup()
|
||||
{
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
|
||||
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin)
|
||||
|
||||
try {
|
||||
if (MetaModel::GetConfig()->Get('demo_mode')) {
|
||||
throw new Exception('Sorry the installation of extensions is not allowed in demo mode');
|
||||
}
|
||||
SetupLog::Info('Backup starts...');
|
||||
set_time_limit(0);
|
||||
$sBackupPath = APPROOT.'/data/backups/manual/backup-';
|
||||
$iSuffix = 1;
|
||||
$sSuffix = '';
|
||||
// Generate a unique name...
|
||||
do {
|
||||
$sBackupFile = $sBackupPath.date('Y-m-d-His').$sSuffix;
|
||||
$sSuffix = '-'.$iSuffix;
|
||||
$iSuffix++ ;
|
||||
} while (file_exists($sBackupFile));
|
||||
|
||||
$oBackup = $this->DoBackup($sBackupFile);
|
||||
$aErrors = $oBackup->GetErrors();
|
||||
if (count($aErrors) > 0) {
|
||||
SetupLog::Error('Backup failed.');
|
||||
SetupLog::Error(implode("\n", $aErrors));
|
||||
$this->ReportError(Dict::S('iTopHub:BackupFailed'), -1, $aErrors);
|
||||
} else {
|
||||
SetupLog::Info('Backup successfully completed.');
|
||||
$this->ReportSuccess(Dict::S('iTopHub:BackupOk'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
SetupLog::Error($e->getMessage());
|
||||
$this->ReportError($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $sTargetFile
|
||||
* @throws Exception
|
||||
* @return DBBackupWithErrorReporting
|
||||
*/
|
||||
public function DoBackup($sTargetFile): DBBackupWithErrorReporting
|
||||
{
|
||||
// Make sure the target directory exists
|
||||
$sBackupDir = dirname($sTargetFile);
|
||||
SetupUtils::builddir($sBackupDir);
|
||||
|
||||
$oBackup = new DBBackupWithErrorReporting();
|
||||
$oBackup->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
|
||||
$sSourceConfigFile = APPCONF.utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
|
||||
|
||||
$oMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
|
||||
$oMutex->Lock();
|
||||
try {
|
||||
$oBackup->CreateCompressedBackup($sTargetFile, $sSourceConfigFile);
|
||||
} catch (Exception $e) {
|
||||
$oMutex->Unlock();
|
||||
throw $e;
|
||||
}
|
||||
$oMutex->Unlock();
|
||||
return $oBackup;
|
||||
}
|
||||
|
||||
public function LaunchCompile()
|
||||
{
|
||||
SetupLog::Info('Deployment starts...');
|
||||
$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
|
||||
if (!file_exists(utils::GetDataPath().'hub/compile_authent') || $sAuthent !== file_get_contents(utils::GetDataPath().'hub/compile_authent')) {
|
||||
throw new SecurityException(Dict::S('iTopHub:FailAuthent'));
|
||||
}
|
||||
// First step: prepare the datamodel, if it fails, roll-back
|
||||
$aSelectedExtensionCodes = utils::ReadParam('extension_codes', []);
|
||||
$aSelectedExtensionDirs = utils::ReadParam('extension_dirs', []);
|
||||
|
||||
$oRuntimeEnv = new HubRunTimeEnvironment('production', false); // use a temp environment: production-build
|
||||
$oRuntimeEnv->MoveSelectedExtensions(APPROOT.'/data/downloaded-extensions/', $aSelectedExtensionDirs);
|
||||
|
||||
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
|
||||
if ($oConfig->Get('demo_mode')) {
|
||||
throw new Exception('Sorry the installation of extensions is not allowed in demo mode');
|
||||
}
|
||||
|
||||
$aSelectModules = $oRuntimeEnv->CompileFrom('production'); // WARNING symlinks does not seem to be compatible with manual Commit
|
||||
|
||||
$oRuntimeEnv->UpdateIncludes($oConfig);
|
||||
|
||||
$oRuntimeEnv->InitDataModel($oConfig, true /* model only */);
|
||||
|
||||
// Safety check: check the inter dependencies, will throw an exception in case of inconsistency
|
||||
$oRuntimeEnv->AnalyzeInstallation($oConfig, $oRuntimeEnv->GetBuildDir(), true);
|
||||
|
||||
$oRuntimeEnv->CheckMetaModel(); // Will throw an exception if a problem is detected
|
||||
|
||||
// Everything seems Ok so far, commit in env-production!
|
||||
$oRuntimeEnv->WriteConfigFileSafe($oConfig);
|
||||
$oRuntimeEnv->Commit();
|
||||
|
||||
// Report the success in a way that will be detected by the ajax caller
|
||||
SetupLog::Info('Compilation completed...');
|
||||
|
||||
$this->ReportSuccess('Ok'); // No access to Dict::S here
|
||||
}
|
||||
|
||||
public function LaunchDeploy()
|
||||
{
|
||||
// Second step: update the schema and the data
|
||||
// Everything happening below is based on env-production
|
||||
$oRuntimeEnv = new RunTimeEnvironment('production', true);
|
||||
|
||||
try {
|
||||
SetupLog::Info('Move to production starts...');
|
||||
$sAuthent = utils::ReadParam('authent', '', false, 'raw_data');
|
||||
if (!file_exists(utils::GetDataPath().'hub/compile_authent') || $sAuthent !== file_get_contents(utils::GetDataPath().'hub/compile_authent')) {
|
||||
throw new SecurityException(Dict::S('iTopHub:FailAuthent'));
|
||||
}
|
||||
unlink(utils::GetDataPath().'hub/compile_authent');
|
||||
// Load the "production" config file to clone & update it
|
||||
$oConfig = new Config(APPCONF.'production/'.ITOP_CONFIG_FILE);
|
||||
SetupUtils::EnterReadOnlyMode($oConfig);
|
||||
|
||||
$oRuntimeEnv->InitDataModel($oConfig, true /* model only */);
|
||||
|
||||
$aAvailableModules = $oRuntimeEnv->AnalyzeInstallation($oConfig, $oRuntimeEnv->GetBuildDir(), true);
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'BeforeDatabaseCreation');
|
||||
|
||||
$oRuntimeEnv->CreateDatabaseStructure($oConfig, 'upgrade');
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseCreation');
|
||||
|
||||
$oRuntimeEnv->UpdatePredefinedObjects();
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseSetup');
|
||||
|
||||
$oRuntimeEnv->LoadData($aAvailableModules, false /* no sample data*/);
|
||||
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDataLoad');
|
||||
|
||||
// Record the installation so that the "about box" knows about the installed modules
|
||||
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
|
||||
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
|
||||
// Default choices = as before
|
||||
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
// Plus all "remote" extensions
|
||||
if ($oExtension->sSource == iTopExtension::SOURCE_REMOTE) {
|
||||
$oExtensionsMap->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
$aSelectedExtensionCodes = [];
|
||||
foreach ($oExtensionsMap->GetChoices() as $oExtension) {
|
||||
$aSelectedExtensionCodes[] = $oExtension->sCode;
|
||||
}
|
||||
$aSelectedExtensions = $oExtensionsMap->GetChoices();
|
||||
$oRuntimeEnv->RecordInstallation($oConfig, $sDataModelVersion, array_keys($aAvailableModules), $aSelectedExtensionCodes, 'Done by the iTop Hub Connector');
|
||||
|
||||
// Report the success in a way that will be detected by the ajax caller
|
||||
SetupLog::Info('Deployment successfully completed.');
|
||||
$this->ReportSuccess(Dict::S('iTopHub:CompiledOK'));
|
||||
} catch (Exception $e) {
|
||||
if (file_exists(utils::GetDataPath().'hub/compile_authent')) {
|
||||
unlink(utils::GetDataPath().'hub/compile_authent');
|
||||
}
|
||||
// Note: at this point, the dictionnary is not necessarily loaded
|
||||
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted')."\n".$e->getMessage());
|
||||
SetupLog::Error('Debug trace: '.$e->getTraceAsString());
|
||||
$this->ReportError($e->getMessage(), $e->getCode());
|
||||
} finally {
|
||||
SetupUtils::ExitReadOnlyMode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the status of the current ajax execution (as a JSON structure)
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param bool $bSuccess
|
||||
* @param number $iErrorCode
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
public function ReportStatus($sMessage, $bSuccess, $iErrorCode = 0, $aMoreFields = [])
|
||||
{
|
||||
// Do not use AjaxPage during setup phases, because it uses InterfaceDiscovery in Twig compilation
|
||||
$this->oLastJsonPage = new JsonPage();
|
||||
$this->oLastJsonPage->SetOutputHeaders($this->bOutputHeaders);
|
||||
$aResult = [
|
||||
'code' => $iErrorCode,
|
||||
'message' => $sMessage,
|
||||
'fields' => $aMoreFields,
|
||||
];
|
||||
$this->oLastJsonPage->SetData($aResult);
|
||||
$this->oLastJsonPage->SetOutputDataOnly(true);
|
||||
$this->oLastJsonPage->output();
|
||||
}
|
||||
|
||||
private ?JsonPage $oLastJsonPage = null;
|
||||
|
||||
public function GetLastJsonPage(): ?JsonPage
|
||||
{
|
||||
return $this->oLastJsonPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to output the status of a successful execution
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
public function ReportSuccess($sMessage, $aMoreFields = [])
|
||||
{
|
||||
$this->ReportStatus($sMessage, true, 0, $aMoreFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to output the status of a failed execution
|
||||
*
|
||||
* @param string $sMessage
|
||||
* @param number $iErrorCode
|
||||
* @param array $aMoreFields
|
||||
* Extra fields to pass to the caller, if needed
|
||||
*/
|
||||
public function ReportError($sMessage, $iErrorCode, $aMoreFields = [])
|
||||
{
|
||||
if ($iErrorCode == 0) {
|
||||
// 0 means no error, so change it if no meaningful error code is supplied
|
||||
$iErrorCode = -1;
|
||||
}
|
||||
$this->ReportStatus($sMessage, false, $iErrorCode, $aMoreFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dont print headers for testing purpose mainly
|
||||
* @param bool bOutputHeaders
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function SetOutputHeaders(bool $bOutputHeaders): void
|
||||
{
|
||||
$this->bOutputHeaders = $bOutputHeaders;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\HubConnector\Model;
|
||||
|
||||
use DBBackup;
|
||||
use IssueLog;
|
||||
|
||||
/**
|
||||
* Overload of DBBackup to handle logging
|
||||
*/
|
||||
class DBBackupWithErrorReporting extends DBBackup
|
||||
{
|
||||
protected $aInfos = [];
|
||||
|
||||
protected $aErrors = [];
|
||||
|
||||
protected function LogInfo($sMsg)
|
||||
{
|
||||
$this->aInfos[] = $sMsg;
|
||||
}
|
||||
|
||||
protected function LogError($sMsg)
|
||||
{
|
||||
IssueLog::Error($sMsg);
|
||||
$this->aErrors[] = $sMsg;
|
||||
}
|
||||
|
||||
public function GetInfos(): array
|
||||
{
|
||||
return $this->aInfos;
|
||||
}
|
||||
|
||||
public function GetErrors(): array
|
||||
{
|
||||
return $this->aErrors;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -76,9 +76,6 @@
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -179,32 +176,17 @@
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</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>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</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="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
@@ -1151,9 +1133,6 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<attribute id="organization_name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -1205,32 +1184,17 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</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>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</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="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
@@ -1573,9 +1537,6 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<attribute id="service_name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -1624,32 +1585,17 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</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>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</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="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
|
||||
@@ -76,9 +76,6 @@
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -179,32 +176,17 @@
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</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>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</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="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
@@ -1140,9 +1122,6 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<attribute id="organization_name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -1194,32 +1173,17 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</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>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</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="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
@@ -1584,9 +1548,6 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<attribute id="service_name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<obsolescence>
|
||||
<condition><![CDATA[status='obsolete']]></condition>
|
||||
</obsolescence>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
@@ -1635,32 +1596,17 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</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>
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>20</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="implementation">
|
||||
<code>implementation</code>
|
||||
<rank>10</rank>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
|
||||
@@ -1486,29 +1486,14 @@
|
||||
<value id="draft">
|
||||
<code>draft</code>
|
||||
<rank>10</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>
|
||||
<value id="published">
|
||||
<code>published</code>
|
||||
<rank>20</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="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>30</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
|
||||
@@ -12,7 +12,7 @@ SetupWebPage::AddModule(
|
||||
// Setup
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-structure/2.7.1 || itop-portal/3.0.0', // itop-portal : module_design_itop_design->module_designs->itop-portal
|
||||
'itop-structure/2.7.1',
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
|
||||
@@ -43,38 +43,18 @@
|
||||
<value id="production">
|
||||
<code>production</code>
|
||||
<rank>30</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="implementation">
|
||||
<code>implementation</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>
|
||||
<value id="stock">
|
||||
<code>stock</code>
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-neutral-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-neutral-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
<value id="obsolete">
|
||||
<code>obsolete</code>
|
||||
<rank>40</rank>
|
||||
<style>
|
||||
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
|
||||
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
|
||||
<decoration_classes/>
|
||||
</style>
|
||||
</value>
|
||||
</values>
|
||||
<sql>status</sql>
|
||||
|
||||
@@ -1333,6 +1333,7 @@ When associated with a trigger, each action is given an "order" number, specifyi
|
||||
'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Day of month for %1$s',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (hour)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (month)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-Year' => '%1$s (year)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (day of week)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (day of month)',
|
||||
'UI:DashletGroupBy:MissingGroupBy' => 'Please select the field on which the objects will be grouped together',
|
||||
|
||||
@@ -1279,6 +1279,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
|
||||
'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Jour du mois pour %1$s',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (heure)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (mois)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-Year' => '%1$s (année)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (jour de la semaine)',
|
||||
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (jour du mois)',
|
||||
'UI:DashletGroupBy:MissingGroupBy' => 'Veuillez sélectionner le champ sur lequel les objets seront groupés',
|
||||
|
||||
@@ -19,8 +19,15 @@
|
||||
*/
|
||||
|
||||
// Input
|
||||
Dict::Add('EN US', 'English', 'English', [
|
||||
'UI:Component:Input:ChangeNotAllowed' => 'This change is not allowed',
|
||||
'UI:Component:Input:Password:DoesNotMatch' => 'Passwords do not match',
|
||||
'UI:Component:Input:Set:MinimumItems' => 'Minimum %1$s item(s) required',
|
||||
]);
|
||||
Dict::Add(
|
||||
'EN US',
|
||||
'English',
|
||||
'English',
|
||||
[
|
||||
'UI:Component:Input:ChangeNotAllowed' => 'This change is not allowed',
|
||||
'UI:Component:Input:Password:DoesNotMatch' => 'Passwords do not match',
|
||||
'UI:Component:Input:Set:MinimumItems' => 'Minimum %1$s item(s) required',
|
||||
|
||||
'UI:Component:Input:Select:Select_item' => 'Select an item...',
|
||||
]
|
||||
);
|
||||
|
||||
@@ -10,8 +10,15 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('FR FR', 'French', 'Français', [
|
||||
'UI:Component:Input:ChangeNotAllowed' => 'Cette modification n\'est pas autorisée',
|
||||
'UI:Component:Input:Password:DoesNotMatch' => 'Les mots de passe ne correspondent pas',
|
||||
'UI:Component:Input:Set:MinimumItems' => 'Minimum %1$s élément(s) requis',
|
||||
]);
|
||||
Dict::Add(
|
||||
'FR FR',
|
||||
'French',
|
||||
'Français',
|
||||
[
|
||||
'UI:Component:Input:ChangeNotAllowed' => 'Cette modification n\'est pas autorisée',
|
||||
'UI:Component:Input:Password:DoesNotMatch' => 'Les mots de passe ne correspondent pas',
|
||||
'UI:Component:Input:Set:MinimumItems' => 'Minimum %1$s élément(s) requis',
|
||||
|
||||
'UI:Component:Input:Select:Select_item' => 'Sélectionnez un élément...',
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
Dict::Add('EN US', 'English', 'English', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('FR FR', 'French', 'Français', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installé',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'va être installé',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'pas installé',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'va être désinstallé',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'non désinstallable',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'supprimé du disque',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'À propos de %1$s',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'Plus d\'informations',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Forcer la désinstallation',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('NL NL', 'Dutch', 'Nederlands', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license https://opensource.org/licenses/AGPL-3.0
|
||||
*
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
]);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user