Compare commits

..

140 Commits

Author SHA1 Message Date
Timothee
f550140f5f N°8955 WIP 2026-03-03 15:09:49 +01:00
Timmy38
5a9d3fee48 N°9161 Add markup for behat in setup recap screen 2026-03-02 10:41:32 +01:00
Timmy38
8e43a69f88 N°8955 add UI block for uninstallation 2026-02-27 17:05:26 +01:00
odain
db3933aa71 cleanup 2026-02-27 10:47:32 +01:00
Timothee
f0629724ea Fix Code Style 2026-02-26 10:30:36 +01:00
Timmy38
9b6d321ab0 N°9009 Add phpunit test to GetSelectedModules 2026-02-26 10:28:52 +01:00
Timothee
329191aa47 N°9144 Fix backup option presence during install 2026-02-26 09:35:58 +01:00
odain
6915df1409 N°9168 - fix ci 2026-02-24 11:36:04 +01:00
odain
fd68558d1c Merge branch 'feature/9168-moveintocore' into feature/uninstallation 2026-02-24 10:56:55 +01:00
odain
bd9129dee2 N°9168 - merge data feature removal to iTop core code
N°9168 - phpstan fix

N°9168 - align module version with itop core
2026-02-24 10:56:23 +01:00
Timothee
fe34a6f9c3 N°9144 Small fixes
> Prevent verification when some dependencies are missing
> Close unclosed div
> Change progress bar title depending on step
> Fix ignored "check uninstall check" flag
> Added phpunit tests to cover "check uninstall check" flag
> Progress bar appropriately reflect error status (red & not animated)
2026-02-23 18:04:49 +01:00
Timothee
24aec0d08d N°9144 Fix back from module choice 2026-02-23 15:03:31 +01:00
Timothee
a287527d29 N°9010 Fix Code Style 2026-02-19 15:45:33 +01:00
Timmy38
a42b061f19 N°9010 fix flags when extension is missing 2026-02-19 14:50:19 +01:00
Timmy38
c4d7c89553 N°9144 Add data audit setup step
N°8864 Fix unneeded char
2026-02-19 14:27:24 +01:00
odain
5b58e40fc9 N°8760 - fix installation choices db query - read code instead of title + add log 2026-02-08 20:34:40 +01:00
odain
2b21556c76 Merge branch 'feature/8760-audit-squashed' into feature/uninstallation 2026-02-06 16:48:26 +01:00
odain
77626f8159 N°8760 - Audit uninstall of extensions that declare final classes
N°8760 - be able to list modules based on extension choices
refactoring: move some classes in a moduleinstallation folder (coming
namespace)

N°8760 - module dependency check applied before audit

N°8760 - make dependency check work during audit

N°8760 - fix ci

N°8760 - fix ci

N°8760 - add GetCreatedIn to get module name based on DBObject class - everything stored in MetaModel during compilation and autoload

N°8760 - be able to describe from which module a datamodel class comes via MetaModel created_in field

N°8760 - rename GetCreatedIn <- GetModuleName + compute module name live instead having complex stuff in MetaModel/compilation

temp review 1

review: renaming InstallationChoicesToModuleConverter

review: renaming InstallationChoicesToModuleConverter

review: ModuleDiscovery:GetModulesOrderedByDependencies replacing deprecated GetAvailableModules method

ci: fix typo

cleanup

review: rework InstallationChoicesToModuleConverter

N°8760 - review tests
2026-02-06 16:48:00 +01:00
odain
bb6248a6e7 N°8764 - enhance setup wizards transition computation/tests 2026-01-28 14:47:52 +01:00
Molkobain
130d98aa3f N°9106 - Adapt extensions pills so they can be read correctly by Behat 2026-01-27 10:44:51 +01:00
odain
00c590232a N°8764 - fix setup wizards transitions - damned missing file 2026-01-23 09:47:52 +01:00
odain
97828225db N°8764 - fix setup wizards transitions 2026-01-23 09:28:54 +01:00
odain
03e59c9749 N°8764 - fix missing setup wizards titles + cache step computation 2026-01-22 15:54:15 +01:00
odain
985a49dc9f code style 2026-01-21 18:37:23 +01:00
odain
adae35ccc4 N°8764 - use last working model in case setup wizard to do setup audit - skip if no model available to make setup work 2026-01-21 17:11:11 +01:00
Timothee
f0c9629f5f N°8763 Add phpunit tests for iTopExtension::CanBeUninstalled 2026-01-21 10:27:49 +01:00
odain
4e96b297c2 N°8864 - code readability 2026-01-20 16:21:52 +01:00
odain
ae620c6663 N°8760 - Audit uninstall of extensions that declare final classes - refactor extension computations and move them in ExtensionMap
N°8760 - Audit uninstall of extensions that declare final classes - refactor extension computations and move them in ExtensionMap
2026-01-20 15:53:27 +01:00
odain
b5c51a2983 simplify WizardStep::GetTitle 2026-01-20 15:53:27 +01:00
odain
3b2d845c00 N°8981 - reduce log verbosity during setup 2026-01-20 15:53:27 +01:00
Timothee
36c545a6c4 N°8763 Fix non-uninstallable check in multi-modules extensions 2026-01-20 15:35:53 +01:00
odain
5ecb4936f0 Merge branch 'feature/8981-prepare' into feature/uninstallation 2026-01-14 10:13:42 +01:00
odain
af2c3b02bc N°8764 - skip audit when itop reinstalled on top of a backup without env-production to read previous datamodel + phpdoc fix 2026-01-14 10:13:10 +01:00
odain
cfc933b92b Merge branch 'feature/8981-prepare' into feature/uninstallation 2026-01-13 16:33:10 +01:00
odain
0582ae1038 setup: phpstan level 2
setup: phpstan level 2
2026-01-13 16:31:46 +01:00
odain
13c18b611c setup: phpstan level 1 2026-01-13 16:29:44 +01:00
odain
8d2e0761e0 setup: phpstan level 0 2026-01-13 16:29:44 +01:00
odain
1e15eb1161 N°8764 halt setup wizard at data issue - 2nd review 2026-01-13 16:29:44 +01:00
odain
7df59427cb N°8764 halt setup wizard at data issue - review
- 2 types of SetupAudit constructors
- setup wizard new step management enhancement
- change SetupAudit GetIssue API behaviour
2026-01-12 15:55:35 +01:00
odain
d647d92acf ci: test enhancement => use GetTemporaryFilePath 2026-01-12 15:55:35 +01:00
odain
693e40b9c7 enhance test sdk: add GetTemporaryFilePath 2026-01-12 15:55:35 +01:00
odain
c3c2135ecc N°7629: log changed 2026-01-12 15:55:35 +01:00
odain
63e473e6d0 N°8981: test ModuleDiscovery filtered by removed extensions 2026-01-12 15:55:35 +01:00
odain
3a3f5736c0 N°8764 - fix first install 2026-01-12 15:55:35 +01:00
odain
0996e8fae0 N°8764 - stop setup and display data to cleanup message 2026-01-12 15:55:35 +01:00
odain
43bd621731 N°7629: improve InterfaceDiscovery::GetCandidateClasses robustness 2026-01-12 15:55:35 +01:00
odain
d1e91087b9 N°8764 - Halt setup if database is not compatible with an uninstallation 2026-01-12 15:55:35 +01:00
odain
73cb58a131 N°9085 - Cannot install itop without a portal 2026-01-12 15:52:55 +01:00
odain
d1fde89129 N°8981: fix broken setup wizard due to invalid array passed 2026-01-08 07:55:54 +01:00
odain
1f4b96798a Merge branch 'feature/8981-prepare' into feature/uninstallation 2026-01-07 20:39:46 +01:00
odain
9768ffb19d N°8981: ModuleInstallationRepository dedicated to module installation queries
N°8981: ModuleInstallationRepository dedicated to module installation queries

fix renaming
2026-01-07 20:15:40 +01:00
odain
e55bbf728b N°8981: review cleanup on module filtering due to extensoin removal 2026-01-07 19:48:34 +01:00
odain
5f2604c610 N°8981: be able to remove extension during setup even when present on disk 2026-01-07 19:37:05 +01:00
odain
57b3610100 N°8981 setup wizard: reuse AnalyzeInstallation result in wizard module choice step 2026-01-07 19:37:05 +01:00
odain
9f3b8ec964 N°8981 setup wizard: optimize IsConnectableToITopHub use (perf)
N°8981: revert IsConnectableToITopHub
2026-01-07 19:37:00 +01:00
odain
193c980057 N°8981 setup wizard: static removal + cleanup 2026-01-07 10:33:11 +01:00
odain
fdfe9224c3 N°8981 : add type in function 2026-01-07 10:33:11 +01:00
odain
774fe22ece N°8981 setup wizard: cache GetParamForConfigArray 2026-01-07 10:33:11 +01:00
odain
27b0f64328 N°8981 setup wizard: cleanup main actions + remove static when possible 2026-01-07 10:33:11 +01:00
odain
8ad4670a2f N°8981 setup wizard: cleanup config use in setup wizard 2026-01-07 10:33:11 +01:00
odain
f2e682c07c test SDK enhancement: be able to compare moduleinstallation
ci: add log and hopefully fix hub test

ci: fix all test that rely on CustomDataTC
2026-01-07 10:33:11 +01:00
odain
83973d102f N°8981: repair previous setup cleanup (broken setups) 2026-01-07 10:33:11 +01:00
odain
85e28931f5 N°8981: prepare hub connector test cover
sdk test enhancement : add call itop api

fix ci

ci: fix broken tests

ci: cover hub setup on compile and launch steps

code style

ci: fix ModuleDiscoveryTest redundant class + add logs to investigate ci setup issues

