Compare commits

..

166 Commits

Author SHA1 Message Date
Eric Espie
8a95f6c605 Faf Service Locator 2026-09-04 15:51:51 +02:00
jf-cbd
5d99042d4c 📝 Update enhancement.yml and pull_request_template.md 2026-09-04 10:25:01 +02:00
Stephen Abello
8ff57dabd0 Dump setup.css 2026-09-04 10:24:33 +02:00
Stephen Abello
b9a7923a40 Merge branch 'support/3.2' into develop 2026-09-04 10:22:40 +02:00
Stephen Abello
6c1206658e N°9916 - Fix menu notification indicator position on Firefox 2026-09-04 10:22:19 +02:00
odain-cbd
1a395f4b50 N°10006 - fix audit display in case of no fields provided (#1039) 2026-09-04 10:07:05 +02:00
Benjamin DALSASS
0ae3bc2821 RC 3.3 - Update libraries licences 2026-09-04 09:37:12 +02:00
Benjamin DALSASS
98a007ff9c N°10002 - Any CI creation in pop-up fails due to "groups_list_operations" not handled 2026-09-04 07:43:45 +02:00
odain-cbd
ee17738e8e N°10006 - Incorrect display of audit errors in iTOP (#1037)
* N°10006 - Incorrect display of audit errors in iTOP - fix export-v2 from audit

* N°10006 - Incorrect display of audit errors in iTOP - fix export-v2 from audit

* N°10006 - handle no class allowed in read corner case

* N°1006 - fix export-v2 with OQLRequest and without fields
2026-09-03 15:02:12 +02:00
Timmy38
da62814e67 N°10021 Fix code style 2026-09-03 10:26:57 +02:00
Timmy38
aef80d60a8 N°10021 Fix handling of removed extension that are also part of product 2026-09-03 10:13:49 +02:00
Timmy38
5597040a8a N°9334 Change extension source label 2026-09-02 15:28:21 +02:00
Eric Espie
7d89a05d8b N°9981 - Offline MTP fail on iTop3.3.0 2026-09-02 14:59:18 +02:00
jf-cbd
ddc3fd8fd1 N°7968 - Renaming column can cause problem when index exists, adding a fallback to the previous method in this specific case 2026-09-02 11:46:40 +02:00
Stephen Abello
59af2e8b96 Merge branch 'support/3.2' into develop 2026-09-02 10:14:27 +02:00
Stephen Abello
7d8a350394 N°7761 - Dehardcode input set SCSS variables and set it in Darkmoon 2026-09-02 10:13:51 +02:00
odain
4ae3a8ec1d N°8413 - Make data synchro work on DBObject - fix replica view in case of DBObject sync - display obj details 2026-09-02 09:48:01 +02:00
odain
34c394718d N°8413 - Make data synchro work on DBObject - fix replica view in case of DBObject sync 2026-09-02 09:40:19 +02:00
Timmy38
1fac630fdb N°10005 Fix phpunit testcases labels 2026-09-02 09:33:40 +02:00
lenaick.moreira
cab1710498 N°9981 - Add remote_env parameter to use data/production-build-modules instead of data/production-modules during MTP 2026-09-01 17:07:25 +02:00
odain
8d1711bef5 N°9875 - fix export from twig controller 2026-09-01 15:01:52 +02:00
odain
f35af2b8b0 Merge branch 'issue/7796-ticket-without-cmdb' into develop 2026-09-01 14:47:31 +02:00
odain
91e997a4e0 N°7796 - itop-ticket and itop-bridge-cmdb-ticket not coherents 2026-09-01 14:42:12 +02:00
Eric Espie
a8eb2a3fff N°10019 - APC cache not erased after MTP (online and offline), unattended install 2026-09-01 14:04:33 +02:00
Stephen Abello
dc5384f8aa Re-compile setup.css 2026-09-01 08:42:26 +02:00
Stephen Abello
2540d7039d Fix code style 2026-08-31 16:43:56 +02:00
Stephen Abello
373942f922 N°9152 - Add extensibility API endpoints for UI elements (#1017)
---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
Co-authored-by: Benjamin DALSASS <benjamin.dalsass@combodo.com>
2026-08-31 16:03:36 +02:00
Timmy38
7d4fc98ecc N°10005 Force check installed extensions 2026-08-31 14:47:14 +02:00
Timmy38
cfb04561ec N°9998 fix remote uninstallable flag 2026-08-31 14:40:36 +02:00
Lenaick
f3bad3538c N°9134 - Detect extension with no code when installing by data feature removal extension (#1022) 2026-08-31 10:01:05 +02:00
Lenaick
9c6baaeef4 N°9984 - Exclude auto-select modules from selection check in unattended-install when not specified in choices (#1032) 2026-08-28 10:38:02 +02:00
Lenaick
e769e53653 N°9984 - Error while compiling iTop 3.3 with PMP Extended (#1029)
* N°9984 - Error while compiling iTop 3.3 with PMP Extended

* N°9984 - Error while compiling iTop 3.3 with PMP Extended
2026-08-28 10:01:44 +02:00
Timmy38
42380f7a7a N°3789 Fix missing import 2026-08-27 11:46:03 +02:00
Vincent Dumas
8b384498e3 Fix FR typo 2026-08-27 09:43:24 +02:00
Timmy38
04f1eaff1f N°9989 Do not show "check compatibility" button at all when it should not be available 2026-08-27 09:33:37 +02:00
Molkobain
175b88de30 N°9144 - Fix typo in English sentence 2026-08-26 19:24:25 +02:00
Vaibhav Srivastava
0a5b0b403e docs: fix typo 2026-08-26 15:22:39 +02:00
odain-cbd
a346b71e9a N°9979 - Hide "keep current choice" setup button in case of a package upgrade (#1026)
* N°9979 - phstan cleanup

* N°9979 - Hide keep current choice setup button in case of a package upgrade

* N°9979 - optimize SQL queries
2026-08-26 14:09:05 +02:00
Timmy38
8065343ea6 N°9974 Show extension code in metadata for extensions (#1025) 2026-08-26 10:37:15 +02:00
Eric Espie
9c6087fd54 N°9982 - Install history is broken for MTP 2026-08-25 17:53:29 +02:00
Eric Espie
66073ba390 N°9982 - Install history is broken for MTP 2026-08-25 17:50:58 +02:00
jf-cbd
4756e2474c Merge remote-tracking branch 'origin/support/3.2' into develop 2026-08-25 17:05:57 +02:00
jf-cbd
7dbbf742c4 Fix code syntax (that was only 8.4+) 2026-08-25 17:05:30 +02:00
jf-cbd
2cd4fd1e0e Merge remote-tracking branch 'origin/support/3.2' into develop 2026-08-25 16:09:31 +02:00
jf-cbd
fffff76c82 N°9975 - PHPmailer: Sending email with same headers as before (#1023) 2026-08-25 16:08:51 +02:00
jf-cbd
06d593631d Merge remote-tracking branch 'origin/support/3.2' into develop 2026-08-25 09:13:59 +02:00
Benjamin DALSASS
54a0dfd06f Merge branch 'support/3.2' into develop
# Conflicts:
#	application/transaction.class.inc.php
#	dictionaries/en.dictionary.itop.ui.php
#	dictionaries/fr.dictionary.itop.ui.php
#	dictionaries/pt_br.dictionary.itop.ui.php
#	dictionaries/ru.dictionary.itop.ui.php
#	dictionaries/zh_cn.dictionary.itop.ui.php
2026-08-25 07:16:55 +02:00
odain
e31492fe27 N°9949 - code style 2026-08-24 19:49:01 +02:00
odain-cbd
dfd7f4cd0a N°9949 - Cannot uninstall IPAM from setup wizard screen - extension management fix (#1024)
* N°9949 - Cannot uninstall IPAM from setup wizard screen - fix extension mgt as well

* N°9949 - remove dupicated line

* N°9949 - fix log level

* N°9949 - log enhancement in ext mgt

* N°9949 - phpstan code cleanup
2026-08-24 19:35:06 +02:00
jf-cbd
69624116db N°9919 - Check if "To" exists when sendind mail with PHPMail (#1021) 2026-08-24 17:42:05 +02:00
Molkobain
f3b69ad12e N°7299 - Migrate remaining usages of deprecated JS libs (#1001) 2026-08-24 13:01:26 +02:00
v-dumas
edb33e31da N°9643 - Move REST and KAFKA sample as Protocol 2026-08-24 09:14:02 +02:00
Molkobain
5542d9882f N°9956 - Fix duplicated translations 2026-08-24 08:40:24 +02:00
Molkobain
629f9ec210 N°9956 - Fix CI due to wrong dictionary entries formatting 2026-08-23 10:00:25 +02:00
Lars Kaltefleiter
4bd5c22d36 N°9973 - Update british english translations (#1008)
Some `en_gb` entries still carried the US spelling, so British users were served
the American form. This pull request:

- corrects 49 entries that used a US spelling (organization, synchronize,
  synchronized, resynchronization, catalog, categorize, customize, customizable,
  containerization, virtualization, personalized, localized, serialized,
  sanitize, and their capitalised forms)
- drops the `~~` marker from 175 entries whose text already is correct British
  English
- adds 1 `en_gb` dictionary for a module that shipped none

Code identifiers keep their US spelling on purpose, for example the PHP setting
`post_max_size`.

Dictionary keys and the EN US entries are untouched. Where an `en_gb` entry is
absent, `Dict` already falls back to EN US, so nothing is duplicated for the
sake of it.
2026-08-22 09:45:34 +02:00
Lars Kaltefleiter
2c53b740fc N°9956 - Update german translations (#1007)
* 🌐 Complete the German (de) translations (iTop 3.3)

German was incomplete in ways that the `~~` marker alone does not reveal.
This pull request:

- translates 317 entries that still carried the `~~` fallback marker
- fills 265 descriptions that were empty while the English entry had text
- adds 69 keys that existed only in the English dictionary
- updates 57 entries that still mirrored an older, much shorter English wording
- restores 8 broken printf placeholders
- adds 3 German dictionaries for modules that shipped none

English source strings and dictionary keys are untouched; only German values
change. Each printf placeholder was checked against its English entry.

* 🌐 Translate the new welcome popup entries (iTop 3.3)

Follow-up to the develop merge: translates the new `330_05_Portal` title and
description, extends `330_01_Welcome` with the sentences added upstream, and
restores the German wording that the merge had reverted to English for
`330_01_Welcome:Title`, `330_02_AIFoundations:Title` and
`330_04_MFA:Description`.

---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2026-08-22 09:21:39 +02:00
odain-cbd
18b7ee4d30 N°9965 - Extensions management and Designer don't have the same view on extension (#1020)
* N°9965 - GetChoicesFromDatabase no more static to mock it

* N°9965 - remove empty line
2026-08-21 15:58:38 +02:00
Eric Espié
98913a2a86 N°9950 - Error loading structural data on upgrade (#1019)
* N°9950 - Error loading structural data on upgrade

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* N°9950 - Error loading structural data on upgrade

* N°9950 - Error loading structural data on upgrade

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-21 15:23:44 +02:00
v-dumas
62ea1c1fd6 N°9763 - Fix error in Model sample data (2) 2026-08-21 14:55:51 +02:00
v-dumas
b919adf4d8 N°9763 - Fix error in Model sample data 2026-08-21 11:57:01 +02:00
v-dumas
4ebd03eebb Ensure extensions menus in Service Management end up after Catalog 2026-08-20 17:43:55 +02:00
odain-cbd
4f891a9170 N°9935 - Disable data compatibility check when env-production empty (#1004)
* N°9935 - Disable data compatibility check when env-production empty

* N°9935 - redundant test cleanup
2026-08-20 15:27:50 +02:00
odain
7583ede3b3 N°9949 - code style 2026-08-20 11:59:56 +02:00
odain-cbd
95cb21cff7 N°9949 - Cannot uninstall IPAM from setup wizard screen (#1015)
* N°9949 - Cannot uninstall IPAM from setup wizard screen

* N°9949 - add test coverage

* N°9949 - handle list in compilation
2026-08-20 10:03:25 +02:00
Molkobain
cdd7bbedcb N°9625 - Update welcome popup introduction message and add message for new portal look 2026-08-20 09:39:51 +02:00
Molkobain
ea1ed03d0c N°9948 - Fix core dashlets after AttributeDefinition split (#1014) 2026-08-20 08:53:09 +02:00
Lenaick
2073e8c10c N°9861 - Check user can go to audit step only when keep current choice button has been clicked (#1013)
* N°9861 - Check user can go to audit step only when keep current choice button has been clicked

* Rename variable to improve clarity
2026-08-19 16:03:25 +02:00
odain
1a41349d7b N°8806 - fix setup tests 2026-08-19 15:54:39 +02:00
odain
fdc1ef40e6 N°9875 - error msg typo 2026-08-19 15:27:49 +02:00
odain-cbd
4722551a16 N°9875 - Error deprecated on component version object (#1010)
* N°9875 - Error deprecated on component version object - propose boilerplate methods to handle ZipArchive

* N°9875 - Error deprecated on component version object - use ZipArchive boilerplate methods

* N°9875 - code style

* N°9875 - review feedbacks

* N°9875 - simplify return type of ZipArchiveCreateWithTempNam

* N°9875 - add @since
2026-08-19 15:14:32 +02:00
odain
a2934ef05b N°8806 - Installation multiple extension with dependence via ITSM Designer - fix check compatibity when new dictionaries not present yet in iTop core like SV-SV 2026-08-19 15:13:30 +02:00
Anne-Catherine
1cdc2c5648 N°9934 - OQL with magic field (#1011) 2026-08-19 11:46:28 +02:00
Lenaick
31eddad029 N°9876 - A non installed extension should not be selected (#992)
* N°9876 - A non installed extension should not be selected if it is not mandatory

* N°9876 - A non installed extension should not be selected if it is not mandatory

* N°9876 - A non installed extension should not be selected if it is not mandatory

* N°9876 Streamline and refactor ComputeChoiceFlags

* N°9876 - Fix several conditions :
* Installed extension with dependency issue must be checked and disabled without force-uninstall
* Mandatory extension with force-uninstall must be checked
* Mandatory or installed extension must keep unchecked status when move to another step

* N°9876 - remove mandatory condition for extensions not included in package

* N°9876 - Undo remove ComputeChoiceFlags method return type

---------

Co-authored-by: Timmy38 <timothee.callet@combodo.com>
2026-08-19 10:47:31 +02:00
Molkobain
b43d71f324 N°824 - Fix typo in notification filling 2026-08-19 09:54:23 +02:00
Benjamin Dalsass
97364af71d Feature/8420 transaction id lifetime (#1009) 2026-08-19 08:45:49 +02:00
Anne-Catherine
26033774b8 N°9622 - Error in the user story life cycle (#915) 2026-08-19 08:22:35 +02:00
Lenaick
27c0fe191b N°9904 - Add explanation message when "Check compatibility" button is not available (#1003) 2026-08-18 16:40:18 +02:00
Stephen Abello
fac132f707 Merge branch 'support/3.2' into develop 2026-08-18 08:52:38 +02:00
Stephen Abello
6f9a4cb55f N°9937 - Restore modal icon spins around the text (#1005) 2026-08-18 08:46:17 +02:00
Anne-Catherine
c07317dc71 N°6071 - Fix prefilled Tagset displayed (in transition or clone) but not saved (#751)
* N°6071 - Prefill Tagset in transition displayed but not saved

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2026-08-17 18:57:44 +02:00
Eric Espie
a3fcc6fdf0 Merge branch 'feature/9902-record_mtp_infos' into develop 2026-08-17 16:45:01 +02:00
Eric Espie
b13065f23f N°9902 - Store MTP info in DB 2026-08-17 16:42:51 +02:00
odain
2eaca40632 N°9936 - Designer - can't upload package 2026-08-17 16:18:42 +02:00
odain
7f3a1811d7 N°9888 - test coverage enhancement regarding php version checks during setup 2026-08-17 13:51:50 +02:00
Lenaick
87b48c638c N°9861 - Keep current choice button is too permissive (#989)
* N°9861 - Keep current choice button is too permissive

* N°9861 - Changes from review

* Add GetLatestWizardStateFromStepClass test and improve CanMoveForwardFromChoiceFlags readability

* Remove $bPopStep parameter from GetLatestWizardStateFromStepClass function
2026-08-17 11:52:24 +02:00
jf-cbd
54a1221e4a N°8123 - Improve on mention data parsing (#749) 2026-08-17 10:37:40 +02:00
v-dumas
97c589c440 N°9643 - Dataflow: fix duplicated format 2026-08-17 10:20:45 +02:00
v-dumas
68e799c0fd N°9643 - Dataflow: add other formats and protocols 2026-08-17 09:34:48 +02:00
Molkobain
658825b308 N°7385 - Fix regression from b21a02dcb8 which made mentions on sub classes no longer working 2026-08-15 12:26:07 +02:00
Molkobain
af08824866 N°8313 - Revert parts of merge that actually loose changes from previous commits of 2026-08-15 12:25:57 +02:00
Eric Espie
8693951aa9 N°9902 - Store MTP info in DB 2026-08-14 16:26:15 +02:00
Eric Espie
a28e481ee4 N°9455 - Feature removal - Access rights 2026-08-14 14:48:48 +02:00
Vincent Dumas
6032628a70 N°9460 - Ticket ITIL : Add Child Incidents tab on UserRequest (#1000) 2026-08-14 10:08:10 +02:00
Benjamin Dalsass
2892949e33 N°8726 - Portal deprecated removal (#990) 2026-08-14 09:14:51 +02:00
Molkobain
ba643ba2ab N°8699 - Add comment about attribute definition class aliases and there non deprecation 2026-08-13 19:30:23 +02:00
Vincent Dumas
3711e8a5a1 N°9666 & N°9658 - Localize sample data (User and Delivery Model) (#999) 2026-08-13 10:57:33 +02:00
Stephen Abello
a38fef5bbf Merge branch 'support/3.2' into develop
# Conflicts:
#	css/setup.css
#	datamodels/2.x/itop-portal-base/portal/public/css/portal.css
#	datamodels/2.x/itop-portal-base/portal/public/css/portal.scss
2026-08-13 09:35:42 +02:00
Stephen Abello
23e4ce7263 N°9128 - Add padding to fullscreen CKEditor (#998) 2026-08-13 09:29:41 +02:00
Eric Espie
1bba486ee8 N°9902 - Can't MTP with 3.3.0-dev 2026-08-12 16:58:00 +02:00
Stephen Abello
801090fddb N°9911 - Fix keyboard shortcuts no working anymore since 5b9e0a1d (#997) 2026-08-12 14:56:12 +02:00
Eric Espié
1d77b9faa2 N°9909 - Fix setup token unexpectedly deleted during setup due to concurrent access (#996) 2026-08-12 14:00:54 +02:00
Benjamin Dalsass
5282913791 N°9908 - GetAllowedValues from AttributeClassAttCodeSet return no value (#995) 2026-08-12 08:43:51 +02:00
Benjamin Dalsass
36fae27355 N°8860 - Régression Export CSV clé externe, plus de critères (#971) 2026-08-11 15:28:30 +02:00
Vincent Dumas
812f0b573d N°9643 - Dataflow: split protocol & type, add url and last change (#994) 2026-08-11 11:44:13 +02:00
Eric Espie
c7c16f091b N°9902 - Can't MTP with 3.3.0-dev 2026-08-11 10:10:57 +02:00
v-dumas
3739067473 N°9900 - The plural of Software is... Software ! 2026-08-10 17:09:33 +02:00
Molkobain
fd553b86dd N°4693 - Fix iBackofficeSassExtension API returning only 1 file 2026-08-09 19:50:52 +02:00
Purple Grape
d7625d9650 N°9891 - Update chinese translations (#937)
* update zh_cn translation

rebase on the latest develop branch

* update zh_cn translation

rebase on the latest develop branch

* update zh_cn translation

* Update zh_cn.dict.itop-oauth-client.php

* Update zh_cn.dictionary.itop.core.php

* Update zh_cn.dict.itop-request-mgmt.php

* update zh_cn translation

rebase on the latest develop branch

* update zh_cn translation

rebase on the latest develop branch

* update zh_cn translation

* Update zh_cn.dict.itop-oauth-client.php

* Update zh_cn.dict.itop-request-mgmt.php

* Update zh_cn.dict.itop-portal-base.php

* Update zh_cn.dictionary.itop.datatable.php

* Update zh_cn.dictionary.itop.welcome-popup.php

* Update zh_cn.dictionary.itop.ui.php
2026-08-09 18:41:18 +02:00
Timmy38
893a1d7f71 N°9888 Add config parameter mention in error message when php cli version is lower than required 2026-08-07 12:37:00 +02:00
Anne-Cath
fda1d3dd16 N°3789 - Bulk actions on replicas: (Resync, Force Update, Delete,...) - Deletes the fake message left by mistake 2026-08-07 11:38:39 +02:00
Thomas Casteleyn
61464fc819 N°9881 - Update dutch translations of itop-config-mgmt module (#986)
* dict(itop-config-mgmt): Update NL translations

* Update datamodels/2.x/itop-config-mgmt/dictionaries/nl.dict.itop-config-mgmt.php
2026-08-07 10:35:40 +02:00
v-dumas
c281677768 N°8142 - Ticket menus label/tooltip standardisation 2026-08-07 08:57:51 +02:00
Molkobain
e280251623 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	dictionaries/ui/application/links/zh_cn.dictionary.itop.links.php
#	dictionaries/zh_cn.dictionary.itop.core.php
#	dictionaries/zh_cn.dictionary.itop.ui.php
2026-08-06 16:36:15 +02:00
Molkobain
f657308137 ✅ Fix PHP code styles for Chinese dictionaries 2026-08-06 16:33:31 +02:00
Molkobain
4596404666 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	.doc/itop-version-history.md
2026-08-06 13:38:39 +02:00
Molkobain
64394099f7 📝 Add 3.2.3-1 and 3.2.3-2 to version history 2026-08-06 13:37:44 +02:00
Stephen Abello
67e2899a60 N°9787 - Fix skipped test 2026-08-06 10:30:37 +02:00
Molkobain
8d75531d95 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	datamodels/2.x/combodo-backoffice-fullmoon-high-contrast-theme/dictionaries/zh_cn.dict.combodo-backoffice-fullmoon-high-contrast-theme.php
#	datamodels/2.x/combodo-backoffice-fullmoon-protanopia-deuteranopia-theme/dictionaries/zh_cn.dict.combodo-backoffice-fullmoon-protanopia-deuteranopia-theme.php
#	datamodels/2.x/combodo-backoffice-fullmoon-tritanopia-theme/dictionaries/zh_cn.dict.combodo-backoffice-fullmoon-tritanopia-theme.php
#	datamodels/2.x/itop-attachments/dictionaries/zh_cn.dict.itop-attachments.php
#	datamodels/2.x/itop-backup/dictionaries/zh_cn.dict.itop-backup.php
#	datamodels/2.x/itop-bridge-cmdb-services/dictionaries/zh_cn.dict.itop-bridge-cmdb-services.php
#	datamodels/2.x/itop-bridge-cmdb-ticket/dictionaries/zh_cn.dict.itop-bridge-cmdb-ticket.php
#	datamodels/2.x/itop-change-mgmt-itil/dictionaries/zh_cn.dict.itop-change-mgmt-itil.php
#	datamodels/2.x/itop-change-mgmt/dictionaries/zh_cn.dict.itop-change-mgmt.php
#	datamodels/2.x/itop-config-mgmt/dictionaries/zh_cn.dict.itop-config-mgmt.php
#	datamodels/2.x/itop-config/dictionaries/zh_cn.dict.itop-config.php
#	datamodels/2.x/itop-core-update/dictionaries/zh_cn.dict.itop-core-update.php
#	datamodels/2.x/itop-hub-connector/dictionaries/zh_cn.dict.itop-hub-connector.php
#	datamodels/2.x/itop-incident-mgmt-itil/dictionaries/zh_cn.dict.itop-incident-mgmt-itil.php
#	datamodels/2.x/itop-knownerror-mgmt/dictionaries/zh_cn.dict.itop-knownerror-mgmt.php
#	datamodels/2.x/itop-portal-base/dictionaries/zh_cn.dict.itop-portal-base.php
#	datamodels/2.x/itop-problem-mgmt/dictionaries/zh_cn.dict.itop-problem-mgmt.php
#	datamodels/2.x/itop-request-mgmt-itil/dictionaries/zh_cn.dict.itop-request-mgmt-itil.php
#	datamodels/2.x/itop-request-mgmt/dictionaries/zh_cn.dict.itop-request-mgmt.php
#	datamodels/2.x/itop-service-mgmt-provider/dictionaries/zh_cn.dict.itop-service-mgmt-provider.php
#	datamodels/2.x/itop-service-mgmt/dictionaries/zh_cn.dict.itop-service-mgmt.php
#	datamodels/2.x/itop-storage-mgmt/dictionaries/zh_cn.dict.itop-storage-mgmt.php
#	datamodels/2.x/itop-structure/dictionaries/zh_cn.dict.itop-structure.php
#	datamodels/2.x/itop-themes-compat/dictionaries/zh_cn.dict.itop-themes-compat.php
#	datamodels/2.x/itop-tickets/dictionaries/zh_cn.dict.itop-tickets.php
#	datamodels/2.x/itop-virtualization-mgmt/dictionaries/zh_cn.dict.itop-virtualization-mgmt.php
#	dictionaries/core/orm-value/orm-document/zh_cn.dictionary.itop.orm-document.php
#	dictionaries/core/uniqueness-rule/zh_cn.dictionary.itop.uniqueness-rule.php
#	dictionaries/ui/application/links/zh_cn.dictionary.itop.links.php
#	dictionaries/ui/application/newsroom/zh_cn.dictionary.itop.newsroom.php
#	dictionaries/ui/application/welcome-popup/zh_cn.dictionary.itop.welcome-popup.php
#	dictionaries/ui/layouts/activity-panel/zh_cn.dictionary.itop.activity-panel.php
#	dictionaries/ui/layouts/navigation-menu/zh_cn.dictionary.itop.navigation-menu.php
#	dictionaries/ui/pages/notifications-center/zh_cn.dictionary.itop.notifications-center.php
#	dictionaries/ui/pages/preferences/zh_cn.dictionary.itop.preferences.php
#	dictionaries/zh_cn.dictionary.itop.core.php
#	dictionaries/zh_cn.dictionary.itop.ui.php
#	setup/wizardsteps/WizStepLicense2.php
2026-08-04 18:00:19 +02:00
Benjamin DALSASS
1e023e5431 Merge branch 'support/3.2' into develop
# Conflicts:
#	datamodels/2.x/itop-portal-base/portal/public/css/portal.css
2026-08-04 17:03:32 +02:00
Anne-Cath
e2a1c3e79e reformat file for php cs fixer 2026-08-04 15:47:59 +02:00
Purple Grape
c7570d62c1 N°9889 - Update zh_cn translation for 3.2 branch (#938)
* rebase on the latest 3.2 branch

rebase

Co-Authored-By: Thomas Casteleyn <thomas.casteleyn@me.com>

* Update zh_cn.dict.authent-cas.php

* Update zh_cn.dict.authent-external.php

* Update zh_cn.dict.authent-ldap.php

* Update zh_cn.dict.authent-local.php

* Update zh_cn.dict.combodo-backoffice-darkmoon-theme.php

* Update zh_cn.dict.itop-backup.php

* Update zh_cn.dict.itop-change-mgmt.php

* Update zh_cn.dict.itop-faq-light.php

* Update zh_cn.dict.itop-config.php

* Update zh_cn.dict.itop-config-mgmt.php

* Update zh_cn.dict.itop-hub-connector.php

* Update zh_cn.dict.itop-knownerror-mgmt.php

* Update zh_cn.dictionary.itop.core.php

* Update zh_cn.dict.authent-cas.php

* Update zh_cn.dict.authent-external.php

* Update zh_cn.dict.authent-ldap.php

* Update zh_cn.dict.authent-local.php

* Update zh_cn.dict.combodo-backoffice-darkmoon-theme.php

* Update zh_cn.dict.itop-backup.php

* Update zh_cn.dict.itop-config-mgmt.php

* Update zh_cn.dict.itop-config.php

* Update zh_cn.dict.itop-faq-light.php

* update  license  url

* Update datamodels/2.x/authent-ldap/dictionaries/zh_cn.dict.authent-ldap.php

Co-authored-by: Thomas Casteleyn <thomas.casteleyn@me.com>

* Update datamodels/2.x/authent-cas/dictionaries/en.dict.authent-cas.php

---------

Co-authored-by: Thomas Casteleyn <thomas.casteleyn@me.com>
Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2026-08-04 15:26:11 +02:00
Anne-Cath
a30ed63a62 N°3789 - Bulk actions on replicas: (Resync, Force Update, Delete,...) - Resynchronization fix + label improvements 2026-08-04 15:15:37 +02:00
Benjamin DALSASS
dd77124b07 N°4720 - Remove old unreferenced methods or pages that are @deprecated
- deleted function GetTileTemplatePath called in aggragte brick
2026-08-04 14:36:09 +02:00
Lenaick
9bfe0430f5 N°9118 - No Safeguard when hub installation fail (#988)
* N°9118 - No Safeguard when hub installation fail

* Apply suggestion from @greptile-apps[bot]

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-08-04 09:01:34 +02:00
Anne-Cath
3fe91ed0b4 N°8397 - Suppress export V1 from iTop packages 2026-08-03 16:39:19 +02:00
Stephen Abello
ccb9633226 Align portal ui_version default value with XML ui_version 2026-08-03 10:14:00 +02:00
Thomas Casteleyn
82b50dc6cd N°9880 - Add dutch translations to itop-flow-map module (#985) 2026-08-03 09:22:14 +02:00
Vladimir Kunin
d3451f8c30 N°9879 - Update russian translations (#983) 2026-08-03 09:17:43 +02:00
Benjamin DALSASS
b5c8a98695 N°9807 - Polishing CKEditor for iTop 3.3.0-beta1
- fix setup css broken unit test
2026-07-30 09:21:51 +02:00
Lenaick
1403914572 N°9116 - Installing modules in extension folder or production-modules doesn't have the same behavior (#981)
* N°9116 - Installing modules in extension folder or production-modules doesn't have the same behavior

* N°9116 - Installing modules in extension folder or production-modules doesn't have the same behavior

* N°9116 - Installing modules in extension folder or production-modules doesn't have the same behavior
2026-07-28 16:40:23 +02:00
Stephen Abello
b976471a0d N°9385 - Action button is not displaying its dropdown in a filtered BrowseBrick 2026-07-28 10:24:02 +02:00
Benjamin DALSASS
fa66a09104 N°9838 - Polishing CKEditor for iTop 3.2.4
- fix php unit test
2026-07-28 09:39:58 +02:00
Benjamin DALSASS
47981f78e3 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	css/setup.css
#	datamodels/2.x/itop-portal-base/portal/public/css/portal.css
#	datamodels/2.x/itop-portal-base/portal/public/css/portal.scss
2026-07-28 08:41:18 +02:00
Benjamin DALSASS
476ec75a2e N°9838 - Polishing CKEditor for iTop 3.2.4 2026-07-28 08:39:24 +02:00
Benjamin DALSASS
10c000ddfe N°9807 - Polishing CKEditor for iTop 3.3.0-beta1
- update with last ckeditor changes
2026-07-28 08:23:20 +02:00
Vincent Dumas
7e55b9ccfc N°9703 - Updates italian dictionnary 2026-07-27 18:51:53 +02:00
v-dumas
5bb288ffca N°9800 - broken setup in CI 2026-07-27 18:36:03 +02:00
Vincent Dumas
56d7e40f4c N°9800 New sample data for KnownError and FAQ (#982)
* N°9800 - Fix typos in sample data in User and Service in FR
* N°9800 - typo in sample Server ids and Rack id
2026-07-27 18:16:58 +02:00
Benjamin Dalsass
e0dc1fa571 N°8886 - Deprecation warning in DashetGroupBy in 3.3-dev (#972) 2026-07-27 08:26:37 +02:00
Stephen Abello
d6466c0bd6 Update setup.css 2026-07-24 14:30:17 +02:00
Stephen Abello
eb16e6cbc8 Merge branch 'support/3.2' into develop 2026-07-24 14:29:24 +02:00
Stephen Abello
42007ffd95 N°9702 - Fix user rights matrix display when an accessibility theme displayed a visual cue 2026-07-24 14:28:59 +02:00
Molkobain
6329c157e8 📝 Update releases history with 3.3.0-beta1 2026-07-24 11:31:07 +02:00
Benjamin Dalsass
9ee0548767 N°9807 - Polishing CKEditor for iTop 3.3.0-beta1 (#976) 2026-07-24 09:22:24 +02:00
Lenaick
620a6b9a51 N°9827 - Unable to launch check compatibility when a module with bad dependencies is in production-modules (#978)
* N°9827 - Unable to launch check compatibility when a module with bad dependencies is in production-modules

* Update unit tests

* N°9827 - Unable to launch check compatibility when a module with bad dependencies is in production-modules
2026-07-23 16:23:06 +02:00
Stephen Abello
968c56745a Merge branch 'support/3.2' into develop
# Conflicts:
#	core/attributedef.class.inc.php
2026-07-23 16:08:45 +02:00
Anne-Catherine
04a5e5463a N°9759 - Truncating AttributeText doesn't work as expected in case of multibytes characters (#964)
* N°9759 - Truncate AttributeText don't work as expected in case of multibytes characters

* WIP

* Move TrimValue() base definition to AttributeDefinition class, assign DBObject::SetTrim old behavior to it

* Fix variable types

* Fix variable types

* Apply code review fixes, add tests

* Define GetSize in AttributeDefinition to easy polymorphic calls instead of obscure AttributeDBFieldVoid

* Update php with right since version and ticket, remove useless method overload

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Update core/attributedef.class.inc.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Update core/attributedef.class.inc.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestion from @Molkobain code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestion from @Molkobain code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestion from @Molkobain code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestion from @Molkobain code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestion from @Molkobain code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Correctly handle null values in new methods

---------

Co-authored-by: Stephen Abello <stephen.abello@combodo.com>
Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2026-07-23 16:07:42 +02:00
Benjamin Dalsass
006dd3d6c2 N°9829 - Add unit tests for parent ticket changes (#979) 2026-07-23 15:32:53 +02:00
lenaick.moreira
a808e89c30 Merge branch 'support/3.3-beta1' into develop 2026-07-23 11:43:00 +02:00
Molkobain
d88687527e Merge remote-tracking branch 'origin/support/3.2.3' into support/3.2 2026-07-23 10:49:09 +02:00
Molkobain
2cc071b07a Merge remote-tracking branch 'origin/support/3.3-beta1' into develop 2026-07-23 10:47:12 +02:00
Benjamin Dalsass
a5c14e4870 N°9723 - IsActionAllowed should work with non instantiated users objects (#973) 2026-07-21 17:41:40 +02:00
Timmy38
0e72b090ee Merge branch 'support/3.3-beta1' into develop 2026-07-21 17:06:30 +02:00
Timmy38
1b70b4bf70 N°9692 - Fix flag name 2026-07-21 12:02:31 +02:00
odain
41f51a2c0c Merge branch 'support/3.2' into develop 2026-07-21 08:03:21 +02:00
odain-cbd
73ede13443 N°9746 - Synchro execution time change fixes (#951)
* N°9746 - Synchro execution can make damage during time change (object deletions)

* N°9746 - handle timechange during synchro execution + cleanup m_bIsImportPhaseDateKnown

* N°9746 - PR fix

* N°9746 - add corner test case
2026-07-21 08:01:31 +02:00
odain-cbd
40dd72ef82 N°9746 - Synchro execution time change fixes (#951)
* N°9746 - Synchro execution can make damage during time change (object deletions)

* N°9746 - handle timechange during synchro execution + cleanup m_bIsImportPhaseDateKnown

* N°9746 - PR fix

* N°9746 - add corner test case
2026-07-21 07:59:50 +02:00
Lenaick
c8aea56171 N°9565 - Extension Mgmt : display progression during analysis (#975)
* N°9565 - Extension Mgmt : display progression during analysis

* N°9565 - Extension Mgmt : display progression during analysis

* N°9565 - Extension Mgmt : display progression during analysis

* N°9565 - Extension Mgmt : display progression during analysis

* N°9565 - Extension Mgmt : display progression during analysis

* N°9565 - Validate transaction id

* N°9565 - Move unset bForceCompilation
2026-07-20 11:38:30 +02:00
lenaick.moreira
041437cbeb Merge branch 'support/3.3-beta1' into develop 2026-07-17 17:05:46 +02:00
Anne-Catherine
5715e0484f N°9759 - Truncating AttributeText doesn't work as expected in case of multibytes characters (#964)
* N°9759 - Truncate AttributeText don't work as expected in case of multibytes characters

* WIP

* Move TrimValue() base definition to AttributeDefinition class, assign DBObject::SetTrim old behavior to it

* Fix variable types

* Fix variable types

* Apply code review fixes, add tests

* Define GetSize in AttributeDefinition to easy polymorphic calls instead of obscure AttributeDBFieldVoid

* Update php with right since version and ticket, remove useless method overload

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Update core/attributedef.class.inc.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Update core/attributedef.class.inc.php

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestion from @Molkobain code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestion from @Molkobain code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestion from @Molkobain code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestion from @Molkobain code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestion from @Molkobain code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Correctly handle null values in new methods

---------

Co-authored-by: Stephen Abello <stephen.abello@combodo.com>
Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2026-07-17 16:40:09 +02:00
lenaick.moreira
109fdfa06b Merge branch 'support/3.3-beta1' into develop
^ Conflicts:
^	datamodels/2.x/combodo-data-feature-removal/src/Controller/DataFeatureRemovalController.php
2026-07-17 14:45:50 +02:00
Molkobain
13fad9efc1 N°9625 - Add Session::UnsetAll() method to ensure that even the "welcome" session value is correctly resetted (#970)
* N°9625 - Add Session::UnsetAll() method to ensure that even the "welcome" session value is correctly resetted

* N°9625 - Add unit tests

* N°9625 - Improve robustness

* N°9625 - Code review updates

* N°9625 - Fix logoff page to ensure all authentication plugins are called before emptying the session
2026-07-17 10:28:58 +02:00
Lenaick
9fbc53cf00 N°9679 - Forced uninstallation is not logged when done by extensions management (#956) 2026-07-17 09:42:58 +02:00
lenaick.moreira
918f110dc9 Merge branch 'support/3.3-beta1' into develop 2026-07-15 14:46:48 +02:00
lenaick.moreira
bd9cc9c15e ✅ Fix unit tests failed 2026-07-15 14:21:17 +02:00
Eric Espie
60ce21509f Merge branch 'support/3.3-beta1' into develop 2026-07-15 12:10:56 +02:00
537 changed files with 19489 additions and 5364 deletions

View File

@@ -2,13 +2,17 @@
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'themeVariables': {
'git0': 'lawngreen',
'git3': 'dodgerblue',
'git4': 'grey',
'git5': 'grey',
'git6': 'grey',
'git7': 'grey',
'git8': 'grey'
'git0': '#7ccf00',
'git1': '#7ccf00',
'git2': '#99a1af',
'git3': '#99a1af',
'git4': '#99a1af',
'git5': '#99a1af',
'git6': '#99a1af',
'git7': '#99a1af',
'git8': '#99a1af',
'git9': '#99a1af',
'git10': '#99a1af'
}, 'gitGraph': {'showBranches': true,'mainBranchName': 'develop','rotateCommitLabel': true}} }%%
gitGraph
commit id: "2016-07-06" tag: "2.3.0" type: HIGHLIGHT
@@ -106,6 +110,11 @@ gitGraph
commit id: "2025-09-25" tag: "2.7.13"
checkout support/3.2
commit id: "2026-04-27 " tag: "3.2.3"
checkout support/3.2.3
commit id: "2026-05-25 " tag: "3.2.3-1"
commit id: "2026-07-17 " tag: "3.2.3-2"
checkout develop
commit id: "2026-07-23" tag: "3.3.0-beta1"
```
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).

View File

@@ -8,6 +8,7 @@ body:
Please explain why you're creating this issue :
- Please describe what's your improvement proposition.
- Then tell us if you're willing to create a PR for this enhancement ? If so, we'll indicate in the issue if we're interested in it.
- The more users are affected and the greater is the impact, the more likely this improvement will be accepted. If you identified a bug and have an improvement proposition, please use the 'Bug report' template instead."
- type: textarea
id: enhancement_details

View File

@@ -1,6 +1,7 @@
<!--
IMPORTANT: Before creating your PR, please create an issue first to know if Combodo is interested in your contribution (not needed for translations PR).
Since we may refuse a PR, it's preferable to create an issue first, to avoid spending time coding something that won't be accepted.
PR that will benefit the most users and has a greater impact on the product are more likely to be accepted, so please explain your use case and why you think it would be useful for the community.
Once you've done it, and we confirmed we're interested in it, please follow the guidelines within this PR template before submitting it, it will greatly help us process your PR. 🙏
@@ -9,10 +10,10 @@ Any PRs not following the guidelines or with missing information will not be con
## Base information
| Question | Answer |
|---------------------------------------------------------------------------------|--------------------------------------|
| Related to a SourceForge thread / Another PR / A GitHub Issue / Combodo ticket? | <!-- Put the URL --> |
| Type of change? | Bug fix / Enhancement / Translations |
| Question | Answer |
|---------------------------------------------------------------------------------|---------------------------------------|
| Related to a SourceForge thread / Another PR / A GitHub Issue / Combodo ticket? | <!-- Put the URL or ticket number --> |
| Type of change? | Bug fix / Enhancement / Translations |
## Symptom (bug) / Objective (enhancement)
@@ -70,6 +71,7 @@ Explain in details how you are proposing to solve this:
Don't remove these lines, check them once done.
-->
- [ ] If my PR is a bug fix or an enhancement, I opened an issue that got reviewed and approved by Combodo
- [ ] I have performed a self-review of my code
- [ ] I have tested all changes I made on an iTop instance
- [ ] I have added a unit test, otherwise I have explained why I couldn't

View File

@@ -582,7 +582,9 @@ class UserRightsProfile extends UserRightsAddOnAPI
*/
public function ListProfiles($oUser)
{
if (count($oUser->ListChanges()) === 0) { // backward compatibility
if (!array_key_exists('profile_list', $oUser->ListChanges())) {
// Profiles list is not modified on the $oUser object, we can use DBObjectSearch with `all data` capabilities
// Note: This is the default behavior
$aRet = [];
$oSearch = new DBObjectSearch('URP_UserProfile');
$oSearch->AllowAllData();
@@ -595,6 +597,8 @@ class UserRightsProfile extends UserRightsAddOnAPI
return $aRet;
} else {
// Profiles list must be computed with memory changes.
// Note: this is a bit tricky because the user object may have been modified in memory (e.g. a profile added or removed) and we need to take that into account
$aRet = [];
$oProfilesSet = $oUser->Get('profile_list');
foreach ($oProfilesSet as $oUserProfile) {

View File

@@ -30,6 +30,15 @@ require_once(APPROOT.'application/newsroomprovider.class.inc.php');
* @license http://opensource.org/licenses/AGPL-3.0
* @since 2.7.0
*/
require_once(APPROOT.'application/applicationextension/common/ApplicationPopupMenuItem.php');
require_once(APPROOT.'application/applicationextension/common/iPopupMenuExtension.php');
require_once(APPROOT.'application/applicationextension/common/JSButtonItem.php');
require_once(APPROOT.'application/applicationextension/common/JSPopupMenuItem.php');
require_once(APPROOT.'application/applicationextension/common/SeparatorPopupMenuItem.php');
require_once(APPROOT.'application/applicationextension/common/URLButtonItem.php');
require_once(APPROOT.'application/applicationextension/common/URLPopupMenuItem.php');
require_once(APPROOT.'application/applicationextension/backoffice/iApplicationUIExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeDictEntriesExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeDictEntriesPrefixesExtension.php');
@@ -43,19 +52,12 @@ require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeScr
require_once(APPROOT.'application/applicationextension/backoffice/iBackofficeStyleExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/iFieldRendererMappingsExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/iPageUIBlockExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/iPopupMenuExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/iPreferencesExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/iWelcomePopupExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/AbstractApplicationUIExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/AbstractPageUIBlockExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/AbstractPreferencesExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/AbstractWelcomePopupExtension.php');
require_once(APPROOT.'application/applicationextension/backoffice/ApplicationPopupMenuItem.php');
require_once(APPROOT.'application/applicationextension/backoffice/JSButtonItem.php');
require_once(APPROOT.'application/applicationextension/backoffice/JSPopupMenuItem.php');
require_once(APPROOT.'application/applicationextension/backoffice/SeparatorPopupMenuItem.php');
require_once(APPROOT.'application/applicationextension/backoffice/URLButtonItem.php');
require_once(APPROOT.'application/applicationextension/backoffice/URLPopupMenuItem.php');
require_once(APPROOT.'application/applicationextension/iBackupExtraFilesExtension.php');
require_once(APPROOT.'application/applicationextension/iKPILoggerExtension.php');

View File

@@ -1,7 +1,7 @@
<?php
/**
* Implement this interface to add sass file (SCSS) to the backoffice pages.
* Implement this interface to add sass files (SCSS) to the backoffice pages.
* example: return "css/setup.scss"
*
* @api
@@ -11,9 +11,9 @@
interface iBackofficeSassExtension
{
/**
* @return string
* @see \iTopWebPage::$a_styles
* @return array An array of relative paths (from loaded import paths) to the files to compile and include
* @see \iTopWebPage::$a_linked_stylesheets
* @api
*/
public function GetSass(): string;
public function GetSassRelPaths(): array;
}

View File

@@ -1,5 +1,9 @@
<?php
use Combodo\iTop\Application\UI\Base\Common\Metadata\AriaAttributes;
use Combodo\iTop\Application\UI\Base\Common\Metadata\DataAttributes;
use Combodo\iTop\Application\UI\Base\Common\Metadata\Grouping;
/**
* Base class for the various types of custom menus
*
@@ -7,7 +11,7 @@
* @package UIExtensibilityAPI
* @since 2.0
*/
abstract class ApplicationPopupMenuItem
abstract class ApplicationPopupMenuItem implements iGroupingProvider, iDataAttributesProvider, iAriaAttributesProvider
{
/** @ignore */
protected $sUID;
@@ -19,6 +23,12 @@ abstract class ApplicationPopupMenuItem
protected $sIconClass;
/** @ignore */
protected $aCssClasses;
/** @since 3.3.0 */
protected ?AriaAttributes $oAriaAttributes = null;
/** @since 3.3.0 */
protected ?DataAttributes $oDataAttributes = null;
/** @since 3.3.0 */
protected ?Grouping $oGrouping = null;
/**
* Constructor
@@ -58,6 +68,17 @@ abstract class ApplicationPopupMenuItem
return $this->sLabel;
}
/**
* @param string $sLabel
*
* @api
* @since 3.3.0
*/
public function SetLabel(string $sLabel): void
{
$this->sLabel = $sLabel;
}
/**
* Get the CSS classes
*
@@ -141,9 +162,76 @@ abstract class ApplicationPopupMenuItem
*/
abstract public function GetMenuItem();
/**
* Get additional data, child classes can use this without having to worry about this class interfaces
* @return array<string, mixed>
* @since 3.3.0
*/
protected function GetMenuItemAdditionalData(): array
{
$aAdditionalData = [];
$aDataAttributes = $this->GetDataAttributes()->GetAttributes();
if (!empty($aDataAttributes)) {
$aAdditionalData['data_attributes'] = $aDataAttributes;
}
$aAriaAttributes = $this->GetAriaAttributes()->GetAttributes();
if (!empty($aAriaAttributes)) {
$aAdditionalData['aria_attributes'] = $aAriaAttributes;
}
$oGrouping = $this->GetGrouping();
if ($oGrouping !== null) {
$aAdditionalData['grouping_uid'] = $oGrouping->GetUID();
}
return $aAdditionalData;
}
/** @ignore */
public function GetLinkedScripts()
{
return [];
}
/**
* @api
* @since 3.3.0
*/
public function GetAriaAttributes(): AriaAttributes
{
return $this->oAriaAttributes ??= new AriaAttributes();
}
/**
* @api
* @since 3.3.0
*/
public function GetDataAttributes(): DataAttributes
{
return $this->oDataAttributes ??= new DataAttributes();
}
/**
* @api
* @since 3.3.0
*/
public function GetGrouping(): ?Grouping
{
return $this->oGrouping;
}
/**
* @param Grouping|null $oGrouping
* @return static
* @api
* @since 3.3.0
*/
public function SetGrouping(?Grouping $oGrouping): static
{
$this->oGrouping = $oGrouping;
return $this;
}
}

View File

@@ -3,7 +3,7 @@
/**
* Class for adding an item into a popup menu that triggers some Javascript code
*
* Note: This works only in the backoffice, {@see \JSButtonItem} for the end-user portal
* Note: This works only in the backoffice, {@see \common\JSButtonItem} for the end-user portal
*
* @api
* @package UIExtensibilityAPI
@@ -40,7 +40,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
public function GetMenuItem()
{
// Note: the semicolumn is a must here!
return [
$aData = [
'label' => $this->GetLabel(),
'onclick' => $this->GetJsCode().'; return false;',
'url' => $this->GetUrl(),
@@ -48,6 +48,8 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
'icon_class' => $this->sIconClass,
'tooltip' => $this->sTooltip,
];
return array_merge($aData, $this->GetMenuItemAdditionalData());
}
/** @ignore */

View File

@@ -24,6 +24,12 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
/** @ignore */
public function GetMenuItem()
{
return ['label' => '<hr class="menu-separator">', 'url' => '', 'css_classes' => $this->aCssClasses];
$aData = [
'label' => '<hr class="menu-separator">',
'url' => '',
'css_classes' => $this->aCssClasses,
];
return array_merge($aData, $this->GetMenuItemAdditionalData());
}
}

View File

@@ -3,7 +3,7 @@
/**
* Class for adding an item into a popup menu that browses to the given URL
*
* Note: This works only in the backoffice, {@see \URLButtonItem} for the end-user portal
* Note: This works only in the backoffice, {@see \common\URLButtonItem} for the end-user portal
*
* @api
* @package UIExtensibilityAPI
@@ -35,13 +35,15 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
/** @ignore */
public function GetMenuItem()
{
return ['label' => $this->GetLabel(),
$aData = ['label' => $this->GetLabel(),
'url' => $this->GetUrl(),
'target' => $this->GetTarget(),
'css_classes' => $this->aCssClasses,
'icon_class' => $this->sIconClass,
'tooltip' => $this->sTooltip,
];
return array_merge($aData, $this->GetMenuItemAdditionalData());
}
/** @ignore */

View File

@@ -0,0 +1,15 @@
<?php
use Combodo\iTop\Application\UI\Base\Common\Metadata\AriaAttributes;
/**
* Allows to provide ARIA attributes to classes that implement this interface.
* The ARIA attributes will be used to improve accessibility of the UI components
*
* @api
* @since 3.3.0
*/
interface iAriaAttributesProvider
{
public function GetAriaAttributes(): AriaAttributes;
}

View File

@@ -0,0 +1,14 @@
<?php
use Combodo\iTop\Application\UI\Base\Common\Metadata\DataAttributes;
/**
* Allows to provide data attributes to classes that implement this interface.
* The data attributes will be used to provide additional information to the UI components
* @api
* @since 3.3.0
*/
interface iDataAttributesProvider
{
public function GetDataAttributes(): DataAttributes;
}

View File

@@ -0,0 +1,13 @@
<?php
use Combodo\iTop\Application\UI\Base\Common\Metadata\Grouping;
/**
* Allows to provide grouping information to classes that implement this interface.
* @api
* @since 3.3.0
*/
interface iGroupingProvider
{
public function GetGrouping(): ?Grouping;
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Implemented by metadata value objects that flatten to HTML attributes.
* Ease the implementation of classes that need to provide HTML attributes to the UI components
*
* @api
* @since 3.3.0
*/
interface iHtmlAttributesSource
{
/** @return array<string, string> Attribute name => value, ready to be escaped and printed */
public function GetHtmlAttributes(): array;
}

View File

@@ -95,7 +95,24 @@ interface iPopupMenuExtension
* @todo
*/
public const PORTAL_MENU_ACTIONS = 10;
/**
* Get the list of items to be added to the backoffice top-bar items
* $param is an array: [] for now, in a distant future we want to give a complete context
* @since 3.3.0
*/
public const MENU_TOPBAR_ACTIONS = 11;
/**
* Get the list of items to be added to the actions on a given form field (i.e. the attribute of an object)
* $param is an array: ['object' => DBObject, att_code => attribute code , mode => edit/read]
* @since 3.3.0
*/
public const MENU_OBJDETAILS_FIELD_ACTIONS = 12;
/**
* Get the list of items to be added to the actions in the activity panel (read-only mode)
* $param is an array: ['object' => DBObject, caselog_att_code => caselog attribute code or 'activity']
* @since 3.3.0
*/
public const MENU_OBJDETAILS_ACTIVITY_PANEL_ACTIONS = 13;
/**
* Get the list of items to be added to a menu.
*

View File

@@ -49,6 +49,7 @@ use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Renderer\BlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
use Combodo\iTop\Service\InterfaceDiscovery\InterfaceDiscovery;
use Combodo\iTop\Service\Links\LinkSetDataTransformer;
use Combodo\iTop\Service\Links\LinkSetModel;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectHelper;
@@ -1046,7 +1047,7 @@ HTML
// Add extra data for markup generation
// - Attribute code and AttributeDef. class
$val['attcode'] = $sAttCode;
$val['atttype'] = $oAttDef->GetType();
$val['atttype'] = $oAttDef->GetTypeShortClassName();
$val['attlabel'] = $sAttLabel;
$val['attflags'] = ($bEditMode) ? $this->GetFormAttributeFlags($sAttCode) : OPT_ATT_READONLY;
@@ -1065,6 +1066,14 @@ HTML
}
}
$val['value_raw'] = ($bExcludeRawValue === false) ? $this->Get($sAttCode) : '';
$val['actions'] = [];
/** @var \iPopupMenuExtension $oExtensionInstance */
foreach (InterfaceDiscovery::GetInstance()->FindItopClasses('iPopupMenuExtension') as $oExtensionInstance) {
foreach ($oExtensionInstance::EnumItems(iPopupMenuExtension::MENU_OBJDETAILS_FIELD_ACTIONS, ['object' => $this, 'att_code' => $sAttCode, 'mode' => $bEditMode ? 'edit' : 'read']) as $oMenuItem) {
$val['actions'][] = $oMenuItem;
}
}
// The field is visible, add it to the current column
$oField = FieldUIBlockFactory::MakeFromParams($val);
@@ -1535,190 +1544,6 @@ HTML
return $sHtml;
}
/**
* @param WebPage $oPage
* @param \CMDBObjectSet $oSet
* @param array $aParams
*
* @throws \Exception
* only used in old and deprecated export.php
*
* @internal Only to be used by `/webservices/export.php` : this is a legacy method that produces wrong HTML (no TR on table body rows)
*/
public static function DisplaySetAsHTMLSpreadsheet(WebPage $oPage, CMDBObjectSet $oSet, $aParams = [])
{
$oPage->add(self::GetSetAsHTMLSpreadsheet($oSet, $aParams));
}
/**
* Spreadsheet output: designed for end users doing some reporting
* Then the ids are excluded and replaced by the corresponding friendlyname
*
* @param \DBObjectSet $oSet
* @param array $aParams
*
* @return string
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \Exception
*
* @internal Only to be used by `/webservices/export.php` : this is a legacy method that produces wrong HTML (no TR on table body rows)
*/
public static function GetSetAsHTMLSpreadsheet(DBObjectSet $oSet, $aParams = [])
{
$aFields = null;
if (isset($aParams['fields']) && (strlen($aParams['fields']) > 0)) {
$aFields = explode(',', $aParams['fields']);
}
$bFieldsAdvanced = false;
if (isset($aParams['fields_advanced'])) {
$bFieldsAdvanced = (bool)$aParams['fields_advanced'];
}
$bLocalize = true;
if (isset($aParams['localize_values'])) {
$bLocalize = (bool)$aParams['localize_values'];
}
$aList = [];
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
$aAuthorizedClasses = [];
foreach ($aClasses as $sAlias => $sClassName) {
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) {
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
$aHeader = [];
foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
$aList[$sAlias] = [];
foreach (MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) {
if (is_null($aFields) || (count($aFields) == 0)) {
// Standard list of attributes (no link sets)
if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField())) {
$sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode;
$aList[$sAlias][$sAttCodeEx] = $oAttDef;
if ($bFieldsAdvanced && $oAttDef->IsExternalKey(EXTKEY_RELATIVE)) {
$sRemoteClass = $oAttDef->GetTargetClass();
foreach (MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) {
$aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef(
$sRemoteClass,
$sRemoteAttCode
);
}
}
}
} else {
// User defined list of attributes
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields)) {
$aList[$sAlias][$sAttCode] = $oAttDef;
}
}
}
// Replace external key by the corresponding friendly name (if not already in the list)
foreach ($aList[$sAlias] as $sAttCode => $oAttDef) {
if ($oAttDef->IsExternalKey()) {
unset($aList[$sAlias][$sAttCode]);
$sFriendlyNameAttCode = $sAttCode.'_friendlyname';
if (!array_key_exists(
$sFriendlyNameAttCode,
$aList[$sAlias]
) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode)) {
$oFriendlyNameAtt = MetaModel::GetAttributeDef($sClassName, $sFriendlyNameAttCode);
$aList[$sAlias][$sFriendlyNameAttCode] = $oFriendlyNameAtt;
}
}
}
foreach ($aList[$sAlias] as $sAttCodeEx => $oAttDef) {
$sColLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx) : $sAttCodeEx;
$oFinalAttDef = $oAttDef->GetFinalAttDef();
if (get_class($oFinalAttDef) == 'AttributeDateTime') {
$aHeader[] = $sColLabel.' ('.Dict::S('UI:SplitDateTime-Date').')';
$aHeader[] = $sColLabel.' ('.Dict::S('UI:SplitDateTime-Time').')';
} else {
$aHeader[] = $sColLabel;
}
}
}
$sHtml = "<table border=\"1\">\n";
$sHtml .= "<tr>\n";
$sHtml .= "<td>".implode("</td><td>", $aHeader)."</td>\n";
$sHtml .= "</tr>\n";
$oSet->Seek(0);
while ($aObjects = $oSet->FetchAssoc()) {
$aRow = [];
foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
$oObj = $aObjects[$sAlias];
foreach ($aList[$sAlias] as $sAttCodeEx => $oAttDef) {
if (is_null($oObj)) {
$aRow[] = '<td></td>';
} else {
$oFinalAttDef = $oAttDef->GetFinalAttDef();
if (get_class($oFinalAttDef) == 'AttributeDateTime') {
$sDate = $oObj->Get($sAttCodeEx);
if ($sDate === null) {
$aRow[] = '<td></td>';
$aRow[] = '<td></td>';
} else {
$iDate = AttributeDateTime::GetAsUnixSeconds($sDate);
$aRow[] = '<td>'.date(
'Y-m-d',
$iDate
).'</td>'; // Format kept as-is for 100% backward compatibility of the exports
$aRow[] = '<td>'.date(
'H:i:s',
$iDate
).'</td>'; // Format kept as-is for 100% backward compatibility of the exports
}
} else {
if ($oAttDef instanceof AttributeCaseLog) {
$rawValue = $oObj->Get($sAttCodeEx);
$outputValue = str_replace(
"\n",
"<br/>",
utils::EscapeHtml($rawValue->__toString())
);
// Trick for Excel: treat the content as text even if it begins with an equal sign
$aRow[] = '<td x:str>'.$outputValue.'</td>';
} else {
$rawValue = $oObj->Get($sAttCodeEx);
// Due to custom formatting rules, empty friendlynames may be rendered as non-empty strings
// let's fix this and make sure we render an empty string if the key == 0
if ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) {
$sKeyAttCode = $oAttDef->GetKeyAttCode();
if ($oObj->Get($sKeyAttCode) == 0) {
$rawValue = '';
}
}
if ($bLocalize) {
$outputValue = utils::EscapeHtml($oFinalAttDef->GetEditValue($rawValue));
} else {
$outputValue = utils::EscapeHtml($rawValue);
}
$aRow[] = '<td>'.$outputValue.'</td>';
}
}
}
}
}
$sHtml .= implode("\n", $aRow);
$sHtml .= "</tr>\n";
}
$sHtml .= "</table>\n";
return $sHtml;
}
/**
* @param WebPage $oPage
* @param \CMDBObjectSet $oSet
@@ -3250,8 +3075,12 @@ EOF
}
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
if (is_array($aAllowedValues) && count($aAllowedValues) == 1) {
$aValues = array_keys($aAllowedValues);
$this->Set($sAttCode, $aValues[0]);
// linkset are ignored
// N°9622 - Error in the user story life cycle
if (!$oAttDef->IsLinkSet()) {
$aValues = array_keys($aAllowedValues);
$this->Set($sAttCode, $aValues[0]);
}
}
}
}
@@ -4167,6 +3996,27 @@ HTML;
$sTagSetJson = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
if ($sTagSetJson !== null) { // bulk modify, direct linked set not handled
$value = json_decode($sTagSetJson, true);
if ($this->IsNew()) {
if (is_array($value['orig_value'])) {
foreach ($value['orig_value'] as $val) {
if (!in_array($val, $value['removed'])) {
$value['added'][] = $val;
}
}
}
} else {
$aCurrentValues = $this->Get($sAttCode)->GetValues();
foreach ($value['orig_value'] as $val) {
if (!in_array($val, $aCurrentValues) && !in_array($val, $value['removed']) && !in_array($val, $value['added'])) {
$value['added'][] = $val;
}
}
foreach ($aCurrentValues as $val) {
if (!in_array($val, $value['orig_value']) && !in_array($val, $value['removed']) && !in_array($val, $value['added'])) {
$value['removed'][] = $val;
}
}
}
}
break;
@@ -4507,7 +4357,7 @@ HTML;
$oDivField = FieldUIBlockFactory::MakeLarge("");
// UIContentBlockUIBlockFactory::MakeStandard(null,["field_container field_large"]);
$oDivField->AddDataAttribute("attribute-type", $oAttDef->GetType());
$oDivField->AddDataAttribute("attribute-type", $oAttDef->GetTypeShortClassName());
$oDivField->AddDataAttribute("attribute-label", $sAttMetaDataLabel);
$oDivField->AddDataAttribute("attribute-flag-hidden", false);
$oDivField->AddDataAttribute("attribute-flag-read-only", false);
@@ -4810,7 +4660,7 @@ EOF
// Not in preview mode, do the update for real
$sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id');
if (!utils::IsTransactionValid($sTransactionId, false)) {
throw new Exception(Dict::S('UI:Error:ObjectAlreadyUpdated'));
throw new Exception(Dict::S('UI:Error:InvalidToken'));
}
utils::RemoveTransaction($sTransactionId);
}

View File

@@ -501,7 +501,7 @@ EOF
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = [], $bCanEdit = true)
{
$aExtraParams['dashboard_div_id'] = utils::Sanitize($aExtraParams['dashboard_div_id'] ?? null, $this->GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
$aExtraParams['dashboard_div_id'] = utils::Sanitize($aExtraParams['dashboard_div_id'] ?? $this->GetId(), $this->GetId(), utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
/** @var \DashboardLayoutMultiCol $oLayout */
$oLayout = new $this->sLayoutClass();
@@ -586,7 +586,7 @@ JS
/** @var \Dashlet $oDashlet */
foreach ($aCell as $oDashlet) {
if ($oDashlet->IsVisible()) {
$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$oDashlet->GetID().'" style="display:none">');
$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$oDashlet->GetID().'" data-role="ibo-dashlet-properties" data-dashlet-class="'.$oDashlet->GetDashletType().'" style="display:none">');
$oForm = $oDashlet->GetForm();
$this->SetFormParams($oForm, $aExtraParams);
$oForm->RenderAsPropertySheet($oPage, false, '.itop-dashboard');
@@ -1203,7 +1203,7 @@ EOF
}
$aRenderParams['dashboard_div_id'] = $aExtraParams['dashboard_div_id'];
$sJSExtraParams = json_encode($aExtraParams);
$oPage->add('<div id="dashboard_editor" class="ibo-dashboard-editor" data-role="ibo-dashboard-editor">');
$oPage->add('<div id="dashboard_editor" class="ibo-dashboard-editor" data-role="ibo-dashboard-editor" data-dashboard-id="'.$this->sId.'">');
$oPage->add('<div class="ui-layout-center">');
$this->SetCustomFlag(true);
$this->Render($oPage, true, $aRenderParams);

View File

@@ -25,6 +25,20 @@ use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\UIBlock;
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Core\AttributeDefinition\AttributeDateTime;
use Combodo\iTop\Core\AttributeDefinition\AttributeDecimal;
use Combodo\iTop\Core\AttributeDefinition\AttributeDuration;
use Combodo\iTop\Core\AttributeDefinition\AttributeEncryptedString;
use Combodo\iTop\Core\AttributeDefinition\AttributeEnum;
use Combodo\iTop\Core\AttributeDefinition\AttributeExternalField;
use Combodo\iTop\Core\AttributeDefinition\AttributeFriendlyName;
use Combodo\iTop\Core\AttributeDefinition\AttributeInteger;
use Combodo\iTop\Core\AttributeDefinition\AttributeLinkedSet;
use Combodo\iTop\Core\AttributeDefinition\AttributeOneWayPassword;
use Combodo\iTop\Core\AttributeDefinition\AttributePassword;
use Combodo\iTop\Core\AttributeDefinition\AttributePercentage;
use Combodo\iTop\Core\AttributeDefinition\AttributeSubItem;
use Combodo\iTop\Core\AttributeDefinition\iAttributeNoGroupBy;
require_once(APPROOT.'application/forms.class.inc.php');
@@ -440,7 +454,7 @@ EOF
// For external fields, find the real type of the target
$sExtFieldAttCode = $sAttCode;
$sTargetClass = $sClass;
while (is_a($sAttType, 'AttributeExternalField', true)) {
while (is_a($sAttType, AttributeExternalField::class, true)) {
$sExtKeyAttCode = $this->oModelReflection->GetAttributeProperty($sTargetClass, $sExtFieldAttCode, 'extkey_attcode');
$sTargetAttCode = $this->oModelReflection->GetAttributeProperty($sTargetClass, $sExtFieldAttCode, 'target_attcode');
$sTargetClass = $this->oModelReflection->GetAttributeProperty($sTargetClass, $sExtKeyAttCode, 'targetclass');
@@ -450,13 +464,13 @@ EOF
}
$aForbidenAttType = [
'AttributeLinkedSet',
'AttributeFriendlyName',
AttributeLinkedSet::class,
AttributeFriendlyName::class,
'iAttributeNoGroupBy', //we cannot only use iAttributeNoGroupBy since this method is also used by the designer who do not have access to the classes' PHP reflection API. So the known classes has to be listed altogether
'AttributeOneWayPassword',
'AttributeEncryptedString',
'AttributePassword',
iAttributeNoGroupBy::class, //we cannot only use iAttributeNoGroupBy since this method is also used by the designer who do not have access to the classes' PHP reflection API. So the known classes has to be listed altogether
AttributeOneWayPassword::class,
AttributeEncryptedString::class,
AttributePassword::class,
];
foreach ($aForbidenAttType as $sForbidenAttType) {
if (is_a($sAttType, $sForbidenAttType, true)) {
@@ -468,7 +482,7 @@ EOF
if (!in_array($sLabel, $aGroupBy)) {
$aGroupBy[$sAttCode] = $sLabel;
if (is_a($sAttType, 'AttributeDateTime', true)) {
if (is_a($sAttType, AttributeDateTime::class, true)) {
$aGroupBy[$sAttCode.':hour'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Hour', $sLabel);
$aGroupBy[$sAttCode.':month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Month', $sLabel);
$aGroupBy[$sAttCode.':day_of_week'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek', $sLabel);
@@ -1102,7 +1116,7 @@ abstract class DashletGroupBy extends Dashlet
$aAttributeTypes = $this->oModelReflection->ListAttributes($this->sClass);
if (isset($aAttributeTypes[$this->sGroupByAttCode])) {
$sAttributeType = $aAttributeTypes[$this->sGroupByAttCode];
if (is_subclass_of($sAttributeType, 'AttributeDateTime') || $sAttributeType == 'AttributeDateTime') {
if (is_subclass_of($sAttributeType, AttributeDateTime::class) || $sAttributeType == AttributeDateTime::class) {
$this->sOrderDirection = 'asc';
} else {
$this->sOrderDirection = 'desc';
@@ -1258,7 +1272,7 @@ abstract class DashletGroupBy extends Dashlet
if ($this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode)) {
$aAttributeTypes = $this->oModelReflection->ListAttributes($sClass);
$sAttributeType = $aAttributeTypes[$this->sGroupByAttCode];
if (is_subclass_of($sAttributeType, 'AttributeDateTime') || $sAttributeType == 'AttributeDateTime') {
if (is_subclass_of($sAttributeType, AttributeDateTime::class) || $sAttributeType == AttributeDateTime::class) {
// Note: an alternative to this somewhat hardcoded way of doing things would be to implement...
//$oExpr = Expression::FromOQL($this->sGroupByExpr);
//$aTranslationData = array($oQuery->GetClassAlias() => array($this->sGroupByAttCode => new ScalarExpression(date('Y-m-d H:i:s', $iTime))));
@@ -1289,7 +1303,7 @@ abstract class DashletGroupBy extends Dashlet
foreach ($aValues as $sValue) {
$aDisplayValues[] = ['label' => $sValue, 'value' => (int)rand(1, 15)];
}
} elseif (is_subclass_of($sAttributeType, 'AttributeEnum') || $sAttributeType == 'AttributeEnum') {
} elseif (is_subclass_of($sAttributeType, AttributeEnum::class) || $sAttributeType == AttributeEnum::class) {
$aAllowed = $this->oModelReflection->GetAllowedValues_att($sClass, $this->sGroupByAttCode);
if ($aAllowed) { // null for non enums
foreach ($aAllowed as $sValue => $sValueLabel) {
@@ -1458,11 +1472,11 @@ abstract class DashletGroupBy extends Dashlet
}
foreach ($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType) {
switch ($sAttType) {
case 'AttributeDecimal':
case 'AttributeDuration':
case 'AttributeInteger':
case 'AttributePercentage':
case 'AttributeSubItem': // TODO: Known limitation: no unit displayed (values in sec)
case AttributeDecimal::class:
case AttributeDuration::class:
case AttributeInteger::class:
case AttributePercentage::class:
case AttributeSubItem::class: // TODO: Known limitation: no unit displayed (values in sec)
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
$aFunctionAttributes[$sAttCode] = $sLabel;
break;

View File

@@ -563,6 +563,17 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
</event_datum>
</event_data>
</event>
<event id="EVENT_SERVICE_LOCATOR_INITIALIZED" _delta="define">
<name>Service Locator Initialized</name>
<internal>true</internal>
<description><![CDATA[Inform the listeners that Service Locator has been (re)initialized]]></description>
<event_data>
<event_datum id="file_name">
<description>The configuration file used to initialize Service Locator</description>
<type>string</type>
</event_datum>
</event_data>
</event>
</events>
<meta>
<classes>

View File

@@ -1707,7 +1707,7 @@ JS
$oBlock->bAdvancedMode = utils::ReadParam('advanced', false);
$oBlock->sCsvFile = strtolower($this->m_oFilter->GetClass()).'.csv';
$oBlock->sDownloadLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?expression='.urlencode($this->m_oFilter->ToOQL(true)).'&format=csv&filename='.urlencode($oBlock->sCsvFile);
$oBlock->sDownloadLink = utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php?expression='.urlencode($this->m_oFilter->ToOQL(true)).'&format=csv&filename='.urlencode($oBlock->sCsvFile);
$oBlock->sLinkToToggle = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search'.$oAppContext->GetForLink(true).'&filter='.rawurlencode($this->m_oFilter->serialize()).'&format=csv';
// Pass the parameters via POST, since expression may be very long
$aParamsToPost = [
@@ -1724,7 +1724,7 @@ JS
$oBlock->sLinkToToggle = $oBlock->sLinkToToggle.'&advanced=1';
$oBlock->sChecked = '';
}
$oBlock->sAjaxLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php';
$oBlock->sAjaxLink = utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php';
$oBlock->sCharsetNotice = false;
$oBlock->sJsonParams = json_encode($aParamsToPost);
@@ -2292,6 +2292,17 @@ class MenuBlock extends DisplayBlock
}
// ResourceId should not be sanitized
$oActionButton->AddDataAttribute('resource-id', $sActionId);
if (isset($aAction['data_attributes']) && is_array($aAction['data_attributes'])) {
foreach ($aAction['data_attributes'] as $sName => $sValue) {
$sDataAttributeName = $sName;
$oActionButton->AddDataAttribute($sDataAttributeName, (string) $sValue);
}
}
if (isset($aAction['aria_attributes']) && is_array($aAction['aria_attributes'])) {
foreach ($aAction['aria_attributes'] as $sName => $sValue) {
$oActionButton->AddAriaAttribute((string) $sName, (string) $sValue);
}
}
$oActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
if (empty($sLabel)) {
if (empty($aAction['tooltip'])) {

View File

@@ -220,10 +220,19 @@ class DesignerForm
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply" >'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel"><span><div class="ibo-button ibo-is-alternative ibo-is-neutral" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-undo"></i></div></span></td>'
.$this->EndRow();
$aPropertyFieldData = [
'code' => $oField->GetCode(),
'type' => $oField->GetInputType(),
'label' => $aRow['label'],
'raw-value' => $oField->GetRawValue(),
];
if (is_null($aRow['label'])) {
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_value" colspan="2">'.$aRow['value'];
$sReturn .= $this->StartRow($sFieldId, $aPropertyFieldData).
'<td class="prop_value" data-role="ibo-property-field-value" colspan="2">'.$aRow['value'];
} else {
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_label">'.$aRow['label'].'</td><td class="prop_value">'.$aRow['value'];
$sReturn .= $this->StartRow($sFieldId, $aPropertyFieldData).
'<td class="prop_label" data-role="ibo-property-field-label">'.$aRow['label'].
'</td><td class="prop_value" data-role="ibo-property-field-value">'.$aRow['value'];
}
if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField)) {
$sReturn .= $sValidationFields;
@@ -294,12 +303,21 @@ EOF
}
}
public function StartRow($sFieldId = null)
public function StartRow($sFieldId = null, $aPropertyFieldData = [])
{
if ($sFieldId != null) {
return '<tr id="row_'.$sFieldId.'" data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'">';
$sPropertyFieldAttributes = '';
foreach ($aPropertyFieldData as $sAttribute => $value) {
if (is_array($value)) {
$value = json_encode($value);
} elseif (is_null($value)) {
$value = '';
}
$sPropertyFieldAttributes .= ' data-property-field-'.$sAttribute.'="'.utils::EscapeHtml((string)$value).'"';
}
return '<tr data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'">';
if ($sFieldId != null) {
return '<tr id="row_'.$sFieldId.'" data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'"'.$sPropertyFieldAttributes.'>';
}
return '<tr data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'"'.$sPropertyFieldAttributes.'>';
}
public function EndRow()
@@ -679,6 +697,14 @@ class DesignerFormField
return $this->sCode;
}
/**
* @return mixed
*/
public function GetRawValue()
{
return $this->defaultValue;
}
/**
* @param \DesignerForm $oForm
*/
@@ -1629,6 +1655,11 @@ class DesignerFormSelectorField extends DesignerFormField
return 'selector_property_field';
}
public function GetRawValue()
{
return $this->defaultRealValue;
}
public function AddSubForm($oSubForm, $sLabel, $sValue)
{
$this->aSubForms[] = ['form' => $oSubForm, 'label' => $sLabel, 'value' => $sValue];

View File

@@ -25,11 +25,11 @@
*/
use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\WebPage\ErrorPage;
use Combodo\iTop\Application\WebPage\NiceWebPage;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\Session\Session;
/**
* Web page used for displaying the login form
@@ -369,16 +369,7 @@ class LoginWebPage extends NiceWebPage
public static function ResetSession()
{
// Unset all of the session variables.
Session::Unset('auth_user');
Session::Unset('login_state');
Session::Unset('can_logoff');
Session::Unset('archive_mode');
Session::Unset('impersonate_user');
Session::Unset('PluginProperties');
Session::Unset('UrlMakerClass');
Session::Unset('itop_env');
Session::Unset('obj_messages');
Session::Unset('profile_list');
Session::UnsetAll();
UserRights::_ResetSessionCache();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!

View File

@@ -115,7 +115,7 @@ class privUITransactionSession
// Strictly speaking, the two lines below should be grouped together
// by a critical section
// sem_acquire($rSemIdentified);
$id = static::GetUserPrefix().str_replace(['.', ' '], '', microtime());
$id = static::GetUserPrefix().str_replace(['.', ' '], '', microtime()).'-'.bin2hex(random_bytes(24));
Session::Set(['transactions', $id], true);
// sem_release($rSemIdentified);
@@ -236,7 +236,7 @@ class privUITransactionFile
self::CleanupOldTransactions();
$sTransactionIdFullPath = tempnam(utils::GetDataPath().'transactions', static::GetUserPrefix());
$sTransactionIdFullPath = static::CreateUniqueTransactionFilePath(utils::GetDataPath().'transactions', static::GetUserPrefix());
file_put_contents($sTransactionIdFullPath, $iCurrentUserId, LOCK_EX);
$sTransactionIdFileName = basename($sTransactionIdFullPath);
@@ -245,6 +245,33 @@ class privUITransactionFile
return $sTransactionIdFileName;
}
/**
* Create a transaction file path with a longer random suffix and create it atomically.
*
* @param string $sDir
* @param string $sPrefix
*
* @return string
*
* @throws CoreException
*/
private static function CreateUniqueTransactionFilePath(string $sDir, string $sPrefix): string
{
$iMaxAttempts = 10;
for ($i = 0; $i < $iMaxAttempts; $i++) {
$sFileName = $sPrefix.bin2hex(random_bytes(24)); // 48 random hex chars
$sFullPath = $sDir.'/'.$sFileName;
$hFile = @fopen($sFullPath, 'x');
if ($hFile !== false) {
fclose($hFile);
return $sFullPath;
}
}
throw new CoreException('Failed to allocate a unique transaction file name after multiple attempts.');
}
/**
* Check whether a transaction is valid or not and (optionally) remove the valid transaction from
* the session so that another call to IsTransactionValid for the same transaction id
@@ -281,6 +308,18 @@ class privUITransactionFile
return false;
}
$iTime = filemtime($sFilepath);
$iLifetime = (int) MetaModel::GetConfig()->Get('transactions_file_lifetime');
$iLimit = time() - $iLifetime;
if ($iTime < $iLimit) {
self::Info("IsTransactionValid: Transaction '$id' expired.");
if ($iLifetime < 3600) {
IssueLog::Warning("IsTransactionValid: Transaction '$id' expired after only $iLifetime seconds. Consider increasing the 'transactions_file_lifetime' configuration parameter.");
}
@unlink($sFilepath);
return false;
}
if ($bRemoveTransaction) {
$bResult = @unlink($sFilepath);
if (!$bResult) {
@@ -314,7 +353,7 @@ class privUITransactionFile
}
/**
* Cleanup old transactions which have been pending since more than 24 hours
* Cleanup old transactions which have been pending since more than the lifetime defined in the configuration parameter 'transactions_file_lifetime'.
* Use filemtime instead of filectime since filectime may be affected by operations on the directory (like changing the access rights)
*/
protected static function CleanupOldTransactions($sTransactionDir = null)
@@ -327,7 +366,7 @@ class privUITransactionFile
}
clearstatcache();
$iLimit = time() - 24 * 3600;
$iLimit = time() - (int) MetaModel::GetConfig()->Get('transactions_file_lifetime');
$sPattern = $sTransactionDir ? "$sTransactionDir/*" : utils::GetDataPath().'transactions/*';
$aTransactions = glob($sPattern);
foreach ($aTransactions as $sFileName) {

View File

@@ -1833,10 +1833,15 @@ SQL;
/**
* Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore)
* @param string $sId The ID to sanitize
* @param bool $bForSelector Whether to apply strict CSS/jQuery selector-safe sanitization
* @return string The sanitized ID
*/
public static function GetSafeId($sId)
public static function GetSafeId($sId, bool $bForSelector = false)
{
if ($bForSelector) {
return preg_replace('/[^a-zA-Z0-9_]/', '_', $sId);
}
return str_replace([':', '[', ']', '+', '-', ' '], '_', $sId);
}
@@ -1886,7 +1891,7 @@ SQL;
CURLOPT_HEADER => false, // don't return the headers in the output
CURLOPT_FOLLOWLOCATION => true, // follow redirects
CURLOPT_ENCODING => "", // handle all encodings
CURLOPT_USERAGENT => "spider", // who am i
CURLOPT_USERAGENT => static::GetConfig()->Get('http.request.user_agent'), // who am i
CURLOPT_AUTOREFERER => true, // set referer on redirect
CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect
CURLOPT_TIMEOUT => 120, // timeout on response
@@ -3022,7 +3027,6 @@ TXT
* Note: Only works for backoffice URLs for now
*
* @param string $sText Text containing the mentioned objects to be found
* @param string $sFormat {@uses static::ENUM_TEXT_FORMAT_HTML, ...}
*
* @return array Array of object classes / IDs for the ones found in $sText
*
@@ -3037,13 +3041,45 @@ TXT
public static function GetMentionedObjectsFromText(string $sText): array
{
$aMentionedObjects = [];
$aMentionMatches = [];
$sText = html_entity_decode($sText);
$aMentionAllowedClasses = MetaModel::GetConfig()->Get('mentions.allowed_classes');
$oDom = new \DOMDocument();
$bPreviousUseInternalErrors = libxml_use_internal_errors(true); // to keep processing even in case of "invalid" HTML, cf. testGetMentionedObjectsFromText
preg_match_all('/<a\s*([^>]*)data-object-class="([^"]*)"\s.*data-object-key="([^"]*)"/Ui', $sText, $aMentionMatches);
foreach ($aMentionMatches[0] as $iMatchIdx => $sCompleteMatch) {
$sMatchedClass = $aMentionMatches[2][$iMatchIdx];
$sMatchedId = $aMentionMatches[3][$iMatchIdx];
try {
$oDom->loadHTML('<?xml encoding="UTF-8">'.$sText);
} finally {
libxml_clear_errors();
libxml_use_internal_errors($bPreviousUseInternalErrors);
}
$oXpath = new \DOMXPath($oDom);
$oNodes = $oXpath->query('//a[@data-object-class and @data-object-key]');
foreach ($oNodes as $oObjNode) {
$sMatchedClass = $oObjNode->getAttribute('data-object-class');
$sMatchedId = $oObjNode->getAttribute('data-object-key');
$sMatchedName = trim($oObjNode->textContent);
// Ensure that what we found is actually configured as a mention
$sMentionPrefix = array_search($sMatchedClass, $aMentionAllowedClasses);
// - No direct configuration for the matched class, let's look for a configuration on parent classes
if (false === $sMentionPrefix) {
$bHasMentionConfigurationForParentClass = false;
foreach (MetaModel::EnumParentClasses($sMatchedClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false) as $aMatchedParentClass) {
$sMentionPrefix = array_search($aMatchedParentClass, $aMentionAllowedClasses);
if (false !== $sMentionPrefix) {
$bHasMentionConfigurationForParentClass = true;
break;
}
}
if (false === $bHasMentionConfigurationForParentClass) {
continue;
}
}
// - Test if the name starts with $sMentionPrefix (e.g. '@' for 'Contact' class)
if (false === str_starts_with($sMatchedName, $sMentionPrefix)) {
continue;
}
// Prepare array for matched class if not already present
if (!array_key_exists($sMatchedClass, $aMentionedObjects)) {
@@ -3218,4 +3254,48 @@ TXT
return (int)$sLimit;
}
}
/**
* @since 3.3.0 N°9875
* Open archive and raise appropriate exception.
* Warning: do not forget to close archive afterwhile
* @param string $sArchiveFilePath
* @param int|null $flags
* @return ZipArchive
* @throws \Exception
*/
public static function ZipArchiveOpen(string $sArchiveFilePath, int|null $flags = null): ZipArchive
{
$oZip = new ZipArchive();
if (is_null($flags)) {
$code = $oZip->open($sArchiveFilePath);
} else {
$code = $oZip->open($sArchiveFilePath, $flags);
}
if (true !== $code) {
//ZipArchive::ZIP_ER_NOZIP : 19
if ($code === 19) {
throw new \Exception(sprintf('Cannot open zip file due to inconsistent or empty content'));
}
throw new \Exception(sprintf('Cannot open zip file due to error code %s', $code));
}
return $oZip;
}
/**
* @since 3.3.0 N°9875
* @param string $sDirectory
* @param string $sPrefix
* @return ZipArchive
* @throws \Exception
*/
public static function ZipArchiveCreateWithTempNam(string $sDirectory, string $sPrefix): ZipArchive
{
$sTempnam = tempnam($sDirectory, $sPrefix);
unlink($sTempnam);
$sArchiveName = $sTempnam.'.zip';
return self::ZipArchiveOpen($sArchiveName, ZipArchive::CREATE);
}
}

View File

@@ -607,6 +607,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'attribute.max_actions_items' => [
'type' => 'integer',
'description' => 'Maximum number of actions to display before it\'s compacted into a single action (0 means no limit)',
'default' => 2,
'value' => 2,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'email_asynchronous' => [
'type' => 'bool',
'description' => 'If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode',
@@ -1288,6 +1296,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'transactions_file_lifetime' => [
'type' => 'integer',
'description' => 'Value in seconds for the lifetime of a transaction file (after this duration, the transaction will be invalid and the garbage collector could delete it). This only works if the "transaction_storage" parameter is set to "File".',
'default' => 3600,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'log_transactions' => [
'type' => 'bool',
'description' => 'Whether or not to enable the debug log for the transactions.',

View File

@@ -730,13 +730,8 @@ abstract class DBObject implements iDisplay
public function SetTrim($sAttCode, $sValue)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$iMaxSize = $oAttDef->GetMaxSize();
$sLength = mb_strlen($sValue);
if ($iMaxSize && ($sLength > $iMaxSize)) {
$sMessage = " -truncated ($sLength chars)";
$sValue = mb_substr($sValue, 0, $iMaxSize - mb_strlen($sMessage)).$sMessage;
}
$this->Set($sAttCode, $sValue);
$this->Set($sAttCode, $oAttDef->TrimValue($sValue));
}
/**
@@ -2038,7 +2033,7 @@ abstract class DBObject implements iDisplay
}
}
if (!is_null($iMaxSize = $oAtt->GetMaxSize())) {
$iLen = mb_strlen($toCheck);
$iLen = $oAtt->GetSize($toCheck);
if ($iLen > $iMaxSize) {
return "String too long (found $iLen, limited to $iMaxSize)";
}

View File

@@ -20,6 +20,7 @@
use Combodo\iTop\Application\EventRegister\ApplicationEvents;
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
use Combodo\iTop\Service\DependencyInjection\ServiceLocator;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
@@ -130,6 +131,8 @@ abstract class MetaModel
private static $m_aClassToFile = [];
/** @var string */
protected static $m_sEnvironment = ITOP_DEFAULT_ENV;
/** @var \Combodo\iTop\Service\DependencyInjection\ServiceLocator */
private static ?ServiceLocator $oServiceLocator = null;
/**
* Objects currently created/updated.
@@ -1642,10 +1645,10 @@ abstract class MetaModel
if ($sFilterCode == 'id') {
return true;
}
if (array_key_exists($sClass, self::$m_aMagicFields) && array_key_exists($sFilterCode, self::$m_aMagicFields[$sClass])) {
if (array_key_exists($sClass, self::$m_aMagicFields) && in_array($sFilterCode, self::$m_aMagicFields[$sClass])) {
return true;
}
if (array_key_exists($sClass, self::$m_aFilterForbiddenAttributes) && array_key_exists($sFilterCode, self::$m_aFilterForbiddenAttributes[$sClass])) {
if (array_key_exists($sClass, self::$m_aFilterForbiddenAttributes) && in_array($sFilterCode, self::$m_aFilterForbiddenAttributes[$sClass])) {
return false;
}
return self::IsValidAttCode($sClass, $sFilterCode);
@@ -2963,8 +2966,9 @@ abstract class MetaModel
self::$m_aMagicFields[] = $sClass;
}
self::$m_aMagicFields[$sClass][] = $sCode;
self::$m_aAttribOrigins[$sClass][$sCode] = self::$m_aAttribOrigins[$sClass][$sAttCode];
}
if (!$oAttDef->IsSearchable()) {
if (!$oAttDef->IsValidFilterAttribute()) {
if (!array_key_exists($sClass, self::$m_aFilterForbiddenAttributes)) {
self::$m_aFilterForbiddenAttributes[] = $sClass;
}
@@ -5777,6 +5781,7 @@ abstract class MetaModel
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface
EventService::InitService();
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));
self::$oServiceLocator = new ServiceLocator(self::GetConfig());
return;
}
}
@@ -5792,6 +5797,8 @@ abstract class MetaModel
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface
EventService::InitService();
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));
self::$oServiceLocator = new ServiceLocator();
self::$oServiceLocator->InitFromConfig(self::GetConfig());
}
/**
@@ -6865,10 +6872,24 @@ abstract class MetaModel
require_once(APPROOT.'/core/dict.class.inc.php');
Dict::ResetCache($sAppIdentity);
$iApcDeleteFailureCount = 0;
if (function_exists('apc_delete')) {
foreach (self::GetCacheEntries($sEnvironmentId) as $sKey => $aAPCInfo) {
$sAPCKey = $aAPCInfo['info'];
apc_delete($sAPCKey);
if (!apc_delete($sAPCKey)) {
$iApcDeleteFailureCount++;
}
}
}
if ($iApcDeleteFailureCount > 0) {
SetupLog::Warning("apc_delete failed for $iApcDeleteFailureCount entries for env $sEnvironment");
}
if (function_exists('apc_clear_cache')) {
// old style APC
if (!apc_clear_cache()) {
SetupLog::Warning("apc_clear_cache failed for env $sEnvironment");
}
}
@@ -6878,7 +6899,9 @@ abstract class MetaModel
// Reset the opcache since otherwise the PHP "model" files may still be cached !!
if (function_exists('opcache_reset')) {
// Zend opcode cache
opcache_reset();
if (!opcache_reset()) {
SetupLog::Warning("opcache_reset failed for env $sEnvironment");
}
}
require_once(APPROOT.'setup/setuputils.class.inc.php');
@@ -7068,6 +7091,11 @@ abstract class MetaModel
}
}
}
public static function GetServiceLocator(): ?ServiceLocator
{
return self::$oServiceLocator;
}
}
// Standard attribute lists

View File

@@ -60,15 +60,13 @@ class OQLActualClassTreeResolver
$aTranslateFields = [];
foreach ($aExpectedAttributes as $sAttCode => $oExpression) {
// 'id' is managed later
if ($sAttCode == 'id') {
if ($sAttCode === 'id') {
continue;
}
// Attributes can be stored in attributes list or for magic ones into filter codes list.
$sOriginClass = null;
if (MetaModel::IsValidAttCode($sClass, $sAttCode)) {
if (MetaModel::IsValidAttCode($sClass, $sAttCode) || MetaModel::IsValidFilterCode($sClass, $sAttCode)) {
$sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode);
} elseif ($sAttCode == 'id') {
$sOriginClass = $sClass;
} else {
continue;
}

View File

@@ -8,6 +8,13 @@
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Core\AttributeDefinition\AttributeExternalField;
use Combodo\iTop\Core\AttributeDefinition\AttributeExternalKey;
use Combodo\iTop\Core\AttributeDefinition\AttributeFriendlyName;
use Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey;
use Combodo\iTop\Core\AttributeDefinition\AttributeLinkedSet;
use Combodo\iTop\Core\AttributeDefinition\AttributeStopWatch;
use Combodo\iTop\Core\AttributeDefinition\AttributeSubItem;
/**
* Bulk export: Tabular export: abstract base class for all "tabular" exports.
@@ -91,8 +98,8 @@ abstract class TabularBulkExport extends BulkExport
{
$aResult = [];
switch (get_class($oAttDef)) {
case 'AttributeExternalKey':
case 'AttributeHierarchicalKey':
case AttributeExternalKey::class:
case AttributeHierarchicalKey::class:
$bAddFriendlyName = true;
$oKeyAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
@@ -142,7 +149,7 @@ abstract class TabularBulkExport extends BulkExport
}
break;
case 'AttributeStopWatch':
case AttributeStopWatch::class:
foreach (MetaModel::ListAttributeDefs($sClass) as $sSubAttCode => $oSubAttDef) {
if ($oSubAttDef instanceof AttributeSubItem) {
if ($oSubAttDef->GetParentAttCode() == $sAttCode) {
@@ -318,7 +325,7 @@ EOF
$sQueryId = utils::ReadParam('query', null, true);
$sFields = utils::ReadParam('fields', null, true, 'raw_data');
if ((($sFields === null) || ($sFields === '')) && ($sQueryId === null)) {
throw new BulkExportMissingParameterException('fields');
$sFields = $this->GetFieldsFromQuery();
} else {
if (($sQueryId !== null) && ($sQueryId !== null)) {
$oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]);
@@ -329,11 +336,11 @@ EOF
// No 'fields' parameter supplied, take the fields from the query phrasebook definition
$sFields = trim($oQuery->Get('fields'));
if ($sFields === '') {
throw new BulkExportMissingParameterException('fields');
$sFields = $this->GetFieldsFromQuery();
}
}
} else {
throw BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId));
throw new BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId));
}
}
}
@@ -341,6 +348,39 @@ EOF
$this->SetFields($sFields);
}
public function GetFieldsFromQuery(): string
{
if (is_null($this->oSearch)) {
throw new BulkExportMissingParameterException("fields");
}
$aSelectedClasses = $this->oSearch->GetSelectedClasses();
$aAuthorizedClasses = [];
$aFields = [];
foreach ($aSelectedClasses as $sAlias => $sClassName) {
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ) == UR_ALLOWED_YES) {
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
foreach (MetaModel::ListAttributeDefs($sClassName) as $oAttribute) {
$sAttCode = $oAttribute->GetCode();
if (! $this->IsExportableField($sClassName, $sAttCode, $oAttribute)) {
continue;
}
$sCurrentAlias = utils::IsNullOrEmptyString($sAlias) ? "" : "$sAlias.";
$aFields[] = "{$sCurrentAlias}{$sAttCode}";
}
}
if (count($aFields) === 0) {
IssueLog::Error("User is not allowed to see any field for exported OQL");
throw new BulkExportMissingParameterException("fields");
}
return implode(',', $aFields);
}
public function SetFields($sFields)
{
// Interpret (and check) the list of fields

View File

@@ -42,7 +42,7 @@ css/backoffice/
|– vendors/ # Third-party libs, should be either:
| # - Overload of the lib SCSS variables (BEST way, but possible only if the lib exposes them. e.g. Bulma)
| # - Overload of the lib necessary CSS classes only (not great as it duplicates some rules in the browser, which add weight and computation. e.g. dataTables)
| # - Duplicate the lib CSS completly to insert SCSS variables (not great as it will be outdated when updating the lib itself. e.g. jQuery UI)
| # - Duplicate the lib CSS completely to insert SCSS variables (not great as it will be outdated when updating the lib itself. e.g. jQuery UI)
| |– _bulma-variables-overload.scss # Bulma CSS framework
| |– _jquery-ui.scss # jQuery UI
| ... # Etc…

View File

@@ -4,7 +4,7 @@
*/
$ibo-user-rights--padding-x: $ibo-spacing-400 !default;
$ibo-user-rights--padding-y: $ibo-spacing-200 !default;
$ibo-user-rights--padding-y: $ibo-spacing-100 !default;
$ibo-user-rights--border-radius: $ibo-border-radius-400 !default;
$ibo-user-rights--is-success--background-color: $ibo-color-success-100 !default;
@@ -18,6 +18,8 @@ $ibo-user-rights--is-failure--border-color: $ibo-color-danger-500 !default;
$ibo-user-rights--is-failure--border: 1px solid $ibo-user-rights--is-failure--border-color !default;
.ibo-user-rights {
display: inline-flex;
align-items: baseline;
padding: $ibo-user-rights--padding-y $ibo-user-rights--padding-x;
border-radius: $ibo-user-rights--border-radius;
&.ibo-is-success {

View File

@@ -5,6 +5,8 @@
@import "alert";
@import "button";
@import "button-bar";
@import "button-separator";
@import "button-group";
@import "breadcrumbs";
@import "quick-create";

View File

@@ -0,0 +1,43 @@
/*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-button-bar--gap: 2px !default;
$ibo-button-bar--track--gap: 6px !default;
/* button-bar.css */
ibo-button-bar{
display: flex;
flex-wrap: nowrap;
align-items: center;
position: relative;
gap: $ibo-button-bar--gap;
overflow: hidden;
}
/* Keep content visible even if JS initialization is delayed. */
ibo-button-bar .ibo-button-bar--track{
display: flex;
flex: 1 1 auto;
flex-wrap: nowrap;
overflow: hidden;
white-space: nowrap;
gap: $ibo-button-bar--track--gap;
align-items: center;
}
ibo-button-bar .ibo-button-bar--item{
flex: 0 0 auto;
white-space: nowrap;
}
ibo-button-bar .ibo-button-bar--item[hidden]{
display: none !important;
}
ibo-button-bar .ibo-button-bar--extra{
flex: 0 0 auto;
position: relative;
}

View File

@@ -0,0 +1,20 @@
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-button-separator--width: 1px !default;
$ibo-button-separator--height: 16px !default;
$ibo-button-separator--margin-x: 0.75rem !default;
$ibo-button-separator--margin-y: $ibo-spacing-0 !default;
$ibo-button-separator--border-size: 1px !default;
$ibo-button-separator--border-color: $ibo-color-grey-500 !default;
.ibo-button-separator {
display: inline-flex;
border-right: $ibo-button-separator--border-size solid $ibo-button-separator--border-color;
width: $ibo-button-separator--width;
height: $ibo-button-separator--height;
margin: $ibo-button-separator--margin-y $ibo-button-separator--margin-x;
}

View File

@@ -37,6 +37,12 @@ $ibo-field--enable-bulk--border-radius: $ibo-border-radius-500 !default;
$ibo-field--enable-bulk--checkbox--margin-left: $ibo-spacing-300 !default;
$ibo-field--label--action--margin-y: $ibo-spacing-0 !default;
$ibo-field--label--action--margin-x: $ibo-spacing-100 !default;
$ibo-field--label--action--size: $ibo-field--fullscreen-toggler--size !default;
$ibo-field--label--action--padding: $ibo-spacing-0 !default;
$ibo-field--label--action--font-size: $ibo-font-size-50 !default;
/* SCSS rules */
.ibo-field {
@extend %ibo-font-size-150;
@@ -264,4 +270,32 @@ $ibo-field--enable-bulk--checkbox--margin-left: $ibo-spacing-300 !default;
.ibo-input-select--action-buttons a {
@extend %ibo-hyperlink-inherited-colors;
}
.ibo-field--label {
.ibo-field--action:not(.ibo-button-separator) {
display: inline-flex;
align-content: center;
align-items: center;
justify-content: center;
vertical-align: middle;
width: $ibo-field--label--action--size;
height: $ibo-field--label--action--size;
padding: $ibo-field--label--action--padding;
border-radius: $ibo-field--fullscreen-toggler--border-radius;
font-size: $ibo-field--label--action--font-size;
~ .ibo-field--action {
margin-left: $ibo-field--label--action--margin-x;
}
}
.ibo-field--action.ibo-button-separator {
margin: $ibo-field--label--action--margin-y $ibo-field--label--action--margin-x;
vertical-align: middle;
}
}
// Specific rule to ensure that the grouped actions popover of a field is visible in fullscreen mode
body.ibo-has-fullscreen-descendant .ibo-popover-menu.ibo-field--grouped-actions-popover {
z-index: 1060 !important;
}

View File

@@ -8,70 +8,74 @@
padding-bottom: 14px;
}
.help-text{
padding: 1px 5px;
background-color: #d7e3f8;
border: 1px solid #c6e7f5;
border-radius: 5px;
margin: 5px 0;
font-size: 0.9em;
}
.form-error ul{
padding: 1px 5px;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 5px;
margin: 5px 0;
font-size: 0.9em;
}
.subform{
background-color: #efefef;
border-radius: 5px;
padding: 10px;
}
.form-buttons{
margin: 20px 0;
}
.form select{
padding: 0;
overflow-y: auto;
}
.form select option{
height: 30px;
display: flex;
align-items: center;
}
.turbo-refreshing{
opacity: .5;
}
.ibo-field legend{
margin-top: 24px;
}
collection-entry-element {
margin-top: 8px;
display: block;
padding: 10px 10px;
background-color: #f5f5f5;
border-radius: 5px;
}
.ts-control{
height: auto;
min-height: 30px;
}
.ibo-form-actions > .ibo-button > span{
margin-right: 5px;
}
.ibo-form textarea{
resize: vertical;
}
// WIP SDK Forms
//form[is="itop-form-element"] {
//
// .help-text{
// padding: 1px 5px;
// background-color: #d7e3f8;
// border: 1px solid #c6e7f5;
// border-radius: 5px;
// margin: 5px 0;
// font-size: 0.9em;
// }
//
// .form-error ul{
// padding: 1px 5px;
// background-color: #f8d7da;
// border: 1px solid #f5c6cb;
// border-radius: 5px;
// margin: 5px 0;
// font-size: 0.9em;
// }
//
// .subform{
// background-color: #efefef;
// border-radius: 5px;
// padding: 10px;
// }
//
// .form-buttons{
// margin: 20px 0;
// }
//
// .form select{
// padding: 0;
// overflow-y: auto;
// }
//
// .form select option{
// height: 30px;
// display: flex;
// align-items: center;
// }
//
// .turbo-refreshing{
// opacity: .5;
// }
//
// .ibo-field legend{
// margin-top: 24px;
// }
//
// collection-entry-element {
// margin-top: 8px;
// display: block;
// padding: 10px 10px;
// background-color: #f5f5f5;
// border-radius: 5px;
// }
// .ts-control{
// height: auto;
// min-height: 30px;
// }
//
// .ibo-form-actions > .ibo-button > span{
// margin-right: 5px;
// }
//
// .ibo-form textarea{
// resize: vertical;
// }
//
//}

View File

@@ -21,6 +21,7 @@ $ibo-input-set--item--padding-x: 6px !default;
$ibo-input-set--item--padding-y: $ibo-spacing-200 !default;
$ibo-input-set--item--max-width: 360px !default;
$ibo-input-set--item--background-color: $ibo-color-white-100 !default;
$ibo-input-set--item--color: $ibo-color-grey-900 !default;
$ibo-input-set--item--border: none !default;
$ibo-input-set--item--border-radius: $ibo-border-radius-300 !default;
$ibo-input-set--item--box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 0 1px 1px rgba(241, 241, 241, 0.7) !default;
@@ -59,7 +60,7 @@ $ibo-input-set--item--box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 0 1px 1px rgb
border-radius: $ibo-input-set--item--border-radius;
box-shadow: $ibo-input-set--item--box-shadow;
color: $ibo-color-grey-900;
color: $ibo-input-set--item--color;
text-shadow: none;
@extend %ibo-text-truncated-with-ellipsis;
/* - Guaranty that if a set item has html sub-node, they correctly display ellipsis (eg. Link set set items) */

View File

@@ -4,7 +4,6 @@
*/
@import "navigation-menu";
@import "top-bar";
@import "content";
@import "details";
@import "tab-container/tab-container";
@@ -15,4 +14,5 @@
@import "wizard-container/wizard-container";
@import "object/all";
@import "activity-panel/all";
@import "extension/all";
@import "extension/all";
@import "top-bar/all";

View File

@@ -793,8 +793,9 @@ $ibo-navigation-menu--user-info--height--is-expanded: 100% !default;
color: $ibo-navigation-menu--user--text-color;
}
.ibo-navigation-menu--user-notifications--toggler--icon {
padding-left: $ibo-navigation-menu--user-notifications--toggler--icon--padding-left;
.ibo-navigation-menu--user-notifications--toggler--icon {
position: relative;
padding-left: $ibo-navigation-menu--user-notifications--toggler--icon--padding-left;
}
}

View File

@@ -0,0 +1,78 @@
$ibo-activity-panel--activity-actions--right: $ibo-activity-panel--add-caselog-entry-button--right !default;
$ibo-activity-panel--activity-actions--top: 76px + $ibo-activity-panel--activity-actions--right !default;
$ibo-activity-panel--activity-actions--gap: $ibo-spacing-300 !default;
$ibo-activity-panel--activity-action--diameter: $ibo-activity-panel--add-caselog-entry-button--diameter !default;
$ibo-activity-panel--activity-action--background-color: $ibo-activity-panel--add-caselog-entry-button--background-color !default;
$ibo-activity-panel--activity-action--background-color--on-hover: $ibo-activity-panel--add-caselog-entry-button--background-color--on-hover !default;
$ibo-activity-panel--activity-action--background-color--is-active: $ibo-activity-panel--add-caselog-entry-button--background-color--is-active !default;
$ibo-activity-panel--activity-action--color: $ibo-activity-panel--add-caselog-entry-button--color !default;
$ibo-activity-panel--activity-action--border-radius: $ibo-border-radius-full !default;
$ibo-activity-panel--activity-action--box-shadow: $ibo-activity-panel--add-caselog-entry-button--box-shadow !default;
$ibo-activity-panel--activity-action--hover--box-shadow: $ibo-activity-panel--add-caselog-entry-button--hover--box-shadow !default;
$ibo-activity-panel--activity-action--icon--height: $ibo-activity-panel--add-caselog-entry-button--icon--height !default;
$ibo-activity-panel--activity-action--icon--width: $ibo-activity-panel--activity-action--icon--height !default;
$ibo-activity-panel--activity-action--icon--font-size: $ibo-activity-panel--add-caselog-entry-button--icon--font-size !default;
$ibo-activity-panel--activity-action--icon--line-height: $ibo-activity-panel--add-caselog-entry-button--icon--line-height !default;
$ibo-activity-panel--activity-action-separator--width: calc(#{$ibo-activity-panel--activity-action--diameter} / 1.5) !default;
$ibo-activity-panel--activity-action-separator--height: 2px !default;
$ibo-activity-panel--activity-action-separator--background-color: $ibo-color-grey-500 !default;
.ibo-activity-panel--activity-actions {
display: flex;
flex-direction: column;
gap: $ibo-activity-panel--activity-actions--gap;
position: absolute;
z-index: 1;
right: $ibo-activity-panel--activity-actions--right;
top: $ibo-activity-panel--activity-actions--top;
&.ibo-is-hidden{
display: none;
}
&:has(+ .ibo-activity-panel--add-caselog-entry-button) {
top: calc(#{$ibo-activity-panel--activity-actions--top} + #{$ibo-activity-panel--activity-action--diameter} + #{$ibo-activity-panel--activity-actions--gap});
}
}
.ibo-activity-panel--activity-action {
@extend %ibo-baseline-centered-content;
width: $ibo-activity-panel--activity-action--diameter;
height: $ibo-activity-panel--activity-action--diameter;
background-color: $ibo-activity-panel--activity-action--background-color;
color: $ibo-activity-panel--activity-action--color;
border-radius: $ibo-activity-panel--activity-action--border-radius;
box-shadow: $ibo-activity-panel--activity-action--box-shadow;
> i{
text-align: center;
height: $ibo-activity-panel--activity-action--icon--height;
width: $ibo-activity-panel--activity-action--icon--width;
font-size: $ibo-activity-panel--activity-action--icon--font-size;
line-height: $ibo-activity-panel--activity-action--icon--line-height;
}
&:hover {
color: $ibo-activity-panel--activity-action--color;
background-color: $ibo-activity-panel--activity-action--background-color--on-hover;
box-shadow: $ibo-activity-panel--activity-action--hover--box-shadow;
}
&:active {
color: $ibo-activity-panel--activity-action--color;
background-color: $ibo-activity-panel--activity-action--background-color--is-active;
}
&.ibo-is-hidden{
display: none;
}
}
.ibo-activity-panel--activity-action-separator {
display: block;
width: $ibo-activity-panel--activity-action-separator--width;
height: $ibo-activity-panel--activity-action-separator--height;
margin: auto;
background-color: $ibo-activity-panel--activity-action-separator--background-color;
}

View File

@@ -137,7 +137,8 @@ $ibo-activity-panel--open-icon--margin-left: 0.75rem !default;
.ibo-activity-panel--header,
.ibo-activity-panel--body,
.ibo-activity-panel--add-caselog-entry-button {
.ibo-activity-panel--add-caselog-entry-button,
.ibo-activity-panel--activity-actions{
display: none;
}

View File

@@ -10,3 +10,4 @@
@import "transition-entry";
@import "edits-entry";
@import "notification-entry";
@import "activity-action";

View File

@@ -59,4 +59,9 @@ $ibo-caselog-entry-form--lock-message--margin-left: 1rem !default;
}
.ibo-caselog-entry-form--action-buttons--main-actions > .ibo-popover-menu {
z-index: 1;
}
.ibo-caselog-entry-form--action-buttons--extra-actions{
overflow: hidden;
flex-grow: 1;
}

View File

@@ -0,0 +1,2 @@
@import "top-bar";
@import "top-bar-action";

View File

@@ -0,0 +1,40 @@
$ibo-top-bar-quick-action--head--background-color: $ibo-color-white-100 !default;
$ibo-top-bar-quick-action--icon-padding-x: $ibo-spacing-500 !default;
$ibo-top-bar-quick-action--icon-padding-y: $ibo-spacing-0 !default;
$ibo-top-bar-quick-action--icon--color: $ibo-color-primary-600 !default;
$ibo-top-bar-quick-action--icon--color--on-hover: $ibo-color-primary-700 !default;
$ibo-top-bar-quick-action--icon--color--on-active: $ibo-color-primary-800 !default;
$ibo-top-bar-quick-action-separator--width: 2px !default;
$ibo-top-bar-quick-action-separator--background-color: $ibo-color-grey-300 !default;
$ibo-top-bar-quick-action-separator--margin-y: $ibo-spacing-0 !default;
$ibo-top-bar-quick-action-separator--margin-x: $ibo-spacing-300 !default;
.ibo-top-bar-quick-action{
position: relative;
@extend %ibo-full-height-content;
}
.ibo-top-bar-quick-action--head{
@extend %ibo-full-height-content;
height: 100%;
background-color: $ibo-top-bar-quick-action--head--background-color;
}
.ibo-top-bar-quick-action--icon{
color: $ibo-top-bar-quick-action--icon--color;
align-self: center;
padding: $ibo-top-bar-quick-action--icon-padding-y $ibo-top-bar-quick-action--icon-padding-x;
@extend %ibo-font-ral-nor-400;
&:hover{
color: $ibo-top-bar-quick-action--icon--color--on-hover;
}
&:active{
color: $ibo-top-bar-quick-action--icon--color--on-active;
}
}
.ibo-top-bar-quick-action-separator {
width: $ibo-top-bar-quick-action-separator--width;
background-color: $ibo-top-bar-quick-action-separator--background-color;
margin: $ibo-top-bar-quick-action-separator--margin-y $ibo-top-bar-quick-action-separator--margin-x;
}

View File

@@ -70,7 +70,7 @@ $ibo-vendors-ckeditor--ck-mentions--item--padding-y: $ibo-spacing-200 !default;
}
}
.ck-editor__editable_inline:not(.ck-comment__input *) {
.ck-editor__editable_inline:not(.ck-comment__input *), .ck-source-editing-area {
height: 200px;
}
@@ -84,11 +84,6 @@ $ibo-vendors-ckeditor--ck-mentions--item--padding-y: $ibo-spacing-200 !default;
}
}
// N°7552 Allow source editing area to be scrollable in full screen
.ck-maximize_editor_main .ck-source-editing-area textarea{
overflow: auto !important;
}
/* Mentions */
.ck-mentions {
.ck-button {

File diff suppressed because one or more lines are too long

View File

@@ -6,8 +6,9 @@
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license https://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('ZH CN', 'Chinese', '简体中文', [
'CAS:Error:UserNotAllowed' => '用户被禁止登录',
'CAS:Login:SignIn' => '使用CAS登录',
'CAS:Login:SignInTooltip' => '点击这里使用CAS服务器认证',
'CAS:Login:SignIn' => '使用 CAS 登录',
'CAS:Login:SignInTooltip' => '点击这里使用 CAS 服务器认证',
]);

View File

@@ -5,10 +5,8 @@
*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license https://opensource.org/licenses/AGPL-3.0
*
*/
/**
* @author Robert Deng <denglx@gmail.com>
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -23,6 +21,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
// Dictionnay conventions
// Class:<class_name>
// Class:<class_name>+
@@ -32,9 +31,11 @@
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
// Class:<class_name>/Stimulus:<stimulus_code>
// Class:<class_name>/Stimulus:<stimulus_code>+
//
// Class: UserExternal
//
Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Class:UserExternal' => '外部用户',
'Class:UserExternal+' => '用户在'.ITOP_APPLICATION_SHORT.'外部验证身份',

View File

@@ -14,7 +14,7 @@
Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:UserLDAP' => 'Пользователь LDAP',
'Class:UserLDAP+' => 'Пользователь, аутентифицируемый через LDAP',
'UserLDAP:server' => 'LDAP specifics~~',
'UserLDAP:server' => 'Особенности LDAP',
]);
//
@@ -22,6 +22,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
//
Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
'Class:UserLDAP/Attribute:ldap_server' => 'Сервер LDAP',
'Class:UserLDAP/Attribute:ldap_server+' => '',
]);

View File

@@ -6,7 +6,6 @@
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license https://opensource.org/licenses/AGPL-3.0
* @author Robert Deng <denglx@gmail.com>
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
@@ -22,6 +21,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
// Dictionnay conventions
// Class:<class_name>
// Class:<class_name>+
@@ -31,13 +31,15 @@
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
// Class:<class_name>/Stimulus:<stimulus_code>
// Class:<class_name>/Stimulus:<stimulus_code>+
//
// Class: UserLDAP
//
Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Class:UserLDAP' => 'LDAP用户',
'Class:UserLDAP+' => '用户身份由LDAP认证',
'UserLDAP:server' => 'LDAP详情',
'Class:UserLDAP' => 'LDAP 用户',
'Class:UserLDAP+' => '用户身份由 LDAP 认证',
'UserLDAP:server' => 'LDAP 详情',
]);
//
@@ -45,6 +47,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
//
Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Class:UserLDAP/Attribute:ldap_server' => 'Ldap server~~',
'Class:UserLDAP/Attribute:ldap_server+' => '~~',
'Class:UserLDAP/Attribute:ldap_server' => 'LDAP 服务器',
'Class:UserLDAP/Attribute:ldap_server+' => '',
]);

View File

@@ -25,7 +25,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:UserLocal/Attribute:expiration/Value:force_expire' => 'abgelaufen',
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '',
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => 'einmaliges Passwort',
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Das Passwort kann vom Benutzer nicht geändert werden.',
'Class:UserLocal/Attribute:password_renewed_date' => 'Letzte Passworterneuerung',
'Class:UserLocal/Attribute:password_renewed_date+' => 'Letztes Änderungsdatum',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Das Passwort muss mindestens 12 Zeichen lang sein und Großbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten.',

View File

@@ -24,11 +24,11 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:UserLocal/Attribute:expiration/Value:never_expire+' => '',
'Class:UserLocal/Attribute:expiration/Value:force_expire' => 'Истёкший',
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '',
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => 'One-time Password~~',
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => 'Одноразовый пароль',
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Пароль не может быть изменён пользователем.',
'Class:UserLocal/Attribute:password_renewed_date' => 'Дата изменения пароля',
'Class:UserLocal/Attribute:password_renewed_date+' => 'Когда пароль был изменен в последний раз',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Пароль должен содержать не менее 12 символов и включать прописные, строчные, числовые и специальные символы.',
'UserLocal:password:expiration' => 'Поля требуют наличия доп. расширения',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Установка срока действия пароля "Одноразовый пароль" для своей собственной учётной записи не разрешена',
]);

View File

@@ -6,7 +6,6 @@
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license https://opensource.org/licenses/AGPL-3.0
* @author Robert Deng <denglx@gmail.com>
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
@@ -22,6 +21,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
// Dictionnay conventions
// Class:<class_name>
// Class:<class_name>+
@@ -31,16 +31,19 @@
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
// Class:<class_name>/Stimulus:<stimulus_code>
// Class:<class_name>/Stimulus:<stimulus_code>+
//
// Class: UserLocal
//
Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Class:UserLocal' => ITOP_APPLICATION_SHORT.' 用户',
'Class:UserLocal+' => '用户由'.ITOP_APPLICATION_SHORT.'验证身份',
'Class:UserLocal/Attribute:password' => '密码',
'Class:UserLocal/Attribute:password+' => '用于验证用户身份的字符串',
'Class:UserLocal/Attribute:expiration' => '密码过期',
'Class:UserLocal/Attribute:expiration+' => '密码过期状态 (需要一个扩展才能生效)',
'Class:UserLocal/Attribute:expiration' => '密码过期时间',
'Class:UserLocal/Attribute:expiration+' => '密码过期状态 (需要扩展才能生效)',
'Class:UserLocal/Attribute:expiration/Value:can_expire' => '允许过期',
'Class:UserLocal/Attribute:expiration/Value:can_expire+' => '',
'Class:UserLocal/Attribute:expiration/Value:never_expire' => '永不过期',
@@ -49,8 +52,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '',
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => '一次性密码',
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '用户不允许修改密码.',
'Class:UserLocal/Attribute:password_renewed_date' => '密码更新',
'Class:UserLocal/Attribute:password_renewed_date' => '密码更新时间',
'Class:UserLocal/Attribute:password_renewed_date+' => '上次修改密码的时间',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => '密码必须至少12个字符, 包含大小写, 数字和特殊字符.',
'UserLocal:password:expiration' => '下面的区域需要插件扩展',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => '不允许用户为自己设置 "一次性密码" 的失效期限',

View File

@@ -11,5 +11,5 @@
*
*/
Dict::Add('RU RU', 'Russian', 'Русский', [
'theme:darkmoon' => 'Dark moon~~',
'theme:darkmoon' => 'Тёмная луна',
]);

View File

@@ -21,6 +21,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
Dict::Add('ZH CN', 'Chinese', '简体中文', [
'theme:darkmoon' => 'Dark moon',
]);

View File

@@ -149,6 +149,8 @@ $ibo-quick-create--input--text-color: $ibo-color-grey-100 !default;
$ibo-quick-create--input--placeholder-color: $ibo-color-grey-200 !default;
$ibo-quick-create--compartment-element--background-color--is-active: $ibo-color-grey-500 !default;
$ibo-top-bar-quick-action--head--background-color: $ibo-top-bar--background-color !default;
$ibo-datatable--toolbar--text-color: $ibo-color-grey-300 !default;
$ibo-vendors-datatables--pagination-button--background-color--on-hover: $ibo-color-grey-700 !default;
$ibo-vendors-datatables--pagination-button--text-color--is-active: $ibo-color-grey-200 !default;
@@ -200,6 +202,10 @@ $ibo-input-select-selectize--item--active--text-color: $ibo-color-grey-100 !defa
$ibo-input-select-selectize--item--active--background-color: $ibo-color-grey-500 !default;
$ibo-input-select--autocomplete-item-image--background-color: $ibo-color-grey-800 !default;
$ibo-input-date--button--color: $ibo-input-select--action-button--color;
$ibo-input-set--item--background-color: $ibo-color-grey-600 !default;
$ibo-input-set--item--color: $ibo-color-grey-200 !default;
$ibo-input-set--item--box-shadow: none !default;
$ibo-input-set--item--border: solid 1px $ibo-color-grey-800 !default;
$ibo-vendors-selectize-input--color: $ibo-body-text-color !default;
$ibo-vendors-selectize-input--background-color: $ibo-input--background-color !default;
$ibo-vendors-selectize--input--border-color: $ibo-input--border-color !default;

View File

@@ -23,5 +23,5 @@
*/
Dict::Add('DE DE', 'German', 'Deutsch', [
'theme:fullmoon-high-contrast' => 'Fullmoon (High contrast)~~',
'theme:fullmoon-high-contrast' => 'Fullmoon (Hoher Kontrast)',
]);

View File

@@ -23,5 +23,5 @@
*/
Dict::Add('RU RU', 'Russian', 'Русский', [
'theme:fullmoon-high-contrast' => 'Fullmoon (High contrast)~~',
'theme:fullmoon-high-contrast' => 'Fullmoon (высокая контрастность)',
]);

View File

@@ -23,5 +23,5 @@
*/
Dict::Add('DE DE', 'German', 'Deutsch', [
'theme:fullmoon-protanopia-deuteranopia' => 'Fullmoon (Protanopia & Deuteranopia)~~',
'theme:fullmoon-protanopia-deuteranopia' => 'Fullmoon (Protanopie & Deuteranopie)',
]);

View File

@@ -23,5 +23,5 @@
*/
Dict::Add('RU RU', 'Russian', 'Русский', [
'theme:fullmoon-protanopia-deuteranopia' => 'Fullmoon (Protanopia & Deuteranopia)~~',
'theme:fullmoon-protanopia-deuteranopia' => 'Fullmoon (протанопия и дейтеранопия)',
]);

View File

@@ -23,5 +23,5 @@
*/
Dict::Add('DE DE', 'German', 'Deutsch', [
'theme:fullmoon-tritanopia' => 'Fullmoon (Tritanopia)~~',
'theme:fullmoon-tritanopia' => 'Fullmoon (Tritanopie)',
]);

View File

@@ -23,5 +23,5 @@
*/
Dict::Add('RU RU', 'Russian', 'Русский', [
'theme:fullmoon-tritanopia' => 'Fullmoon (Tritanopia)~~',
'theme:fullmoon-tritanopia' => 'Fullmoon (тританопия)',
]);

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<service_locator>
<services>
<service id="DataFeatureRemoverExtension">
<class id="Combodo\iTop\DataFeatureRemoval\Service\DataFeatureRemoverExtensionService"/>
</service>
</services>
</service_locator>

View File

@@ -1,11 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
<classes>
<class id="ResourceDataFeatureRemoval" _delta="define">
<parent>AbstractResource</parent>
<properties>
<comment>/* Extension management access control. */</comment>
<abstract>true</abstract>
<category>grant_by_profile</category>
</properties>
<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>
<enable_class>ResourceDataFeatureRemoval</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
</menus>
<module_parameters>

View File

@@ -0,0 +1,71 @@
<?php
/**
* @copyright Copyright (C) 2010-2026 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Localized data
*/
Dict::Add('DE DE', 'German', 'Deutsch', [
'Menu:DataFeatureRemovalMenu' => 'Extension-Verwaltung',
'combodo-data-feature-removal/Operation:Main/Title' => 'Extension-Verwaltung',
'DataFeatureRemoval:Main:Title' => 'Extension-Verwaltung',
'DataFeatureRemoval:Main:SubTitle' => 'In Ihrem iTop installierte Extensions aktivieren oder deaktivieren',
'DataFeatureRemoval:Failure:Title' => 'Fehler beim Testlauf der Extension-Entfernung',
'DataFeatureRemoval:Helper:Title' => 'Prüfen Sie, ob Daten oder Abhängigkeiten dem Hinzufügen bzw. Entfernen einer Extension entgegenstehen.',
'DataFeatureRemoval:Features:Title' => 'Extensions',
'DataFeatureRemoval:Result:Title' => 'Angeforderte Änderung',
'DataFeatureRemoval:NoResult:Title' => 'Keine Änderung angefordert',
'DataFeatureRemoval:Execution:Title' => 'Ausführung der Löschvorgänge',
'DataFeatureRemoval:Analysis:Title' => 'Analyseergebnis',
'DataFeatureRemoval:Analysis:Subtitle' => 'Prüfen Sie alle Elemente, die Ihre Aufmerksamkeit erfordern',
'DataFeatureRemoval:Analysis:SubTitle' => '%1$s Element(e) müssen bereinigt werden, bevor Sie fortfahren können',
'DataFeatureRemoval:DeletionPlan:Title' => 'Plan zur Datenlöschung',
'DataFeatureRemoval:DeletionPlan:SubTitle' => '%1$s Zeilen müssen bereinigt werden, bevor Sie fortfahren können',
'DataFeatureRemoval:DoDeletion:Title' => 'Löschung durchführen',
'DataFeatureRemoval:DoDeletion:SubTitle' => 'Alle Einträge aus der Datenbank entfernen',
'DataFeatureRemoval:DeletionPlan:Error:Issues' => 'Einige Objekte müssen vor der Bereinigung manuell gelöscht werden',
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Zu entfernendes Element',
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Name der Extension',
'DataFeatureRemoval:Table:Analysis:Module' => 'Modulname',
'DataFeatureRemoval:Table:Analysis:Occurrence' => 'Vorkommen',
'DataFeatureRemoval:CleanupComplete:Title' => 'Alles bereinigt.',
'DataFeatureRemoval:CompilComplete' => 'Kompilierung erfolgreich. Keine Bereinigung erforderlich. Sie können mit dem Setup fortfahren.',
'DataFeatureRemoval:Compile:InProgress' => 'Kompilierung läuft...',
'DataFeatureRemoval:Compile:Success' => 'Kompilierung erfolgreich',
'DataFeatureRemoval:Compile:Error' => 'Fehler bei der Kompilierung',
'DataFeatureRemoval:RunAudit:InProgress' => 'Analyse läuft...',
'DataFeatureRemoval:RunAudit:Success' => 'Analyse abgeschlossen',
'DataFeatureRemoval:RunAudit:Error' => 'Fehler während der Analyse',
'UI:Button:Analyze' => 'Analysieren',
'UI:Button:ModifyChoices' => 'Auswahl ändern',
'UI:Button:AnalyzeAndSetup' => 'Analysieren und zum Setup',
'UI:Button:PlanDeletion' => 'Mit der Löschung fortfahren',
'UI:Button:DoDeletion' => 'Mit der Löschung fortfahren',
'UI:Button:BackToMain' => 'Auswahl ändern',
'UI:Button:Setup' => 'Setup ausführen',
'UI:Action:ForceUninstall' => 'Deinstallation erzwingen',
'UI:Action:MoreInfo' => 'Weitere Informationen',
'DataFeatureRemoval:Table:Empty' => 'Keine zu entfernenden Daten',
'DataFeatureRemoval:Column:Class' => 'Klasse',
'DataFeatureRemoval:Column:DeleteCount' => 'Zu löschende Einträge',
'DataFeatureRemoval:Column:UpdateCount' => 'Zu aktualisierende Einträge',
'DataFeatureRemoval:Column:IssueCount' => 'Gefundene Probleme, die eine automatische Bereinigung verhindern',
'DataFeatureRemoval:Column:DeletedCount' => 'Gelöschte Einträge',
'DataFeatureRemoval:Column:UpdatedCount' => 'Aktualisierte Einträge',
]);

View File

@@ -20,6 +20,7 @@ Dict::Add('EN US', 'English', 'English', [
'DataFeatureRemoval:Features:Title' => 'Extensions',
'DataFeatureRemoval:Result:Title' => 'Modification requested',
'DataFeatureRemoval:NoResult:Title' => 'No modification requested',
'DataFeatureRemoval:Execution:Title' => 'Deletion Executions',
'DataFeatureRemoval:Analysis:Title' => 'Analysis result',
'DataFeatureRemoval:Analysis:Subtitle' => 'Review all elements requiring attention',
@@ -39,6 +40,14 @@ Dict::Add('EN US', 'English', 'English', [
'DataFeatureRemoval:CleanupComplete:Title' => 'All clear.',
'DataFeatureRemoval:CompilComplete' => 'Compilation successful. No Cleanup needed. You can proceed to setup.',
'DataFeatureRemoval:Compile:InProgress' => 'Compilation in progress...',
'DataFeatureRemoval:Compile:Success' => 'Compilation successful',
'DataFeatureRemoval:Compile:Error' => 'Compilation error',
'DataFeatureRemoval:RunAudit:InProgress' => 'Analysis in progress...',
'DataFeatureRemoval:RunAudit:Success' => 'Analysis completed',
'DataFeatureRemoval:RunAudit:Error' => 'Error during analysis',
'UI:Button:Analyze' => 'Analyze',
'UI:Button:ModifyChoices' => 'Change my selection',
'UI:Button:AnalyzeAndSetup' => 'Analyze and go to setup',

View File

@@ -0,0 +1,19 @@
<?php
/**
* @copyright Copyright (C) 2010-2026 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Localized data
*
* Only the entries whose spelling differs from EN US are listed here; every
* other string falls back to the EN US dictionary.
*/
Dict::Add('EN GB', 'British English', 'British English', [
'DataFeatureRemoval:Helper:Title' => 'Analyse if there are any data or dependency preventing you from adding/removing an extension.',
'UI:Button:Analyze' => 'Analyse',
'UI:Button:AnalyzeAndSetup' => 'Analyse and go to setup',
]);

View File

@@ -20,6 +20,7 @@ Dict::Add('FR FR', 'French', 'Français', [
'DataFeatureRemoval:Features:Title' => 'Extensions',
'DataFeatureRemoval:Result:Title' => 'Modification demandée',
'DataFeatureRemoval:NoResult:Title' => 'Aucune modification demandée',
'DataFeatureRemoval:Execution:Title' => 'Suppressions',
'DataFeatureRemoval:Analysis:Title' => 'Résultat de l’analyse',
'DataFeatureRemoval:Analysis:Subtitle' => 'Vérifier les éléments à nettoyer',
@@ -39,6 +40,14 @@ Dict::Add('FR FR', 'French', 'Français', [
'DataFeatureRemoval:CleanupComplete:Title' => 'All clear.',
'DataFeatureRemoval:CompilComplete' => 'Compilation successful. No Cleanup needed. You can proceed to setup.',
'DataFeatureRemoval:Compile:InProgress' => 'Compilation en cours...',
'DataFeatureRemoval:Compile:Success' => 'Compilation terminée',
'DataFeatureRemoval:Compile:Error' => 'Erreur lors de la compilation',
'DataFeatureRemoval:RunAudit:InProgress' => 'Analyse en cours...',
'DataFeatureRemoval:RunAudit:Success' => 'Analyse terminée',
'DataFeatureRemoval:RunAudit:Error' => 'Erreur lors de l\'analyse',
'UI:Button:Analyze' => 'Analyser',
'UI:Button:ModifyChoices' => 'Modifier la sélection',
'UI:Button:AnalyzeAndSetup' => 'Analyser et ouvrir l’assistant de configuration',

View File

@@ -0,0 +1,75 @@
<?php
/**
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Localized data
*/
/**
* @author Vladimir Kunin <v.b.kunin@gmail.com>
*
*/
Dict::Add('RU RU', 'Russian', 'Русский', [
'Menu:DataFeatureRemovalMenu' => 'Управление расширениями',
'combodo-data-feature-removal/Operation:Main/Title' => 'Управление расширениями',
'DataFeatureRemoval:Main:Title' => 'Управление расширениями',
'DataFeatureRemoval:Main:SubTitle' => 'Включение и отключение расширений, установленных в вашем iTop',
'DataFeatureRemoval:Failure:Title' => 'Ошибки пробного удаления расширений',
'DataFeatureRemoval:Helper:Title' => 'Проверьте, есть ли данные или зависимости, мешающие добавить/удалить расширение.',
'DataFeatureRemoval:Features:Title' => 'Расширения',
'DataFeatureRemoval:Result:Title' => 'Запрошено изменение',
'DataFeatureRemoval:NoResult:Title' => 'Изменений не запрошено',
'DataFeatureRemoval:Execution:Title' => 'Выполнения удаления',
'DataFeatureRemoval:Analysis:Title' => 'Результат анализа',
'DataFeatureRemoval:Analysis:Subtitle' => 'Просмотрите все элементы, требующие внимания',
'DataFeatureRemoval:Analysis:SubTitle' => 'Элементов для очистки перед продолжением: %1$s',
'DataFeatureRemoval:DeletionPlan:Title' => 'План удаления данных',
'DataFeatureRemoval:DeletionPlan:SubTitle' => 'Строк для очистки перед продолжением: %1$s',
'DataFeatureRemoval:DoDeletion:Title' => 'Выполнить удаление',
'DataFeatureRemoval:DoDeletion:SubTitle' => 'Удалить все записи из базы данных',
'DataFeatureRemoval:DeletionPlan:Error:Issues' => 'Некоторые объекты нужно удалить вручную перед очисткой',
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Элемент для удаления',
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Название расширения',
'DataFeatureRemoval:Table:Analysis:Module' => 'Название модуля',
'DataFeatureRemoval:Table:Analysis:Occurrence' => 'Количество',
'DataFeatureRemoval:CleanupComplete:Title' => 'Всё чисто.',
'DataFeatureRemoval:CompilComplete' => 'Компиляция выполнена успешно. Очистка не требуется. Можно переходить к установке.',
'DataFeatureRemoval:Compile:InProgress' => 'Идёт компиляция...',
'DataFeatureRemoval:Compile:Success' => 'Компиляция выполнена успешно',
'DataFeatureRemoval:Compile:Error' => 'Ошибка компиляции',
'DataFeatureRemoval:RunAudit:InProgress' => 'Идёт анализ...',
'DataFeatureRemoval:RunAudit:Success' => 'Анализ завершён',
'DataFeatureRemoval:RunAudit:Error' => 'Ошибка при анализе',
'UI:Button:Analyze' => 'Анализировать',
'UI:Button:ModifyChoices' => 'Изменить выбор',
'UI:Button:AnalyzeAndSetup' => 'Анализировать и перейти к установке',
'UI:Button:PlanDeletion' => 'Продолжить удаление',
'UI:Button:DoDeletion' => 'Продолжить удаление',
'UI:Button:BackToMain' => 'Изменить выбор',
'UI:Button:Setup' => 'Запустить установку',
'UI:Action:ForceUninstall' => 'Принудительно удалить',
'UI:Action:MoreInfo' => 'Подробнее',
'DataFeatureRemoval:Table:Empty' => 'Нет данных для удаления',
'DataFeatureRemoval:Column:Class' => 'Класс',
'DataFeatureRemoval:Column:DeleteCount' => 'Записей к удалению',
'DataFeatureRemoval:Column:UpdateCount' => 'Записей к обновлению',
'DataFeatureRemoval:Column:IssueCount' => 'Найдено проблем, мешающих автоматической очистке',
'DataFeatureRemoval:Column:DeletedCount' => 'Удалено записей',
'DataFeatureRemoval:Column:UpdatedCount' => 'Обновлено записей',
]);

View File

@@ -0,0 +1,62 @@
<?php
/**
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Localized data
*/
Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Menu:DataFeatureRemovalMenu' => '扩展管理',
'combodo-data-feature-removal/Operation:Main/Title' => '扩展管理',
'DataFeatureRemoval:Main:Title' => '扩展管理',
'DataFeatureRemoval:Main:SubTitle' => '切换安装在您的 iTop 上的扩展',
'DataFeatureRemoval:Failure:Title' => '扩展预删除错误',
'DataFeatureRemoval:Helper:Title' => '分析是否有任何数据或依赖关系阻止您添加/删除扩展。',
'DataFeatureRemoval:Features:Title' => '扩展',
'DataFeatureRemoval:Result:Title' => '请求的修改',
'DataFeatureRemoval:Execution:Title' => '删除执行',
'DataFeatureRemoval:Analysis:Title' => '分析结果',
'DataFeatureRemoval:Analysis:Subtitle' => '审查所有需要关注的元素',
'DataFeatureRemoval:Analysis:SubTitle' => '%1$s 个元素需要在继续之前清理',
'DataFeatureRemoval:DeletionPlan:Title' => '数据删除计划',
'DataFeatureRemoval:DeletionPlan:SubTitle' => '%1$s 行需要在继续之前清理',
'DataFeatureRemoval:DoDeletion:Title' => '执行删除',
'DataFeatureRemoval:DoDeletion:SubTitle' => '从数据库中删除所有条目',
'DataFeatureRemoval:DeletionPlan:Error:Issues' => '某些对象必须在清理前手动删除',
'DataFeatureRemoval:Table:Analysis:ClassName' => '要删除的元素',
'DataFeatureRemoval:Table:Analysis:FeatureName' => '扩展名称',
'DataFeatureRemoval:Table:Analysis:Module' => '模块名称',
'DataFeatureRemoval:Table:Analysis:Occurrence' => '出现次数',
'DataFeatureRemoval:CleanupComplete:Title' => '全部清除.',
'DataFeatureRemoval:CompilComplete' => '编译成功. 无需清理. 您可以继续进行设置.',
'UI:Button:Analyze' => '分析',
'UI:Button:ModifyChoices' => '改变我的选择',
'UI:Button:AnalyzeAndSetup' => '分析并进入设置',
'UI:Button:PlanDeletion' => '继续删除',
'UI:Button:DoDeletion' => '继续删除',
'UI:Button:BackToMain' => '改变我的选择',
'UI:Button:Setup' => '运行安装向导',
'UI:Action:ForceUninstall' => '强制卸载',
'UI:Action:MoreInfo' => '更多信息',
'DataFeatureRemoval:Table:Empty' => '没有数据需要删除',
'DataFeatureRemoval:Column:Class' => '类',
'DataFeatureRemoval:Column:DeleteCount' => '待删除的条目',
'DataFeatureRemoval:Column:UpdateCount' => '待更新的条目',
'DataFeatureRemoval:Column:IssueCount' => '发现阻止自动清理的问题',
'DataFeatureRemoval:Column:DeletedCount' => '已删除的条目',
'DataFeatureRemoval:Column:UpdatedCount' => '已更新的条目',
]);

View File

@@ -16,7 +16,6 @@ use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalLog;
use Combodo\iTop\DataFeatureRemoval\Service\DataCleanupService;
use Combodo\iTop\DataFeatureRemoval\Service\DataFeatureRemoverExtensionService;
use Combodo\iTop\DataFeatureRemoval\Service\StaticDeletionPlan;
use Combodo\iTop\Service\Session\SessionParameters;
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
@@ -24,6 +23,7 @@ use ContextTag;
use CoreException;
use Dict;
use Exception;
use iTopExtension;
use MetaModel;
use MFCompiler;
use RunTimeEnvironment;
@@ -47,7 +47,11 @@ class DataFeatureRemovalController extends Controller
$aParams = [];
SetupUtils::EraseSetupToken();
(new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME))->Erase();
$oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME);
$oParameters->Erase();
Session::Unset('aDeletionExecutionSummary');
Session::Set('bForceCompilation', true);
$oParameters->SetParameter('return_application', 'DataFeatureRemoval');
$this->AddAnalyzeParams();
$aParams['sTransactionId'] = utils::GetNewTransactionId();
@@ -60,7 +64,6 @@ class DataFeatureRemovalController extends Controller
$aParams['sSetupUrl'] = utils::GetAbsoluteUrlAppRoot().'setup';
$aParams['iCount'] = $this->iCount;
Session::Set('bForceCompilation', true);
$this->AddLinkedStylesheet(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/css/DataFeatureRemoval.css');
$this->AddLinkedScript(utils::GetAbsoluteUrlModulesRoot().DataFeatureRemovalHelper::MODULE_NAME.'/assets/js/DataFeatureRemoval.js');
$this->DisplayPage($aParams);
@@ -73,7 +76,7 @@ class DataFeatureRemovalController extends Controller
$this->iCount = 0;
foreach ($this->aCountClassesToCleanup as $sClass => $iCount) {
$sModuleName = MetaModel::GetModuleName($sClass);
$aExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetIncludingExtensions($sModuleName);
$aExtensions = Metamodel::GetServiceLocator()->Get('DataFeatureRemoverExtension')->GetIncludingExtensions($sModuleName);
$sExtensions = implode(' ', $aExtensions);
$aColumns = ['ClassName','FeatureName','Module','Occurrence'];
$aData[] = [$sClass,$sExtensions,$sModuleName,$iCount];
@@ -94,7 +97,7 @@ class DataFeatureRemovalController extends Controller
}
// Display changed extensions
$aHiddenInputNames = [
$aSetupParameterNames = [
'selected_extensions' => '[]',
'selected_modules' => '[]',
'display_choices' => '',
@@ -108,95 +111,38 @@ class DataFeatureRemovalController extends Controller
'target_env' => ITOP_DEFAULT_ENV,
];
$aHiddenInputs = [];
foreach ($aHiddenInputNames as $sInputName => $defaultValue) {
$aHiddenInputs[$sInputName] = utils::ReadPostedParam($sInputName, $defaultValue, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME);
foreach ($aSetupParameterNames as $sInputName => $defaultValue) {
$oParameters->SetParameter($sInputName, $oParameters->GetParameter($sInputName, $defaultValue));
}
$aParams['aHiddenInputs'] = $aHiddenInputs;
$aAddedExtensions = json_decode($aHiddenInputs['added_extensions'], true);
$aRemovedExtensions = json_decode($aHiddenInputs['removed_extensions'], true);
if ("[]" === $aHiddenInputs['selected_modules']) {
$aAddedExtensions = json_decode($oParameters->GetParameter('added_extensions', '[]'), true);
$aRemovedExtensions = json_decode($oParameters->GetParameter('removed_extensions', '[]'), true);
if ('[]' === $oParameters->GetParameter('selected_modules', '[]')) {
//it does not come from setup
// we get extensions from 1st screen uiblocks
$this->ReadExtensionsDiff();
$aHiddenInputs['force-uninstall'] = $this->bForcedUninstallation ? 'on' : '';
$oParameters->SetParameter('force-uninstall', $this->bForcedUninstallation ? 'on' : '');
$aAddedExtensions = $this->aExtensionsToCheck['to_be_installed'];
$aHiddenInputs['added_extensions'] = $this->ConvertIntoSetupFormat($aAddedExtensions);
$oParameters->SetParameter('added_extensions', $this->ConvertIntoSetupFormat($aAddedExtensions));
$aRemovedExtensions = $this->aExtensionsToCheck['to_be_removed'];
$aHiddenInputs['removed_extensions'] = $this->ConvertIntoSetupFormat($aRemovedExtensions);
$oParameters->SetParameter('removed_extensions', $this->ConvertIntoSetupFormat($aRemovedExtensions));
$aExtensionsNotUninstallable = $this->aExtensionsToCheck['extensions_not_uninstallable'];
$aHiddenInputs['extensions_not_uninstallable'] = $this->ConvertIntoSetupFormat($aExtensionsNotUninstallable);
$oParameters->SetParameter('extensions_not_uninstallable', $this->ConvertIntoSetupFormat($aExtensionsNotUninstallable));
}
$aParams['aAddedExtensions'] = $aAddedExtensions;
$aParams['aRemovedExtensions'] = $aRemovedExtensions;
DataFeatureRemovalLog::Debug(__METHOD__.' Extensions given in parameter', null, [
'added_extensions' => $aAddedExtensions,
'removed_extensions' => $aRemovedExtensions]);
$aParams['sTransactionId'] = utils::GetNewTransactionId();
$aParams['iColumnCount'] = $this->iColumnCount;
$aParams['aAvailableExtensions'] = $this->SplitArrayIntoColumns($this->GetExtensionsDiff($aAddedExtensions, $aRemovedExtensions), $this->iColumnCount);
//to make setup redirection work, we need to pass complex data structures to setup wizards (ie extension/module lists)
$sSourceEnv = MetaModel::GetEnvironment();
$this->oRuntimeEnvironment = new RunTimeEnvironment($sSourceEnv, false);
if ('[]' === $aHiddenInputs['selected_modules']) {
$oConfig = MetaModel::GetConfig();
$aSelectedExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetExtensionMap()->GetSelectedExtensions($oConfig, array_keys($aAddedExtensions), array_keys($aRemovedExtensions));
$aHiddenInputs['selected_extensions'] = $this->ConvertIntoSetupFormat($aSelectedExtensions);
$aSelectedModules = []; // keep it to compile method
} else {
$aSelectedExtensions = json_decode($aHiddenInputs['selected_extensions'], true);
$aSelectedModules = json_decode($aHiddenInputs['selected_modules'], true);
}
try {
$this->Compile($aSelectedExtensions, array_keys($aRemovedExtensions), $aSelectedModules);
$aHiddenInputs['selected_modules'] = $this->ConvertIntoSetupFormat($aSelectedModules);
} catch (CoreException $e) {
$aParams['DataFeatureRemovalErrorMessage'] = $e->getHtmlDesc();
$this->DisplayPage($aParams, 'AnalysisResult');
return;
} catch (Exception $e) {
$aParams['DataFeatureRemovalErrorMessage'] = $e->getMessage();
$this->DisplayPage($aParams, 'AnalysisResult');
return;
}
$oSetupAudit = new SetupAudit($sSourceEnv);
$aGetRemovedClasses = array_keys($oSetupAudit->RunDataAudit());
DataFeatureRemovalLog::Debug(__METHOD__, null, ['aGetRemovedClasses' => $aGetRemovedClasses]);
$aParams['aClasses'] = $aGetRemovedClasses;
new ContextTag(ContextTag::TAG_SETUP);
$aParams['sLaunchSetupUrl'] = utils::GetAbsoluteUrlAppRoot().'setup/wizard.php';
$aParams['aSetupParams'] = [
"_class" => "WizStepLandingBeforeAudit",
"operation" => "next",
];
foreach ($aHiddenInputs as $sInputName => $sInputValue) {
$aParams['aSetupParams'][$sInputName] = $sInputValue;
}
[$aParams['aDeletionPlanSummary'], $aParams['iQueryCount'], $aParams['bDeletionPossible']] = $this->GetDeletionPlanSummaryTable($aGetRemovedClasses);
[$aParams['aDeletionExecutionSummary'], $aParams['bHasDeletionExecution']] = $this->GetExecutionSummaryTable();
$aParams['bDeletionNeeded'] = ($aParams['iQueryCount'] > 0);
Session::Set('aDeletionExecutionSummary', serialize($this->aDeletionExecutionSummary));
if (!$aParams['bDeletionNeeded']) {
// Erase session setup parameters
SetupUtils::CreateSetupToken();
}
$aAvailableExtensions = $this->GetExtensionsDiff($aAddedExtensions, $aRemovedExtensions);
$aParams['aAvailableExtensionsCount'] = count($aAvailableExtensions);
$aParams['aAvailableExtensions'] = $this->SplitArrayIntoColumns($aAvailableExtensions, $this->iColumnCount);
$aParams['sAjaxURL'] = utils::GetAbsoluteUrlModulePage(DataFeatureRemovalHelper::MODULE_NAME, 'index.php');
$this->DisplayPage($aParams, 'AnalysisResult');
}
@@ -204,55 +150,137 @@ class DataFeatureRemovalController extends Controller
private function ConvertIntoSetupFormat(array $aData): string
{
$aNewData = [];
foreach ($aData as $k => $sVal) {
$aNewData[] = sprintf('"%s":"%s"', $k, $sVal);
foreach ($aData as $sCode => $sLabel) {
$aNewData[] = sprintf('"%s":"%s"', $sCode, $sLabel);
}
return "{".implode(',', $aNewData)."}";
}
/**
* @param array $aSelectedExtensions
* @param array $aRemovedExtensions
* @param array $aSelectedModules
*
* @return void
* @throws \ConfigException
* @throws \CoreException
*/
private function Compile(array $aSelectedExtensions, array $aRemovedExtensions, array &$aSelectedModules): void
public function OperationAjaxCompile(): void
{
$aParams = [];
//to make setup redirection work, we need to pass complex data structures to setup wizards (ie extension/module lists)
$sSourceEnv = MetaModel::GetEnvironment();
$sBuildDir = APPROOT."/env-$sSourceEnv-build";
if (! is_dir($sBuildDir)) {
SetupUtils::builddir($sBuildDir);
}
$bIsDirEmpty = count(scandir($sBuildDir)) === 2;
$bForceCompilation = Session::Get('bForceCompilation', false);
$oRuntimeEnvironment = new RunTimeEnvironment($sSourceEnv, false);
$oConfig = MetaModel::GetConfig();
if ($bIsDirEmpty || $bForceCompilation) {
Session::Unset('bForceCompilation');
$this->oRuntimeEnvironment->CopySetupFiles();
if (count($aSelectedModules) === 0) {
$aSelectedModules = $this->oRuntimeEnvironment->GetModulesToLoadFromChoices($oConfig, $aSelectedExtensions);
}
$oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME);
$aSelectedModules = json_decode($oParameters->GetParameter('selected_modules', '[]'), true);
$aSelectedExtensions = json_decode($oParameters->GetParameter('selected_extensions', '[]'), true);
$aAddedExtensions = json_decode($oParameters->GetParameter('added_extensions', '[]'), true);
$aRemovedExtensions = json_decode($oParameters->GetParameter('removed_extensions', '[]'), true);
try {
$this->ValidateTransactionId();
$oConfig = MetaModel::GetConfig();
DataFeatureRemovalLog::Debug(
__METHOD__,
__METHOD__.": extension lists",
null,
['sSourceEnv' => $sSourceEnv, 'sBuildDir' => $sBuildDir, 'bIsDirEmpty' => $bIsDirEmpty, glob("$sBuildDir/*")]
['removed_extensions' => $aRemovedExtensions,
'selected_extensions' => $aSelectedExtensions,
'added_extensions' => $aAddedExtensions,
]
);
$this->oRuntimeEnvironment->DoCompile($aSelectedExtensions, $aRemovedExtensions, $aSelectedModules, MFCompiler::CanUseSymbolicLinks());
} else {
if (count($aSelectedModules) === 0) {
$aSelectedModules = $this->oRuntimeEnvironment->GetModulesToLoadFromChoices($oConfig, $aSelectedExtensions);
$aSelectedExtensions = Metamodel::GetServiceLocator()->Get('DataFeatureRemoverExtension')->GetExtensionMap()->GetSelectedExtensions($oConfig, array_keys($aAddedExtensions), array_keys($aRemovedExtensions));
$oParameters->SetParameter('selected_extensions', $this->ConvertIntoSetupFormat($aSelectedExtensions));
}
$sBuildDir = APPROOT."/env-$sSourceEnv-build";
if (! is_dir($sBuildDir)) {
SetupUtils::builddir($sBuildDir);
}
$bIsDirEmpty = count(scandir($sBuildDir)) === 2;
$bForceCompilation = Session::Get('bForceCompilation', false);
if ($bIsDirEmpty || $bForceCompilation) {
$oRuntimeEnvironment->CopySetupFiles();
if (count($aSelectedModules) === 0) {
$oExtensionsMap = \iTopExtensionsMap::GetExtensionsMap($oRuntimeEnvironment->GetBuildEnv());
// Removed modules are stored as static for FindModules()
$oExtensionsMap->DeclareExtensionAsRemoved($aRemovedExtensions);
$aSelectedModules = $oRuntimeEnvironment->GetModulesToLoadFromSelectedExtensions($oConfig, $aSelectedExtensions);
}
DataFeatureRemovalLog::Debug(
__METHOD__,
null,
['sSourceEnv' => $sSourceEnv, 'sBuildDir' => $sBuildDir, 'bIsDirEmpty' => $bIsDirEmpty, glob("$sBuildDir/*")]
);
$oRuntimeEnvironment->DoCompile($aSelectedExtensions, $aRemovedExtensions, $aSelectedModules, MFCompiler::CanUseSymbolicLinks());
Session::Unset('bForceCompilation');
} else {
if (count($aSelectedModules) === 0) {
$aSelectedModules = $oRuntimeEnvironment->GetModulesToLoadFromSelectedExtensions($oConfig, $aSelectedExtensions);
}
}
DataFeatureRemovalLog::Debug(__METHOD__.": modules", null, ['selected_modules' => $aSelectedModules]);
} catch (CoreException $e) {
$aParams['error_message'] = $e->getHtmlDesc();
} catch (Exception $e) {
$aParams['error_message'] = $e->getMessage();
}
$aParams['success_message'] = Dict::S('DataFeatureRemoval:Compile:Success');
$aParams['transaction_id'] = utils::GetNewTransactionId();
$oParameters->SetParameter('selected_modules', json_encode($aSelectedModules));
$this->DisplayJSONPage($aParams);
}
public function OperationAjaxRunAudit(): void
{
$oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME);
$aParams = [];
$aPageParams = [];
try {
$this->ValidateTransactionId();
$sSourceEnv = MetaModel::GetEnvironment();
$oSetupAudit = new SetupAudit($sSourceEnv);
$aRemovedClasses = array_keys($oSetupAudit->RunDataAudit());
$oParameters->SetParameter('classes', $aRemovedClasses);
new ContextTag(ContextTag::TAG_SETUP);
$aPageParams['sLaunchSetupUrl'] = utils::GetAbsoluteUrlAppRoot().'setup/wizard.php';
$aPageParams['aSetupParams'] = [
"_class" => "WizStepLandingBeforeAudit",
"operation" => "next",
];
$aPageParams['sTransactionId'] = utils::GetNewTransactionId();
$aDeletionPlanSummaryEntities = $this->GetDeletionPlanSummaryEntities($aRemovedClasses);
[$aPageParams['aDeletionPlanSummary'], $aPageParams['iQueryCount'], $aPageParams['bDeletionPossible']] = $this->GetDeletionPlanSummaryTable($aDeletionPlanSummaryEntities);
[$aPageParams['aDeletionExecutionSummary'], $aPageParams['bHasDeletionExecution']] = $this->GetExecutionSummaryTable();
$aPageParams['bDeletionNeeded'] = ($aPageParams['iQueryCount'] > 0);
if (!$aPageParams['bDeletionNeeded']) {
// Erase session setup parameters
SetupUtils::CreateSetupToken();
}
$this->DisplayAjaxPage($aPageParams, 'AjaxRunAudit');
return;
} catch (CoreException $e) {
$aParams['error_message'] = $e->getHtmlDesc();
} catch (Exception $e) {
$aParams['error_message'] = $e->getMessage();
}
$this->DisplayJSONPage($aParams);
}
private function GetExecutionSummaryTable(): array
{
$sName = 'ExcutionSummary';
$sName = 'ExecutionSummary';
$this->aDeletionExecutionSummary = unserialize(Session::Get('aDeletionExecutionSummary') ?? serialize([]));
$aTableData = [];
if (count($this->aDeletionExecutionSummary) === 0) {
@@ -278,11 +306,15 @@ class DataFeatureRemovalController extends Controller
}
private function GetDeletionPlanSummaryTable(array $aRemovedClasses): array
private function GetDeletionPlanSummaryEntities(array $aRemovedClasses): array
{
$oDataCleanupService = new StaticDeletionPlan();
return $oDataCleanupService->GetCleanupSummary($aRemovedClasses);
}
private function GetDeletionPlanSummaryTable(array $aDeletionPlanSummaryEntities): array
{
$sName = 'DeletionPlanSummary';
$oDataCleanupService = new StaticDeletionPlan();
$aDeletionPlanSummaryEntities = $oDataCleanupService->GetCleanupSummary($aRemovedClasses);
$aColumns = ['Class', 'Delete Count' , 'Update Count', 'Issue Count'];
$aRows = [];
$iQueryCount = 0;
@@ -305,9 +337,10 @@ class DataFeatureRemovalController extends Controller
{
$this->ValidateTransactionId();
$this->aDeletionExecutionSummary = unserialize(Session::Get('aDeletionExecutionSummary'));
$this->aDeletionExecutionSummary = unserialize(Session::Get('aDeletionExecutionSummary') ?? serialize([]));
Session::Unset('aDeletionExecutionSummary');
$aClasses = utils::ReadPostedParam('classes', null, utils::ENUM_SANITIZATION_FILTER_CLASS);
$oParameters = new SessionParameters(SetupUtils::SESSION_PARAMETERS_NAME);
$aClasses = $oParameters->GetParameter('classes', []);
$oDataCleanupService = new DataCleanupService();
$aDeletionExecutionSummary = $oDataCleanupService->ExecuteCleanup($aClasses);
@@ -322,6 +355,7 @@ class DataFeatureRemovalController extends Controller
$oSummary->iTotalUpdateCount += $oExecutionSummary->iUpdateCount;
}
Session::Set('aDeletionExecutionSummary', serialize($this->aDeletionExecutionSummary));
$this->OperationAnalysisResult();
}
@@ -329,9 +363,9 @@ class DataFeatureRemovalController extends Controller
{
$aExtensionsData = [];
if ($bIncludePackageExtensions) {
$aExtensionsRef = DataFeatureRemoverExtensionService::GetInstance()->GetExtensionMap()->GetAllExtensionsWithPreviouslyInstalled();
$aExtensionsRef = Metamodel::GetServiceLocator()->Get('DataFeatureRemoverExtension')->GetExtensionMap()->GetAllExtensionsWithPreviouslyInstalled();
} else {
$aExtensionsRef = DataFeatureRemoverExtensionService::GetInstance()->ReadItopExtensions();
$aExtensionsRef = Metamodel::GetServiceLocator()->Get('DataFeatureRemoverExtension')->ReadItopExtensions();
}
foreach ($aExtensionsRef as $oExtension) {
@@ -415,12 +449,11 @@ class DataFeatureRemovalController extends Controller
/**
* Read extensions selected from posted parameters
* @return int Number of extensions to be added or removed
*/
public function ReadExtensionsDiff(): int
public function ReadExtensionsDiff(): void
{
if (!is_null($this->aExtensionsToCheck)) {
return count($this->aExtensionsToCheck['to_be_installed']) + count($this->aExtensionsToCheck['to_be_removed']);
return;
}
$aAvailableExtensions = $this->GetAvailableExtensions();
@@ -431,13 +464,14 @@ class DataFeatureRemovalController extends Controller
'extensions_not_uninstallable' => [],
];
foreach ($aAvailableExtensions as $sCode => &$aExtensionData) {
if (!isset($aSelectedExtensionsFromUI[$sCode])) {
$sCodeFromUI = $sCode !== '' ? $sCode : iTopExtension::CODE_NO_CODE;
if (!isset($aSelectedExtensionsFromUI[$sCodeFromUI])) {
continue;
}
if ($aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCode] !== 'on') {
if ($aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCodeFromUI] !== 'on') {
$aExtensionData['extra_flags']['selected'] = false;
$sLabel = $aAvailableExtensions[$sCode]['label'];
$sLabel = $aExtensionData['label'];
$this->aExtensionsToCheck['to_be_removed'][$sCode] = $sLabel;
if (! $this->bForcedUninstallation && $aExtensionData['extra_flags']['uninstallable']) {
$this->bForcedUninstallation = true;
@@ -445,13 +479,12 @@ class DataFeatureRemovalController extends Controller
if (false === $aExtensionData['extra_flags']['uninstallable'] || true === $aExtensionData['extra_flags']['remote']) {
$this->aExtensionsToCheck['extensions_not_uninstallable'][] = $sCode;
}
} elseif (!$aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCode] === 'on') {
} elseif (!$aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCodeFromUI] === 'on') {
$aExtensionData['extra_flags']['selected'] = true;
$sLabel = $aAvailableExtensions[$sCode]['label'];
$sLabel = $aExtensionData['label'];
$this->aExtensionsToCheck['to_be_installed'][$sCode] = $sLabel;
}
}
return count($this->aExtensionsToCheck['to_be_installed']) + count($this->aExtensionsToCheck['to_be_removed']);
}
/**

View File

@@ -13,16 +13,11 @@ class DeletionPlanEntity
public readonly DeletionPlanItem $oUpdate;
public readonly DeletionPlanItem $oIssue;
/**
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oDelete
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oUpdate
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oIssue
*/
public function __construct()
{
$this->oDelete = $oDelete ?? new DeletionPlanItem();
$this->oUpdate = $oUpdate ?? new DeletionPlanItem();
$this->oIssue = $oIssue ?? new DeletionPlanItem();
$this->oDelete = new DeletionPlanItem();
$this->oUpdate = new DeletionPlanItem();
$this->oIssue = new DeletionPlanItem();
}
public function TotalCount(): int

View File

@@ -1,52 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2025 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\DataFeatureRemoval\Helper;
use MetaModel;
use utils;
class DataFeatureRemovalConfig
{
private static DataFeatureRemovalConfig $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): DataFeatureRemovalConfig
{
if (!isset(self::$oInstance)) {
self::$oInstance = new DataFeatureRemovalConfig();
}
return self::$oInstance;
}
public function Get(string $sParamName, $default = null)
{
return MetaModel::GetModuleSetting(DataFeatureRemovalHelper::MODULE_NAME, $sParamName, $default);
}
public function GetBoolean(string $sParamName, $default = null): bool
{
$res = $this->Get($sParamName, $default);
return boolval($res);
}
public function IsEnabled(): bool
{
return $this->GetBoolean('enable', false);
}
public function Set(string $sParamName, $value)
{
$oConfig = utils::GetConfig();
$oConfig->SetModuleSetting(DataFeatureRemovalHelper::MODULE_NAME, $sParamName, $value);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\DataFeatureRemoval\Hook;
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\Events\iEventServiceSetup;
use MetaModel;
use utils;
class EventListener implements iEventServiceSetup
{
/**
* @inheritDoc
*/
public function RegisterEventsAndListeners()
{
EventService::RegisterListener(
sEvent: \EVENT_SERVICE_LOCATOR_INITIALIZED,
callback: [$this, 'OnServiceLocatorInitialized']
);
}
public function OnServiceLocatorInitialized(EventData $oEventData): void
{
MetaModel::GetServiceLocator()->AddClassesFromFile(utils::GetAbsoluteModulePath(DataFeatureRemovalHelper::MODULE_NAME).'Config/ServiceLocator.xml');
}
}

View File

@@ -13,34 +13,19 @@ use MetaModel;
class DataFeatureRemoverExtensionService
{
private static DataFeatureRemoverExtensionService $oInstance;
private ?iTopExtensionsMap $oMap = null;
private array $aItopExtensions = [];
private array $aIncludingExtensionsByModuleName = [];
protected function __construct()
public function __construct()
{
}
final public static function GetInstance(): DataFeatureRemoverExtensionService
{
if (!isset(self::$oInstance)) {
self::$oInstance = new DataFeatureRemoverExtensionService();
}
return self::$oInstance;
}
final public static function SetInstance(?DataFeatureRemoverExtensionService $oInstance): void
{
self::$oInstance = $oInstance;
}
/**
* @param string $sModuleName
*
* @return array
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
* @throws \Exception
*/
public function GetIncludingExtensions(string $sModuleName): array
{
@@ -62,6 +47,7 @@ class DataFeatureRemoverExtensionService
/**
* @return \iTopExtensionsMap
* @throws \Exception
*/
public function GetExtensionMap(): iTopExtensionsMap
{
@@ -74,6 +60,7 @@ class DataFeatureRemoverExtensionService
/**
* @return iTopExtension[]
* @throws \Exception
*/
public function ReadItopExtensions(): array
{

View File

@@ -0,0 +1,34 @@
{# @copyright Copyright (C) 2010-2026 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% if bDeletionNeeded %}
{% UIFieldSet Standard {sLegend:'DataFeatureRemoval:DeletionPlan:Title'|dict_s} %}
{% UIDataTable ForForm { sRef:'aDeletionPlanSummary', aColumns:aDeletionPlanSummary.Columns, aData:aDeletionPlanSummary.Data} %}{% EndUIDataTable %}
{% EndUIFieldSet %}
{% if bDeletionPossible %}
{% UIForm Standard {} %}
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
{% UIInput ForHidden { sName:'operation', sValue:'DoDeletion'} %}
{% UIToolbar ForButton {} %}
{% UIButton ForPrimaryAction {sLabel:'UI:Button:DoDeletion'|dict_s, sName:'btn_deletion', sId:'btn_deletion', bIsSubmit:true} %}
{% EndUIToolbar %}
{% EndUIForm %}
{% else %}
{% UIAlert ForFailure { sTitle: '', sContent: 'DataFeatureRemoval:DeletionPlan:Error:Issues'|dict_s } %}{% EndUIAlert %}
{% endif %}
{% else %}
{% UIAlert ForSuccess { sTitle:'DataFeatureRemoval:CleanupComplete:Title'|dict_s, sContent:'DataFeatureRemoval:CompilComplete'|dict_s } %}{% EndUIAlert %}
{% UIForm Standard {sId:'launch-setup-form', Action:sLaunchSetupUrl, EncType: 'application/x-www-form-urlencoded'} %}
{% for sKey, sValue in aSetupParams %}
{% UIInput ForHidden { sName:sKey, sValue:sValue } %}
{% endfor %}
{% UIButton ForPrimaryAction {sLabel:'UI:Button:Setup'|dict_s, sName:'btn_setup', sId:'btn_setup', bIsSubmit:true} %}
{% EndUIForm %}
{% endif %}
{% if bHasDeletionExecution %}
{% UIFieldSet Standard {sLegend:'DataFeatureRemoval:Execution:Title'|dict_s} %}
{% UIDataTable ForForm { sRef:'aDeletionExecutionSummary', aColumns:aDeletionExecutionSummary.Columns, aData:aDeletionExecutionSummary.Data} %}{% EndUIDataTable %}
{% EndUIFieldSet %}
{% endif %}

View File

@@ -1,92 +1,50 @@
{# @copyright Copyright (C) 2010-2026 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% UIPanel ForInformation { sTitle:'DataFeatureRemoval:Analysis:Title'|dict_s, sSubTitle: 'DataFeatureRemoval:Analysis:Subtitle'|dict_s} %}
{% 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 %}
{% UIAlert ForInformation { sTitle: '', sId: 'ajax_compile_in_progress_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}
{{ 'DataFeatureRemoval:Compile:InProgress'|dict_s }}
{% UISpinner Standard { } %}
{% EndUIAlert %}
{% UIAlert ForSuccess { sTitle:'success', sId: 'ajax_compile_success_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}{% EndUIAlert %}
{% UIAlert ForFailure { sTitle:'error', sId: 'ajax_compile_error_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}{% EndUIAlert %}
{% UIAlert ForInformation { sTitle: '', sId: 'ajax_run_audit_in_progress_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}
{{ 'DataFeatureRemoval:RunAudit:InProgress'|dict_s }}
{% UISpinner Standard { } %}
{% EndUIAlert %}
{% UIAlert ForSuccess { sTitle:'DataFeatureRemoval:RunAudit:Success'|dict_s, sId: 'ajax_run_audit_success_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}{% EndUIAlert %}
{% UIAlert ForFailure { sTitle:'error', sId: 'ajax_run_audit_error_msg', AddCSSClasses: ['ibo-is-hidden', 'ibo-is-html-content'] } %}{% EndUIAlert %}
{% if aAvailableExtensionsCount == 0 %}
{% UITitle Neutral { sTitle:'DataFeatureRemoval:NoResult:Title'|dict_s, iLevel:2 } %}{% EndUITitle %}
{% else %}
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Result:Title'|dict_s, sSubTitle: '' } %}
{% UIMultiColumn Standard {} %}
{% for iColumnIndex in 0..iColumnCount-1 %}
{% UIColumn Standard {} %}
{% for aExtension in aAvailableExtensions[iColumnIndex] %}
{% if aExtension['installed'] %}
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% else %}
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% endif %}
{% endfor %}
{% EndUIColumn %}
{% endfor %}
{% EndUIMultiColumn %}
{% EndUIPanel %}
{% else %}
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Result:Title'|dict_s, sSubTitle: '' } %}
{% UIMultiColumn Standard {} %}
{% for iColumnIndex in 0..iColumnCount-1 %}
{% UIColumn Standard {} %}
{% for aExtension in aAvailableExtensions[iColumnIndex] %}
{% if aExtension['installed'] %}
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% else %}
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% endif %}
{% endfor %}
{% EndUIColumn %}
{% endfor %}
{% EndUIMultiColumn %}
{% EndUIPanel %}
{% if bDeletionNeeded %}
{% UIFieldSet Standard {sLegend:'DataFeatureRemoval:DeletionPlan:Title'|dict_s} %}
{% UIDataTable ForForm { sRef:'aDeletionPlanSummary', aColumns:aDeletionPlanSummary.Columns, aData:aDeletionPlanSummary.Data} %}{% EndUIDataTable %}
{% EndUIFieldSet %}
{% if bDeletionPossible %}
{% UIForm Standard {} %}
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
{% UIInput ForHidden { sName:'operation', sValue:'DoDeletion'} %}
{% for sKey, sClass in aClasses %}
{% UIInput ForHidden { sName:"classes[" ~ sKey ~ "]", sValue:sClass } %}
{% endfor %}
{% for sCode, sLabel in aAddedExtensions %}
{% UIInput ForHidden { sName:"aAddedExtensions[" ~ sCode ~ "]", sValue:sLabel } %}
{% endfor %}
{% for sCode, sLabel in aRemovedExtensions %}
{% UIInput ForHidden { sName:"aRemovedExtensions[" ~ sCode ~ "]", sValue:sLabel } %}
{% endfor %}
{% for sInputName, sValue in aHiddenInputs %}
{% UIInput ForHidden { sName:sInputName, sValue:sValue } %}
{% endfor %}
{% UIToolbar ForButton {} %}
{% UIButton ForPrimaryAction {sLabel:'UI:Button:DoDeletion'|dict_s, sName:'btn_deletion', sId:'btn_deletion', bIsSubmit:true} %}
{% EndUIToolbar %}
{% EndUIForm %}
{% else %}
{% UIAlert ForFailure { sContent: 'DataFeatureRemoval:DeletionPlan:Error:Issues'|dict_s } %}{% EndUIAlert %}
{% endif %}
{% else %}
{% UIAlert ForSuccess { sTitle:'DataFeatureRemoval:CleanupComplete:Title'|dict_s, sContent:'DataFeatureRemoval:CompilComplete'|dict_s, sId:value } %}{% EndUIAlert %}
{% UIForm Standard {'sId':'launch-setup-form', Action:sLaunchSetupUrl, 'EncType': 'application/x-www-form-urlencoded'} %}
{% for sKey, sValue in aSetupParams %}
{% UIInput ForHidden { sName:sKey, sValue:sValue } %}
{% endfor %}
{% UIButton ForPrimaryAction {sLabel:'UI:Button:Setup'|dict_s, sName:'btn_setup', sId:'btn_setup', bIsSubmit:true} %}
{% EndUIForm %}
{% endif %}
{% if bHasDeletionExecution %}
{% UIFieldSet Standard {sLegend:'DataFeatureRemoval:Execution:Title'|dict_s} %}
{% UIDataTable ForForm { sRef:'aDeletionExecutionSummary', aColumns:aDeletionExecutionSummary.Columns, aData:aDeletionExecutionSummary.Data} %}{% EndUIDataTable %}
{% EndUIFieldSet %}
{% endif %}
{% endif %}
<div id="ajax_run_audit" class="ibo-block"></div>
{% UIForm Standard {} %}
{% UIInput ForHidden { sName:'transaction_id', sValue:sTransactionId} %}
{% UIInput ForHidden { sName:'operation', sValue:'Main'} %}

View File

@@ -0,0 +1,56 @@
{# @copyright Copyright (C) 2010-2026 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
function ajax_run_audit(sTransactionId) {
$('#ajax_run_audit_in_progress_msg').removeClass('ibo-is-hidden');
$.post(
'{{ sAjaxURL|raw }}',
{ operation: 'ajax_run_audit', transaction_id: sTransactionId },
function (data) {
$('#ajax_run_audit_in_progress_msg').addClass('ibo-is-hidden');
if (data.error_message) {
$('#ajax_run_audit_error_msg .ibo-alert--title').html(data.error_message);
$('#ajax_run_audit_error_msg').removeClass('ibo-is-hidden');
} else {
$('#ajax_run_audit_success_msg').removeClass('ibo-is-hidden');
$('#ajax_run_audit').html(data);
}
}
)
.fail(function() {
$('#ajax_run_audit_in_progress_msg').addClass('ibo-is-hidden');
$('#ajax_run_audit_error_msg .ibo-alert--title').html('{{ 'DataFeatureRemoval:RunAudit:Error'|dict_s }}');
$('#ajax_run_audit_error_msg').removeClass('ibo-is-hidden');
});
}
function ajax_compile() {
$('#ajax_compile_in_progress_msg').removeClass('ibo-is-hidden');
$.post(
'{{ sAjaxURL|raw }}',
{ operation: 'ajax_compile', transaction_id: '{{ sTransactionId }}' },
function (data) {
$('#ajax_compile_in_progress_msg').addClass('ibo-is-hidden');
if (data.error_message) {
$('#ajax_compile_error_msg .ibo-alert--title').html(data.error_message);
$('#ajax_compile_error_msg').removeClass('ibo-is-hidden');
} else {
$('#ajax_compile_success_msg .ibo-alert--title').html(data.success_message);
$('#ajax_compile_success_msg').removeClass('ibo-is-hidden');
ajax_run_audit(data.transaction_id);
}
},
'json'
)
.fail(function() {
$('#ajax_compile_in_progress_msg').addClass('ibo-is-hidden');
$('#ajax_compile_error_msg .ibo-alert--title').html('{{ 'DataFeatureRemoval:Compile:Error'|dict_s }}');
$('#ajax_compile_error_msg').removeClass('ibo-is-hidden');
});
}
ajax_compile();

View File

@@ -5,7 +5,6 @@
{% UIForm Standard {} %}
{% UIInput ForHidden {sName:'operation', sValue:'AnalysisResult'} %}
{% UIInput ForHidden {sName:'transaction_id', sValue:sTransactionId} %}
{% UIInput ForHidden {sName:'return_application', sValue:'itop'} %}
{% UIPanel Neutral { sTitle:'DataFeatureRemoval:Features:Title'|dict_s, sSubTitle: '' } %}
{% UIMultiColumn Standard {} %}
@@ -13,9 +12,9 @@
{% UIColumn Standard {} %}
{% for aExtension in aAvailableExtensions[iColumnIndex] %}
{% if aExtension['installed'] %}
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% UIExtensionDetails Installed { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% else %}
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% UIExtensionDetails NotInstalled { sCode : aExtension['code'], sLabel : aExtension['label'], sDescription : aExtension['description'], aMetaData : [aExtension['version'], aExtension['source'], aExtension['code']], aExtraFlags : aExtension['extra_flags']} %}{% EndUIExtensionDetails %}
{% endif %}
{% endfor %}
{% EndUIColumn %}

View File

@@ -10,10 +10,10 @@ return array(
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DataCleanupSummaryEntity' => $baseDir . '/src/Entity/DataCleanupSummaryEntity.php',
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanEntity' => $baseDir . '/src/Entity/DeletionPlanEntity.php',
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanItem' => $baseDir . '/src/Entity/DeletionPlanItem.php',
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalConfig' => $baseDir . '/src/Helper/DataFeatureRemovalConfig.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\\Hook\\EventListener' => $baseDir . '/src/Hook/EventListener.php',
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataCleanupService' => $baseDir . '/src/Service/DataCleanupService.php',
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => $baseDir . '/src/Service/DataFeatureRemoverExtensionService.php',
'Combodo\\iTop\\DataFeatureRemoval\\Service\\ObjectService' => $baseDir . '/src/Service/ObjectService.php',

View File

@@ -25,10 +25,10 @@ class ComposerStaticInit4f96a7199e2c0d90e547333758b26464
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DataCleanupSummaryEntity' => __DIR__ . '/../..' . '/src/Entity/DataCleanupSummaryEntity.php',
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanEntity' => __DIR__ . '/../..' . '/src/Entity/DeletionPlanEntity.php',
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanItem' => __DIR__ . '/../..' . '/src/Entity/DeletionPlanItem.php',
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalConfig' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalConfig.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\\Hook\\EventListener' => __DIR__ . '/../..' . '/src/Hook/EventListener.php',
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataCleanupService' => __DIR__ . '/../..' . '/src/Service/DataCleanupService.php',
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => __DIR__ . '/../..' . '/src/Service/DataFeatureRemoverExtensionService.php',
'Combodo\\iTop\\DataFeatureRemoval\\Service\\ObjectService' => __DIR__ . '/../..' . '/src/Service/ObjectService.php',

View File

@@ -64,7 +64,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'DBTools:LostAttachments:Step:Analyze' => 'Suche zunächst nach verlorenen / falsch platzierten Attachments mittels einer Analyse der Datenbank',
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Analyseergebnisse:',
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Toll! Alles scheint am richtigen Ort zu sein.',
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Manche Attachments scheinen am falschen Ort zu sein. Werfen Sie einen Blick auf die folgende Liste und wählen Sie diejenigen aus, die Sie gerne verschieben möchten.',
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Manche Attachments (%1$d) scheinen am falschen Ort zu sein. Werfen Sie einen Blick auf die folgende Liste und wählen Sie diejenigen aus, die Sie gerne verschieben möchten.',
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Dateiname',
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Derzeitiger Ort',
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Verschieben nach...',

View File

@@ -16,8 +16,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'DBTools:Class' => 'Класс',
'DBTools:Title' => 'Инструменты обслуживания базы данных',
'DBTools:ErrorsFound' => 'Найденные ошибки',
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated~~',
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES~~',
'DBTools:Indication' => 'Важно: после исправления ошибок в базе данных нужно будет запустить анализ заново, так как появятся новые несоответствия',
'DBTools:Disclaimer' => 'ВНИМАНИЕ: СДЕЛАЙТЕ РЕЗЕРВНУЮ КОПИЮ БАЗЫ ДАННЫХ ПЕРЕД ЗАПУСКОМ ИСПРАВЛЕНИЙ',
'DBTools:Error' => 'Ошибка',
'DBTools:Count' => 'Количество',
'DBTools:SQLquery' => 'SQL-запрос',
@@ -28,23 +28,23 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'DBTools:ShowIds' => 'Подробный вид',
'DBTools:ShowReport' => 'Отчёт',
'DBTools:IntegrityCheck' => 'Проверка целостности',
'DBTools:FetchCheck' => 'Fetch Check (long)~~',
'DBTools:SelectAnalysisType' => 'Select analysis type~~',
'DBTools:FetchCheck' => 'Проверка выборки (долго)',
'DBTools:SelectAnalysisType' => 'Выберите тип анализа',
'DBTools:Analyze' => 'Анализировать',
'DBTools:Details' => 'Показать подробности',
'DBTools:ShowAll' => 'Показать все ошибки',
'DBTools:Inconsistencies' => 'Несоответствия базы данных',
'DBTools:DetailedErrorTitle' => '%2$s error(s) in class %1$s: %3$s~~',
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
'DBTools:DetailedErrorTitle' => 'Ошибок (%2$s) в классе %1$s: %3$s',
'DBTools:DetailedErrorLimit' => 'Список ограничен %1$s ошибками',
'DBAnalyzer-Integrity-OrphanRecord' => 'Сиротская запись в `%1$s`, она должна иметь свой аналог в таблице `%2$s`',
'DBAnalyzer-Integrity-InvalidExtKey' => 'Недопустимый внешний ключ %1$s (столбец: `%2$s.%3$s`)',
'DBAnalyzer-Integrity-MissingExtKey' => 'Отсутствует внешний ключ %1$s (столбец: `%2$s.%3$s`)',
'DBAnalyzer-Integrity-InvalidValue' => 'Недопустимое значение для %1$s (столбец: `%2$s.%3$s`)',
'DBAnalyzer-Integrity-UsersWithoutProfile' => 'Некоторые учетные записи пользователей не имеют профилей',
'DBAnalyzer-Integrity-HKInvalid' => 'Broken hierarchical key `%1$s`~~',
'DBAnalyzer-Fetch-Count-Error' => 'Fetch count error in `%1$s`, %2$d entries fetched / %3$d counted~~',
'DBAnalyzer-Integrity-FinalClass' => 'Field `%2$s`.`%1$s` must have the same value as `%3$s`.`%1$s`~~',
'DBAnalyzer-Integrity-RootFinalClass' => 'Field `%2$s`.`%1$s` must contain a valid class~~',
'DBAnalyzer-Integrity-HKInvalid' => 'Повреждён иерархический ключ `%1$s`',
'DBAnalyzer-Fetch-Count-Error' => 'Ошибка количества выборки в `%1$s`: получено записей %2$d / посчитано %3$d',
'DBAnalyzer-Integrity-FinalClass' => 'Поле `%2$s`.`%1$s` должно иметь то же значение, что и `%3$s`.`%1$s`',
'DBAnalyzer-Integrity-RootFinalClass' => 'Поле `%2$s`.`%1$s` должно содержать допустимый класс',
]);
// Database Info

View File

@@ -33,10 +33,10 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'DBTools:Disclaimer' => '免责申明: 在应用修复之前, 应先备份数据库',
'DBTools:Error' => '错误',
'DBTools:Count' => '个数',
'DBTools:SQLquery' => 'SQL查询',
'DBTools:FixitSQLquery' => '修复问题的SQL查询 (指示)',
'DBTools:SQLresult' => 'SQL结果',
'DBTools:NoError' => '数据库正确',
'DBTools:SQLquery' => 'SQL 查询',
'DBTools:FixitSQLquery' => '用于修复问题的 SQL 查询(说明)',
'DBTools:SQLresult' => 'SQL 结果',
'DBTools:NoError' => '数据库 OK',
'DBTools:HideIds' => '错误列表',
'DBTools:ShowIds' => '详细视图',
'DBTools:ShowReport' => '报告',
@@ -73,7 +73,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
// Lost attachments
Dict::Add('ZH CN', 'Chinese', '简体中文', [
'DBTools:LostAttachments' => '附件缺失',
'DBTools:LostAttachments:Disclaimer' => '可以在此搜索数据库中丢失或错放的附件. 这不是数据恢复工具, 其无法恢复已删除的数据.',
'DBTools:LostAttachments:Disclaimer' => '可以在此搜索数据库中丢失或错放的附件. 请注意, 这不是数据恢复工具, 无法恢复已删除的数据.',
'DBTools:LostAttachments:Button:Analyze' => '分析',
'DBTools:LostAttachments:Button:Restore' => '还原',

View File

@@ -40,7 +40,7 @@ oder melden Sie dem '.ITOP_APPLICATION_SHORT.' Administrator diesen Fehler, weil
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:Attachment' => 'Attachment',
'Class:Attachment+' => '',
'Class:Attachment+' => 'Datei – Text oder Bild –, die mit genau einem Objekt verknüpft ist. Sie kann nicht geändert, sondern nur gelöscht werden. Anhänge lassen sich ausschließlich beim Bearbeiten ihres Objekts anlegen.',
'Class:Attachment/Attribute:expire' => 'Läuft ab',
'Class:Attachment/Attribute:expire+' => '',
'Class:Attachment/Attribute:temp_id' => 'Temporäre ID',
@@ -84,13 +84,13 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:TriggerOnAttachmentDownload' => 'Trigger (beim Herunterladen eines Attachment eines Objekts)',
'Class:TriggerOnAttachmentDownload+' => 'Trigger für das Herunterladen des Attachments der angegebenen Klasse oder einer Unterklasse',
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
'Class:TriggerOnAttachmentCreate' => 'Trigger (bei Erstellung eines Anhangs)',
'Class:TriggerOnAttachmentCreate+' => 'Trigger bei Erstellung eines Anhangs an einem Objekt',
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Datei an E-Mail anhängen',
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'Wenn aktiviert, wird die Datei automatisch an die E-Mail angehängt, sobald eine E-Mail-Aktion ausgelöst wird',
'Class:TriggerOnAttachmentDelete' => 'Trigger (bei Löschung eines Anhangs)',
'Class:TriggerOnAttachmentDelete+' => 'Trigger bei Löschung eines Anhangs an einem Objekt',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Gelöschte Datei an E-Mail anhängen',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'Wenn aktiviert, wird die gelöschte Datei automatisch an die E-Mail angehängt, sobald eine E-Mail-Aktion ausgelöst wird',
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Ein klassenunabhängiger Trigger ist für die Klasse Attachment nicht zulässig. Bitte verwenden Sie einen spezifischen Trigger',
]);

View File

@@ -26,12 +26,12 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'Attachments:NoAttachment' => 'Нет вложений.',
'Attachments:PreviewNotAvailable' => 'Предварительный просмотр не доступен для этого типа вложений.',
'Attachments:Error:FileTooLarge' => 'Файл слишком велик для загрузки. %1$s',
'Attachments:Error:UploadedFileEmpty' => 'The received file is empty and cannot be attached.
Either you have pushed an empty file,
or ask your '.ITOP_APPLICATION_SHORT.' administrator if the '.ITOP_APPLICATION_SHORT.' server disk is full.~~',
'Attachments:Render:Icons' => 'Display as icons~~',
'Attachments:Render:Table' => 'Display as list~~',
'UI:Attachments:DropYourFileHint' => 'Drop files anywhere in this area~~',
'Attachments:Error:UploadedFileEmpty' => 'Полученный файл пуст и не может быть прикреплён.
Либо вы загрузили пустой файл,
либо обратитесь к администратору '.ITOP_APPLICATION_SHORT.' — возможно, диск сервера '.ITOP_APPLICATION_SHORT.' переполнен.',
'Attachments:Render:Icons' => 'Отображать как иконки',
'Attachments:Render:Table' => 'Отображать как список',
'UI:Attachments:DropYourFileHint' => 'Перетащите файлы в любое место этой области',
]);
//
@@ -62,7 +62,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'Attachments:File:Uploader' => 'Пользователь',
'Attachments:File:Size' => 'Размер',
'Attachments:File:MimeType' => 'Тип',
'Attachments:File:DownloadsCount' => 'Downloads~~',
'Attachments:File:DownloadsCount' => 'Скачиваний',
]);
//
// Class: Attachment
@@ -82,15 +82,15 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
//
Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
'Class:TriggerOnAttachmentCreate' => 'Trigger (on object\'s attachment creation)~~',
'Class:TriggerOnAttachmentCreate+' => 'Trigger on object\'s attachment creation~~',
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Add file in email~~',
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'If checked, the file will be automatically attached to the email when an email action is triggered~~',
'Class:TriggerOnAttachmentDelete' => 'Trigger (on object\'s attachment deletion)~~',
'Class:TriggerOnAttachmentDelete+' => 'Trigger on object\'s attachment deletion~~',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Add deleted file in email~~',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Trigger on object is not allowed on class Attachment. Please use specific trigger~~',
'Class:TriggerOnAttachmentDownload' => 'Триггер (на скачивание вложения объекта)',
'Class:TriggerOnAttachmentDownload+' => 'Триггер на скачивание вложения объекта заданного класса (или его дочернего класса)',
'Class:TriggerOnAttachmentCreate' => 'Триггер (на создание вложения объекта)',
'Class:TriggerOnAttachmentCreate+' => 'Триггер на создание вложения объекта',
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email' => 'Добавлять файл в email',
'Class:TriggerOnAttachmentCreate/Attribute:file_in_email+' => 'Если отмечено, файл будет автоматически прикреплён к письму при срабатывании действия email',
'Class:TriggerOnAttachmentDelete' => 'Триггер (на удаление вложения объекта)',
'Class:TriggerOnAttachmentDelete+' => 'Триггер на удаление вложения объекта',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Добавлять удалённый файл в email',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'Если отмечено, удалённый файл будет автоматически прикреплён к письму при срабатывании действия email',
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Триггер на объект не допускается для класса Attachment. Используйте специальный триггер',
]);

View File

@@ -20,7 +20,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'bkp-mysqldump-ok' => 'Утилита mysqldump найдена: %1$s',
'bkp-mysqldump-notfound' => 'Утилиту mysqldump найти не удалось: %1$s - пожалуйста, убедитесь в том, что она установлена, и путь до директории с бинарными файлами добавлен в PATH, либо измените параметр mysql_bindir в файле конфигурации.',
'bkp-mysqldump-issue' => 'Утилита mysqldump на может быть запущена (retcode=%1$d) Пожалуйста, убедитесь в том, что она установлена, и путь до директории с бинарными файлами добавлен в PATH, либо измените параметр mysql_bindir в файле конфигурации.',
'bkp-missing-dir' => 'The target directory <code>%1$s</code> could not be found~~',
'bkp-missing-dir' => 'Целевой каталог <code>%1$s</code> не найден',
'bkp-free-disk-space' => '<b>%1$s свободно</b> в <code>%2$s</code>',
'bkp-dir-not-writeable' => '%1$s недоступен для записи',
'bkp-wrong-format-spec' => 'Неправильный формат шаблона названия файлов резервных копий (%1$s). Будет использован шаблон по умолчанию: %2$s',
@@ -38,7 +38,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'bkp-status-backups-manual' => 'Резервное копирование вручную',
'bkp-status-backups-none' => 'Резервных копий ещё нет',
'bkp-next-backup' => 'Следующее резервное копирование будет выполняться в <b>%1$s</b> (%2$s) в %3$s',
'bkp-next-backup-unknown' => 'The next backup is <b>not scheduled</b> yet.~~',
'bkp-next-backup-unknown' => 'Следующее резервное копирование пока <b>не запланировано</b>.',
'bkp-button-backup-now' => 'Запустить сейчас!',
'bkp-button-restore-now' => 'Восстановить!',
'bkp-confirm-backup' => 'Пожалуйста, подтвердите, что вы хотите выполнить резервное копирование прямо сейчас.',

View File

@@ -21,7 +21,6 @@
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSet;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Spinner\SpinnerUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
@@ -410,8 +409,11 @@ JS
$sEnvironment = addslashes(utils::GetCurrentEnvironment());
$oModalSpinner = SpinnerUIBlockFactory::MakeMedium(null, $sPleaseWaitBackup);
$sModalSpinnerHtml = BlockRenderer::RenderBlockTemplates($oModalSpinner);
$oBackupModalSpinner = SpinnerUIBlockFactory::MakeMedium(null, $sPleaseWaitBackup);
$sBackupModalSpinnerHtml = BlockRenderer::RenderBlockTemplates($oBackupModalSpinner);
$oRestoreModalSpinner = SpinnerUIBlockFactory::MakeMedium(null, $sPleaseWaitRestore);
$sRestoreModalSpinnerHtml = BlockRenderer::RenderBlockTemplates($oRestoreModalSpinner);
$oP->add_script(
<<<JS
@@ -424,7 +426,7 @@ function LaunchBackupNow()
{
const oModal = CombodoModal.OpenModal({
title: '$sBackUpNow',
content: `$sModalSpinnerHtml`
content: `$sBackupModalSpinnerHtml`
});
var oParams = {};
@@ -450,10 +452,10 @@ function LaunchRestoreNow(sBackupFile, sConfirmationMessage)
{
return;
}
const oModal = CombodoModal.OpenModal({
title: '$sRestore',
content: '<i class="ajax-spin fas fa-sync-alt fa-spin"></i> $sPleaseWaitRestore'
content: `$sRestoreModalSpinnerHtml`
});
$('#backup_success').addClass('ibo-is-hidden');

View File

@@ -13,7 +13,7 @@
*/
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:lnkFunctionalCIToProviderContract' => 'Verknüpfung FunctionalCI/Provider-Vertrag',
'Class:lnkFunctionalCIToProviderContract+' => '',
'Class:lnkFunctionalCIToProviderContract+' => 'Diese Verknüpfung bildet ab, welche funktionalen CIs über einen Provider-Vertrag von einem externen Unternehmen betreut werden.',
'Class:lnkFunctionalCIToProviderContract/Name' => '%1$s / %2$s',
'Class:lnkFunctionalCIToProviderContract/Attribute:providercontract_id' => 'Provider-Vertrag',
'Class:lnkFunctionalCIToProviderContract/Attribute:providercontract_id+' => '',
@@ -31,7 +31,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:lnkFunctionalCIToService' => 'Verknüpfung FunctionalCI/Service',
'Class:lnkFunctionalCIToService+' => '',
'Class:lnkFunctionalCIToService+' => 'Diese Verknüpfung bildet ab, welche funktionalen CIs zur Erbringung eines Services benötigt werden und deren Ausfall die Qualität des Services beeinträchtigen würde.',
'Class:lnkFunctionalCIToService/Name' => '%1$s / %2$s',
'Class:lnkFunctionalCIToService/Attribute:service_id' => 'Service',
'Class:lnkFunctionalCIToService/Attribute:service_id+' => '',
@@ -49,9 +49,9 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:FunctionalCI/Attribute:providercontracts_list' => 'Provider-Verträge',
'Class:FunctionalCI/Attribute:providercontracts_list+' => '',
'Class:FunctionalCI/Attribute:providercontracts_list+' => 'Alle Provider-Verträge zu diesem Konfigurationselement',
'Class:FunctionalCI/Attribute:services_list' => 'Services',
'Class:FunctionalCI/Attribute:services_list+' => '',
'Class:FunctionalCI/Attribute:services_list+' => 'Alle Services, die von diesem Konfigurationselement beeinträchtigt werden',
]);
//

View File

@@ -14,7 +14,7 @@
Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:lnkFunctionalCIToProviderContract' => 'Связь Функциональная КЕ/Договор с поставщиком',
'Class:lnkFunctionalCIToProviderContract+' => '',
'Class:lnkFunctionalCIToProviderContract/Name' => '%1$s / %2$s~~',
'Class:lnkFunctionalCIToProviderContract/Name' => '%1$s / %2$s',
'Class:lnkFunctionalCIToProviderContract/Attribute:providercontract_id' => 'Договор с поставщиком',
'Class:lnkFunctionalCIToProviderContract/Attribute:providercontract_id+' => '',
'Class:lnkFunctionalCIToProviderContract/Attribute:providercontract_name' => 'Договор с поставщиком',
@@ -32,7 +32,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:lnkFunctionalCIToService' => 'Связь Функциональная КЕ/Услуга',
'Class:lnkFunctionalCIToService+' => '',
'Class:lnkFunctionalCIToService/Name' => '%1$s / %2$s~~',
'Class:lnkFunctionalCIToService/Name' => '%1$s / %2$s',
'Class:lnkFunctionalCIToService/Attribute:service_id' => 'Услуга',
'Class:lnkFunctionalCIToService/Attribute:service_id+' => '',
'Class:lnkFunctionalCIToService/Attribute:service_name' => 'Услуга',

View File

@@ -155,6 +155,28 @@
</field>
</fields>
</class>
<class id="UserRequest" _delta="if_exists">
<lifecycle>
<states>
<state id="closed">
<flags>
<attribute id="functionalcis_list" _delta="define">
<read_only/>
</attribute>
</flags>
</state>
</states>
</lifecycle>
<presentation>
<details>
<items>
<item id="functionalcis_list" _delta="define">
<rank>10</rank>
</item>
</items>
</details>
</presentation>
</class>
</classes>
<menus>
</menus>

View File

@@ -13,7 +13,7 @@
*/
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:lnkFunctionalCIToTicket' => 'Verknüpfung FunctionalCI/Ticket',
'Class:lnkFunctionalCIToTicket+' => '',
'Class:lnkFunctionalCIToTicket+' => 'Diese Verknüpfung bildet ab, welche funktionalen CIs von einem Ticket betroffen sind (etwa von einer Benutzeranfrage, einem Incident, einem Change ...)',
'Class:lnkFunctionalCIToTicket/Name' => '%1$s / %2$s',
'Class:lnkFunctionalCIToTicket/Attribute:ticket_id' => 'Ticket',
'Class:lnkFunctionalCIToTicket/Attribute:ticket_id+' => '',

View File

@@ -14,7 +14,7 @@
Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:lnkFunctionalCIToTicket' => 'Связь Функциональная КЕ/Тикет',
'Class:lnkFunctionalCIToTicket+' => '',
'Class:lnkFunctionalCIToTicket/Name' => '%1$s / %2$s~~',
'Class:lnkFunctionalCIToTicket/Name' => '%1$s / %2$s',
'Class:lnkFunctionalCIToTicket/Attribute:ticket_id' => 'Тикет',
'Class:lnkFunctionalCIToTicket/Attribute:ticket_id+' => '',
'Class:lnkFunctionalCIToTicket/Attribute:ticket_ref' => 'Тикет',

View File

@@ -19,14 +19,14 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Menu:NewChange+' => 'Einen neuen Change erstellen',
'Menu:SearchChanges' => 'Suche nach Changes',
'Menu:SearchChanges+' => 'Unter den bestehenden Changes suchen',
'Menu:Change:Shortcuts' => 'Changes~~',
'Menu:Change:Shortcuts+' => 'Shortcuts to predefined sets of Changes~~',
'Menu:Change:Shortcuts' => 'Changes',
'Menu:Change:Shortcuts+' => 'Verknüpfungen zu vordefinierten Change-Listen',
'Menu:WaitingAcceptance' => 'Changes, die auf Annahme warten',
'Menu:WaitingAcceptance+' => 'Changes, die auf Annahme warten',
'Menu:WaitingApproval' => 'Changes, die auf Genehmigung warten',
'Menu:WaitingApproval+' => 'Changes, die auf Genehmigung warten',
'Menu:Changes' => 'Offene Changes',
'Menu:Changes+' => 'Offene Changes',
'Menu:Changes+' => 'Alle Changes, die nicht im Status "geschlossen" sind',
'Menu:MyChanges' => 'Changes, die mir zugewiesen sind',
'Menu:MyChanges+' => 'Changes, die mir zugewiesen sind',
'UI-ChangeManagementOverview-ChangeByCategory-last-7-days' => 'Changes der letzten sieben Tage nach Kategorie',
@@ -53,7 +53,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:Change' => 'Change',
'Class:Change+' => '',
'Class:Change+' => 'Abstrakte Klasse. Ein Ticket-Typ zur Behandlung von Changes, die sich auf erbrachte Services auswirken.',
'Class:Change/Attribute:status' => 'Status',
'Class:Change/Attribute:status+' => '',
'Class:Change/Attribute:status/Value:new' => 'Neu',
@@ -76,7 +76,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:Change/Attribute:status/Value:monitored+' => '',
'Class:Change/Attribute:status/Value:closed' => 'Geschlossen',
'Class:Change/Attribute:status/Value:closed+' => '',
'Class:Change/Attribute:reason' => 'Ursache',
'Class:Change/Attribute:reason' => 'Ablehnungsgrund',
'Class:Change/Attribute:reason+' => '',
'Class:Change/Attribute:requestor_id' => 'Antragsteller',
'Class:Change/Attribute:requestor_id+' => '',
@@ -92,7 +92,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:Change/Attribute:supervisor_group_name+' => '',
'Class:Change/Attribute:supervisor_id' => 'Aufsicht',
'Class:Change/Attribute:supervisor_id+' => '',
'Class:Change/Attribute:supervisor_email' => 'Aufsicht',
'Class:Change/Attribute:supervisor_email' => 'E-Mail der Aufsicht',
'Class:Change/Attribute:supervisor_email+' => '',
'Class:Change/Attribute:manager_group_id' => 'Manager-Team',
'Class:Change/Attribute:manager_group_id+' => '',
@@ -100,7 +100,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:Change/Attribute:manager_group_name+' => '',
'Class:Change/Attribute:manager_id' => 'Manager',
'Class:Change/Attribute:manager_id+' => '',
'Class:Change/Attribute:manager_email' => 'Manager',
'Class:Change/Attribute:manager_email' => 'E-Mail des Managers',
'Class:Change/Attribute:manager_email+' => '',
'Class:Change/Attribute:outage' => 'Ausfall',
'Class:Change/Attribute:outage+' => '',
@@ -156,7 +156,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:RoutineChange' => 'Routine Change',
'Class:RoutineChange+' => '',
'Class:RoutineChange+' => 'Standard-Change mit geringem Risiko, ohne zusätzliche Genehmigung, für den ein freigegebenes Verfahren existiert, das angewendet werden muss.',
'Class:RoutineChange/Stimulus:ev_validate' => 'Validieren',
'Class:RoutineChange/Stimulus:ev_validate+' => '',
'Class:RoutineChange/Stimulus:ev_reject' => 'Ablehnen',
@@ -187,7 +187,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:ApprovedChange' => 'Genehmigte Changes',
'Class:ApprovedChange+' => '',
'Class:ApprovedChange+' => 'Abstrakte Klasse. Normale und Notfall-Changes, die eine Genehmigung erfordern.',
'Class:ApprovedChange/Attribute:approval_date' => 'Datum der Genehmigung',
'Class:ApprovedChange/Attribute:approval_date+' => '',
'Class:ApprovedChange/Attribute:approval_comment' => 'Kommentar zur Genehmigung',
@@ -222,7 +222,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:NormalChange' => 'Normaler Change',
'Class:NormalChange+' => '',
'Class:NormalChange+' => 'Change-Typ',
'Class:NormalChange/Attribute:acceptance_date' => 'Datum der Annahme',
'Class:NormalChange/Attribute:acceptance_date+' => '',
'Class:NormalChange/Attribute:acceptance_comment' => 'Kommentar zur Annahme',
@@ -257,7 +257,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:EmergencyChange' => 'Emergency Change',
'Class:EmergencyChange+' => '',
'Class:EmergencyChange+' => 'Change-Ticket für Notfallsituationen (Fehlfunktionen, Sicherheit). Der Genehmigungsprozess ist verkürzt, der Notfall-Change wird so schnell wie möglich umgesetzt.',
'Class:EmergencyChange/Stimulus:ev_validate' => 'Validieren',
'Class:EmergencyChange/Stimulus:ev_validate+' => '',
'Class:EmergencyChange/Stimulus:ev_reject' => 'Ablehnen',

View File

@@ -30,12 +30,12 @@ Dict::Add('EN GB', 'British English', 'British English', [
'Menu:NewChange+' => 'Create a new change ticket',
'Menu:SearchChanges' => 'Search for changes',
'Menu:SearchChanges+' => 'Search for change tickets',
'Menu:Change:Shortcuts' => 'Changes~~',
'Menu:Change:Shortcuts+' => 'Shortcuts to predefined sets of Changes~~',
'Menu:Change:Shortcuts' => 'Changes',
'Menu:Change:Shortcuts+' => 'Shortcuts to predefined sets of Changes',
'Menu:WaitingAcceptance' => 'Changes awaiting acceptance',
'Menu:WaitingAcceptance+' => '',
'Menu:WaitingApproval' => 'Changes awaiting approval',
'Menu:WaitingApproval+' => 'Changes in planned status~~',
'Menu:WaitingApproval+' => 'Changes in planned status',
'Menu:Changes' => 'Open changes',
'Menu:Changes+' => 'All changes which are not in closed status',
'Menu:MyChanges' => 'Changes assigned to me',

View File

@@ -27,24 +27,24 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Menu:Change:Overview' => '概况',
'Menu:Change:Overview+' => '',
'Menu:NewChange' => '新建变更',
'Menu:NewChange+' => '新建变更',
'Menu:NewChange+' => '新建变更工单',
'Menu:SearchChanges' => '搜索变更',
'Menu:SearchChanges+' => '搜索变更',
'Menu:Change:Shortcuts' => '快捷方式',
'Menu:Change:Shortcuts+' => 'Shortcuts to predefined sets of Changes~~',
'Menu:SearchChanges+' => '搜索变更工单',
'Menu:Change:Shortcuts' => '变更',
'Menu:Change:Shortcuts+' => '快速访问预定义的变更数据',
'Menu:WaitingAcceptance' => '等待审核的变更',
'Menu:WaitingAcceptance+' => '',
'Menu:WaitingApproval' => '等待批准的变更',
'Menu:WaitingApproval+' => 'Changes in planned status~~',
'Menu:Changes' => '所有打开的变更',
'Menu:Changes+' => '所有打开的变更',
'Menu:WaitingApproval+' => '处于计划状态的变更',
'Menu:Changes' => '所有待处理的变更',
'Menu:Changes+' => '所有待处理的变更',
'Menu:MyChanges' => '分配给我的变更',
'Menu:MyChanges+' => '分配给我的变更 (作为办理人)',
'UI-ChangeManagementOverview-ChangeByCategory-last-7-days' => '最近一周的变更 (按类型)',
'UI-ChangeManagementOverview-Last-7-days' => '最近一周的变更 (按数量)',
'UI-ChangeManagementOverview-ChangeByDomain-last-7-days' => '最近一周的变更 (按范围)',
'UI-ChangeManagementOverview-ChangeByStatus-last-7-days' => '最近一周的变更 (按状态)',
'Tickets:Related:OpenChanges' => '打开的变更',
'Tickets:Related:OpenChanges' => '待处理的变更',
'Tickets:Related:RecentChanges' => '最近的变更 (72小时)',
]);
@@ -132,7 +132,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Class:Change/Attribute:related_incident_list' => '相关事件',
'Class:Change/Attribute:related_incident_list+' => '此变更相关的所有事件',
'Class:Change/Attribute:child_changes_list' => '子变更',
'Class:Change/Attribute:child_changes_list+' => '此变更相关的字变更',
'Class:Change/Attribute:child_changes_list+' => '此变更相关的子变更',
'Class:Change/Attribute:parent_id_friendlyname' => '父级变更昵称',
'Class:Change/Attribute:parent_id_friendlyname+' => '',
'Class:Change/Attribute:parent_id_finalclass_recall' => '变更类型',

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