ci: fix log during setup tests
2026-01-07 10:33:11 +01:00
odain
d33ca81198 N°4789 - do not call empty ModuleInstallerAPI class + logs
code style
2026-01-07 10:33:11 +01:00
odain
1ef4462517 N°8981 - cleanup/simplify setup
fix RecordInstallation due to previous refactoring
2026-01-07 10:33:10 +01:00
odain
a2b0ad6c11 ci: fix broken LoginTest 2026-01-07 10:33:10 +01:00
Timothee
7844c9ffd0 N°8864 Passing array instead of html 2026-01-06 14:29:23 +01:00
Timothee
1c45a4c49e N°9010 Improve tests readability 2026-01-06 08:17:31 +01:00
Timmy38
8f47ca00a7 N°8864 list extensions installation in setup recap 2026-01-06 08:09:22 +01:00
Timothee
f9a1b444ab N°9010 Setup wizard : manage multiple level extension choice 2025-12-23 16:05:23 +01:00
odain
3b38dac8c6 Merge branch 'develop' into feature/uninstallation 2025-12-19 18:45:34 +01:00
odain
3d46fe6ef1 Merge branch 'support/3.2' into develop 2025-12-19 18:44:55 +01:00
odain
4dba47798c ci: fix broken test due to POST CURL file not sent 2025-12-19 18:35:42 +01:00
Anne-Cath
9480c4053d N°8786 - configuration: allow conditions on the allowed_login_types field - fix tests 2025-12-19 16:37:47 +01:00
odain
b967fb7f20 Merge branch 'develop' into feature/uninstallation 2025-12-19 16:17:12 +01:00
odain
9ea197148c Merge branch 'support/3.2' into develop 2025-12-19 16:17:00 +01:00
odain
49a7f3118d ci: fix Array to String Conversion in CallUrl with POST array of array 2025-12-19 16:12:53 +01:00
odain
195038c941 Merge branch 'develop' into feature/uninstallation 2025-12-19 15:48:56 +01:00
odain
bf80b5dca2 Merge branch 'support/3.2' into develop 2025-12-19 15:46:00 +01:00
odain
09afcb229c ci: fix callUrl with posted params 2025-12-19 11:11:03 +01:00
odain
92f843f676 Merge branch 'support/3.2' into develop 2025-12-19 10:39:24 +01:00
odain
3df4ddc696 ci: fix code style 2025-12-19 10:39:15 +01:00
odain
d552727c55 ci: fix code style 2025-12-19 10:38:48 +01:00
odain
1fe401c102 ci: rename CallItopUrl by CallUrl + cleanup 2025-12-19 10:01:37 +01:00
odain
a4b0b6e855 ci: rename CallItopUrl by CallUrl + cleanup 2025-12-19 09:54:43 +01:00
odain
9a8d87e2b5 Merge branch 'develop' into feature/uninstallation 2025-12-18 14:02:45 +01:00
odain
545028d68a Merge branch 'support/3.2' into develop 2025-12-18 13:51:54 +01:00
odain-cbd
6cb08ba361 Add few APIs to ease tests (#788)
* add few APIs to ease tests

* code style

* log testname via IssueLog only through ItopDataTestCase

* code style

* ci: phpunit fix fatal error
2025-12-18 13:31:48 +01:00
Molkobain
b3cd79605d Merge remote-tracking branch 'refs/remotes/origin/support/3.2' into develop 2025-12-17 22:22:36 +01:00
Molkobain
f9db405343 N°6644 - PHP Static Analysis: Update PHPStan to v2.1 2025-12-17 22:21:43 +01:00
Molkobain
4f1f144c51 N°6644 - PHP Static Analysis: Update configuration to:
- Ignore compiled folders other than "env-production"
 - Ignore Lempar.php as its content isn't valid PHP and it won't be included in the baseline
2025-12-17 22:04:49 +01:00
Molkobain
ef8ade5d20 📝 Update unit tests documentation 2025-12-17 18:36:18 +01:00
v-dumas
cef4a52081 N°8950 - Fix color of implementation status 2025-12-17 12:21:54 +01:00
v-dumas
dc9fb2d693 N°8950 - Add obsolescence rule on Contract, Service and ServiceSubcategory 2025-12-17 12:21:54 +01:00
v-dumas
e9ffbe5b09 N°8950 - Add colors on 'status' on non-CI classes 2025-12-17 12:21:54 +01:00
v-dumas
9c792a601f N°8951 - Add colors on 'status' field on CIs 2025-12-17 12:21:54 +01:00
Molkobain
cda6c1dfa8 Merge remote-tracking branch 'refs/remotes/origin/support/3.2' into develop 2025-12-17 11:09:22 +01:00
Molkobain
0b242d872a N°6644 - Tests: Restore proper CI branch, unit tests run and fix a typo 2025-12-17 11:06:36 +01:00
Molkobain
c144c80663 Merge remote-tracking branch 'refs/remotes/origin/support/3.2' into develop 2025-12-17 10:56:31 +01:00
Molkobain
d9261b8342 N°6644 - Tests: Add static analysis for PHP (#536) 2025-12-17 10:45:53 +01:00
Anne-Cath
df567fb9fe N°8786 - configuration: allow conditions on the allowed_login_types field 2025-12-09 18:24:44 +01:00
Timothee
028767768c N°8763 Code style fix 2025-12-09 16:04:50 +01:00
Timothee
11ec80830e N°8763 Fix issue with previously installed extensions not installed after upgrade 2025-12-09 15:46:10 +01:00
odain
edf992ef2b N°8724 - reduce log verbosity on ModuleInstallerAPI calls 2025-12-09 10:28:24 +01:00
Anne-Cath
7c8fb1a51d N°8852 - improve display attachment in properties tab 2025-12-09 09:15:51 +01:00
Anne-Cath
aa27b3601b N°8786 - configuration: allow conditions on the allowed_login_types field 2025-12-08 16:47:30 +01:00
odain
385302c44c ci: fix nightly 2025-12-08 10:51:28 +01:00
odain
b563f113d0 add combodo-data-feature-removal 2025-12-08 10:38:30 +01:00
odain
715d9d3b1c less logs during setup 2025-12-05 17:37:09 +01:00
odain
7a6c2bc6a4 ci: fix with package 2025-12-05 17:36:57 +01:00
odain
8919184ef9 ci: adapt test to run also with itop packages 2025-12-05 11:36:19 +01:00
odain
7bfaebe4db ci: fix merge and issues with LoadChoicesFromDatabase 2025-12-04 18:40:21 +01:00
odain
8e10cf9b72 N°8760 : fix broken setup
ci: fix merge
2025-12-04 18:28:47 +01:00
odain
9f3d7d2c36 N°8760: revert dry removal audit and use real file deletion again
code formatting
2025-12-04 17:19:18 +01:00
odain
ae980e365d N°8760 :fix ModuleInstallation db query + refactor query in ModuleInstallationService
extensionmap cleanup

fix setup broken
2025-12-04 17:19:18 +01:00
odain
a2b01b3ed4 N°8760 - setupaudit and dry removal API review with Romain - avoid file deletion
N°8760 - be able to simulate extension removal by oerriding GetExtensionMap

be able to simulate SetupAudit errors in Setups for integration tests

fix rebase
2025-12-04 17:19:18 +01:00
odain
9cdc707bc5 N°8760 - Audit uninstall of extensions that declare classes - first prototype
N°8760 - Audit uninstall of extensions that declare classes - be able to trace DM classes created_in

N°8760 - be able to test with additional extensions installed in test SDK

N°8760 - provide a service dedicated to extension removal

N°8760 - compute all rules by default

add comment

adapt audit to both extension and mtp
2025-12-04 17:19:18 +01:00
odain
76178c16b8 N°8760: code legacy refactoring around module computation (AnalyzeInstallation)
AnalyzeInstallation refactoring

fix ci call to ModuleInstallationService

ci: fix code style

get rid of itop_version

refactoring: make code simpler

get rid of unused name_db

refactoring setup: rename version_db by installed_version

refactoring setup: rename version_code by available_version

code cleanup: avoid useless runtimeenv instanciation
2025-12-04 17:19:18 +01:00
Timmy38
73f868ac83 N°8763 Halt setup if non-uninstallable extension is missing (#781)
* N°8763 Halt setup if an installed & non-uninstallable extension is missing from disk
2025-12-04 11:01:31 +01:00
odain
5a2157ba21 N°8724 - refactoring for maintenability 2025-11-27 15:47:25 +01:00
odain
03e25a226e Merge branch 'faf/moduledependency-enhancement' into develop 2025-11-26 19:25:37 +01:00
odain
24048d2b9c N°8724 - Enhance setup feedback in case of module dependency issue (#700)
code style

last test cleanup

review + enhance UI output and display only failed module dependencies

real life test cleanup

review: add more tests + refacto

code review: enhance algo and APIs

review: renaming

enhance test coverage

refactoring

renaming + reorder functions/tests

compute GetDependencyResolutionFeedback in Module class

review2 : renaming things

fix rebase + code formatting

fix code formatting

review changes

refactoring: code cleanup/standardization/remove all prototype stuffs

refactoring: code cleanup/standardization/remove all prototype stuffs

add deps validation to extension ci job

fix ci

fix ci: test broken when dir to scan did not exist like production-modules

fix tests

module dependency validation moved in a core folder + cleanup dedicated unit/integration tests

forget dependency computation optimization seen as too risky + keep only user friendly sort in case of setup error

rebase on develop + split new sort computation apart from modulediscovery

revert to previous legacy order + gather new module computation classes in a dedicated folder

make validation work (dirty way) + cleanup

make setup deterministic: complete dependency order with alphabetical one when 2 module elements are at same position

final deps validation bases on DM and PHP classes

init in beforeclass + read defined classes/interfaces by module

module discovery classes renaming to avoid collision with customer DM definitions

read module file data apart from ModuleDiscovery

cleanup

cleanup

fix inconsistent module dependencies

fix integration check

save tmp work before trying to fetch other wml deps

fix module dependencies

fix DM filename typo

rename ModuleXXX classes by iTopCoreModuleXXX to reduce collisions with extensions

add phpdoc + add more tests

module dependency optimization - refacto + dependency new sort order

module dependency optimization - stop computation when no new dependency is resolved

enhance module dependency computation for optimization and admin feedback
2025-11-26 19:23:26 +01:00
odain
d8121b563a synchro code: fix Deprecated: strlen(): Passing null to parameter 2025-11-21 10:38:26 +01:00
Anne-Cath
f266f5ff36 N°8911 - Attachment visualisation broken 2025-11-20 08:06:00 +01:00
odain
4a6b129eb8 Merge branch 'support/3.2' into develop 2025-11-18 15:36:47 +01:00
odain
4187f552a9 Merge branch 'support/3.2.1' into support/3.2 2025-11-18 15:36:32 +01:00
odain
7f7ce0837e Merge branch 'designer-3.2.1' into support/3.2.1 2025-11-18 15:35:40 +01:00
jf-cbd
bc4d50dd0b ✏️ fix a typo 2025-11-17 17:14:15 +01:00
Vincent Dumas
e21a541b70 N°8533 - Impact Analysis, add icons and tooltips in shortcut_actions (#767)
* N°8533 - Impact Analysis, add icons and tooltips in shortcut_actions
2025-11-17 15:55:05 +01:00
v-dumas
ed360edb83 Fix PHP CS 2025-11-17 15:04:19 +01:00
Vincent Dumas
53de040934 N°8534 - Prevent Admin, SuperUser from loose of rights (#774)
* N°8534 - Prevent Admin & SuperUser from suicide
Prevent creation/modification of Administrator, SuperUser, REST User, combined with a Profile denying access to the backoffice
2025-11-17 14:17:18 +01:00
odain
b8345de553 Merge branch 'support/3.2' into develop 2025-11-16 08:15:29 +01:00
odain
fd10887849 N°8306 - ci fixes
php code style
2025-11-16 08:15:03 +01:00
odain
7df09541ac N°8306 - ci fixes 2025-11-16 08:11:34 +01:00
odain
e09987e442 Merge branch 'support/3.2' into develop 2025-11-15 21:29:52 +01:00
odain
6d5660ca67 fix code style 2025-11-15 21:28:38 +01:00
odain
0013b8cfee Merge branch 'designer-3.2.1' into support/3.2 2025-11-15 21:27:37 +01:00
odain
7fb0ae48f9 Fix deprecated warning
<b>Deprecated</b>:  Return type of Combodo\iTop\DesignDocument::loadXML(string $source, int $options = 0) should either be compatible with DOMDocument::loadXML(string $source, int $options = 0): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in <b>/var/www/html/iTop/core/designdocument.class.inc.php</b> on line <b>73</b><br />
2025-11-15 21:26:36 +01:00
odain
e89a8edae0 N°8306 - fix MFEException use 2025-11-15 21:19:01 +01:00
Vincent Dumas
262cc3c206 N°5882 - AuditRule: Add Owner and Process (#775)
* N°5882 - Audit Rule: Add "Owner" and "Correction Process" fields
* Fix PHP CS
2025-11-14 17:04:25 +01:00
246 changed files with 27760 additions and 6441 deletions

View File

@@ -9,7 +9,7 @@ Any PRs not following the guidelines or with missing information will not be con
## Base information
| Question | Answer
|---------------------------------------------------------------|--------
| Related to a SourceForge thead / Another PR / Combodo ticket? | <!-- Put the URL -->
| Related to a SourceForge thread / Another PR / Combodo ticket? | <!-- Put the URL -->
| Type of change? | Bug fix / Enhancement / Translations

View File

@@ -536,6 +536,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
// Cache
$this->m_aObjectActionGrants = [];
$this->m_aAdministrators = null;
$this->aUsersProfilesList = [];
}
public function LoadCache()

View File

@@ -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.class.inc.php');
require_once(APPROOT.'/setup/moduleinstallation/moduleinstallation.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');

View File

@@ -48,7 +48,7 @@ class AuditCategory extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeString("name", ["description" => "Short name for this category", "allowed_values" => null, "sql" => "name", "default_value" => "", "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeString("description", ["allowed_values" => null, "sql" => "description", "default_value" => "", "is_null_allowed" => true, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeOQL("definition_set", ["allowed_values" => null, "sql" => "definition_set", "default_value" => "", "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", ["linked_class" => "AuditRule", "ext_key_to_me" => "category_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => [], "edit_mode" => LINKSET_EDITMODE_INPLACE, "tracking_level" => LINKSET_TRACKING_ALL]));
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", ["linked_class" => "AuditRule", "ext_key_to_me" => "category_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => [], "edit_mode" => LINKSET_EDITMODE_INPLACE, "edit_when" => LINKSET_EDITWHEN_ALWAYS, "tracking_level" => LINKSET_TRACKING_ALL]));
MetaModel::Init_AddAttribute(new AttributeInteger("ok_error_tolerance", ["allowed_values" => null, "sql" => "ok_error_tolerance", "default_value" => 5, "is_null_allowed" => true, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeInteger("warning_error_tolerance", ["allowed_values" => null, "sql" => "warning_error_tolerance", "default_value" => 25, "is_null_allowed" => true, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect(

View File

@@ -52,13 +52,14 @@ class AuditRule extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeEnum("valid_flag", ["allowed_values" => new ValueSetEnum('true,false'), "sql" => "valid_flag", "default_value" => "true", "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", ["allowed_values" => null, "sql" => "category_id", "targetclass" => "AuditCategory", "is_null_allowed" => false, "on_target_delete" => DEL_MANUAL, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", ["allowed_values" => null, "extkey_attcode" => 'category_id', "target_attcode" => "name"]));
MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", ["allowed_values" => null, "sql" => "contact_id", "targetclass" => "Contact", "is_null_allowed" => true, "on_target_delete" => DEL_MANUAL, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeHTML("process", ["allowed_values" => null, "sql" => "process", "default_value" => "", "is_null_allowed" => true, "depends_on" => []]));
// Display lists
MetaModel::Init_SetZListItems('details', ['category_id', 'name', 'description', 'query', 'valid_flag']); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['category_id', 'description', 'valid_flag']); // Attributes to be displayed for a list
MetaModel::Init_SetZListItems('details', ['category_id', 'name', 'description', 'query', 'valid_flag', 'process', 'contact_id']); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['category_id', 'description', 'query']); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', ['category_id', 'name', 'description', 'valid_flag', 'query']); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', ['name', 'description', 'category_id']); // Criteria of the advanced search form
MetaModel::Init_SetZListItems('default_search', ['name', 'description', 'category_id', 'contact_id', 'query']); // Criteria of the advanced search form
}
public static function GetShortcutActions($sFinalClass)

View File

@@ -1,4 +1,5 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
@@ -9,5 +10,4 @@
*/
class CoreOqlException extends CoreException
{
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
@@ -9,5 +10,4 @@
*/
class CoreOqlMultipleResultsForbiddenException extends CoreOqlException
{
}

View File

@@ -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.class.inc.php');
MetaModel::IncludeModule('setup/moduleinstallation/moduleinstallation.class.inc.php');
MetaModel::IncludeModule('core/event.class.inc.php');
MetaModel::IncludeModule('core/action.class.inc.php');

View File

@@ -211,6 +211,14 @@ 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)',
@@ -2410,6 +2418,7 @@ class Config
public function SetAllowedLoginTypes($aAllowedLoginTypes)
{
$this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes);
$this->Set('allowed_login_types', implode('|', $aAllowedLoginTypes));
}
/**
@@ -2676,14 +2685,13 @@ 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, $bPreserveModuleSettings = false)
public function UpdateFromParams($aParamValues, $sModulesDir = null)
{
if (isset($aParamValues['application_path'])) {
$this->Set('app_root_url', $aParamValues['application_path']);
@@ -2731,7 +2739,10 @@ class Config
} else {
$aSelectedModules = null;
}
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
if (! is_null($sModulesDir)) {
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
}
if (isset($aParamValues['source_dir'])) {
$this->Set('source_dir', $aParamValues['source_dir']);
@@ -2749,17 +2760,13 @@ class Config
*
* @throws Exception
*/
public function UpdateIncludes($sModulesDir, $aSelectedModules = null)
public function UpdateIncludes(string $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::GetAvailableModules([APPROOT.$sModulesDir]);
$aModules = ModuleDiscovery::GetModulesOrderedByDependencies([APPROOT.$sModulesDir]);
foreach ($aModules as $sModuleId => $aModuleInfo) {
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules)) {

View File

@@ -71,12 +71,9 @@ class DesignDocument extends DOMDocument
}
/**
* @param string $source
* @param int $options
*
* @return bool|\DOMDocument
* @inheritDoc
*/
public function loadXML(string $source, int $options = 0): bool|DOMDocument
public function loadXML(string $source, int $options = 0): bool
{
return parent::loadXML($source, $options | LIBXML_BIGLINES);
}

View File

@@ -22,6 +22,8 @@ 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';
@@ -462,6 +464,43 @@ 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
*

View File

@@ -404,14 +404,14 @@ abstract class User extends cmdbAbstractObject
}
if (!in_array(ADMIN_PROFILE_NAME, $aProfiles)) {
// Check if the user is yet allowed to modify Users
// Prevent a User to lose the right to modify Users
if (method_exists($oAddon, 'ResetCache')) {
$aCurrentProfiles = Session::Get('profile_list');
// Set the current profiles into a session variable (not yet in the database)
Session::Set('profile_list', $aProfiles);
$oAddon->ResetCache();
if (!$oAddon->IsActionAllowed($this, 'User', UR_ACTION_MODIFY, null)) {
if (!$oAddon->IsActionAllowed($this, get_class($this), UR_ACTION_MODIFY, null)) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:CurrentProfilesHaveInsufficientRights');
}
$oAddon->ResetCache();
@@ -422,6 +422,19 @@ abstract class User extends cmdbAbstractObject
Session::Set('profile_list', $aCurrentProfiles);
}
}
// Prevent an administrator to remove their own admin profile
if (UserRights::IsAdministrator($this)) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AdminProfileCannotBeRemovedBySelf');
}
}
} elseif ($this->IsPrivilegedUser()) {
// Prevent Privileged User to be saved with profiles denying the access to the backoffice
$oSet->Rewind();
while ($oUserProfile = $oSet->Fetch()) {
$sProfile = $oUserProfile->Get('profile');
if (in_array($sProfile, $aForbiddenProfiles)) {
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice', $sProfile);
}
}
}
}
@@ -635,6 +648,21 @@ abstract class User extends cmdbAbstractObject
}
return UserRights::GetUserId() == $this->GetKey();
}
private function IsPrivilegedUser(): bool
{
$aPrivilegedProfiles = ['Administrator' => '1', 'REST Services User' => '1024', 'SuperUser' => '117'];
$oSet = $this->Get('profile_list');
$oSet->Rewind();
while ($oUserProfile = $oSet->Fetch()) {
$iProfile = $oUserProfile->Get('profileid');
if (in_array($iProfile, $aPrivilegedProfiles)) {
return true;
}
}
return false;
}
}
/**

View File

@@ -22,4 +22,5 @@
@import "medallion-with-blocklist";
@import "field-badge-within-datatable";
@import "jquery-blockui-within-dialog";
@import "jquery-blockui-within-datatable";
@import "jquery-blockui-within-datatable";
@import "badge-with-badge";

View File

@@ -0,0 +1,10 @@
/*
* @copyright Copyright (C) 2010-2024 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;
}

View File

@@ -33,4 +33,5 @@
@import "field-badge";
@import "file-select";
@import "medallion-icon";
@import "toast";
@import "toast";
@import "badge";

View File

@@ -0,0 +1,41 @@
$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;
}
}
}

View File

@@ -8,6 +8,7 @@ $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;
@@ -17,6 +18,7 @@ $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;
@@ -61,6 +63,13 @@ $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;
}

View File

@@ -15,3 +15,4 @@
@import "wizard-container/wizard-container";
@import "object/all";
@import "activity-panel/all";
@import "extension/all";

View File

@@ -0,0 +1 @@
@import "extension-details";

View File

@@ -0,0 +1,54 @@
$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:has(input:checked) .ibo-badge.unchecked, .ibo-extension-details:has(input:not(:checked)) .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;
}

File diff suppressed because one or more lines are too long

View File

@@ -311,29 +311,35 @@ fieldset {
}
.module-selection-body {
overflow: auto;
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, .06) !important;
background-color: #F7FAFC;
padding: 10px;
.wiz-choice:checked ~ .description {
#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked),
#itop-ticket-mgmt-itil-enhanced-portal:not(:checked) {
~ .description::after {
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
display: block;
margin-top: 0.5em;
font-weight: bold;
color: $legacy-portal-removal-text-color;
}
}
}
overflow: auto;
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, .06) !important;
background-color: #F7FAFC;
padding: 10px;
.wiz-choice{
&:checked ~ .description {
#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked),
#itop-ticket-mgmt-itil-enhanced-portal:not(:checked) {
~ .description::after {
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
display: block;
margin-top: 0.5em;
font-weight: bold;
color: $legacy-portal-removal-text-color;
}
}
}
&:not(:checked) ~ label .checked{
display:none;
}
&:checked ~ label .unchecked{
display:none;
}
}
}
body {
font-size: 1.17rem;
font-family: "Raleway";
@@ -516,10 +522,12 @@ body {
}
}
.ibo-setup-summary-title {
font-size: $ibo-font-size-150;
.ibo-setup-summary-title, .ibo-setup-summary-title:visited, .ibo-setup-summary-title:hover {
font-size: $ibo-font-size-150;
color: inherit;
}
#ibo-setup-licenses--components-list {
background-color: $ibo-color-white-200;
padding: 12px;
@@ -595,6 +603,36 @@ 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
}
}
.setup--wizard-choice--label + .setup--wizard-choice--more-info {
margin-left: 0.5rem;
}
@@ -646,9 +684,6 @@ body {
overflow: auto;
text-align: center;
}
#installation_progress {
display: none;
}
#fresh_content{
border: 0;
min-height: 300px;

View File

@@ -0,0 +1,9 @@
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/*
* CSS of the template page
*/

View File

@@ -0,0 +1,9 @@
/*
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/*
* Javascript file loaded in template page
*/

View File

@@ -0,0 +1,17 @@
{
"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"
}
}

View File

@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
<classes>
<class id="DataFeatureRemoverExtension" _delta="define">
<properties>
<category>grant_by_profile</category>
<db_table>data_feature_removal_extension</db_table>
<naming>
<attributes/>
</naming>
<reconciliation>
<attributes>
<attribute id="extension_code"/>
</attributes>
</reconciliation>
<uniqueness_rules/>
</properties>
<fields>
<field id="extension_code" xsi:type="AttributeString">
<sql>extension_code</sql>
<is_null_allowed>false</is_null_allowed>
<tracking_level>all</tracking_level>
</field>
<field id="label" xsi:type="AttributeString">
<sql>label</sql>
<is_null_allowed>false</is_null_allowed>
<tracking_level>all</tracking_level>
</field>
<field id="version" xsi:type="AttributeString">
<sql>version</sql>
<is_null_allowed>false</is_null_allowed>
<tracking_level>all</tracking_level>
</field>
<field id="module_names" xsi:type="AttributeText">
<sql>module_names</sql>
<is_null_allowed>false</is_null_allowed>
<tracking_level>all</tracking_level>
</field>
<field id="status" xsi:type="AttributeString">
<sql>status</sql>
<default_value>none</default_value>
<is_null_allowed>true</is_null_allowed>
<tracking_level>all</tracking_level>
</field>
</fields>
<presentation>
<list>
<items>
<item id="extension_code">
<rank>10</rank>
</item>
<item id="status">
<rank>20</rank>
</item>
</items>
</list>
<search>
<item id="extension_code">
<rank>10</rank>
</item>
<item id="status">
<rank>20</rank>
</item>
</search>
<details>
<item id="extension_code">
<rank>10</rank>
</item>
<item id="status">
<rank>20</rank>
</item>
</details>
</presentation>
<methods/>
</class>
<class id="DataFeatureRemoverAuditRule" _delta="define">
<properties>
<category>grant_by_profile</category>
<db_table>data_feature_removal_auditrule</db_table>
<naming>
<attributes/>
</naming>
<reconciliation>
<attributes>
<attribute id="rule_name"/>
<attribute id="extension_code"/>
<attribute id="class_name"/>
</attributes>
</reconciliation>
<uniqueness_rules/>
</properties>
<fields>
<field id="rule_name" xsi:type="AttributeString">
<sql>rule_name</sql>
<is_null_allowed>false</is_null_allowed>
<tracking_level>all</tracking_level>
</field>
<field id="extension_code" xsi:type="AttributeString">
<sql>extension_code</sql>
<is_null_allowed>false</is_null_allowed>
<tracking_level>all</tracking_level>
</field>
<field id="class_name" xsi:type="AttributeText">
<sql>class_name</sql>
<default_value>none</default_value>
<is_null_allowed>true</is_null_allowed>
<tracking_level>all</tracking_level>
</field>
<field id="count" xsi:type="AttributeInteger">
<sql>count</sql>
<default_value>0</default_value>
<tracking_level>all</tracking_level>
</field>
</fields>
<presentation>
<list>
<items>
<item id="rule_name">
<rank>10</rank>
</item>
<item id="extension_code">
<rank>20</rank>
</item>
<item id="class_name">
<rank>30</rank>
</item>
</items>
</list>
<search>
<item id="rule_name">
<rank>10</rank>
</item>
<item id="extension_code">
<rank>20</rank>
</item>
<item id="class_name">
<rank>30</rank>
</item>
</search>
<details>
<item id="rule_name">
<rank>10</rank>
</item>
<item id="extension_code">
<rank>20</rank>
</item>
<item id="class_name">
<rank>30</rank>
</item>
</details>
</presentation>
<methods/>
</class>
</classes>
<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&amp;exec_page=index.php&amp;c[menu]=DataFeatureRemovalMenu</url>
<enable_admin_only>1</enable_admin_only>
</menu>
</menus>
</itop_design>

View File

@@ -0,0 +1,41 @@
<?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' => 'This utilitary allows you to 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' => 'You will need to 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:Table:Analysis:ClassName' => 'Element to remove',
'DataFeatureRemoval:Table:Analysis:RemovalType' => 'Type of element',
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Feature name',
'DataFeatureRemoval:Table:Analysis:Occurence' => 'Occurence',
'UI:Button:Analyze' => 'Analyze',
'UI:Button:ModifyChoices' => 'Modify Choices',
'UI:Button:AnalyzeAndSetup' => 'Analyze and go to setup',
'UI:Action:ForceUninstall' => 'Force uninstall',
'UI:Action:MoreInfo' => 'More information',
'DataFeatureRemoval:Table:Analysis:RemovalType:FINAL_CLASS' => 'Final class',
]);

View File

@@ -0,0 +1,40 @@
<?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' => 'This utilitary allows you to 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' => 'You will need to 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:Table:Analysis:ClassName' => 'Element to remove',
'DataFeatureRemoval:Table:Analysis:RemovalType' => 'Type of element',
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Feature name',
'DataFeatureRemoval:Table:Analysis:Occurence' => 'Occurence',
'UI:Button:Analyze' => 'Analyze',
'UI:Button:ModifyChoices' => 'Modify Choices',
'UI:Button:AnalyzeAndSetup' => 'Analyze and go to setup',
'UI:Action:ForceUninstall' => 'Force uninstall',
'UI:Action:MoreInfo' => 'More information',
'DataFeatureRemoval:Table:Analysis:RemovalType:FINAL_CLASS' => 'Final class',
]);

View File

@@ -0,0 +1,20 @@
<?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();

View File

@@ -0,0 +1,16 @@
<?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

View File

@@ -0,0 +1,54 @@
<?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
],
]
);

View File

@@ -0,0 +1,189 @@
<?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\AuthentToken\Helper\TokenAuthLog;
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
use Combodo\iTop\DataFeatureRemoval\Model\DataFeatureRemoverAuditRuleService;
use Combodo\iTop\DataFeatureRemoval\Model\DataFeatureRemoverExtensionService;
use Combodo\iTop\Setup\FeatureRemoval\DryRemovalRuntimeEnvironment;
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
use Dict;
use Exception;
use MetaModel;
use utils;
class DataFeatureRemovalController extends Controller
{
private array $aSelectedExtensionsForCheck = [];
public function OperationMain($sErrorMessage = null)
{
$aParams = [];
$this->AddLinkedStylesheet(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/css/DataFeatureRemoval.css');
$this->AddLinkedScript(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/js/DataFeatureRemoval.js');
$aParams['sTransactionId'] = utils::GetNewTransactionId();
$this->AddFeatureParams($aParams);
$this->AddAnalyzeParams($aParams);
$aParams['DataFeatureRemovalErrorMessage'] = $sErrorMessage;
$this->DisplayPage($aParams);
}
public function AddFeatureParams(array &$aParams)
{
$aParams['aExtensions'] = $this->GetExtensionsTable();
$aParams['sModule'] = DataFeatureRemovalHelper::MODULE_NAME;
}
public function AddAnalyzeParams(array &$aParams)
{
$iTotalCount = 0;
$aData = [];
$aColumns = [];
foreach (DataFeatureRemoverAuditRuleService::GetInstance()->ReadCheckRules() as $oRule) {
$sContent = $oRule->Get('class_name');
$sModuleName = MetaModel::GetModuleName($sContent);
$aExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetIncludingExtensions($sModuleName);
$sExtensions = implode(' ', $aExtensions);
$sTypeName = $oRule->Get('rule_name');
$sTypeDesc = \Dict::S("DataFeatureRemoval:Table:Analysis:RemovalType:$sTypeName");
$iCount = $oRule->Get('count');
$iTotalCount += $iCount;
$aColumns = ['ClassName', 'RemovalType','FeatureName','Occurence'];
$aData[] = [
<<<HTML
<label>$sContent</label>
HTML,
<<<HTML
<label>$sTypeDesc</label>
HTML,
<<<HTML
<label title="$sModuleName">$sExtensions</label>
HTML,
<<<HTML
<label>$iCount</label>
HTML,
];
}
$aParams['aCheckRules'] = $this->GetTableData('Analysis', $aColumns, $aData);
$aParams['rule_count'] = $iTotalCount;
}
public function OperationAnalyze()
{
$aSelectedExtensionsFromUI = utils::ReadPostedParam('aExtensions', []);
$this->aSelectedExtensionsForCheck = [];
foreach ($aSelectedExtensionsFromUI as $sCode => $aData) {
$sValue = $aData['enable'] ?? 'off';
if (($sValue) === 'on') {
$this->aSelectedExtensionsForCheck[] = $sCode;
}
}
$this->m_sOperation = 'Main';
try {
$this->Analyze();
$this->OperationMain();
} catch (Exception $e) {
\IssueLog::Error(__METHOD__, null, ['stack' => $e->getTraceAsString(), 'exception' => $e->getMessage()]);
$this->OperationMain($e->getMessage());
}
}
private function GetExtensionsTable(): array
{
$aExtensions = [];
$aColumns = ['', 'Version', 'Name', 'Code'];
$this->aSelectedExtensionsForCheck = DataFeatureRemoverExtensionService::GetInstance()->ReadAuditedExtensions();
foreach (DataFeatureRemoverExtensionService::GetInstance()->ReadItopExtensions() as $sCode => $oExtension) {
/** @var \iTopExtension $oExtension */
$sChecked = "checked";
$sDisabledHtml = '';
if ($oExtension->bRemovedFromDisk) {
$sDisabledHtml = 'disabled=""';
} elseif (! array_key_exists($sCode, $this->aSelectedExtensionsForCheck)) {
$sChecked = "";
}
$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,
<<<HTML
<label>$sVersion</label>
HTML,
<<<HTML
<label for="$sIdEnable">$sLabel</label>
HTML,
<<<HTML
<label for="$sIdEnable">$sCode</label>
HTML,
];
}
return $this->GetTableData('Extensions', $aColumns, $aExtensions);
}
public function GetTableData(string $sTableName, array $aColumns, array $aData): array
{
if (empty($aData)) {
return [
'Type' => 'Table',
'Columns' => [['label' => '']],
'Data' => [[ Dict::S('DbCleaner:Table:Empty')]],
];
}
$aNewColumns = [];
foreach ($aColumns as $sColumn) {
$aNewColumns[] = ['label' => Dict::S("DataFeatureRemoval:Table:$sTableName:$sColumn", $sColumn)];
}
$aColumns = $aNewColumns;
return [
'Type' => 'Table',
'Columns' => $aColumns,
'Data' => $aData,
];
}
private function Analyze()
{
DataFeatureRemoverExtensionService::GetInstance()->SaveExtensions($this->aSelectedExtensionsForCheck);
$sSourceEnvt = \MetaModel::GetEnvironment();
$oDryRemovalRuntimeEnvironment = new DryRemovalRuntimeEnvironment();
$oDryRemovalRuntimeEnvironment->Prepare($sSourceEnvt, $this->aSelectedExtensionsForCheck);
$oDryRemovalRuntimeEnvironment->CompileFrom($sSourceEnvt);
$oSetupAudit = new SetupAudit($sSourceEnvt, DryRemovalRuntimeEnvironment::DRY_REMOVAL_AUDIT_ENV);
$this->Save($oSetupAudit->GetIssues());
}
private function Save(array $aGetRemovedClasses)
{
\IssueLog::Debug(__METHOD__, null, ['aGetRemovedClasses' => $aGetRemovedClasses]);
DataFeatureRemoverAuditRuleService::GetInstance()->SaveChecks($aGetRemovedClasses);
}
}

View File

@@ -0,0 +1,30 @@
<?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);
}
}

View File

@@ -0,0 +1,13 @@
<?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';
}

View File

@@ -0,0 +1,25 @@
<?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);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Combodo\iTop\DataFeatureRemoval\Model;
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
use DataFeatureRemoverAuditRule;
use DBObjectSearch;
use DBObjectSet;
use Exception;
class DataFeatureRemoverAuditRuleService
{
private static DataFeatureRemoverAuditRuleService $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): DataFeatureRemoverAuditRuleService
{
if (!isset(self::$oInstance)) {
self::$oInstance = new DataFeatureRemoverAuditRuleService();
}
return self::$oInstance;
}
final public static function SetInstance(?DataFeatureRemoverAuditRuleService $oInstance): void
{
self::$oInstance = $oInstance;
}
public function SaveChecks(array $aGetRemovedClasses)
{
$oSearch = DBObjectSearch::FromOQL('SELECT DataFeatureRemoverAuditRule', []);
$oSearch->AllowAllData();
$oSet = new DBObjectSet($oSearch);
while (null != ($oObj = $oSet->Fetch())) {
$oObj->DBDelete();
}
foreach ($aGetRemovedClasses as $sClass => $iCount) {
$oObj = new DataFeatureRemoverAuditRule();
$oObj->Set('rule_name', 'FINAL_CLASS');
$oObj->Set('extension_code', $sClass);
$oObj->Set('class_name', $sClass);
$oObj->Set('count', $iCount);
$oObj->DBWrite();
}
}
public function ReadCheckRules(): array
{
try {
$oSearch = DBObjectSearch::FromOQL('SELECT DataFeatureRemoverAuditRule', []);
$oSearch->AllowAllData();
$oSet = new DBObjectSet($oSearch);
$aRes = [];
while (null != ($oObj = $oSet->Fetch())) {
$aRes[] = $oObj;
}
return $aRes;
} catch (Exception $e) {
throw new DataFeatureRemovalException(__FUNCTION__.' failed', 0, $e);
}
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace Combodo\iTop\DataFeatureRemoval\Model;
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
use DataFeatureRemoverExtension;
use DBObjectSearch;
use DBObjectSet;
use Exception;
use iTopExtension;
use iTopExtensionsMap;
use MetaModel;
class DataFeatureRemoverExtensionService
{
private static DataFeatureRemoverExtensionService $oInstance;
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;
}
public function SaveExtensions(array $aSelectedExtensionsForCheck)
{
$this->ReadItopExtensions();
$oSearch = DBObjectSearch::FromOQL('SELECT DataFeatureRemoverExtension', []);
$oSearch->AllowAllData();
$oSet = new DBObjectSet($oSearch);
while (null != ($oObj = $oSet->Fetch())) {
$oObj->DBDelete();
}
foreach ($aSelectedExtensionsForCheck as $i => $sCode) {
$oObj = new DataFeatureRemoverExtension();
$oObj->Set('extension_code', $sCode);
/** @var iTopExtension $oExtension */
$oExtension = $this->aItopExtensions[$sCode];
$oObj->Set('module_names', json_encode($oExtension->aModules));
$oObj->Set('label', $oExtension->sLabel);
$oObj->Set('version', $oExtension->sVersion);
$oObj->DBWrite();
}
}
private array $aSelectedExtensions = [];
private array $aItopExtensions = [];
private array $aIncludingExtensionsByModuleName = [];
public function ReadAuditedExtensions(): array
{
if (count($this->aSelectedExtensions) == 0) {
try {
$oSearch = DBObjectSearch::FromOQL('SELECT DataFeatureRemoverExtension', []);
$oSearch->AllowAllData();
$oSet = new DBObjectSet($oSearch);
while (null != ($oObj = $oSet->Fetch())) {
$sCode = $oObj->Get('extension_code');
$sLabel = $oObj->Get('label');
$sVersion = $oObj->Get('version');
$sModuleNames = $oObj->Get('module_names');
$aModuleNames = json_decode($sModuleNames, true);
if (is_array($aModuleNames) && count($aModuleNames) > 0) {
foreach ($aModuleNames as $sModuleName) {
$aExtensions = $this->aIncludingExtensionsByModuleName[$sModuleName] ?? [];
$aExtensions[] = "$sLabel / $sVersion";
$this->aIncludingExtensionsByModuleName[$sModuleName] = $aExtensions;
}
}
$this->aSelectedExtensions[$sCode] = $oObj;
}
} catch (Exception $e) {
throw new DataFeatureRemovalException(__FUNCTION__.' failed', 0, $e);
}
}
\IssueLog::Debug(__METHOD__, null, ['aSelectedExtensionsForCheck' => $this->aSelectedExtensions]);
\IssueLog::Debug(__METHOD__, null, ['aIncludingExtensionsByModuleName' => $this->aIncludingExtensionsByModuleName]);
return $this->aSelectedExtensions;
}
public function GetIncludingExtensions(string $sModuleName): array
{
$this->ReadAuditedExtensions();
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;
}
}

View File

@@ -0,0 +1,8 @@
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% UIForm Standard {} %}
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Analysis:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:Analysis:SubTitle'|dict_format(rule_count) } %}
{% UIDataTable ForForm { sRef:'aCheckRules', aColumns:aCheckRules.Columns, aData:aCheckRules.Data} %}{% EndUIDataTable %}
{% EndUIPanel %}
{% EndUIForm %}

View File

@@ -0,0 +1,18 @@
{# @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 %}

View File

@@ -0,0 +1,28 @@
{# @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 Neutral { 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 'FeaturesTab.html.twig' %}
{% include 'ExtensionRemovalDataTab.html.twig' %}
{% EndUIPanel %}

View File

@@ -0,0 +1,9 @@
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
$(document).on('click', '#checkAllExtensions', function() {
var bChecked = this.checked;
$('.extension_check').each( function() { this.checked = bChecked });
});

View File

@@ -0,0 +1,2 @@
{# @copyright Copyright (C) 2010-2024 Combodo SAS #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}

View File

@@ -0,0 +1,25 @@
<?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;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit4f96a7199e2c0d90e547333758b26464::getLoader();

View File

@@ -0,0 +1,579 @@
<?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);
}
}

View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,17 @@
<?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\\Helper\\DataFeatureRemovalException' => $baseDir . '/src/Helper/DataFeatureRemovalException.php',
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => $baseDir . '/src/Helper/DataFeatureRemovalHelper.php',
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => $baseDir . '/src/Helper/DataFeatureRemovalLog.php',
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverAuditRuleService' => $baseDir . '/src/Model/DataFeatureRemoverAuditRuleService.php',
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverExtensionService' => $baseDir . '/src/Model/DataFeatureRemoverExtensionService.php',
'Combodo\\iTop\\DataFeatureRemoval\\Service\\SetupAudit' => $baseDir . '/src/Service/SetupAudit.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,11 @@
<?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'),
);

View File

@@ -0,0 +1,37 @@
<?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;
}
}

View File

@@ -0,0 +1,48 @@
<?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\\Helper\\DataFeatureRemovalException' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalException.php',
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalHelper.php',
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalLog.php',
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverAuditRuleService' => __DIR__ . '/../..' . '/src/Model/DataFeatureRemoverAuditRuleService.php',
'Combodo\\iTop\\DataFeatureRemoval\\Model\\DataFeatureRemoverExtensionService' => __DIR__ . '/../..' . '/src/Model/DataFeatureRemoverExtensionService.php',
'Combodo\\iTop\\DataFeatureRemoval\\Service\\SetupAudit' => __DIR__ . '/../..' . '/src/Service/SetupAudit.php',
'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);
}
}

View File

@@ -10,6 +10,7 @@ namespace Combodo\iTop\DBTools\Service;
use CMDBSource;
use DBObjectSearch;
use DBObjectSet;
use IssueLog;
class DBToolsUtils
{

View File

@@ -5,10 +5,12 @@
* @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
{
@@ -236,10 +238,14 @@ 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);

View File

@@ -67,15 +67,17 @@ 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);
/** @var \ormDocument $oDocument */
$oDocument = $oEventData->Get('document');
if ($oHostObj != null) {
/** @var \ormDocument $oDocument */
$oDocument = $oEventData->Get('document');
$this->OnAttachmentActivateTriggers(
$oHostObj,
$oAttachment,
$oDocument,
TriggerOnAttachmentDownload::class
);
$this->OnAttachmentActivateTriggers(
$oHostObj,
$oAttachment,
$oDocument,
TriggerOnAttachmentDownload::class
);
}
}
/**

View File

@@ -323,18 +323,38 @@
<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>
@@ -6897,14 +6917,29 @@
<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>

View File

@@ -92,7 +92,7 @@ final class CoreUpdater
$sFinalEnv = 'production';
$oRuntimeEnv = new RunTimeEnvironmentCoreUpdater($sFinalEnv, false);
$oRuntimeEnv->CheckDirectories($sFinalEnv);
$oRuntimeEnv->CompileFrom('production');
$oRuntimeEnv->CompileFrom($sFinalEnv);
$oRuntimeEnv->Rollback();
@@ -155,21 +155,13 @@ final class CoreUpdater
APPROOT.'extensions',
];
$aAvailableModules = $oRuntimeEnv->AnalyzeInstallation($oConfig, $aDirsToScanForModules);
$aSelectedModules = [];
foreach ($aAvailableModules as $sModuleId => $aModule) {
if (($sModuleId == ROOT_MODULE) || ($sModuleId == DATAMODEL_MODULE)) {
continue;
} else {
$aSelectedModules[] = $sModuleId;
}
}
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'BeforeDatabaseCreation');
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'BeforeDatabaseCreation');
$oRuntimeEnv->CreateDatabaseStructure($oConfig, 'upgrade');
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseCreation');
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseCreation');
$oRuntimeEnv->UpdatePredefinedObjects();
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDatabaseSetup');
$oRuntimeEnv->LoadData($aAvailableModules, $aSelectedModules, false /* no sample data*/);
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDataLoad');
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDatabaseSetup');
$oRuntimeEnv->LoadData($aAvailableModules, false /* no sample data*/);
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDataLoad');
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
$oExtensionsMap = new iTopExtensionsMap();
// Default choices = as before
@@ -187,7 +179,7 @@ final class CoreUpdater
$oRuntimeEnv->RecordInstallation(
$oConfig,
$sDataModelVersion,
$aSelectedModules,
array_keys($aAvailableModules),
$aSelectedExtensionCodes,
'Done by the iTop Core Updater'
);

View File

@@ -135,7 +135,7 @@ class RunTimeEnvironmentCoreUpdater extends RunTimeEnvironment
$aAvailableModules[$oModule->GetName()] = $oModule;
}
// TODO check the auto-selected modules here
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
if ($oExtension->bMarkedAsChosen) {
foreach ($oExtension->aModules as $sModuleName) {
if (!isset($aRet[$sModuleName]) && isset($aAvailableModules[$sModuleName])) {

View File

@@ -24,129 +24,13 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\WebPage\JsonPage;
use Combodo\iTop\HubConnector\Controller\HubController;
require_once(APPROOT.'application/utils.inc.php');
require_once(APPROOT.'core/log.class.inc.php');
IssueLog::Enable(APPROOT.'log/error.log');
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);
}
require_once(__DIR__.'/src/Controller/HubController.php');
try {
SetupUtils::ExitMaintenanceMode(false); // Reset maintenance mode in case of problem
@@ -183,7 +67,7 @@ try {
foreach ($aChecks as $oCheckResult) {
if ($oCheckResult->iSeverity == CheckResult::ERROR) {
$bFailed = true;
ReportError($oCheckResult->sLabel, -2);
HubController::GetInstance()->ReportError($oCheckResult->sLabel, -2);
}
}
if (!$bFailed) {
@@ -191,169 +75,27 @@ try {
$fFreeSpace = SetupUtils::CheckDiskSpace($sDBBackupPath);
if ($fFreeSpace !== false) {
$sMessage = Dict::Format('iTopHub:BackupFreeDiskSpaceIn', SetupUtils::HumanReadableSize($fFreeSpace), dirname($sDBBackupPath));
ReportSuccess($sMessage);
HubController::GetInstance()->ReportSuccess($sMessage);
} else {
ReportError(Dict::S('iTopHub:FailedToCheckFreeDiskSpace'), -1);
HubController::GetInstance()->ReportError(Dict::S('iTopHub:FailedToCheckFreeDiskSpace'), -1);
}
}
break;
case 'do_backup':
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());
}
HubController::GetInstance()->LaunchBackup();
break;
case 'compile':
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
HubController::GetInstance()->LaunchCompile();
break;
case 'move_to_production':
// 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();
}
HubController::GetInstance()->LaunchDeploy();
break;
default:
ReportError("Invalid operation: '$sOperation'", -1);
HubController::GetInstance()->ReportError("Invalid operation: '$sOperation'", -1);
}
} catch (Exception $e) {
SetupLog::Error(get_class($e).': '.Dict::S('iTopHub:ConfigurationSafelyReverted')."\n".$e->getMessage());
@@ -361,5 +103,5 @@ try {
utils::PopArchiveMode();
ReportError($e->getMessage(), $e->getCode());
HubController::GetInstance()->ReportError($e->getMessage(), $e->getCode());
}

View File

@@ -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', true, $aExtraDirs);
$oExtensionsMap = new iTopExtensionsMap('production', $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', true, $aExtraDirs);
$oExtensionsMap = new iTopExtensionsMap('production', $aExtraDirs);
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {

View File

@@ -186,9 +186,7 @@ function collect_configuration()
// iTop modules
$oConfig = MetaModel::GetConfig();
$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");
$aInstalledModules = ModuleInstallationRepository::GetInstance()->ReadFromDB($oConfig);
foreach ($aInstalledModules as $aDBInfo) {
$aConfiguration['itop_modules'][$aDBInfo['name']] = $aDBInfo['version'];

View File

@@ -0,0 +1,300 @@
<?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;
}
}

View File

@@ -0,0 +1,37 @@
<?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;
}
}

View File

@@ -1,72 +1,83 @@
<?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 (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 (!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);
}
}

View File

@@ -17,6 +17,7 @@ SetupWebPage::AddModule(
//
'dependencies' => [
'itop-welcome-itil/3.1.0,',
'itop-profiles-itil/3.1.0', //SuperUser id 117
],
'mandatory' => false,
'visible' => true,

View File

@@ -28,6 +28,7 @@ SetupWebPage::AddModule(
'category' => 'Portal',
// Setup
'dependencies' => [
'itop-attachments/3.2.1', //CMDBChangeOpAttachmentRemoved
],
'mandatory' => true,
'visible' => false,

View File

@@ -76,6 +76,9 @@
<attribute id="finalclass"/>
</attributes>
</reconciliation>
<obsolescence>
<condition><![CDATA[status='obsolete']]></condition>
</obsolescence>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
@@ -176,17 +179,32 @@
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="production">
<code>production</code>
<rank>20</rank>
</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="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="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>
@@ -1133,6 +1151,9 @@ 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">
@@ -1184,17 +1205,32 @@ public function PrefillSearchForm(&$aContextParam)
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="production">
<code>production</code>
<rank>20</rank>
</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="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="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>
@@ -1537,6 +1573,9 @@ 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">
@@ -1585,17 +1624,32 @@ public function PrefillSearchForm(&$aContextParam)
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="production">
<code>production</code>
<rank>20</rank>
</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="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="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>

View File

@@ -76,6 +76,9 @@
<attribute id="finalclass"/>
</attributes>
</reconciliation>
<obsolescence>
<condition><![CDATA[status='obsolete']]></condition>
</obsolescence>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
@@ -176,17 +179,32 @@
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="production">
<code>production</code>
<rank>20</rank>
</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="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="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>
@@ -1122,6 +1140,9 @@ 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">
@@ -1173,17 +1194,32 @@ public function PrefillSearchForm(&$aContextParam)
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="production">
<code>production</code>
<rank>20</rank>
</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="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="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>
@@ -1548,6 +1584,9 @@ 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">
@@ -1596,17 +1635,32 @@ public function PrefillSearchForm(&$aContextParam)
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="production">
<code>production</code>
<rank>20</rank>
</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="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="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>

View File

@@ -1486,14 +1486,29 @@
<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>

View File

@@ -12,7 +12,7 @@ SetupWebPage::AddModule(
// Setup
//
'dependencies' => [
'itop-structure/2.7.1',
'itop-structure/2.7.1 || itop-portal/3.0.0', // itop-portal : module_design_itop_design->module_designs->itop-portal
],
'mandatory' => false,
'visible' => true,

View File

@@ -43,18 +43,38 @@
<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>

View File

@@ -40,6 +40,8 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
'Class:AuditRule/Attribute:name+' => 'Krátký název pro toto pravidlo',
'Class:AuditRule/Attribute:description' => 'Popis pravidla',
'Class:AuditRule/Attribute:description+' => 'Dlouhý popis tohoto pravidla auditu',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Dotaz ke spuštění',
'Class:AuditRule/Attribute:query+' => 'OQL výraz ke spuštění',
'Class:AuditRule/Attribute:valid_flag' => 'Interpretace',
@@ -52,7 +54,9 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
'Class:AuditRule/Attribute:category_id+' => 'Kategorie pro toto pravidlo',
'Class:AuditRule/Attribute:category_name' => 'Kategorie',
'Class:AuditRule/Attribute:category_name+' => 'Název kategorie pro toto pravidlo',
]);
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
// Class: AuditDomain
@@ -164,9 +168,11 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
'Class:User/Attribute:status/Value:disabled' => 'Neaktivní',
'Class:User/Error:LoginMustBeUnique' => 'Uživatelské jméno musí být jedinečné - "%1s" je již použito.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Uživateli musí být přidělen alespoň jeden profil.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Profil "%1$s" nemůže být přidán, byl by mu odepřen přístup do backoffice',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Změna není povolena pro vašeho vlastního uživatele',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Přístupné organizace musí obsahovat organizaci uživatele.',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Aktuální seznam profilů neposkytuje dostatečná přístupová práva (uživatele již nelze upravovat)',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Profil Portal power user neposkytuje dostatečná přístupová práva (je třeba přidat jiný profil)',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Uživatel musí být přiřazen minimálně do jedné organizace.',

View File

@@ -40,6 +40,8 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'Class:AuditRule/Attribute:name+' => 'Kort navn for denne regel',
'Class:AuditRule/Attribute:description' => 'Audit-regel beskrivelse',
'Class:AuditRule/Attribute:description+' => 'Udførlig beskrivelse af denne Audit-regel',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Søgning at udføre',
'Class:AuditRule/Attribute:query+' => 'Den OQL forespørgsel, der skal udføres',
'Class:AuditRule/Attribute:valid_flag' => 'Gyldige objekter?',
@@ -52,6 +54,8 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'Class:AuditRule/Attribute:category_id+' => 'Kategori for denne regel',
'Class:AuditRule/Attribute:category_name' => 'Kategori',
'Class:AuditRule/Attribute:category_name+' => 'Kategorinavn for denne regel',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -164,10 +168,12 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'Class:User/Attribute:status/Value:disabled' => 'Disabled~~',
'Class:User/Error:LoginMustBeUnique' => 'Login skal være entydig - "%1s" er allerede i brug.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Mindst en profil skal knyttes til denne bruger.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users~~',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.~~',
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.~~',

View File

@@ -40,6 +40,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:AuditRule/Attribute:name+' => 'Kurzname für diese Regel',
'Class:AuditRule/Attribute:description' => 'Beschreibung der Audit-Regel',
'Class:AuditRule/Attribute:description+' => 'Ausführliche Beschreibung dieser Audit-Regel',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Durchzuführende Abfrage',
'Class:AuditRule/Attribute:query+' => 'Die auszuführende OQL-Abfrage',
'Class:AuditRule/Attribute:valid_flag' => 'Gültiges Objekt?',
@@ -52,6 +54,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:AuditRule/Attribute:category_id+' => 'Kategorie für diese Regel',
'Class:AuditRule/Attribute:category_name' => 'Kategorie',
'Class:AuditRule/Attribute:category_name+' => 'Kategoriename für diese Regel',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -163,9 +167,11 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:User/Attribute:status/Value:disabled' => 'Inaktiv',
'Class:User/Error:LoginMustBeUnique' => 'Login-Namen müssen unterschiedlich sein - "%1s" benutzt diesen Login-Name bereits.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Mindestens ein Profil muss diesem Benutzer zugewiesen sein.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Profil "%1$s" kann nicht hinzugefügt werde, es verhindert den Zugriff auf das Backoffice.',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Statusänderungen sind für den eigenen Benutzer nicht erlaubt.',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Die Organisation des Benutzers muss in den erlaubten Organisationen enthalten sein.',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Die aktuelle Liste an Profilen vergibt unzureichende Berechtigungen (Benutzer können nicht mehr geändert werden)',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Das Profil des Portal-Power-Benutzers hat nicht ausreichend Zugriffsrechte (ein weiteres Profil muss hinzugefügt werden)',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Mindestens eine Organisation muss diesem Benutzer zugewiesen sein.',

View File

@@ -50,7 +50,9 @@ It is applied on the scope of objects defined by the audit category',
'Class:AuditRule/Attribute:name' => 'Rule name',
'Class:AuditRule/Attribute:name+' => 'Short name for this rule',
'Class:AuditRule/Attribute:description' => 'Description',
'Class:AuditRule/Attribute:description+' => 'What is checked? How should it be fixed? Who should do it? ...',
'Class:AuditRule/Attribute:description+' => 'What is checked?',
'Class:AuditRule/Attribute:process' => 'Correction process',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...',
'Class:AuditRule/Attribute:query' => 'Query to run',
'Class:AuditRule/Attribute:query+' => 'The OQL expression to run. Returned classes must be aligned with those of the category\'s scope',
'Class:AuditRule/Attribute:valid_flag' => 'Returned objects: ',
@@ -65,6 +67,8 @@ It is applied on the scope of objects defined by the audit category',
'Class:AuditRule/Attribute:category_id+' => 'The category of this rule',
'Class:AuditRule/Attribute:category_name' => 'Category name',
'Class:AuditRule/Attribute:category_name+' => 'Name of the category of this rule',
'Class:AuditRule/Attribute:contact_id' => 'Owner',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule',
]);
//
@@ -173,15 +177,17 @@ Dict::Add('EN US', 'English', 'English', [
'Class:User/Attribute:allowed_org_list+' => 'The end user is allowed to see data belonging to the following organizations. If no organization is specified, there is no restriction.',
'Class:User/Attribute:status' => 'Status',
'Class:User/Attribute:status+' => 'Whether the user account is enabled or disabled.',
'Class:User/Attribute:status/Value:enabled' => 'Enabled',
'Class:User/Attribute:status/Value:enabled' => 'Enabled',
'Class:User/Attribute:status/Value:disabled' => 'Disabled',
'Class:User/Error:LoginMustBeUnique' => 'Login must be unique - "%1$s" is already being used.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'At least one profile must be assigned to this user.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)',
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.',
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.',

View File

@@ -50,7 +50,9 @@ It is applied on the scope of objects defined by the audit category',
'Class:AuditRule/Attribute:name' => 'Rule name',
'Class:AuditRule/Attribute:name+' => 'Short name for this rule',
'Class:AuditRule/Attribute:description' => 'Description',
'Class:AuditRule/Attribute:description+' => 'What is checked? How should it be fixed? Who should do it? ...',
'Class:AuditRule/Attribute:description+' => 'What is checked?~~',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Query to run',
'Class:AuditRule/Attribute:query+' => 'The OQL expression to run. Returned classes must be aligned with those of the category\'s scope',
'Class:AuditRule/Attribute:valid_flag' => 'Returned objects: ',
@@ -65,6 +67,8 @@ It is applied on the scope of objects defined by the audit category',
'Class:AuditRule/Attribute:category_id+' => 'The category of this rule',
'Class:AuditRule/Attribute:category_name' => 'Category name',
'Class:AuditRule/Attribute:category_name+' => 'Name of the category of this rule',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -178,9 +182,11 @@ Dict::Add('EN GB', 'British English', 'British English', [
'Class:User/Error:LoginMustBeUnique' => 'Login must be unique - "%1$s" is already being used.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'At least one profile must be assigned to this user.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added as it will deny access to the back office.',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organisations must contain User organisation',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable any more)',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organisation must be assigned to this user.',

View File

@@ -38,6 +38,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'Class:AuditRule/Attribute:name+' => 'Nombre corto para esta regla',
'Class:AuditRule/Attribute:description' => 'Descripción de regla de auditoría',
'Class:AuditRule/Attribute:description+' => 'Descripción larga para esta regla de auditoría',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Consulta a Ejecutar',
'Class:AuditRule/Attribute:query+' => 'Expresión OQL a ejecutar',
'Class:AuditRule/Attribute:valid_flag' => '¿Objetos Válidos?',
@@ -50,6 +52,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'Class:AuditRule/Attribute:category_id+' => 'La categoría para esta regla',
'Class:AuditRule/Attribute:category_name' => 'Categoría',
'Class:AuditRule/Attribute:category_name+' => 'Nombre de la categoría para esta regla',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -162,9 +166,11 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'Class:User/Attribute:status/Value:disabled' => 'Deshabilitado',
'Class:User/Error:LoginMustBeUnique' => 'Usuario debe ser único - "%1s" ya se encuentra en uso.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Al menos un Perfil debe ser asignado a este usuario.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'No se puede agregar el perfil "%1$s"; denegará el acceso al backoffice',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Cambiar estatus no está permitido para su propio usuario',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Las organizaciones permitidas deben contener una organización de usuario',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'La lista actual de perfiles no otorga suficientes permisos de acceso (los usuarios ya no son modificables)',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'El perfil de usuario avanzado del Portal no otorga suficientes derechos de acceso (se debe agregar otro perfil)',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Al menos una organización debe ser asignada a este usuario.',

View File

@@ -41,7 +41,9 @@ Elle s\'applique à tous les objets dans le périmètre de sa catégorie d\'audi
'Class:AuditRule/Attribute:name' => 'Nom',
'Class:AuditRule/Attribute:name+' => 'Une vérification particulière',
'Class:AuditRule/Attribute:description' => 'Description',
'Class:AuditRule/Attribute:description+' => 'Qu\'est ce qu\'on vérifie ? Comment le corriger ? Qui doit le faire ? ...',
'Class:AuditRule/Attribute:description+' => 'Qu\'est ce qu\'on vérifie ?',
'Class:AuditRule/Attribute:process' => 'Processus de correction',
'Class:AuditRule/Attribute:process+' => 'Comment le corriger ? Qui doit le faire ? ...',
'Class:AuditRule/Attribute:query' => 'Requête',
'Class:AuditRule/Attribute:query+' => 'Requête OQL à executer. Les classes retournées doivent être cohérentes avec celles définies dans le périmètre de la catégorie',
'Class:AuditRule/Attribute:valid_flag' => 'Objets retournés :',
@@ -56,6 +58,8 @@ Elle s\'applique à tous les objets dans le périmètre de sa catégorie d\'audi
'Class:AuditRule/Attribute:category_id+' => '',
'Class:AuditRule/Attribute:category_name' => 'Nom de la catégorie',
'Class:AuditRule/Attribute:category_name+' => '',
'Class:AuditRule/Attribute:contact_id' => 'Responsable',
'Class:AuditRule/Attribute:contact_id+' => 'Personne ou équipe responsable de la correction des erreurs détectées par cette règle',
]);
//
@@ -164,15 +168,17 @@ Dict::Add('FR FR', 'French', 'Français', [
'Class:User/Attribute:allowed_org_list' => 'Organisations permises',
'Class:User/Attribute:allowed_org_list+' => 'L\'utilisateur a le droit de voir les données des organisations listées ici. Si aucune organisation n\'est spécifiée, alors aucune restriction ne s\'applique.',
'Class:User/Attribute:status' => 'Etat',
'Class:User/Attribute:status+' => 'Est-ce que ce compte utilisateur est actif, ou non?',
'Class:User/Attribute:status+' => 'Est-ce que ce compte utilisateur est actif, ou non ?',
'Class:User/Attribute:status/Value:enabled' => 'Actif',
'Class:User/Attribute:status/Value:disabled' => 'Désactivé',
'Class:User/Error:LoginMustBeUnique' => 'Le login doit être unique - "%1s" est déjà utilisé.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'L\'utilisateur doit avoir au moins un profil.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Le profil "%1$s" ne peut pas être donné aux Administrateurs, SuperUsers et REST Services Users',
'Class:User/Error:ProfileNotAllowed' => 'Le profil "%1$s" ne peux pas être ajouté à son propre utilisateur, il interdit l\'accès à la console',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Impossible de changer l\'état de son propre utilisateur',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Les organisations permises doivent contenir l\'organisation de l\'utilisateur',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Les profils existants ne permettent pas de modifier les utilisateurs',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'Vous ne pouvez pas supprimer votre propre profil Administrateur. Demandez à un autre Administrateur de le faire pour vous',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Vous ne pouvez pas supprimer vos propres droits de modification des utilisateurs.',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Le profil Portal power user ne donne pas suffisamment de droits à l\'utilisateur (un autre profil doit être ajouté)',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'L\'utilisateur doit avoir au moins une organisation.',
'Class:User/Error:OrganizationNotAllowed' => 'Organisation non autorisée.',

View File

@@ -40,6 +40,8 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'Class:AuditRule/Attribute:name+' => '',
'Class:AuditRule/Attribute:description' => 'Leírás',
'Class:AuditRule/Attribute:description+' => '',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Lekérdezés',
'Class:AuditRule/Attribute:query+' => '',
'Class:AuditRule/Attribute:valid_flag' => 'Érvényes objektum?',
@@ -52,6 +54,8 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'Class:AuditRule/Attribute:category_id+' => '',
'Class:AuditRule/Attribute:category_name' => 'Kategórianév',
'Class:AuditRule/Attribute:category_name+' => '',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -164,9 +168,11 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'Class:User/Attribute:status/Value:disabled' => 'Letiltott',
'Class:User/Error:LoginMustBeUnique' => 'A felhasználónévnek egyedinek kell lennie - "%1s" már létezik.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Legalább egy profilt a felhasználóhoz kell rendelni.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'A "%1$s" profil nem adható hozzá, le lesz tiltva',
'Class:User/Error:StatusChangeIsNotAllowed' => 'A saját felhasználó státuszának cseréje nem engedélyezett',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Az engedélyezett szervezeteknek tartalmazniuk kell a felhasználói szervezetet',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'A profilok jelenlegi listája nem ad elegendő hozzáférési jogot (a felhasználók már nem módosíthatók)',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'A felhasználóhoz legalább egy szervezeti egységet hozzá kell rendelni',

View File

@@ -40,6 +40,8 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
'Class:AuditRule/Attribute:name+' => '',
'Class:AuditRule/Attribute:description' => 'Descrizione della regola di Audit',
'Class:AuditRule/Attribute:description+' => 'Descrizione dettagliata per questa regola di audit ',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Query da eseguire',
'Class:AuditRule/Attribute:query+' => 'Espressio OQL da eseguire',
'Class:AuditRule/Attribute:valid_flag' => 'Oggetti validi?',
@@ -52,6 +54,8 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
'Class:AuditRule/Attribute:category_id+' => 'Categoria per questa regola',
'Class:AuditRule/Attribute:category_name' => 'Categoria',
'Class:AuditRule/Attribute:category_name+' => 'Nome della categoria per questa regola',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -164,9 +168,11 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
'Class:User/Attribute:status/Value:disabled' => 'Disabilitato',
'Class:User/Error:LoginMustBeUnique' => 'Il Login deve essere unico - "%1s" già in uso',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'È necessario almeno un profilo assegnato all\'utente.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Il profilo "%1$s" non può essere aggiunto poiché nega l\'accesso al back office.',
'Class:User/Error:StatusChangeIsNotAllowed' => 'La modifica dello stato non è consentita per il proprio utente.',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Le organizzazioni consentite devono includere l\'organizzazione dell\'utente.',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'L\'elenco attuale dei profili non conferisce diritti di accesso sufficienti (gli utenti non sono più modificabili).',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Il profilo utente con poteri del portale non concede diritti di accesso sufficienti (deve essere aggiunto un altro profilo)',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'È necessario assegnare almeno un\'organizzazione a questo utente.',

View File

@@ -40,6 +40,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'Class:AuditRule/Attribute:name+' => 'ルールの短縮名',
'Class:AuditRule/Attribute:description' => '監査ルール説明',
'Class:AuditRule/Attribute:description+' => 'この監査ルールの長い説明',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => '実行するクエリ',
'Class:AuditRule/Attribute:query+' => '実行するOQL式',
'Class:AuditRule/Attribute:valid_flag' => '有効なオブジェクト',
@@ -52,6 +54,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'Class:AuditRule/Attribute:category_id+' => 'このルールのカテゴリ',
'Class:AuditRule/Attribute:category_name' => 'カテゴリ',
'Class:AuditRule/Attribute:category_name+' => 'このルールのカテゴリ名',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -164,10 +168,12 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'Class:User/Attribute:status/Value:disabled' => 'Disabled~~',
'Class:User/Error:LoginMustBeUnique' => 'ログイン名は一意でないといけません。- "%1s" はすでに使われています。',
'Class:User/Error:AtLeastOneProfileIsNeeded' => '少なくとも1件のプロフィールがこのユーザに指定されなければなりません。',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users~~',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.~~',
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.~~',

View File

@@ -40,6 +40,8 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'Class:AuditRule/Attribute:name+' => 'Naam van de regel',
'Class:AuditRule/Attribute:description' => 'Beschrijving',
'Class:AuditRule/Attribute:description+' => 'Uitgebreide beschrijving van deze Auditregel',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Query om uit te voeren',
'Class:AuditRule/Attribute:query+' => 'De OQL-expressie voor het uitvoeren',
'Class:AuditRule/Attribute:valid_flag' => 'Geldige objecten?',
@@ -52,6 +54,8 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'Class:AuditRule/Attribute:category_id+' => 'De categorie voor deze regel',
'Class:AuditRule/Attribute:category_name' => 'Categorie',
'Class:AuditRule/Attribute:category_name+' => 'Naam van de categorie voor deze regel',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -164,9 +168,11 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'Class:User/Attribute:status/Value:disabled' => 'Uitgeschakeld',
'Class:User/Error:LoginMustBeUnique' => 'Login moet uniek zijn - "%1s" is al in gebruik',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Minstens één profiel moet toegewezen zijn aan deze gebruiker',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Profiel "%1$s" kan niet toegevoegd worden omdat het de toegang tot de backoffice zou ontzeggen.',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Je kan de status voor je eigen gebruikersaccount niet wijzigen.',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'De toegestande organisaties moeten minstens de organisatie bevatten waartoe de gebruikersaccount behoort.',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'De huidige lijst van profielen heeft niet voldoende toegangsrechten (gebruikersaccount zijn niet meer wijzigbaar).',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Minstens één organisatie moet toegewezen zijn aan deze gebruiker',

View File

@@ -40,6 +40,8 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
'Class:AuditRule/Attribute:name+' => 'Krótka nazwa reguły',
'Class:AuditRule/Attribute:description' => 'Opis reguły audytu',
'Class:AuditRule/Attribute:description+' => 'Długi opis reguły inspekcji',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Zapytanie do wykonania',
'Class:AuditRule/Attribute:query+' => 'Wyrażenie OQL do wykonania',
'Class:AuditRule/Attribute:valid_flag' => 'Prawidłowe obiekty?',
@@ -52,6 +54,8 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
'Class:AuditRule/Attribute:category_id+' => 'Kategoria dla reguły',
'Class:AuditRule/Attribute:category_name' => 'Kategoria',
'Class:AuditRule/Attribute:category_name+' => 'Nazwa kategorii dla reguły',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -164,9 +168,11 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
'Class:User/Attribute:status/Value:disabled' => 'Wyłączone',
'Class:User/Error:LoginMustBeUnique' => 'Login musi być unikatowy - "%1s" jest już używany.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Do użytkownika musi być przypisany co najmniej jeden profil.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Nie można dodać profilu "%1$s" nie ma on dostępu do zaplecza',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Zmiana statusu nie jest dozwolona dla własnego użytkownika',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Dozwolone organizacje muszą zawierać organizację użytkownika',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Aktualna lista profili nie daje wystarczających praw dostępu (Użytkowników nie można już modyfikować)',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Profil użytkownika zaawansowanego Portalu nie zapewnia wystarczających praw dostępu (należy dodać kolejny profil)',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Do użytkownika musi być przypisana co najmniej jedna organizacja.',

View File

@@ -40,6 +40,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
'Class:AuditRule/Attribute:name+' => 'Nome curto para esta regra',
'Class:AuditRule/Attribute:description' => 'Descrição',
'Class:AuditRule/Attribute:description+' => 'Descrição longa para essa regra',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Executar consulta',
'Class:AuditRule/Attribute:query+' => 'Executar a expressão OQL',
'Class:AuditRule/Attribute:valid_flag' => 'Objetos válidos?',
@@ -52,6 +54,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
'Class:AuditRule/Attribute:category_id+' => 'A categoria para esta regra',
'Class:AuditRule/Attribute:category_name' => 'Categoria',
'Class:AuditRule/Attribute:category_name+' => 'Nome da categoria para essa regra',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -164,9 +168,11 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
'Class:User/Attribute:status/Value:disabled' => 'Inativa',
'Class:User/Error:LoginMustBeUnique' => 'Login deve ser único - "%1s" já existe',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Pelo menos um perfil deve ser atribuído a esse usuário',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'O perfil "%1$s" não pôde ser adicionado, ele negará o acesso ao backoffice',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Alterar o status da conta não é permitido para o seu próprio usuário',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'As organizações permitidas devem conter apenas usuários pertencentes a organização',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'A lista atual de perfis não fornece permissões de acesso suficientes (os usuários não são mais modificáveis)',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Pelo menos uma organização deve ser atribuída a esse usuário',

View File

@@ -41,6 +41,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:AuditRule/Attribute:name+' => 'Краткое название этого правила',
'Class:AuditRule/Attribute:description' => 'Описание правила аудита',
'Class:AuditRule/Attribute:description+' => 'Полное описание этого правила аудита',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Запрос для выполнения',
'Class:AuditRule/Attribute:query+' => 'OQL выражение, выполняющее проверку набора объектов категории аудита',
'Class:AuditRule/Attribute:valid_flag' => 'Валидные объекты?',
@@ -53,6 +55,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:AuditRule/Attribute:category_id+' => 'Категория для этого правила',
'Class:AuditRule/Attribute:category_name' => 'Категория',
'Class:AuditRule/Attribute:category_name+' => 'Категория для этого правила',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -165,10 +169,12 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:User/Attribute:status/Value:disabled' => 'Отключен',
'Class:User/Error:LoginMustBeUnique' => 'Логин должен быть уникальным - "%1s" уже используется.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Как минимум один профиль должен быть назначен данному пользователю.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users~~',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Этому пользователю должна быть назначена хотя бы одна организация.',
'Class:User/Error:OrganizationNotAllowed' => 'Организация не разрешена.',

View File

@@ -41,7 +41,9 @@ It is applied on the scope of objects defined by the audit category~~',
'Class:AuditRule/Attribute:name' => 'Názov pravidla',
'Class:AuditRule/Attribute:name+' => 'Short name for this rule~~',
'Class:AuditRule/Attribute:description' => 'Popis pravidla auditu',
'Class:AuditRule/Attribute:description+' => 'What is checked? How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:description+' => 'What is checked?~~',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Spustenie dopytu',
'Class:AuditRule/Attribute:query+' => 'The OQL expression to run. Returned classes must be aligned with those of the category\'s scope~~',
'Class:AuditRule/Attribute:valid_flag' => 'Platný objekt?',
@@ -56,6 +58,8 @@ It is applied on the scope of objects defined by the audit category~~',
'Class:AuditRule/Attribute:category_id+' => 'The category of this rule~~',
'Class:AuditRule/Attribute:category_name' => 'Kategória',
'Class:AuditRule/Attribute:category_name+' => 'Name of the category of this rule~~',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -168,10 +172,12 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'Class:User/Attribute:status/Value:disabled' => 'Disabled~~',
'Class:User/Error:LoginMustBeUnique' => 'Prihlasovacie meno musí byť jedinečné - "%1s" sa už používa.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Aspoň jeden profil musí byť priradený k profilu.',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users~~',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.~~',
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.~~',

View File

@@ -40,6 +40,8 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'Class:AuditRule/Attribute:name+' => 'Kural Adı',
'Class:AuditRule/Attribute:description' => 'Kural tanımlaması',
'Class:AuditRule/Attribute:description+' => 'Kural tanımlaması',
'Class:AuditRule/Attribute:process' => 'Correction process~~',
'Class:AuditRule/Attribute:process+' => 'How should it be fixed? Who should do it? ...~~',
'Class:AuditRule/Attribute:query' => 'Çalıştırılacak Sorgu',
'Class:AuditRule/Attribute:query+' => 'Çalıştırılcak OQL ifadesi',
'Class:AuditRule/Attribute:valid_flag' => 'Geçerli nesneler?',
@@ -52,6 +54,8 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'Class:AuditRule/Attribute:category_id+' => 'Kuralın kategorisi',
'Class:AuditRule/Attribute:category_name' => 'Kategori',
'Class:AuditRule/Attribute:category_name+' => 'Kural için kategori adı',
'Class:AuditRule/Attribute:contact_id' => 'Owner~~',
'Class:AuditRule/Attribute:contact_id+' => 'Team or person in charge of fixing the errors detected by this rule~~',
]);
//
@@ -164,10 +168,12 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'Class:User/Attribute:status/Value:disabled' => 'Disabled~~',
'Class:User/Error:LoginMustBeUnique' => 'Kullanıcı adı tekil olmalı - "%1s" mevcut bir kullanıcıya ait.',
'Class:User/Error:AtLeastOneProfileIsNeeded' => 'En az bir profil kullanıcıya atanmalı',
'Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice' => 'Profile "%1$s" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)~~',
'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~',
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~',
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~',
'Class:User/Error:AdminProfileCannotBeRemovedBySelf' => 'You cannot remove your own Administrator profile. Ask another Administrator to do it for you~~',
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'You cannot remove your own rights to edit Users~~',
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)~~',
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.~~',
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.~~',

View File

@@ -0,0 +1,23 @@
<?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~~',
]);

View File

@@ -0,0 +1,23 @@
<?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~~',
]);

View File

@@ -0,0 +1,23 @@
<?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~~',
]);

View File

@@ -0,0 +1,21 @@
<?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',
]);

View File

@@ -0,0 +1,23 @@
<?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~~',
]);

View File

@@ -0,0 +1,23 @@
<?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',
]);

View File

@@ -0,0 +1,23 @@
<?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~~',
]);

View File

@@ -0,0 +1,23 @@
<?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~~',
]);

View File

@@ -0,0 +1,23 @@
<?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~~',
]);

View File

@@ -0,0 +1,23 @@
<?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~~',
]);

View File

@@ -0,0 +1,23 @@
<?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~~',
]);

View File

@@ -0,0 +1,23 @@
<?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~~',
]);

Some files were not shown because too many files have changed in this diff Show More