Compare commits

..

192 Commits

Author SHA1 Message Date
jf-cbd
37ed4b1294 Remove console.log 2025-12-23 18:36:06 +01:00
jf-cbd
35d3194e6c Proposition that allows to include modules from ar array. Works both on usual page + ajax page 2025-12-23 14:55:00 +01:00
Benjamin Dalsass
a627f8a471 form as custom element 2025-12-23 09:03:01 +01:00
Benjamin Dalsass
7713a113cc dashboard editor 2025-12-22 08:25:23 +01:00
Benjamin Dalsass
1a93b303e3 Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager 2025-12-22 07:54:18 +01:00
Benjamin Dalsass
0dc32b3b13 dashboard editor 2025-12-22 07:53:09 +01:00
Eric Espie
048b1a94b1 N°8772 - Form compiler for collections 2025-12-19 10:56:27 +01:00
Eric Espie
d21b1eb151 N°8772 - Form compiler for collections 2025-12-19 10:23:42 +01:00
Eric Espie
d17c3aabbb Fix false positive in tests 2025-12-19 09:56:18 +01:00
Eric Espie
f010476923 🎨 Add automatic class return type to MetaModel::GetObject() and MetaModel::NewObject() 2025-12-18 17:24:20 +01:00
Eric Espie
49e58692e3 🎨 Add automatic class return type to MetaModel::GetObject() and MetaModel::NewObject() 2025-12-18 09:58:54 +01:00
Benjamin Dalsass
fb3cbcd779 form for dashboard 2025-12-17 16:36:05 +01:00
Eric Espie
c898df461f N°8772 - Added themes extensibility to controller 2025-12-17 15:51:24 +01:00
Eric Espie
e50109a898 N°8772 - Added DashletHeaderDynamic 2025-12-16 17:13:05 +01:00
Eric Espie
c6589b07f1 N°8772 - Form compiler for classes 2025-12-16 16:52:46 +01:00
Eric Espie
bd20818b66 N°8772 - cache now accept subdirectories 2025-12-16 15:41:55 +01:00
Eric Espie
b653236e86 N°8772 - cache fix 2025-12-16 11:14:39 +01:00
Eric Espie
75afd3ba46 N°8772 - compilation of choices from inputs 2025-12-16 11:03:52 +01:00
Eric Espie
2530d59e08 N°8772 - compilation of choices values 2025-12-15 17:15:07 +01:00
Eric Espie
a77e54c8cb N°8772 - PropertyTrees definition in DataModel 2025-12-15 16:19:22 +01:00
Eric Espie
eff59a93fd N°8772 - CI 2025-12-11 16:00:55 +01:00
Eric Espie
fdc958a734 N°8772 - Compiler: Expressions in input 2025-12-11 15:57:53 +01:00
Eric Espie
2b8de85af8 N°8772 - Compiler: Add errors check 2025-12-11 14:10:50 +01:00
Eric Espie
41d15f1b33 N°8772 - Compiler: Add errors check 2025-12-11 12:16:14 +01:00
Eric Espie
94fed54529 N°8772 - Compiler: add relevance conditions (with test fail) 2025-12-11 10:56:44 +01:00
Benjamin Dalsass
5352047ce4 Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager 2025-12-11 08:12:27 +01:00
Benjamin Dalsass
2f36846d87 io array option 2025-12-11 08:09:47 +01:00
Eric Espie
49ff9378aa N°8772 - Compiler: add dynamic bindings 2025-12-10 18:07:39 +01:00
Eric Espie
4d7183e067 N°8772 - Compiler: add static inputs 2025-12-10 17:53:31 +01:00
Eric Espie
c40e7ab10e N°8772 - test driven compiler wip 2025-12-10 16:21:31 +01:00
Benjamin Dalsass
090925e28b collections 2025-12-10 08:53:34 +01:00
Benjamin Dalsass
9543624a0d collections 2025-12-08 10:03:34 +01:00
Eric Espie
0d3f7c5f07 N°8772 - test driven compiler wip 2025-12-05 16:53:02 +01:00
Eric Espie
8374d12869 N°8772 - removed use and set back full ns 2025-12-05 14:31:37 +01:00
Eric Espie
9c00ea2bbc N°8772 - fix tests 2025-12-05 13:58:10 +01:00
Eric Espie
be54bd5602 N°8772 - add &debug in request 2025-12-05 11:51:35 +01:00
Eric Espie
b315b97e9e N°8772 - "generic" form controller wip 2025-12-04 17:06:41 +01:00
Benjamin Dalsass
1fe6103d4f Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager 2025-12-04 15:28:04 +01:00
Benjamin Dalsass
3503805fe1 form input 2025-12-04 15:25:52 +01:00
Eric Espie
e092f87ae9 N°8772 - "generic" form controller 2025-12-04 14:57:28 +01:00
Eric Espie
af8fb733fb N°8772 - "generic" form controller 2025-12-04 14:56:12 +01:00
Eric Espie
b9591b05d8 N°8772 - "generic" form controller 2025-12-04 14:53:58 +01:00
Eric Espie
ee762b91c1 N°8772 - form controller 2025-12-04 14:23:45 +01:00
Eric Espie
ba1d62a147 N°8772 - form controller 2025-12-04 12:10:04 +01:00
Eric Espie
4349303a78 N°8772 - Fix broken cache 2025-12-04 10:49:20 +01:00
Eric Espie
4e6c12b903 N°8772 - XML description wip 2025-12-04 10:40:20 +01:00
Eric Espie
099b996c9a N°8772 - XML description wip 2025-12-04 10:01:09 +01:00
Eric Espie
5295dec868 N°8772 - XML description wip 2025-12-04 09:29:24 +01:00
Benjamin Dalsass
9c540b6227 form collection 2025-12-04 07:15:37 +01:00
Benjamin Dalsass
842bbcee9d form type correction 2025-12-04 06:52:37 +01:00
Benjamin Dalsass
4620710f5a collections 2025-12-03 17:47:22 +01:00
jf-cbd
b1129f19d7 Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager 2025-12-02 16:50:14 +01:00
jf-cbd
d8c0cd7f21 With form in modal 2025-12-02 16:38:11 +01:00
Benjamin Dalsass
8f8a0e2672 validator component 2025-12-02 15:03:26 +01:00
Eric Espie
3d8e429c2b N°8772 - XML description traduction to PHP wip 2025-12-01 16:34:10 +01:00
Eric Espie
54aaf55a92 N°8772 - XML description traduction to PHP wip 2025-12-01 14:28:45 +01:00
Eric Espie
a88f33575d N°8772 - xml description wip 2025-12-01 08:54:49 +01:00
Benjamin Dalsass
e790929cbe validator component 2025-12-01 08:08:58 +01:00
Benjamin Dalsass
81f056a91c validator component 2025-12-01 07:58:28 +01:00
Benjamin Dalsass
06e5c80786 test and corrections 2025-11-27 16:51:46 +01:00
Benjamin Dalsass
e67fcbcfdc test and corrections 2025-11-27 16:50:47 +01:00
Eric Espie
8401aa1aec 🎨 PHP CS Fixer 2025-11-27 11:45:38 +01:00
Eric Espie
8f39e69002 autoload 2025-11-27 11:45:12 +01:00
Eric Espie
1e859ef890 🎨 PHP CS Fixer 2025-11-27 11:02:15 +01:00
Benjamin Dalsass
b8a093e625 test and corrections 2025-11-27 10:58:21 +01:00
Eric Espie
d0a2af44ac N°8772 - new LabelFormBlock and xml description wip 2025-11-27 10:23:51 +01:00
jf-cbd
827250357e Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager 2025-11-26 18:04:08 +01:00
jf-cbd
6aabed3063 Add default value for max_items_selected 2025-11-26 18:03:26 +01:00
Eric Espie
041ee2af95 N°8772 - exceptions 2025-11-26 12:33:17 +01:00
Eric Espie
72c5cbfedf N°8772 - FormBlock tests 2025-11-26 12:25:22 +01:00
jf-cbd
a0f28f725c OK for Tom Select 2025-11-26 12:04:08 +01:00
jf-cbd
02650f8cf7 WIP Tom Select options 2025-11-26 12:04:08 +01:00
Eric Espie
91958646d2 🎨 PHP CS Fixer 2025-11-26 10:57:52 +01:00
Benjamin Dalsass
87ca11b771 change turbo for form errors 2025-11-26 10:35:26 +01:00
Eric Espie
852238a110 N°8772 - OQL exceptions 2025-11-26 09:34:21 +01:00
Eric Espie
8e3d132714 🎨 PHP CS Fixer 2025-11-25 17:23:29 +01:00
Eric Espie
7a515d04dd N°8772 - exceptions 2025-11-25 17:08:41 +01:00
Benjamin Dalsass
7a67267c7d wip cleaning and other stuff 2025-11-25 10:59:27 +01:00
Benjamin Dalsass
10ac497a18 Update expression blocks 2025-11-25 08:58:14 +01:00
Benjamin Dalsass
283ecb6e67 Remove empty tests files 2025-11-25 08:52:40 +01:00
Benjamin Dalsass
33e8f9c3ae Remove string to bool converter 2025-11-25 08:49:03 +01:00
Eric Espie
039beb4c07 🎨 PHP CS Fixer 2025-11-24 17:36:57 +01:00
Eric Espie
f2a5559eea N°8772 - Removed RawFormat and OUTPUT_VALUE 2025-11-24 16:56:20 +01:00
Benjamin Dalsass
ce53487093 Quick and dirty patch TomSelect CSS style 2025-11-24 13:51:50 +01:00
Benjamin Dalsass
dd4eb9b9f0 OQLToCLassConverter class conversion 2025-11-24 13:50:51 +01:00
Eric Espie
a19472cfd1 🎨 N°8772 - CS Fixer 2025-11-20 10:55:10 +01:00
Eric Espie
7b29d562c8 N°8772 - wip 2025-11-20 10:31:18 +01:00
Eric Espie
3e471edefd 🎨 N°8772 - Moved DI under Service 2025-11-19 15:27:18 +01:00
Eric Espie
bd44f971e4 🎨 N°8772 - CS Fixer 2025-11-19 14:49:20 +01:00
Eric Espie
65bd6d9fd0 Add ModelReflection Service as dependency injection + tests 2025-11-19 14:42:32 +01:00
jf-cbd
6678689b77 Fix wrong path for turbo 2025-11-19 11:04:36 +01:00
jf-cbd
8f05c2bcba Fix CI error (cf. 2651) 2025-11-19 10:52:33 +01:00
jf-cbd
4169e10eec Fix wrong path for turbo 2025-11-19 10:49:01 +01:00
Eric Espie
e95ce21188 Add sAPPROOTURL to the data of the twig 2025-11-19 10:06:16 +01:00
Eric Espie
24d78df21d 🎨 N°8772 - CS Fixer 2025-11-19 09:21:34 +01:00
Eric Espie
546912ad77 N°8772 - 2025-11-18 18:08:50 +01:00
jf-cbd
f93926528f Add Tom-Select lib 2025-11-18 17:18:46 +01:00
Eric Espie
0cb7c7359f N°8772 - AbstractFormIO tests 2025-11-18 16:45:11 +01:00
Eric Espie
7c0f454936 Runtime Model Reflection & namespace 2025-11-18 15:36:22 +01:00
Eric Espie
11973d61ec 🎨 CS Fixer 2025-11-18 14:52:10 +01:00
Eric Espie
e08835c1a2 N°8772 - AbstractFormIO tests 2025-11-18 14:00:41 +01:00
Eric Espie
8ecb36b1fe N°8772 - FormBinding tests 2025-11-18 11:24:05 +01:00
Eric Espie
6a4ed98299 Merge remote-tracking branch 'origin/develop' into feature/8772_form_dependencies_manager 2025-11-18 09:54:29 +01:00
Eric Espie
2a8ed931e9 N°8772 - AbstractFormIO and FormBinding tests 2025-11-18 09:25:37 +01:00
Eric Espie
07a61d5f0d N°8772 - AbstractFormIO and FormBinding tests 2025-11-17 17:26:47 +01:00
Eric Espie
047c820466 N°8772 - AbstractFormIO and FormBinding tests 2025-11-17 17:26:12 +01:00
Benjamin Dalsass
753d0acce4 N°8772 - dynamic form 2025-11-17 14:51:11 +01:00
Eric Espie
51ebbc4274 N°8772 - Refactor 2025-11-14 16:42:42 +01:00
Eric Espie
3085023267 N°8772 - Error message 2025-11-14 16:24:18 +01:00
Eric Espie
e02fc9cb44 Merge dicos... 2025-11-14 16:14:34 +01:00
Eric Espie
ea95ae60a6 Merge remote-tracking branch 'origin/develop' into feature/8772_form_dependencies_manager 2025-11-14 16:08:37 +01:00
Eric Espie
b9d905ad1b N°8772 - refactor 2025-11-14 15:57:17 +01:00
Eric Espie
5b114285cc N°8772 - Error message 2025-11-14 15:39:16 +01:00
Eric Espie
b34566076e N°8772 - Form Expressions using iTop Expressions 2025-11-14 15:33:47 +01:00
Benjamin Dalsass
750b2b9cfa Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager 2025-11-14 14:32:56 +01:00
Benjamin Dalsass
bdde63a39c N°8772 - dynamic form 2025-11-14 14:30:50 +01:00
Eric Espie
891df6ab1c N°8772 - Expressions inputs are now :input instead of [[input]] (and quotes are added automatically) 2025-11-14 14:05:22 +01:00
Benjamin Dalsass
dc1ce2dc64 Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager
# Conflicts:
#	lib/composer/autoload_classmap.php
#	lib/composer/autoload_static.php
#	sources/Forms/Block/Base/ChoiceFormBlock.php
#	sources/Forms/IO/Format/StringIOFormat.php
2025-11-14 10:53:42 +01:00
Benjamin Dalsass
e5058fb8f7 N°8772 - dynamic form 2025-11-14 10:50:57 +01:00
Eric Espie
f68082da96 N°8772 - Dashlet group by 2025-11-13 17:10:39 +01:00
jf-cbd
7733f13d14 Add Tom-Select lib 2025-11-13 16:43:49 +01:00
Eric Espie
cef8fbc859 N°8772 - Dashlet group by 2025-11-13 16:42:04 +01:00
Eric Espie
36d49d3550 N°8772 - wip 2025-11-13 15:02:09 +01:00
Eric Espie
c2ec3a9f02 N°8772 - remove unused class 2025-11-13 09:36:36 +01:00
Benjamin Dalsass
4d159ea3f1 Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager 2025-11-12 17:11:29 +01:00
Benjamin Dalsass
5cacfcb754 N°8772 - dynamic form 2025-11-12 17:09:40 +01:00
Eric Espie
101b1b217e N°8772 - remove debug if not wanted like in config edition 2025-11-12 14:23:19 +01:00
Eric Espie
5af8adf7ce N°8772 - fix debug display 2025-11-12 13:07:37 +01:00
Benjamin Dalsass
3d2485a004 N°8772 - dynamic form 2025-11-12 09:33:29 +01:00
Benjamin Dalsass
cbb0e2ef7e N°8772 - dynamic form 2025-11-12 08:33:28 +01:00
Benjamin Dalsass
7095e5e959 Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager 2025-11-12 08:24:04 +01:00
Benjamin Dalsass
7fd27913f4 N°8772 - dynamic form 2025-11-12 08:20:17 +01:00
Eric Espie
cfdf4121d3 N°8772 - Dynamic debug and error 2025-11-07 11:24:31 +01:00
Benjamin Dalsass
a448f668bc N°8772 - dynamic form 2025-11-07 11:00:15 +01:00
Eric Espie
b6ec29c6ec N°8772 - ExpressionFormBlock WIP 2025-11-06 17:12:25 +01:00
Eric Espie
03c37f2021 N°8772 - Fix IO tests 2025-11-05 16:54:18 +01:00
Benjamin Dalsass
5b7a1ee44f N°8772 - dynamic form 2025-11-05 15:52:23 +01:00
Eric Espie
9ea546ebf6 N°8772 - Turbo + Collections WIP 2025-11-05 11:19:03 +01:00
Eric Espie
8fdad7997e N°8772 - Turbo Ok 2025-11-05 10:15:58 +01:00
Eric Espie
b3ed7f4f5b N°8772 - UIBlocks 2025-11-04 17:36:44 +01:00
jf-cbd
4f4ba7167d WIP 2025-11-04 17:02:56 +01:00
Eric Espie
a092b65be7 N°8772 - WIP dependencies - Does not work yet 2025-11-04 15:22:14 +01:00
Eric Espie
5a1f6ffde9 N°8772 - WIP dependencies - Does not work yet 2025-11-04 09:05:29 +01:00
Benjamin Dalsass
0909ddfb3a N°8772 - dynamic form 2025-11-03 14:01:32 +01:00
Benjamin Dalsass
20da9664c2 N°8772 - dynamic form 2025-11-03 13:50:07 +01:00
Benjamin Dalsass
68d2038488 N°8772 - dynamic form 2025-11-03 13:47:07 +01:00
Eric Espie
4c9373d034 N°8772 - Fix error 2025-11-03 10:07:49 +01:00
Eric Espie
0d7ccd7d82 N°8772 - Moved debug forms 2025-10-31 16:48:06 +01:00
jf-cbd
077c48870f Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager 2025-10-31 16:44:03 +01:00
jf-cbd
5df44b8a18 Remove .compilation-symlinks 2025-10-31 16:42:30 +01:00
Eric Espie
d4415d6c3a N°8772 - some tests 2025-10-31 14:35:44 +01:00
Eric Espie
0196765eb6 N°8772 - automatic dependency in controller WIP 2025-10-31 08:58:50 +01:00
Eric Espie
45e4d9239c N°8772 - automatic dependency in controller WIP 2025-10-31 08:55:45 +01:00
Eric Espie
0df54413b3 N°8772 - cleanup code 2025-10-30 14:24:09 +01:00
Eric Espie
601cde0e2d N°8772 - Traditional parameters declaration 2025-10-30 14:16:36 +01:00
Eric Espie
4c10cfee60 N°8772 - Errors & turbo 2025-10-30 11:53:29 +01:00
Eric Espie
7a6f36b395 N°8772 - Debug & turbo 2025-10-30 10:00:12 +01:00
Eric Espie
06dbdcb5cd N°8772 - Turbo WIP 2025-10-29 16:52:19 +01:00
Benjamin Dalsass
c00bcbcd81 Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager 2025-10-29 14:00:08 +01:00
Eric Espie
1394edc111 N°8772 - Turbo WIP
N°8772 - Twig factorization
2025-10-29 13:59:35 +01:00
Benjamin Dalsass
8582e89b8c Merge remote-tracking branch 'origin/feature/8772_form_dependencies_manager' into feature/8772_form_dependencies_manager
# Conflicts:
#	sources/Forms/Block/AbstractFormBlock.php
#	sources/Forms/Block/DataModel/AttributeValueChoiceFormBlock.php
#	templates/application/forms/itop_console_layout.html.twig
2025-10-29 13:31:02 +01:00
Benjamin Dalsass
6952bfa978 N°8771 - Add Symfony form component to iTop core
- IO debug
2025-10-29 13:14:15 +01:00
Eric Espie
98048219e7 N°8772 - Twig factorization 2025-10-29 09:31:40 +01:00
Eric Espie
8134d9a592 N°8772 - Form turbo stream WIP 2025-10-28 16:41:59 +01:00
Eric Espie
2a3de68652 N°8772 - Form turbo stream WIP 2025-10-28 15:34:19 +01:00
Benjamin Dalsass
5d335b39d2 N°8771 - Add Symfony form component to iTop core
- IO debug
2025-10-28 15:22:29 +01:00
Eric Espie
c2fcf4144b N°8772 - Sub-forms WIP 2025-10-24 16:56:45 +02:00
Eric Espie
675db85131 N°8772 - log for exception 2025-10-24 15:44:41 +02:00
Benjamin Dalsass
212309e938 N°8771 - Add Symfony form component to iTop core
- IO debug
2025-10-24 15:42:33 +02:00
Benjamin Dalsass
a4fbe90579 N°8771 - Add Symfony form component to iTop core
- IO debug
2025-10-24 15:36:51 +02:00
Benjamin Dalsass
eb3c5e4eee N°8771 - Add Symfony form component to iTop core
- IO debug
2025-10-24 14:46:59 +02:00
Benjamin Dalsass
33c03f9493 N°8771 - Add Symfony form component to iTop core
- IO debug
2025-10-24 09:29:53 +02:00
Benjamin Dalsass
243a525e15 N°8771 - Add Symfony form component to iTop core
- IO debug
2025-10-24 08:56:04 +02:00
Benjamin Dalsass
a1025ac837 N°8771 - Add Symfony form component to iTop core
- IO
2025-10-24 08:35:10 +02:00
Benjamin Dalsass
7708f8e00e N°8771 - Add Symfony form component to iTop core
- IO
2025-10-24 07:54:51 +02:00
Benjamin Dalsass
a51272f107 N°8771 - Add Symfony form component to iTop core
- IO
2025-10-24 07:15:35 +02:00
Benjamin Dalsass
5582afe5ae N°8771 - Add Symfony form component to iTop core
- add licence info
2025-10-24 06:37:22 +02:00
Eric Espie
21b786e0fa N°8772 - Form dependencies manager implementation - WIP 2025-10-23 16:57:35 +02:00
Eric Espie
fd449d195d N°8772 - Form dependencies manager implementation - WIP 2025-10-23 15:11:14 +02:00
Eric Espie
98c0b11db7 N°8772 - Form dependencies manager implementation - WIP 2025-10-23 13:51:24 +02:00
Benjamin Dalsass
8c3543363e N°8771 - Add Symfony form component to iTop core
- WIP
2025-10-23 13:29:04 +02:00
Benjamin Dalsass
5dea3f5299 N°8771 - Add Symfony form component to iTop core
- WIP
2025-10-23 12:11:02 +02:00
Benjamin Dalsass
239814a38c N°8771 - Add Symfony form component to iTop core
- restore branch
2025-10-23 11:28:01 +02:00
Benjamin Dalsass
c4e2cc6c1c Merge remote-tracking branch 'origin/develop' into feature/8772_form_dependencies_manager
# Conflicts:
#	sources/Application/TwigBase/Controller/Controller.php
2025-10-23 11:14:24 +02:00
Benjamin Dalsass
ee745f8be9 N°8772 - Form dependencies manager implementation
- turbo implementation
2025-10-23 11:11:51 +02:00
Benjamin Dalsass
1a4a64cec1 N°8772 - Form dependencies manager implementation 2025-10-23 11:11:39 +02:00
Benjamin Dalsass
834d4d461b N°8771 - Add Symfony form component to iTop core
- restore $aAdditionalPaths missing parameter
2025-10-23 11:10:59 +02:00
Eric Espie
5df73f5820 N°8781 - Improve twig base controller render error report 2025-10-23 11:10:54 +02:00
odain
c83d998924 N°4058 - fix PHP Deprecated base64_decodestrlen : Passing nul 2025-10-23 11:10:17 +02:00
Stephen Abello
ccdfbbe0bf N°8701 - Fix class icon alt text displaying in portal tile when there's no icon defined 2025-10-23 11:10:17 +02:00
Benjamin Dalsass
0dae7346d1 N°8772 - Form dependencies manager implementation
- turbo implementation
2025-10-20 15:16:44 +02:00
Benjamin Dalsass
cdfded766f N°8772 - Form dependencies manager implementation 2025-10-17 09:03:45 +02:00
216 changed files with 4115 additions and 6719 deletions

View File

@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://www.combodo.com/itop-schema/3.3"
version="3.3">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
<classes>
<class id="AbstractResource" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<comment>/* Resource access control abstraction. Can be herited by abstract resource access control classes. Generally controlled using UR_ACTION_MODIFY access right. */</comment>
<comment>/* Resource access control abstraction. Can be herited by abstract resource access control classes. Generaly controlled using UR_ACTION_MODIFY access right. */</comment>
<abstract>true</abstract>
</properties>
<presentation/>
@@ -554,7 +552,7 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
<description><![CDATA[Inform the listeners about the connection states]]></description>
<event_data>
<event_datum id="code">
<description>The login step result code (LoginWebPage::EXIT_CODE_...)</description>
<description>The login step result code (LoginWebPage::EXIT_CODE_...) </description>
<type>integer</type>
</event_datum>
<event_datum id="state">
@@ -851,25 +849,36 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
</methods>
</class>
</classes>
<property_types _delta="define">
<property_type id="Dashlet" xsi:type="Combodo-AbstractPropertyType"/>
<property_type id="DashletGroupBy" xsi:type="Combodo-PropertyType">
<property_trees _delta="define" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://www.combodo.com/itop-schema/3.3">
<property_tree id="DashletGroupBy" xsi:type="Combodo-PropertyTree">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<label>UI:DashletGroupBy:Title</label>
<nodes>
<node id="title" xsi:type="Combodo-ValueType-Label">
<label>UI:DashletGroupBy:Prop-Title</label>
</node>
<node id="query" xsi:type="Combodo-ValueType-OQL">
<label>UI:DashletGroupBy:Prop-Query</label>
</node>
<node id="group_by" xsi:type="Combodo-ValueType-ClassAttributeGroupBy">
<label>UI:DashletGroupBy:Prop-GroupBy</label>
<nodes>
<node id="title" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-Title</label>
<value-type xsi:type="Combodo-ValueType-Label"/><!-- LabelFormBlock -->
</node>
<node id="query" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-Query</label>
<data-transform>
<xml-node-empty-not-allowed/> <!-- mandatory quoi ! -->
</data-transform>
<value-type xsi:type="Combodo-ValueType-OQL"/><!-- OqlFormBlock -->
</node>
<node id="group_by" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-GroupBy</label>
<data-transform>
<xml-node-empty-not-allowed/>
</data-transform>
<value-type xsi:type="Combodo-ValueType-ClassAttributeGroupBy"> <!-- par défaut, il proposera le state attribute de la classe -->
<class>{{query.selected_class}}</class>
</node>
<node id="style" xsi:type="Combodo-ValueType-Choice"> <!-- Possible de le cacher, etc celui-ci nous met dedans -->
<label>UI:DashletGroupBy:Prop-Style</label>
</value-type>
</node>
<node id="style" xsi:type="Combodo-Property"> <!-- possible de le cacher, etc.... celui-ci nous met dedans -->
<label>UI:DashletGroupBy:Prop-Style</label>
<data-transform>
<xml-node-empty-defaults-to>bars</xml-node-empty-defaults-to>
</data-transform>
<value-type xsi:type="Combodo-ValueType-Choice">
<values>
<value id="bars">
<label>UI:DashletGroupByBars:Label</label>
@@ -881,19 +890,31 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
<label>UI:DashletGroupByTable:Label</label>
</value>
</values>
</node>
<node id="aggregation_function" xsi:type="Combodo-ValueType-AggregateFunction">
<label>UI:DashletGroupBy:Prop-Function</label>
</value-type>
</node>
<node id="aggregation_function" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-Function</label>
<data-transform>
<xml-node-empty-not-allowed/> <!-- mandatory quoi ! -->
</data-transform>
<value-type xsi:type="Combodo-ValueType-AggregateFunction"> <!-- par défaut : count -->
<class>{{query.selected_class}}</class> <!-- pour savoir si il y a des attributs additionnables -->
</node>
<node id="aggregation_attribute" xsi:type="Combodo-ValueType-ClassAttribute">
<label>UI:DashletGroupBy:Prop-FunctionAttribute</label>
<relevance-condition>{{aggregation_function.value != 'count'}}</relevance-condition>
</value-type>
</node>
<node id="aggregation_attribute" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-FunctionAttribute</label>
<relevance-condition>{{aggregation_function.value != 'count'}}</relevance-condition>
<data-transform>
<xml-node-empty-not-allowed/> <!-- mandatory quoi ! -->
</data-transform>
<value-type xsi:type="Combodo-ValueType-ClassAttribute">
<class>{{query.selected_class}}</class>
<category>numeric</category>
</node>
<node id="order_by" xsi:type="Combodo-ValueType-ChoiceFromInput">
<label>UI:DashletGroupBy:Prop-OrderField</label>
</value-type>
</node>
<node id="order_by" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-OrderField</label>
<value-type xsi:type="Combodo-ValueType-ChoiceFromInput">
<values>
<value id="attribute">
<label>{{aggregation_attribute.label}}</label>
@@ -902,13 +923,19 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
<label>{{aggregation_function.label}}</label>
</value>
</values>
</node>
<node id="limit" xsi:type="Combodo-ValueType-Integer">
<label>UI:DashletGroupBy:Prop-Limit</label>
<relevance-condition>{{order_by.value = 'function'}}</relevance-condition>
</node>
<node id="order_direction" xsi:type="Combodo-ValueType-Choice">
<label>UI:DashletGroupBy:Prop-OrderDirection</label>
</value-type>
</node>
<node id="limit" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-Limit</label>
<relevance-condition>{{order_by.value = 'function'}}</relevance-condition>
<value-type xsi:type="Combodo-ValueType-Integer"/>
</node>
<node id="order_direction" xsi:type="Combodo-Property">
<label>UI:DashletGroupBy:Prop-OrderDirection</label>
<data-transform>
<xml-node-empty-not-allowed/>
</data-transform>
<value-type xsi:type="Combodo-ValueType-Choice">
<values>
<value id="asc">
<label>UI:DashletGroupBy:Order:asc</label>
@@ -917,79 +944,102 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
<label>UI:DashletGroupBy:Order:desc</label>
</value>
</values>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletBadge" xsi:type="Combodo-PropertyType">
</value-type>
<display-default>desc</display-default>
</node>
</nodes>
</property_tree>
<property_tree id="DashletBadge" xsi:type="Combodo-PropertyTree">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="class" xsi:type="Combodo-ValueType-Class">
<label>UI:DashletBadge:Prop-Class</label>
<nodes>
<node id="class" xsi:type="Combodo-Property">
<label>UI:DashletBadge:Prop-Class</label>
<value-type xsi:type="Combodo-ValueType-Class">
<categories-csv>bizmodel</categories-csv>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletHeaderDynamic" xsi:type="Combodo-PropertyType">
</value-type>
</node>
</nodes>
</property_tree>
<property_tree id="DashletHeaderDynamic" xsi:type="Combodo-PropertyTree">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<label>UI:DashletHeaderDynamic:Title</label>
<nodes>
<node id="title" xsi:type="Combodo-ValueType-Label">
<label>UI:DashletHeaderDynamic:Prop-Title</label>
</node>
<node id="icon" xsi:type="Combodo-ValueType-Icon">
<label>UI:DashletHeaderDynamic:Prop-Icon</label>
</node>
<node id="subtitle" xsi:type="Combodo-ValueType-Label">
<label>UI:DashletHeaderDynamic:Prop-Subtitle</label>
</node>
<node id="query" xsi:type="Combodo-ValueType-OQL">
<label>UI:DashletHeaderDynamic:Prop-Query</label>
</node>
<node id="group_by" xsi:type="Combodo-ValueType-ClassAttribute">
<label>UI:DashletHeaderDynamic:Prop-GroupBy</label>
<nodes>
<node id="title" xsi:type="Combodo-Property">
<label>UI:DashletHeaderDynamic:Prop-Title</label>
<value-type xsi:type="Combodo-ValueType-Label">
<not-blank/>
</value-type>
</node>
<node id="icon" xsi:type="Combodo-Property">
<label>UI:DashletHeaderDynamic:Prop-Icon</label>
<value-type xsi:type="Combodo-ValueType-Choice">
<values>
<value id="todo">
<label>ToDo</label>
</value>
</values>
</value-type>
</node>
<node id="subtitle" xsi:type="Combodo-Property">
<label>UI:DashletHeaderDynamic:Prop-Subtitle</label>
<value-type xsi:type="Combodo-ValueType-Label"/>
</node>
<node id="query" xsi:type="Combodo-Property">
<label>UI:DashletHeaderDynamic:Prop-Query</label>
<data-transform>
<xml-node-empty-not-allowed/>
</data-transform>
<value-type xsi:type="Combodo-ValueType-OQL"/>
</node>
<node id="group_by" xsi:type="Combodo-Property">
<label>UI:DashletHeaderDynamic:Prop-GroupBy</label>
<data-transform>
<xml-node-empty-not-allowed/>
</data-transform>
<value-type xsi:type="Combodo-ValueType-ClassAttribute">
<class>{{query.selected_class}}</class>
<category>enum</category>
</node>
<node id="values" xsi:type="Combodo-ValueType-CollectionOfValues">
<label>UI:DashletHeaderDynamic:Prop-Values</label>
<xml-format xsi:type="Combodo-XMLFormat-CSV"/>
<value-type xsi:type="Combodo-ValueType-ClassAttributeValue">
<class>{{query.selected_class}}</class>
<attribute>{{group_by.attribute}}</attribute>
</value-type>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletHeaderStatic" xsi:type="Combodo-PropertyType">
</value-type>
</node>
<node id="values" xsi:type="Combodo-CollectionOfValues">
<label>UI:DashletHeaderDynamic:Prop-Values</label>
<data-transform>
<xml-format xsi:type="xml-format-csv"/>
</data-transform>
<value-type xsi:type="Combodo-ValueType-ClassAttributeValue">
<class>{{query.selected_class}}</class>
<attribute>{{group_by.value}}</attribute>
</value-type>
</node>
</nodes>
</property_tree>
<property_tree id="DashletHeaderStatic" xsi:type="Combodo-PropertyTree">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="title" xsi:type="Combodo-ValueType-Label">
<label>UI:DashletHeaderStatic:Prop-Title</label>
</node>
<node id="icon" xsi:type="Combodo-ValueType-Icon">
<label>UI:DashletHeaderStatic:Prop-Icon</label>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletObjectList" xsi:type="Combodo-PropertyType">
<nodes>
<node id="title" xsi:type="Combodo-Property">
<label>UI:DashletHeaderStatic:Prop-Title</label>
<value-type xsi:type="Combodo-ValueType-Label"/>
</node>
<node id="icon" xsi:type="Combodo-Property">
<label>UI:DashletHeaderStatic:Prop-Icon</label>
<value-type xsi:type="Combodo-ValueType-Icon"/>
</node>
</nodes>
</property_tree>
<property_tree id="DashletObjectList" xsi:type="Combodo-PropertyTree">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="title" xsi:type="Combodo-ValueType-Label">
<label>UI:DashletObjectList:Prop-Title</label>
</node>
<node id="query" xsi:type="Combodo-ValueType-OQL">
<label>UI:DashletObjectList:Prop-Query</label>
</node>
<node id="menu" xsi:type="Combodo-ValueType-Boolean">
<label>UI:DashletObjectList:Prop-Menu</label>
<nodes>
<node id="title" xsi:type="Combodo-Property">
<label>UI:DashletObjectList:Prop-Title</label>
<value-type xsi:type="Combodo-ValueType-Label">
</value-type>
</node>
<node id="query" xsi:type="Combodo-Property">
<label>UI:DashletObjectList:Prop-Query</label>
<value-type xsi:type="Combodo-ValueType-OQL"/>
</node>
<node id="menu" xsi:type="Combodo-Property">
<label>UI:DashletObjectList:Prop-Menu</label>
<value-type xsi:type="Combodo-ValueType-Boolean">
<on>
<!-- not so cute, but matches exactly 3.2 implementation of boolean fields -->
<label>UI:UserManagement:ActionAllowed:Yes</label>
@@ -999,20 +1049,22 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
<label>UI:UserManagement:ActionAllowed:No</label>
<value>false</value>
</off>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletPlainText" xsi:type="Combodo-PropertyType">
</value-type>
</node>
</nodes>
</property_tree>
<property_tree id="DashletPlainText" xsi:type="Combodo-PropertyTree">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="text" xsi:type="Combodo-ValueType-Text">
<label>UI:DashletPlainText:Prop-Text</label>
</node>
</nodes>
</definition>
</property_type>
</property_types>
<nodes>
<node id="text" xsi:type="Combodo-Property">
<label>UI:DashletPlainText:Prop-Text</label>
<constraints>
<not_blank/>
</constraints>
<value-type xsi:type="Combodo-ValueType-Text"/>
</node>
</nodes>
</property_tree>
</property_trees>
</meta>
</itop_design>

View File

@@ -211,14 +211,6 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
],
'allowed_login_types' => [
'type' => 'string',
'description' => 'List of login types allowed (separated by | ): form, external, basic, token',
'default' => DEFAULT_ALLOWED_LOGIN_TYPES,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
],
'app_icon_url' => [
'type' => 'string',
'description' => 'Hyperlink to redirect the user when clicking on the application icon (in the main window, or login/logoff pages)',
@@ -1954,6 +1946,11 @@ class Config
*/
protected $m_sDefaultLanguage;
/**
* @var string Type of login process allowed: form|basic|url|external
*/
protected $m_sAllowedLoginTypes;
/**
* @var string Name of the PHP variable in which external authentication information is passed by the web server
*/
@@ -2027,6 +2024,7 @@ class Config
$this->m_iFastReloadInterval = DEFAULT_FAST_RELOAD_INTERVAL;
$this->m_bSecureConnectionRequired = DEFAULT_SECURE_CONNECTION_REQUIRED;
$this->m_sDefaultLanguage = 'EN US';
$this->m_sAllowedLoginTypes = DEFAULT_ALLOWED_LOGIN_TYPES;
$this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE;
$this->m_aCharsets = [];
$this->m_bQueryCacheEnabled = DEFAULT_QUERY_CACHE_ENABLED;
@@ -2173,6 +2171,7 @@ class Config
$this->m_aModuleSettings = isset($MyModuleSettings) ? $MyModuleSettings : [];
$this->m_sDefaultLanguage = isset($MySettings['default_language']) ? trim($MySettings['default_language']) : 'EN US';
$this->m_sAllowedLoginTypes = isset($MySettings['allowed_login_types']) ? trim($MySettings['allowed_login_types']) : DEFAULT_ALLOWED_LOGIN_TYPES;
$this->m_sExtAuthVariable = isset($MySettings['ext_auth_variable']) ? trim($MySettings['ext_auth_variable']) : DEFAULT_EXT_AUTH_VARIABLE;
$this->m_sEncryptionKey = isset($MySettings['encryption_key']) ? trim($MySettings['encryption_key']) : $this->m_sEncryptionKey;
$this->m_sEncryptionLibrary = isset($MySettings['encryption_library']) ? trim($MySettings['encryption_library']) : $this->m_sEncryptionLibrary;
@@ -2332,7 +2331,7 @@ class Config
public function GetAllowedLoginTypes()
{
return explode('|', $this->m_aSettings['allowed_login_types']['value']);
return explode('|', $this->m_sAllowedLoginTypes);
}
public function GetExternalAuthenticationVariable()
@@ -2410,7 +2409,7 @@ class Config
public function SetAllowedLoginTypes($aAllowedLoginTypes)
{
$this->Set('allowed_login_types', implode('|', $aAllowedLoginTypes));
$this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes);
}
/**
@@ -2487,6 +2486,7 @@ class Config
$aSettings['fast_reload_interval'] = $this->m_iFastReloadInterval;
$aSettings['secure_connection_required'] = $this->m_bSecureConnectionRequired;
$aSettings['default_language'] = $this->m_sDefaultLanguage;
$aSettings['allowed_login_types'] = $this->m_sAllowedLoginTypes;
$aSettings['ext_auth_variable'] = $this->m_sExtAuthVariable;
$aSettings['encryption_key'] = $this->m_sEncryptionKey;
$aSettings['encryption_library'] = $this->m_sEncryptionLibrary;
@@ -2590,6 +2590,7 @@ class Config
// Old fashioned remaining values
$aOtherValues = [
'default_language' => $this->m_sDefaultLanguage,
'allowed_login_types' => $this->m_sAllowedLoginTypes,
'ext_auth_variable' => $this->m_sExtAuthVariable,
'encryption_key' => $this->m_sEncryptionKey,
'encryption_library' => $this->m_sEncryptionLibrary,

View File

@@ -415,7 +415,12 @@ abstract class User extends cmdbAbstractObject
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:CurrentProfilesHaveInsufficientRights');
}
$oAddon->ResetCache();
Session::Set('profile_list', $aCurrentProfiles);
if (is_null($aCurrentProfiles)) {
Session::IsSet('profile_list');
} else {
Session::Set('profile_list', $aCurrentProfiles);
}
}
// Prevent an administrator to remove their own admin profile
if (UserRights::IsAdministrator($this)) {

View File

@@ -4,7 +4,7 @@
*/
$ibo-field--spacing-top--with-same-block: $ibo-spacing-500 !default;
.ibo-field + .ibo-field:not(:empty) {
.ibo-field + .ibo-field {
margin-top: $ibo-field--spacing-top--with-same-block;
}

View File

@@ -61,17 +61,4 @@ collection-entry-element {
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;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -311,35 +311,29 @@ fieldset {
}
.module-selection-body {
overflow: auto;
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, .06) !important;
background-color: #F7FAFC;
padding: 10px;
.wiz-choice{
&:checked ~ .description {
#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked),
#itop-ticket-mgmt-itil-enhanced-portal:not(:checked) {
~ .description::after {
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
display: block;
margin-top: 0.5em;
font-weight: bold;
color: $legacy-portal-removal-text-color;
}
}
}
&:not(:checked) ~ label .setup-extension-tag.checked{
display:none;
}
&:checked ~ label .setup-extension-tag.unchecked{
display:none;
}
}
overflow: auto;
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, .06) !important;
background-color: #F7FAFC;
padding: 10px;
.wiz-choice:checked ~ .description {
#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked),
#itop-ticket-mgmt-itil-enhanced-portal:not(:checked) {
~ .description::after {
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
display: block;
margin-top: 0.5em;
font-weight: bold;
color: $legacy-portal-removal-text-color;
}
}
}
}
body {
font-size: 1.17rem;
font-family: "Raleway";
@@ -601,35 +595,6 @@ body {
color: $ibo-color-blue-700;
font-size: $ibo-font-size-200;
}
.setup-extension--missing .setup-extension--icon{
color:#a00000;
}
.setup-extension-tag {
background-color: grey;
border-radius: 8px;
padding-left: 3px;
padding-right: 3px;
margin-right: 3px;
&.installed{
background-color:#9eff9e
}
&.notinstalled{
background-color:#ed9eff
}
&.tobeinstalled{
background-color:#9ef0ff
}
&.tobeuninstalled{
background-color:#ff9e9e
}
&.notuninstallable{
background-color:#ffc98c
}
&.removed{
background-color: #969594
}
}
.setup--wizard-choice--label + .setup--wizard-choice--more-info {
margin-left: 0.5rem;
}

View File

@@ -5,12 +5,10 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\Events\iEventServiceSetup;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
class AttachmentPlugIn implements iApplicationUIExtension, iEventServiceSetup
{
@@ -238,14 +236,10 @@ class AttachmentPlugIn implements iApplicationUIExtension, iEventServiceSetup
}
$oAttachmentsRenderer = AttachmentsRendererFactory::GetInstance($oPage, $sObjClass, $iObjKey, $sTransactionId);
$iCount = $oAttachmentsRenderer->GetAttachmentsSet()->Count() + $oAttachmentsRenderer->GetTempAttachmentsSet()->Count();
$sTitle = ($iCount > 0) ? Dict::Format('Attachments:TabTitle_Count', $iCount) : Dict::S('Attachments:EmptyTabTitle');
if ($this->GetAttachmentsPosition() === 'relations') {
$iCount = $oAttachmentsRenderer->GetAttachmentsSet()->Count() + $oAttachmentsRenderer->GetTempAttachmentsSet()->Count();
$sTitle = ($iCount > 0) ? Dict::Format('Attachments:TabTitle_Count', $iCount) : Dict::S('Attachments:EmptyTabTitle');
$oPage->SetCurrentTab('Attachments:Tab', $sTitle);
} else {
$oBlock = FieldSetUIBlockFactory::MakeStandard($sTitle);
$oBlock->AddSubBlock(new Html(''));
$oPage->AddUiBlock($oBlock);
}
$bIsReadOnlyState = self::IsReadonlyState($oObject, $oObject->GetState(), AttachmentPlugIn::ENUM_GUI_BACKOFFICE);

View File

@@ -67,17 +67,15 @@ class EventListener implements iEventServiceSetup
/** @var \DBObject $oAttachment */
$oAttachment = $oEventData->Get('object');
$oHostObj = MetaModel::GetObject($oAttachment->Get('item_class'), $oAttachment->Get('item_id'), false /* false to avoid exception during trigger */, true);
if ($oHostObj != null) {
/** @var \ormDocument $oDocument */
$oDocument = $oEventData->Get('document');
/** @var \ormDocument $oDocument */
$oDocument = $oEventData->Get('document');
$this->OnAttachmentActivateTriggers(
$oHostObj,
$oAttachment,
$oDocument,
TriggerOnAttachmentDownload::class
);
}
$this->OnAttachmentActivateTriggers(
$oHostObj,
$oAttachment,
$oDocument,
TriggerOnAttachmentDownload::class
);
}
/**

View File

@@ -323,38 +323,18 @@
<value id="production">
<code>production</code>
<rank>30</rank>
<style>
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="implementation">
<code>implementation</code>
<rank>20</rank>
<style>
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="stock">
<code>stock</code>
<rank>10</rank>
<style>
<main_color>$ibo-lifecycle-neutral-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-neutral-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="obsolete">
<code>obsolete</code>
<rank>40</rank>
<style>
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
</values>
<sql>status</sql>
@@ -6917,29 +6897,14 @@
<value id="production">
<code>production</code>
<rank>20</rank>
<style>
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
<style>
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="obsolete">
<code>obsolete</code>
<rank>30</rank>
<style>
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
</values>
<sql>status</sql>

View File

@@ -1,7 +1,6 @@
<?php
class HubRunTimeEnvironment extends RunTimeEnvironment
{
{
/**
* Constructor
* @param string $sEnvironment
@@ -10,18 +9,21 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
public function __construct($sEnvironment = 'production', $bAutoCommit = true)
{
parent::__construct($sEnvironment, $bAutoCommit);
if ($sEnvironment != $this->sTargetEnv) {
if (is_dir(APPROOT.'/env-'.$this->sTargetEnv)) {
SetupUtils::rrmdir(APPROOT.'/env-'.$this->sTargetEnv);
}
if (is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) {
SetupUtils::rrmdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules');
}
if ($sEnvironment != $this->sTargetEnv)
{
if (is_dir(APPROOT.'/env-'.$this->sTargetEnv))
{
SetupUtils::rrmdir(APPROOT.'/env-'.$this->sTargetEnv);
}
if (is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules'))
{
SetupUtils::rrmdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules');
}
SetupUtils::copydir(APPROOT.'/data/'.$sEnvironment.'-modules', APPROOT.'/data/'.$this->sTargetEnv.'-modules');
}
}
/**
* Update the includes for the target environment
* @param Config $oConfig
@@ -30,7 +32,7 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
{
$oConfig->UpdateIncludes('env-'.$this->sTargetEnv); // TargetEnv != FinalEnv
}
/**
* Move an extension (path to folder of this extension) to the target environment
* @param string $sExtensionDirectory The folder of the extension
@@ -38,23 +40,21 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
*/
public function MoveExtension($sExtensionDirectory)
{
if (!is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) {
if (!mkdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) {
throw new Exception("ERROR: failed to create directory:'".(APPROOT.'/data/'.$this->sTargetEnv.'-modules')."'");
}
if (!is_dir(APPROOT.'/data/'.$this->sTargetEnv.'-modules'))
{
if (!mkdir(APPROOT.'/data/'.$this->sTargetEnv.'-modules')) throw new Exception("ERROR: failed to create directory:'".(APPROOT.'/data/'.$this->sTargetEnv.'-modules')."'");
}
$sDestinationPath = APPROOT.'/data/'.$this->sTargetEnv.'-modules/';
// Make sure that the destination directory of the extension does not already exist
if (is_dir($sDestinationPath.basename($sExtensionDirectory))) {
// Cleanup before moving...
SetupUtils::rrmdir($sDestinationPath.basename($sExtensionDirectory));
}
if (!rename($sExtensionDirectory, $sDestinationPath.basename($sExtensionDirectory))) {
throw new Exception("ERROR: failed move directory:'$sExtensionDirectory' to '".$sDestinationPath.basename($sExtensionDirectory)."'");
if (is_dir($sDestinationPath.basename($sExtensionDirectory)))
{
// Cleanup before moving...
SetupUtils::rrmdir($sDestinationPath.basename($sExtensionDirectory));
}
if (!rename($sExtensionDirectory, $sDestinationPath.basename($sExtensionDirectory))) throw new Exception("ERROR: failed move directory:'$sExtensionDirectory' to '".$sDestinationPath.basename($sExtensionDirectory)."'");
}
/**
* Move the selected extensions located in the given directory in data/<target-env>-modules
* @param string $sDownloadedExtensionsDir The directory to scan
@@ -63,8 +63,10 @@ class HubRunTimeEnvironment extends RunTimeEnvironment
*/
public function MoveSelectedExtensions($sDownloadedExtensionsDir, $aSelectedExtensionDirs)
{
foreach (glob($sDownloadedExtensionsDir.'*', GLOB_ONLYDIR) as $sExtensionDir) {
if (in_array(basename($sExtensionDir), $aSelectedExtensionDirs)) {
foreach(glob($sDownloadedExtensionsDir.'*', GLOB_ONLYDIR) as $sExtensionDir)
{
if (in_array(basename($sExtensionDir), $aSelectedExtensionDirs))
{
$this->MoveExtension($sExtensionDir);
}
}

View File

@@ -20,7 +20,7 @@ function DisplayStatus(WebPage $oPage)
if (is_dir($sPath)) {
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
}
$oExtensionsMap = new iTopExtensionsMap('production', $aExtraDirs);
$oExtensionsMap = new iTopExtensionsMap('production', true, $aExtraDirs);
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
@@ -154,7 +154,7 @@ function DoInstall(WebPage $oPage)
if (is_dir($sPath)) {
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
}
$oExtensionsMap = new iTopExtensionsMap('production', $aExtraDirs);
$oExtensionsMap = new iTopExtensionsMap('production', true, $aExtraDirs);
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {

View File

@@ -698,29 +698,6 @@
</action>
</actions>
</transition>
<transition id="ev_autoresolve">
<target>resolved</target>
<actions>
<action>
<verb>SetCurrentDate</verb>
<params>
<param xsi:type="attcode">resolution_date</param>
</params>
</action>
<action>
<verb>SetElapsedTime</verb>
<params>
<param xsi:type="attcode">time_spent</param>
<param xsi:type="attcode">start_date</param>
<param xsi:type="string">DefaultWorkingTimeComputer</param>
</params>
</action>
<action>
<verb>ResolveChildTickets</verb>
<params/>
</action>
</actions>
</transition>
</transitions>
</state>
<state id="assigned">
@@ -1052,6 +1029,29 @@
<actions>
</actions>
</transition>
<transition id="ev_autoresolve">
<target>resolved</target>
<actions>
<action>
<verb>SetCurrentDate</verb>
<params>
<param xsi:type="attcode">resolution_date</param>
</params>
</action>
<action>
<verb>SetElapsedTime</verb>
<params>
<param xsi:type="attcode">time_spent</param>
<param xsi:type="attcode">start_date</param>
<param xsi:type="string">DefaultWorkingTimeComputer</param>
</params>
</action>
<action>
<verb>ResolveChildTickets</verb>
<params/>
</action>
</actions>
</transition>
</transitions>
</state>
<state id="closed">

View File

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

View File

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

View File

@@ -764,29 +764,6 @@
</action>
</actions>
</transition>
<transition id="ev_autoresolve">
<target>resolved</target>
<actions>
<action>
<verb>SetCurrentDate</verb>
<params>
<param xsi:type="attcode">resolution_date</param>
</params>
</action>
<action>
<verb>SetElapsedTime</verb>
<params>
<param xsi:type="attcode">time_spent</param>
<param xsi:type="attcode">start_date</param>
<param xsi:type="string">DefaultWorkingTimeComputer</param>
</params>
</action>
<action>
<verb>ResolveChildTickets</verb>
<params/>
</action>
</actions>
</transition>
</transitions>
</state>
<state id="assigned">
@@ -987,29 +964,6 @@
<target>rejected</target>
<actions/>
</transition>
<transition id="ev_autoresolve">
<target>resolved</target>
<actions>
<action>
<verb>SetCurrentDate</verb>
<params>
<param xsi:type="attcode">resolution_date</param>
</params>
</action>
<action>
<verb>SetElapsedTime</verb>
<params>
<param xsi:type="attcode">time_spent</param>
<param xsi:type="attcode">start_date</param>
<param xsi:type="string">DefaultWorkingTimeComputer</param>
</params>
</action>
<action>
<verb>ResolveChildTickets</verb>
<params/>
</action>
</actions>
</transition>
</transitions>
</state>
<state id="approved">
@@ -1219,6 +1173,29 @@
<actions>
</actions>
</transition>
<transition id="ev_autoresolve">
<target>resolved</target>
<actions>
<action>
<verb>SetCurrentDate</verb>
<params>
<param xsi:type="attcode">resolution_date</param>
</params>
</action>
<action>
<verb>SetElapsedTime</verb>
<params>
<param xsi:type="attcode">time_spent</param>
<param xsi:type="attcode">start_date</param>
<param xsi:type="string">DefaultWorkingTimeComputer</param>
</params>
</action>
<action>
<verb>ResolveChildTickets</verb>
<params/>
</action>
</actions>
</transition>
</transitions>
</state>
<state id="closed">

View File

@@ -306,9 +306,10 @@
<is_null_allowed>true</is_null_allowed>
</field>
<field id="servicesubcategory_id" xsi:type="AttributeExternalKey">
<filter><![CDATA[SELECT ServiceSubcategory WHERE service_id = :this->service_id AND status != 'obsolete']]></filter>
<filter><![CDATA[SELECT ServiceSubcategory WHERE service_id = :this->service_id AND (ISNULL(:this->request_type) OR request_type = :this->request_type) AND status != 'obsolete']]></filter>
<dependencies>
<attribute id="service_id"/>
<attribute id="request_type"/>
</dependencies>
<sql>servicesubcategory_id</sql>
<target_class>ServiceSubcategory</target_class>
@@ -767,29 +768,6 @@
</action>
</actions>
</transition>
<transition id="ev_autoresolve">
<target>resolved</target>
<actions>
<action>
<verb>SetCurrentDate</verb>
<params>
<param xsi:type="attcode">resolution_date</param>
</params>
</action>
<action>
<verb>SetElapsedTime</verb>
<params>
<param xsi:type="attcode">time_spent</param>
<param xsi:type="attcode">start_date</param>
<param xsi:type="string">DefaultWorkingTimeComputer</param>
</params>
</action>
<action>
<verb>ResolveChildTickets</verb>
<params/>
</action>
</actions>
</transition>
</transitions>
</state>
<state id="assigned">
@@ -993,29 +971,6 @@
<target>rejected</target>
<actions/>
</transition>
<transition id="ev_autoresolve">
<target>resolved</target>
<actions>
<action>
<verb>SetCurrentDate</verb>
<params>
<param xsi:type="attcode">resolution_date</param>
</params>
</action>
<action>
<verb>SetElapsedTime</verb>
<params>
<param xsi:type="attcode">time_spent</param>
<param xsi:type="attcode">start_date</param>
<param xsi:type="string">DefaultWorkingTimeComputer</param>
</params>
</action>
<action>
<verb>ResolveChildTickets</verb>
<params/>
</action>
</actions>
</transition>
</transitions>
</state>
<state id="approved">
@@ -1228,6 +1183,29 @@
<actions>
</actions>
</transition>
<transition id="ev_autoresolve">
<target>resolved</target>
<actions>
<action>
<verb>SetCurrentDate</verb>
<params>
<param xsi:type="attcode">resolution_date</param>
</params>
</action>
<action>
<verb>SetElapsedTime</verb>
<params>
<param xsi:type="attcode">time_spent</param>
<param xsi:type="attcode">start_date</param>
<param xsi:type="string">DefaultWorkingTimeComputer</param>
</params>
</action>
<action>
<verb>ResolveChildTickets</verb>
<params/>
</action>
</actions>
</transition>
</transitions>
</state>
<state id="closed">
@@ -1356,21 +1334,19 @@
// Compute the priority of the ticket
$this->Set('priority', $this->ComputePriority());
return parent::ComputeValues();
}]]></code>
</method>
<method id="EvtComputeRequestType">
<static>false</static>
<access>public</access>
<type>EventListener</type>
<code><![CDATA[ public function EvtComputeRequestType(?Combodo\iTop\Service\Events\EventData $oEventData = null)
{
$iSvcSubcat = $this->Get('servicesubcategory_id');
if ($iSvcSubcat != 0)
// Compute the request_type if not already defined (by the user)
$sType = $this->Get('request_type');
if (is_null($sType) || ($sType === ''))
{
$oSvcSubcat = MetaModel::GetObject(ServiceSubcategory::class, $iSvcSubcat, true, true);
$this->Set('request_type', $oSvcSubcat->Get('request_type'));
$iSvcSubcat = $this->Get('servicesubcategory_id');
if ($iSvcSubcat != 0)
{
$oSvcSubcat = MetaModel::GetObject(ServiceSubcategory::class, $iSvcSubcat, true, true);
$this->Set('request_type', $oSvcSubcat->Get('request_type'));
}
}
return parent::ComputeValues();
}]]></code>
</method>
<method id="DisplayBareRelations">
@@ -1552,13 +1528,6 @@
}]]></code>
</method>
</methods>
<event_listeners>
<event_listener id="EVENT_DB_BEFORE_WRITE">
<event>EVENT_DB_BEFORE_WRITE</event>
<callback>EvtComputeRequestType</callback>
<rank>0</rank>
</event_listener>
</event_listeners>
<presentation>
<details>
<items>

View File

@@ -76,9 +76,6 @@
<attribute id="finalclass"/>
</attributes>
</reconciliation>
<obsolescence>
<condition><![CDATA[status='obsolete']]></condition>
</obsolescence>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
@@ -179,32 +176,17 @@
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
<style>
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="production">
<code>production</code>
<rank>20</rank>
<style>
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
</value>
<value id="obsolete">
<code>obsolete</code>
<rank>30</rank>
<style>
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
</values>
<sql>status</sql>
@@ -1151,9 +1133,6 @@ public function PrefillSearchForm(&$aContextParam)
<attribute id="organization_name"/>
</attributes>
</reconciliation>
<obsolescence>
<condition><![CDATA[status='obsolete']]></condition>
</obsolescence>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
@@ -1205,32 +1184,17 @@ public function PrefillSearchForm(&$aContextParam)
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
<style>
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="production">
<code>production</code>
<rank>20</rank>
<style>
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
</value>
<value id="obsolete">
<code>obsolete</code>
<rank>30</rank>
<style>
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
</values>
<sql>status</sql>
@@ -1573,9 +1537,6 @@ public function PrefillSearchForm(&$aContextParam)
<attribute id="service_name"/>
</attributes>
</reconciliation>
<obsolescence>
<condition><![CDATA[status='obsolete']]></condition>
</obsolescence>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
@@ -1624,32 +1585,17 @@ public function PrefillSearchForm(&$aContextParam)
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
<style>
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="production">
<code>production</code>
<rank>20</rank>
<style>
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
</value>
<value id="obsolete">
<code>obsolete</code>
<rank>30</rank>
<style>
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
</values>
<sql>status</sql>

View File

@@ -243,7 +243,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
'Class:Service/Attribute:description' => 'Popis',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Balíček služeb',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Název rodiny služeb',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => 'Dokumenty',

View File

@@ -242,7 +242,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'Class:Service/Attribute:description' => 'Beskrivelse',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Service familie',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Ydelses familie navn',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => 'Dokument',
@@ -500,7 +500,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'Class:DeliveryModel/Attribute:description' => 'Beskrivelse',
'Class:DeliveryModel/Attribute:description+' => '',
'Class:DeliveryModel/Attribute:contacts_list' => 'Kontakt',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment~~',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Persons) for this delivery model~~',
'Class:DeliveryModel/Attribute:customers_list' => 'Kunde',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model~~',
]);

View File

@@ -242,7 +242,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:Service/Attribute:description' => 'Beschreibung',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Service-Familie',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Service-Familien-Name',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => 'Dokumente',

View File

@@ -268,7 +268,7 @@ Dict::Add('EN US', 'English', 'English', [
'Class:Service/Attribute:description' => 'Description',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Service Family',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Service Family Name',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => 'Documents',
@@ -526,7 +526,7 @@ Dict::Add('EN US', 'English', 'English', [
'Class:DeliveryModel/Attribute:description' => 'Description',
'Class:DeliveryModel/Attribute:description+' => '',
'Class:DeliveryModel/Attribute:contacts_list' => 'Contacts',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Persons) for this delivery model',
'Class:DeliveryModel/Attribute:customers_list' => 'Customers',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model',
]);

View File

@@ -268,7 +268,7 @@ Dict::Add('EN GB', 'British English', 'British English', [
'Class:Service/Attribute:description' => 'Description',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Service Family',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Service Family Name',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => 'Documents',
@@ -526,7 +526,7 @@ Dict::Add('EN GB', 'British English', 'British English', [
'Class:DeliveryModel/Attribute:description' => 'Description',
'Class:DeliveryModel/Attribute:description+' => '',
'Class:DeliveryModel/Attribute:contacts_list' => 'Contacts',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Persons) for this delivery model',
'Class:DeliveryModel/Attribute:customers_list' => 'Customers',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model',
]);

View File

@@ -239,7 +239,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'Class:Service/Attribute:description' => 'Descripción',
'Class:Service/Attribute:description+' => 'Descripción',
'Class:Service/Attribute:servicefamily_id' => 'Familia de Servicios',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => 'Familia de Servicios',
'Class:Service/Attribute:servicefamily_name' => 'Familia de Servicios',
'Class:Service/Attribute:servicefamily_name+' => 'Familia de Servicios',
'Class:Service/Attribute:documents_list' => 'Documentos',

View File

@@ -247,7 +247,7 @@ Dict::Add('FR FR', 'French', 'Français', [
'Class:Service/Attribute:description' => 'Description',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Famille de service',
'Class:Service/Attribute:servicefamily_id+' => 'Obligatoire pour que ce service soit visible dans le portal utilisateur',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Nom Famille de service',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => 'Documents',
@@ -511,7 +511,7 @@ Dict::Add('FR FR', 'French', 'Français', [
'Class:DeliveryModel/Attribute:description' => 'Description',
'Class:DeliveryModel/Attribute:description+' => '',
'Class:DeliveryModel/Attribute:contacts_list' => 'Contacts',
'Class:DeliveryModel/Attribute:contacts_list+' => 'Il doit y avoir au moins une équipe pour permettre l\'assignation des Tickets',
'Class:DeliveryModel/Attribute:contacts_list+' => 'Tous les contacts (Equipe ou Personne) pour ce modèle de support',
'Class:DeliveryModel/Attribute:customers_list' => 'Clients',
'Class:DeliveryModel/Attribute:customers_list+' => 'Tous les clients ayant ce modèle de support',
'Class:DeliveryModel/Attribute:customers_list/UI:Links:Create:Button+' => 'Créer un %4$s',

View File

@@ -241,7 +241,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'Class:Service/Attribute:description' => 'Leírás',
'Class:Service/Attribute:description+' => '~~',
'Class:Service/Attribute:servicefamily_id' => 'Szolgáltatáscsalád',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '~~',
'Class:Service/Attribute:servicefamily_name' => 'Szolgáltatáscsalád név',
'Class:Service/Attribute:servicefamily_name+' => '~~',
'Class:Service/Attribute:documents_list' => 'Dokumentumok',

View File

@@ -241,7 +241,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
'Class:Service/Attribute:description' => 'Descrizione',
'Class:Service/Attribute:description+' => '~~',
'Class:Service/Attribute:servicefamily_id' => 'Famiglia di Servizi',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '~~',
'Class:Service/Attribute:servicefamily_name' => 'Nome della Famiglia di Servizi',
'Class:Service/Attribute:servicefamily_name+' => '~~',
'Class:Service/Attribute:documents_list' => 'Documenti',

View File

@@ -241,7 +241,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'Class:Service/Attribute:description' => '説明',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'サービスファミリ',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'サービスファミリ名',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => '文書',
@@ -499,7 +499,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'Class:DeliveryModel/Attribute:description' => '説明',
'Class:DeliveryModel/Attribute:description+' => '',
'Class:DeliveryModel/Attribute:contacts_list' => '連絡先',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment~~',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Persons) for this delivery model~~',
'Class:DeliveryModel/Attribute:customers_list' => '顧客',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model~~',
]);

View File

@@ -243,7 +243,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'Class:Service/Attribute:description' => 'Omschrijving',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Servicecategorie',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Naam servicecategorie',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => 'Documenten',

View File

@@ -241,7 +241,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
'Class:Service/Attribute:description' => 'Opis',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Rodzina usług',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Nazwa rodziny usług',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => 'Dokumenty',

View File

@@ -241,7 +241,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
'Class:Service/Attribute:description' => 'Descrição',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Família de serviços',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Nome da família de serviços',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => 'Documentos',

View File

@@ -242,7 +242,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:Service/Attribute:description' => 'Описание',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Пакет услуг',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Пакет услуг',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => 'Документы',

View File

@@ -241,7 +241,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'Class:Service/Attribute:description' => 'Popis',
'Class:Service/Attribute:description+' => '~~',
'Class:Service/Attribute:servicefamily_id' => 'Kategória služieb',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '~~',
'Class:Service/Attribute:servicefamily_name' => 'Názov rodiny služieb',
'Class:Service/Attribute:servicefamily_name+' => '~~',
'Class:Service/Attribute:documents_list' => 'Dokumenty',
@@ -499,7 +499,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'Class:DeliveryModel/Attribute:description' => 'Popis',
'Class:DeliveryModel/Attribute:description+' => '~~',
'Class:DeliveryModel/Attribute:contacts_list' => 'Kontakty',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment~~',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Persons) for this delivery model~~',
'Class:DeliveryModel/Attribute:customers_list' => 'Zákazníci',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model~~',
]);

View File

@@ -241,7 +241,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'Class:Service/Attribute:description' => 'Tanımlama',
'Class:Service/Attribute:description+' => '~~',
'Class:Service/Attribute:servicefamily_id' => 'Service Family~~',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '~~',
'Class:Service/Attribute:servicefamily_name' => 'Service Family Name~~',
'Class:Service/Attribute:servicefamily_name+' => '~~',
'Class:Service/Attribute:documents_list' => 'Documents~~',
@@ -499,7 +499,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'Class:DeliveryModel/Attribute:description' => 'Description~~',
'Class:DeliveryModel/Attribute:description+' => '~~',
'Class:DeliveryModel/Attribute:contacts_list' => 'Contacts~~',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment~~',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Persons) for this delivery model~~',
'Class:DeliveryModel/Attribute:customers_list' => 'Customers~~',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model~~',
]);

View File

@@ -264,7 +264,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Class:Service/Attribute:description' => '描述',
'Class:Service/Attribute:description+' => '',
'Class:Service/Attribute:servicefamily_id' => '服务系列',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => '服务系列名称',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:documents_list' => '文档',

View File

@@ -76,9 +76,6 @@
<attribute id="finalclass"/>
</attributes>
</reconciliation>
<obsolescence>
<condition><![CDATA[status='obsolete']]></condition>
</obsolescence>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
@@ -179,32 +176,17 @@
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
<style>
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="production">
<code>production</code>
<rank>20</rank>
<style>
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
</value>
<value id="obsolete">
<code>obsolete</code>
<rank>30</rank>
<style>
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
</values>
<sql>status</sql>
@@ -1140,9 +1122,6 @@ public function PrefillSearchForm(&$aContextParam)
<attribute id="organization_name"/>
</attributes>
</reconciliation>
<obsolescence>
<condition><![CDATA[status='obsolete']]></condition>
</obsolescence>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
@@ -1194,32 +1173,17 @@ public function PrefillSearchForm(&$aContextParam)
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
<style>
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="production">
<code>production</code>
<rank>20</rank>
<style>
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
</value>
<value id="obsolete">
<code>obsolete</code>
<rank>30</rank>
<style>
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
</values>
<sql>status</sql>
@@ -1584,9 +1548,6 @@ public function PrefillSearchForm(&$aContextParam)
<attribute id="service_name"/>
</attributes>
</reconciliation>
<obsolescence>
<condition><![CDATA[status='obsolete']]></condition>
</obsolescence>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
@@ -1635,32 +1596,17 @@ public function PrefillSearchForm(&$aContextParam)
<field id="status" xsi:type="AttributeEnum">
<sort_type>rank</sort_type>
<values>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
<style>
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="production">
<code>production</code>
<rank>20</rank>
<style>
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="implementation">
<code>implementation</code>
<rank>10</rank>
</value>
<value id="obsolete">
<code>obsolete</code>
<rank>30</rank>
<style>
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
</values>
<sql>status</sql>

View File

@@ -218,7 +218,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
'Class:Service/Attribute:organization_name' => 'Název poskytovatele',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Balíček služeb',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Název rodiny služeb',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => 'Popis',

View File

@@ -217,7 +217,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'Class:Service/Attribute:organization_name' => 'Leverandør navn',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Service familie',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Ydelses familie navn',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => 'Beskrivelse',
@@ -463,7 +463,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'Class:DeliveryModel/Attribute:description' => 'Beskrivelse',
'Class:DeliveryModel/Attribute:description+' => '',
'Class:DeliveryModel/Attribute:contacts_list' => 'Kontakt',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment~~',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Persons) for this delivery model~~',
'Class:DeliveryModel/Attribute:customers_list' => 'Kunde',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model~~',
]);

View File

@@ -217,7 +217,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:Service/Attribute:organization_name' => 'Provider-Name',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Service-Familie',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Service-Familien-Name',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => 'Beschreibung',

View File

@@ -240,7 +240,7 @@ Dict::Add('EN US', 'English', 'English', [
'Class:Service/Attribute:organization_name' => 'Provider Name',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Service Family',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Service Family Name',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => 'Description',
@@ -486,7 +486,7 @@ Dict::Add('EN US', 'English', 'English', [
'Class:DeliveryModel/Attribute:description' => 'Description',
'Class:DeliveryModel/Attribute:description+' => '',
'Class:DeliveryModel/Attribute:contacts_list' => 'Contacts',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Persons) for this delivery model',
'Class:DeliveryModel/Attribute:customers_list' => 'Customers',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model',
]);

View File

@@ -240,7 +240,7 @@ Dict::Add('EN GB', 'British English', 'British English', [
'Class:Service/Attribute:organization_name' => 'Provider Name',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Service Family',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Service Family Name',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => 'Description',
@@ -486,7 +486,7 @@ Dict::Add('EN GB', 'British English', 'British English', [
'Class:DeliveryModel/Attribute:description' => 'Description',
'Class:DeliveryModel/Attribute:description+' => '',
'Class:DeliveryModel/Attribute:contacts_list' => 'Contacts',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Person) for this delivery model',
'Class:DeliveryModel/Attribute:customers_list' => 'Customers',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model',
]);

View File

@@ -214,7 +214,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'Class:Service/Attribute:organization_name' => 'Proveedor',
'Class:Service/Attribute:organization_name+' => 'Proveedor',
'Class:Service/Attribute:servicefamily_id' => 'Familia de Servicios',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => 'Familia de Servicios',
'Class:Service/Attribute:servicefamily_name' => 'Familia de Servicios',
'Class:Service/Attribute:servicefamily_name+' => 'Familia de Servicios',
'Class:Service/Attribute:description' => 'Descripción',

View File

@@ -214,7 +214,7 @@ Dict::Add('FR FR', 'French', 'Français', [
'Class:Service/Attribute:organization_name' => 'Nom fournisseur',
'Class:Service/Attribute:organization_name+' => 'Nom commun',
'Class:Service/Attribute:servicefamily_id' => 'Famille de service',
'Class:Service/Attribute:servicefamily_id+' => 'Obligatoire pour que ce service soit visible dans le portal utilisateur',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Nom Famille de service',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:services_list/UI:Links:Create:Button+' => 'Créer un %4$s',
@@ -478,7 +478,7 @@ Dict::Add('FR FR', 'French', 'Français', [
'Class:DeliveryModel/Attribute:description' => 'Description',
'Class:DeliveryModel/Attribute:description+' => '',
'Class:DeliveryModel/Attribute:contacts_list' => 'Contacts',
'Class:DeliveryModel/Attribute:contacts_list+' => 'Il doit y avoir au moins une équipe pour permettre l\'assignation des Tickets',
'Class:DeliveryModel/Attribute:contacts_list+' => 'Tous les contacts (Equipe ou Personne) pour ce modèle de support',
'Class:DeliveryModel/Attribute:customers_list' => 'Clients',
'Class:DeliveryModel/Attribute:customers_list+' => 'Tous les clients ayant ce modèle de support',
'Class:DeliveryModel/Attribute:customers_list/UI:Links:Create:Button+' => 'Créer un %4$s',

View File

@@ -216,7 +216,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'Class:Service/Attribute:organization_name' => 'Szolgáltató név',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Szolgáltatáscsalád',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Szolgáltatáscsalád név',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => 'Leírás',

View File

@@ -215,7 +215,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
'Class:Service/Attribute:organization_name' => 'Nome del Fornitore',
'Class:Service/Attribute:organization_name+' => '~~',
'Class:Service/Attribute:servicefamily_id' => 'Famiglia di Servizi',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '~~',
'Class:Service/Attribute:servicefamily_name' => 'Nome della Famiglia di Servizi',
'Class:Service/Attribute:servicefamily_name+' => '~~',
'Class:Service/Attribute:description' => 'Descrizione',

View File

@@ -215,7 +215,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'Class:Service/Attribute:organization_name' => 'プロバイダー名',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'サービスファミリ',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'サービスファミリ名',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => '説明',
@@ -461,7 +461,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'Class:DeliveryModel/Attribute:description' => '説明',
'Class:DeliveryModel/Attribute:description+' => '',
'Class:DeliveryModel/Attribute:contacts_list' => '連絡先',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment~~',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Persons) for this delivery model~~',
'Class:DeliveryModel/Attribute:customers_list' => '顧客',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model~~',
]);

View File

@@ -217,7 +217,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'Class:Service/Attribute:organization_name' => 'Naam leverancier',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Servicecategorie',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Naam servicecategorie',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => 'Omschrijving',

View File

@@ -215,7 +215,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
'Class:Service/Attribute:organization_name' => 'Nazwa dostawcy',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Rodzina usług',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Nazwa rodziny usług',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => 'Opis',

View File

@@ -215,7 +215,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
'Class:Service/Attribute:organization_name' => 'Nome do provedor',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Família de serviços',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Nome da família de serviços',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => 'Descrição',

View File

@@ -216,7 +216,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:Service/Attribute:organization_name' => 'Поставщик',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => 'Пакет услуг',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => 'Пакет услуг',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => 'Описание',

View File

@@ -215,7 +215,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'Class:Service/Attribute:organization_name' => 'Meno poskytovateľa',
'Class:Service/Attribute:organization_name+' => '~~',
'Class:Service/Attribute:servicefamily_id' => 'Kategória služieb',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '~~',
'Class:Service/Attribute:servicefamily_name' => 'Názov rodiny služieb',
'Class:Service/Attribute:servicefamily_name+' => '~~',
'Class:Service/Attribute:description' => 'Popis',
@@ -461,7 +461,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'Class:DeliveryModel/Attribute:description' => 'Popis',
'Class:DeliveryModel/Attribute:description+' => '~~',
'Class:DeliveryModel/Attribute:contacts_list' => 'Kontakty',
'Class:DeliveryModel/Attribute:contacts_list+' => 'There must be at least one team to enable Ticket assignment~~',
'Class:DeliveryModel/Attribute:contacts_list+' => 'All the contacts (Teams and Persons) for this delivery model~~',
'Class:DeliveryModel/Attribute:customers_list' => 'Zákazníci',
'Class:DeliveryModel/Attribute:customers_list+' => 'All the customers having this delivering model~~',
]);

View File

@@ -216,7 +216,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'Class:Service/Attribute:organization_name' => 'Sağlayıcı Adı',
'Class:Service/Attribute:organization_name+' => '~~',
'Class:Service/Attribute:servicefamily_id' => 'Servis Ailesi',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '~~',
'Class:Service/Attribute:servicefamily_name' => 'Servis Aile Adı',
'Class:Service/Attribute:servicefamily_name+' => '~~',
'Class:Service/Attribute:description' => 'Tanımlama',

View File

@@ -236,7 +236,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Class:Service/Attribute:organization_name' => '供应商名称',
'Class:Service/Attribute:organization_name+' => '',
'Class:Service/Attribute:servicefamily_id' => '服务系列',
'Class:Service/Attribute:servicefamily_id+' => 'Required for this service to be visible on User Portal~~',
'Class:Service/Attribute:servicefamily_id+' => '',
'Class:Service/Attribute:servicefamily_name' => '服务系列名称',
'Class:Service/Attribute:servicefamily_name+' => '',
'Class:Service/Attribute:description' => '描述',

View File

@@ -1486,29 +1486,14 @@
<value id="draft">
<code>draft</code>
<rank>10</rank>
<style>
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="published">
<code>published</code>
<rank>20</rank>
<style>
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="obsolete">
<code>obsolete</code>
<rank>30</rank>
<style>
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
</values>
<sql>status</sql>

View File

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

View File

@@ -43,38 +43,18 @@
<value id="production">
<code>production</code>
<rank>30</rank>
<style>
<main_color>$ibo-lifecycle-active-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-active-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="implementation">
<code>implementation</code>
<rank>20</rank>
<style>
<main_color>$ibo-lifecycle-inactive-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-inactive-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="stock">
<code>stock</code>
<rank>10</rank>
<style>
<main_color>$ibo-lifecycle-neutral-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-neutral-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
<value id="obsolete">
<code>obsolete</code>
<rank>40</rank>
<style>
<main_color>$ibo-lifecycle-frozen-state-primary-color</main_color>
<complementary_color>$ibo-lifecycle-frozen-state-secondary-color</complementary_color>
<decoration_classes/>
</style>
</value>
</values>
<sql>status</sql>

View File

@@ -708,7 +708,6 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
Dict::Add('CS CZ', 'Czech', 'Čeština', [
'Class:TriggerOnObjectUpdate' => 'Triger \'aktualizace objektu\'',
'Class:TriggerOnObjectUpdate+' => 'Spustit při aktualizaci objektu [podřízené třídy] dané třídy',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Cílová pole',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -707,7 +707,6 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
Dict::Add('DA DA', 'Danish', 'Dansk', [
'Class:TriggerOnObjectUpdate' => 'Trigger (on object update)~~',
'Class:TriggerOnObjectUpdate+' => 'Trigger on object update of [a child class of] the given class~~',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target fields~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -704,7 +704,6 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:TriggerOnObjectUpdate' => 'Trigger (bei Objektanpassung)',
'Class:TriggerOnObjectUpdate+' => 'Trigger bei Objektanpassung einer gegebenen Klasse oder Kindklasse',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Ziel-Felder',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -801,7 +801,6 @@ Dict::Add('EN US', 'English', 'English', [
Dict::Add('EN US', 'English', 'English', [
'Class:TriggerOnObjectUpdate' => 'Trigger (on object update)',
'Class:TriggerOnObjectUpdate+' => 'Trigger on object update of [a child class of] the given class',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target fields',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -1654,8 +1654,6 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:Search:Criteria:Raw:FilteredOn' => 'Filtered on %1$s',
'UI:StateChanged' => 'State changed',
'UI:AddSubTree' => 'Add entry',
]);
//

View File

@@ -784,7 +784,6 @@ Dict::Add('EN GB', 'British English', 'British English', [
Dict::Add('EN GB', 'British English', 'British English', [
'Class:TriggerOnObjectUpdate' => 'Trigger (on object update)',
'Class:TriggerOnObjectUpdate+' => 'Trigger on object update of [a child class of] the given class',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target fields',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -695,7 +695,6 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'Class:TriggerOnObjectUpdate' => 'Disparador (actualizando un objecto)',
'Class:TriggerOnObjectUpdate+' => 'Disparador al actualizar un objeto de la clase dada [o una clase hija]',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Campos objetivo',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => 'Campos que serán monitorizados',
]);

View File

@@ -746,7 +746,6 @@ Dict::Add('FR FR', 'French', 'Français', [
Dict::Add('FR FR', 'French', 'Français', [
'Class:TriggerOnObjectUpdate' => 'Déclencheur sur la modification d\'un objet',
'Class:TriggerOnObjectUpdate+' => '',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'Ce filtre est appliqué après la sauvegarde en base de l\'objet modifié. Il restreint les objets qui vont déclencher les actions.',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Attributs cible',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -1545,8 +1545,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:Search:Criteria:HierarchicalKey:ChildrenIncluded:Hint' => 'Les descendants des objets sélectionnés seront inclus.',
'UI:Search:Criteria:Raw:Filtered' => 'Filtré',
'UI:Search:Criteria:Raw:FilteredOn' => 'Filtré sur %1$s',
'UI:StateChanged' => 'État modifié',
'UI:AddSubTree' => 'Ajouter une entrée',
'UI:StateChanged' => 'Etat modifié',
]);
//

View File

@@ -702,7 +702,6 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'Class:TriggerOnObjectUpdate' => 'Eseményindító (objektum frissítéskor)',
'Class:TriggerOnObjectUpdate+' => 'Az adott osztály [egy gyermekosztálya] objektumának frissítésekor elinduló eseményindító',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Célmezők',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -702,7 +702,6 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
Dict::Add('IT IT', 'Italian', 'Italiano', [
'Class:TriggerOnObjectUpdate' => 'Trigger (alla modifica dell\'oggetto)',
'Class:TriggerOnObjectUpdate+' => 'Trigger alla modifica dell\'oggetto di [una classe figlia della] classe specificata',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Campi di destinazione',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -706,7 +706,6 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
Dict::Add('JA JP', 'Japanese', '日本語', [
'Class:TriggerOnObjectUpdate' => 'Trigger (on object update)~~',
'Class:TriggerOnObjectUpdate+' => 'Trigger on object update of [a child class of] the given class~~',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target fields~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '~~',
]);

View File

@@ -704,7 +704,6 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'Class:TriggerOnObjectUpdate' => 'Trigger (bij het aanpassen van een object)',
'Class:TriggerOnObjectUpdate+' => 'Trigger bij het aanpassen van een object van de opgegeven klasse (of subklasse ervan)',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Doelvelden',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -704,7 +704,6 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
Dict::Add('PL PL', 'Polish', 'Polski', [
'Class:TriggerOnObjectUpdate' => 'Wyzwalacz (przy aktualizacji obiektu)',
'Class:TriggerOnObjectUpdate+' => 'Wyzwalanie przy aktualizacji obiektu [klasy potomnej] danej klasy',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Pola docelowe',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -702,7 +702,6 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
'Class:TriggerOnObjectUpdate' => 'Gatilho (na atualização do objeto)',
'Class:TriggerOnObjectUpdate+' => 'Gatilho na atualização de objeto de [uma classe filha] de uma determinada classe',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Campos de destino',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -707,7 +707,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
Dict::Add('RU RU', 'Russian', 'Русский', [
'Class:TriggerOnObjectUpdate' => 'Триггер на обновление объекта',
'Class:TriggerOnObjectUpdate+' => 'Триггер на обновление объекта данного или дочернего класса',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Отслеживаемые поля',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => 'Поля объекта, при обновлении которых сработает триггер',
]);

View File

@@ -720,7 +720,6 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'Class:TriggerOnObjectUpdate' => 'Trigger (on object update)~~',
'Class:TriggerOnObjectUpdate+' => 'Trigger on object update of [a child class of] the given class~~',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target fields~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '~~',
]);

View File

@@ -707,7 +707,6 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'Class:TriggerOnObjectUpdate' => 'Trigger (on object update)~~',
'Class:TriggerOnObjectUpdate+' => 'Trigger on object update of [a child class of] the given class~~',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target fields~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -739,7 +739,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Class:TriggerOnObjectUpdate' => '触发器 (对象更新时)',
'Class:TriggerOnObjectUpdate+' => '指定类型或子类型对象更新时的触发器',
'Class:TriggerOnObjectUpdate/Attribute:filter+' => 'This filter is computed after the object update in database. It restricts the objects which can trigger the actions~~',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => '目标字段',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
]);

View File

@@ -43,13 +43,8 @@ class FormElement extends HTMLFormElement
*/
#StartRefreshingUI(sId)
{
Array.from(this.querySelectorAll(`.ibo-content-block`)).forEach(block => {
if(block.dataset.impactedBy !== undefined){
const aImpactedBy = block.dataset.impactedBy.split(',');
if(aImpactedBy.includes(sId)){
block.classList.add(FormElement.#TURBO_REFRESHING_CLASS);
}
}
Array.from(this.querySelectorAll(`.ibo-content-block[data-impacted-by*="${sId}"]`)).forEach(block => {
block.classList.add(FormElement.#TURBO_REFRESHING_CLASS);
});
}

View File

@@ -1,7 +1,5 @@
class OqlElement extends HTMLTextAreaElement {
static #DEBONCE = 400;
// register the custom element
static{
customElements.define('oql-element', OqlElement, {extends: 'textarea'});
@@ -12,20 +10,12 @@ class OqlElement extends HTMLTextAreaElement {
#iconValid = 'fa-check-double';
#iconNotValid = 'fa-exclamation-triangle';
#debounceTimer = null;
#debounce = OqlElement.#DEBONCE;
#debounce = 300;
/** connectedCallback **/
connectedCallback() {
this.addEventListener('input', this.#onInput.bind(this));
this.#callValidateQuery();
this.addEventListener('focus', this.#onFocus.bind(this));
const oBtnBook = this.closest('.ibo-content-block').querySelector('[data-role="ibo-button"][data-action="book"]');
oBtnBook.addEventListener('click', this.#search.bind(this))
const oBtnRun = this.closest('.ibo-content-block').querySelector('[data-role="ibo-button"][data-action="run"]');
oBtnRun.addEventListener('click', this.#run.bind(this))
}
/**
@@ -38,13 +28,6 @@ class OqlElement extends HTMLTextAreaElement {
}, this.#debounce);
}
/**
* Call oql verification with debounce when focus event is fired.
*/
#onFocus() {
this.#callValidateQuery();
}
/**
* Call the ajax to validate the query.
*
@@ -74,7 +57,6 @@ class OqlElement extends HTMLTextAreaElement {
marqueeEl.style.color = response.is_valid ? 'green' : 'orange';
marqueeEl.classList.toggle(this.#iconNotValid, !response.is_valid);
marqueeEl.classList.toggle(this.#iconValid, response.is_valid);
marqueeEl.setAttribute('title', response.is_valid ? Dict.S(this.dataset.validQueryText) : Dict.S(this.dataset.invalidQueryText));
});
}
@@ -85,39 +67,4 @@ class OqlElement extends HTMLTextAreaElement {
const changeEvent = new Event('change', { bubbles: true, cancelable: true });
this.dispatchEvent(changeEvent);
}
#search(){
const sId = this.getAttribute('id');
const sDialogId = `ac_dlg_${sId}`;
const sModalTitle = Dict.S(this.dataset.modalTitleText);
const sEmptyText = Dict.S(this.dataset.emptyText);
// Instance the widget
const oACWidget = new ExtKeyWidget(sId, 'QueryOQL', 'SELECT QueryOQL WHERE is_template = \'yes\'', sModalTitle, true, null, null, true, true, 'oql');
oACWidget.emptyHtml = `<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p><${sEmptyText}/p></div>`;
// Store in window to be accessible from dialog
window[`oACWidget_${sId}`] = oACWidget;
// Open the dialog
if ($(`#${sDialogId}`).length === 0)
{
$('body').append(`<div id="${sDialogId}"></div>`);
$(`#${sDialogId}`).dialog({
width: $(window).width()*0.8,
height: $(window).height()*0.8,
autoOpen: false,
modal: true,
resizeStop: oACWidget.UpdateSizes,
});
}
// Start searching
oACWidget.Search();
}
#run(){
window.open('../pages/run_query.php?expression=' + encodeURI(this.value), '_blank');
}
}

View File

@@ -507,6 +507,8 @@ return array(
'Combodo\\iTop\\Forms\\Block\\FormBlockHelper' => $baseDir . '/sources/Forms/Block/FormBlockHelper.php',
'Combodo\\iTop\\Forms\\Block\\FormBlockService' => $baseDir . '/sources/Forms/Block/FormBlockService.php',
'Combodo\\iTop\\Forms\\Block\\IFormBlock' => $baseDir . '/sources/Forms/Block/IFormBlock.php',
'Combodo\\iTop\\Forms\\Compiler\\FormsCompiler' => $baseDir . '/sources/Forms/Compiler/FormsCompiler.php',
'Combodo\\iTop\\Forms\\Compiler\\FormsCompilerException' => $baseDir . '/sources/Forms/Compiler/FormsCompilerException.php',
'Combodo\\iTop\\Forms\\Controller\\FormsController' => $baseDir . '/sources/Forms/Controller/FormsController.php',
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => $baseDir . '/sources/Forms/FormBuilder/DependencyHandler.php',
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => $baseDir . '/sources/Forms/FormBuilder/DependencyMap.php',
@@ -526,7 +528,6 @@ return array(
'Combodo\\iTop\\Forms\\IO\\AbstractFormIO' => $baseDir . '/sources/Forms/IO/AbstractFormIO.php',
'Combodo\\iTop\\Forms\\IO\\Converter\\AbstractConverter' => $baseDir . '/sources/Forms/IO/Converter/AbstractConverter.php',
'Combodo\\iTop\\Forms\\IO\\Converter\\ChoiceValueToLabelConverter' => $baseDir . '/sources/Forms/IO/Converter/ChoiceValueToLabelConverter.php',
'Combodo\\iTop\\Forms\\IO\\Converter\\CollectionToCountConverter' => $baseDir . '/sources/Forms/IO/Converter/CollectionToCountConverter.php',
'Combodo\\iTop\\Forms\\IO\\Converter\\OqlToClassConverter' => $baseDir . '/sources/Forms/IO/Converter/OqlToClassConverter.php',
'Combodo\\iTop\\Forms\\IO\\FormBinding' => $baseDir . '/sources/Forms/IO/FormBinding.php',
'Combodo\\iTop\\Forms\\IO\\FormBlockIOException' => $baseDir . '/sources/Forms/IO/FormBlockIOException.php',
@@ -549,42 +550,34 @@ return array(
'Combodo\\iTop\\Forms\\Validator\\AttributeExist' => $baseDir . '/sources/Forms/Validator/AttributeExist.php',
'Combodo\\iTop\\Forms\\Validator\\AttributeExistValidator' => $baseDir . '/sources/Forms/Validator/AttributeExistValidator.php',
'Combodo\\iTop\\PhpParser\\Evaluation\\PhpExpressionEvaluator' => $baseDir . '/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php',
'Combodo\\iTop\\PropertyType\\Compiler\\PropertyTypeCompiler' => $baseDir . '/sources/PropertyType/Compiler/PropertyTypeCompiler.php',
'Combodo\\iTop\\PropertyType\\Compiler\\PropertyTypeCompilerException' => $baseDir . '/sources/PropertyType/Compiler/PropertyTypeCompilerException.php',
'Combodo\\iTop\\PropertyType\\PropertyType' => $baseDir . '/sources/PropertyType/PropertyType.php',
'Combodo\\iTop\\PropertyType\\PropertyTypeDesign' => $baseDir . '/sources/PropertyType/PropertyTypeDesign.php',
'Combodo\\iTop\\PropertyType\\PropertyTypeException' => $baseDir . '/sources/PropertyType/PropertyTypeException.php',
'Combodo\\iTop\\PropertyType\\PropertyTypeFactory' => $baseDir . '/sources/PropertyType/PropertyTypeFactory.php',
'Combodo\\iTop\\PropertyType\\PropertyTypeService' => $baseDir . '/sources/PropertyType/PropertyTypeService.php',
'Combodo\\iTop\\PropertyType\\Serializer\\SerializerException' => $baseDir . '/sources/PropertyType/Serializer/SerializerException.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\AbstractXMLFormat' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/AbstractXMLFormat.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatCSV' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatCSV.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFactory' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFactory.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFlatArray' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFlatArray.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatValueAsId' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatValueAsId.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLSerializer' => $baseDir . '/sources/PropertyType/Serializer/XMLSerializer.php',
'Combodo\\iTop\\PropertyType\\ValueType\\AbstractValueType' => $baseDir . '/sources/PropertyType/ValueType/AbstractValueType.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\AbstractBranchValueType' => $baseDir . '/sources/PropertyType/ValueType/Branch/AbstractBranchValueType.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\ValueTypeCollection' => $baseDir . '/sources/PropertyType/ValueType/Branch/ValueTypeCollection.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\ValueTypePropertyTree' => $baseDir . '/sources/PropertyType/ValueType/Branch/ValueTypePropertyTree.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\AbstractLeafValueType' => $baseDir . '/sources/PropertyType/ValueType/Leaf/AbstractLeafValueType.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeAggregateFunction' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeAggregateFunction.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeBoolean' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeBoolean.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeChoice' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeChoice.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeChoiceFromInput' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeChoiceFromInput.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeClass' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeClass.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeClassAttribute' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeClassAttribute.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeClassAttributeGroupBy' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeClassAttributeGroupBy.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeClassAttributeValue' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeClassAttributeValue.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeCollectionOfValues' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeCollectionOfValues.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeIcon' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeIcon.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeInteger' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeInteger.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeLabel' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeLabel.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeOQL' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeOQL.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeProfileName' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeProfileName.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeString' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeString.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeText' => $baseDir . '/sources/PropertyType/ValueType/Leaf/ValueTypeText.php',
'Combodo\\iTop\\PropertyType\\ValueType\\ValueTypeFactory' => $baseDir . '/sources/PropertyType/ValueType/ValueTypeFactory.php',
'Combodo\\iTop\\PropertyTree\\AbstractProperty' => $baseDir . '/sources/PropertyTree/AbstractProperty.php',
'Combodo\\iTop\\PropertyTree\\CollectionOfTrees' => $baseDir . '/sources/PropertyTree/CollectionOfTrees.php',
'Combodo\\iTop\\PropertyTree\\CollectionOfValues' => $baseDir . '/sources/PropertyTree/CollectionOfValues.php',
'Combodo\\iTop\\PropertyTree\\CollectionType\\AbstractCollectionType' => $baseDir . '/sources/PropertyTree/CollectionType/AbstractCollectionType.php',
'Combodo\\iTop\\PropertyTree\\CollectionType\\CollectionTypeCollection' => $baseDir . '/sources/PropertyTree/CollectionType/CollectionTypeCollection.php',
'Combodo\\iTop\\PropertyTree\\CollectionType\\CollectionTypeFactory' => $baseDir . '/sources/PropertyTree/CollectionType/CollectionTypeFactory.php',
'Combodo\\iTop\\PropertyTree\\Property' => $baseDir . '/sources/PropertyTree/Property.php',
'Combodo\\iTop\\PropertyTree\\PropertyTree' => $baseDir . '/sources/PropertyTree/PropertyTree.php',
'Combodo\\iTop\\PropertyTree\\PropertyTreeDesign' => $baseDir . '/sources/PropertyTree/PropertyTreeDesign.php',
'Combodo\\iTop\\PropertyTree\\PropertyTreeException' => $baseDir . '/sources/PropertyTree/PropertyTreeException.php',
'Combodo\\iTop\\PropertyTree\\PropertyTreeFactory' => $baseDir . '/sources/PropertyTree/PropertyTreeFactory.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\AbstractValueType' => $baseDir . '/sources/PropertyTree/ValueType/AbstractValueType.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeAggregateFunction' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeAggregateFunction.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeBoolean' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeBoolean.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeChoice' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeChoice.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeChoiceFromInput' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeChoiceFromInput.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClass' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeClass.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttribute' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeClassAttribute.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttributeGroupBy' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeClassAttributeGroupBy.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttributeValue' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeClassAttributeValue.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeFactory' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeFactory.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeIcon' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeIcon.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeInteger' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeInteger.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeLabel' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeLabel.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeOQL' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeOQL.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeProfileName' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeProfileName.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeString' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeString.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeText' => $baseDir . '/sources/PropertyTree/ValueType/ValueTypeText.php',
'Combodo\\iTop\\Renderer\\BlockRenderer' => $baseDir . '/sources/Renderer/BlockRenderer.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => $baseDir . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\BsFormRenderer' => $baseDir . '/sources/Renderer/Bootstrap/BsFormRenderer.php',
@@ -608,7 +601,7 @@ return array(
'Combodo\\iTop\\Service\\Base\\iDataPostProcessor' => $baseDir . '/sources/Service/Base/iDataPostProcessor.php',
'Combodo\\iTop\\Service\\Cache\\DataModelDependantCache' => $baseDir . '/sources/Service/Cache/DataModelDependantCache.php',
'Combodo\\iTop\\Service\\DependencyInjection\\DIException' => $baseDir . '/sources/Service/DependencyInjection/DIException.php',
'Combodo\\iTop\\Service\\DependencyInjection\\ServiceLocator' => $baseDir . '/sources/Service/DependencyInjection/ServiceLocator.php',
'Combodo\\iTop\\Service\\DependencyInjection\\DIService' => $baseDir . '/sources/Service/DependencyInjection/DIService.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDataDescription' => $baseDir . '/sources/Service/Events/Description/EventDataDescription.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDescription' => $baseDir . '/sources/Service/Events/Description/EventDescription.php',
'Combodo\\iTop\\Service\\Events\\EventData' => $baseDir . '/sources/Service/Events/EventData.php',

View File

@@ -893,6 +893,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Forms\\Block\\FormBlockHelper' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockHelper.php',
'Combodo\\iTop\\Forms\\Block\\FormBlockService' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockService.php',
'Combodo\\iTop\\Forms\\Block\\IFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/IFormBlock.php',
'Combodo\\iTop\\Forms\\Compiler\\FormsCompiler' => __DIR__ . '/../..' . '/sources/Forms/Compiler/FormsCompiler.php',
'Combodo\\iTop\\Forms\\Compiler\\FormsCompilerException' => __DIR__ . '/../..' . '/sources/Forms/Compiler/FormsCompilerException.php',
'Combodo\\iTop\\Forms\\Controller\\FormsController' => __DIR__ . '/../..' . '/sources/Forms/Controller/FormsController.php',
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyHandler.php',
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyMap.php',
@@ -912,7 +914,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Forms\\IO\\AbstractFormIO' => __DIR__ . '/../..' . '/sources/Forms/IO/AbstractFormIO.php',
'Combodo\\iTop\\Forms\\IO\\Converter\\AbstractConverter' => __DIR__ . '/../..' . '/sources/Forms/IO/Converter/AbstractConverter.php',
'Combodo\\iTop\\Forms\\IO\\Converter\\ChoiceValueToLabelConverter' => __DIR__ . '/../..' . '/sources/Forms/IO/Converter/ChoiceValueToLabelConverter.php',
'Combodo\\iTop\\Forms\\IO\\Converter\\CollectionToCountConverter' => __DIR__ . '/../..' . '/sources/Forms/IO/Converter/CollectionToCountConverter.php',
'Combodo\\iTop\\Forms\\IO\\Converter\\OqlToClassConverter' => __DIR__ . '/../..' . '/sources/Forms/IO/Converter/OqlToClassConverter.php',
'Combodo\\iTop\\Forms\\IO\\FormBinding' => __DIR__ . '/../..' . '/sources/Forms/IO/FormBinding.php',
'Combodo\\iTop\\Forms\\IO\\FormBlockIOException' => __DIR__ . '/../..' . '/sources/Forms/IO/FormBlockIOException.php',
@@ -935,42 +936,34 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Forms\\Validator\\AttributeExist' => __DIR__ . '/../..' . '/sources/Forms/Validator/AttributeExist.php',
'Combodo\\iTop\\Forms\\Validator\\AttributeExistValidator' => __DIR__ . '/../..' . '/sources/Forms/Validator/AttributeExistValidator.php',
'Combodo\\iTop\\PhpParser\\Evaluation\\PhpExpressionEvaluator' => __DIR__ . '/../..' . '/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php',
'Combodo\\iTop\\PropertyType\\Compiler\\PropertyTypeCompiler' => __DIR__ . '/../..' . '/sources/PropertyType/Compiler/PropertyTypeCompiler.php',
'Combodo\\iTop\\PropertyType\\Compiler\\PropertyTypeCompilerException' => __DIR__ . '/../..' . '/sources/PropertyType/Compiler/PropertyTypeCompilerException.php',
'Combodo\\iTop\\PropertyType\\PropertyType' => __DIR__ . '/../..' . '/sources/PropertyType/PropertyType.php',
'Combodo\\iTop\\PropertyType\\PropertyTypeDesign' => __DIR__ . '/../..' . '/sources/PropertyType/PropertyTypeDesign.php',
'Combodo\\iTop\\PropertyType\\PropertyTypeException' => __DIR__ . '/../..' . '/sources/PropertyType/PropertyTypeException.php',
'Combodo\\iTop\\PropertyType\\PropertyTypeFactory' => __DIR__ . '/../..' . '/sources/PropertyType/PropertyTypeFactory.php',
'Combodo\\iTop\\PropertyType\\PropertyTypeService' => __DIR__ . '/../..' . '/sources/PropertyType/PropertyTypeService.php',
'Combodo\\iTop\\PropertyType\\Serializer\\SerializerException' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/SerializerException.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\AbstractXMLFormat' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/AbstractXMLFormat.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatCSV' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatCSV.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFactory' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFactory.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFlatArray' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFlatArray.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatValueAsId' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatValueAsId.php',
'Combodo\\iTop\\PropertyType\\Serializer\\XMLSerializer' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLSerializer.php',
'Combodo\\iTop\\PropertyType\\ValueType\\AbstractValueType' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/AbstractValueType.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\AbstractBranchValueType' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Branch/AbstractBranchValueType.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\ValueTypeCollection' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Branch/ValueTypeCollection.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\ValueTypePropertyTree' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Branch/ValueTypePropertyTree.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\AbstractLeafValueType' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/AbstractLeafValueType.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeAggregateFunction' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeAggregateFunction.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeBoolean' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeBoolean.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeChoice' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeChoice.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeChoiceFromInput' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeChoiceFromInput.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeClass' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeClass.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeClassAttribute' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeClassAttribute.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeClassAttributeGroupBy' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeClassAttributeGroupBy.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeClassAttributeValue' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeClassAttributeValue.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeCollectionOfValues' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeCollectionOfValues.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeIcon' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeIcon.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeInteger' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeInteger.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeLabel' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeLabel.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeOQL' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeOQL.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeProfileName' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeProfileName.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeString' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeString.php',
'Combodo\\iTop\\PropertyType\\ValueType\\Leaf\\ValueTypeText' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Leaf/ValueTypeText.php',
'Combodo\\iTop\\PropertyType\\ValueType\\ValueTypeFactory' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/ValueTypeFactory.php',
'Combodo\\iTop\\PropertyTree\\AbstractProperty' => __DIR__ . '/../..' . '/sources/PropertyTree/AbstractProperty.php',
'Combodo\\iTop\\PropertyTree\\CollectionOfTrees' => __DIR__ . '/../..' . '/sources/PropertyTree/CollectionOfTrees.php',
'Combodo\\iTop\\PropertyTree\\CollectionOfValues' => __DIR__ . '/../..' . '/sources/PropertyTree/CollectionOfValues.php',
'Combodo\\iTop\\PropertyTree\\CollectionType\\AbstractCollectionType' => __DIR__ . '/../..' . '/sources/PropertyTree/CollectionType/AbstractCollectionType.php',
'Combodo\\iTop\\PropertyTree\\CollectionType\\CollectionTypeCollection' => __DIR__ . '/../..' . '/sources/PropertyTree/CollectionType/CollectionTypeCollection.php',
'Combodo\\iTop\\PropertyTree\\CollectionType\\CollectionTypeFactory' => __DIR__ . '/../..' . '/sources/PropertyTree/CollectionType/CollectionTypeFactory.php',
'Combodo\\iTop\\PropertyTree\\Property' => __DIR__ . '/../..' . '/sources/PropertyTree/Property.php',
'Combodo\\iTop\\PropertyTree\\PropertyTree' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTree.php',
'Combodo\\iTop\\PropertyTree\\PropertyTreeDesign' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTreeDesign.php',
'Combodo\\iTop\\PropertyTree\\PropertyTreeException' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTreeException.php',
'Combodo\\iTop\\PropertyTree\\PropertyTreeFactory' => __DIR__ . '/../..' . '/sources/PropertyTree/PropertyTreeFactory.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\AbstractValueType' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/AbstractValueType.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeAggregateFunction' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeAggregateFunction.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeBoolean' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeBoolean.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeChoice' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeChoice.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeChoiceFromInput' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeChoiceFromInput.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClass' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeClass.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttribute' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeClassAttribute.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttributeGroupBy' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeClassAttributeGroupBy.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeClassAttributeValue' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeClassAttributeValue.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeFactory' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeFactory.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeIcon' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeIcon.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeInteger' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeInteger.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeLabel' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeLabel.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeOQL' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeOQL.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeProfileName' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeProfileName.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeString' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeString.php',
'Combodo\\iTop\\PropertyTree\\ValueType\\ValueTypeText' => __DIR__ . '/../..' . '/sources/PropertyTree/ValueType/ValueTypeText.php',
'Combodo\\iTop\\Renderer\\BlockRenderer' => __DIR__ . '/../..' . '/sources/Renderer/BlockRenderer.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.php',
'Combodo\\iTop\\Renderer\\Bootstrap\\BsFormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/BsFormRenderer.php',
@@ -994,7 +987,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Service\\Base\\iDataPostProcessor' => __DIR__ . '/../..' . '/sources/Service/Base/iDataPostProcessor.php',
'Combodo\\iTop\\Service\\Cache\\DataModelDependantCache' => __DIR__ . '/../..' . '/sources/Service/Cache/DataModelDependantCache.php',
'Combodo\\iTop\\Service\\DependencyInjection\\DIException' => __DIR__ . '/../..' . '/sources/Service/DependencyInjection/DIException.php',
'Combodo\\iTop\\Service\\DependencyInjection\\ServiceLocator' => __DIR__ . '/../..' . '/sources/Service/DependencyInjection/ServiceLocator.php',
'Combodo\\iTop\\Service\\DependencyInjection\\DIService' => __DIR__ . '/../..' . '/sources/Service/DependencyInjection/DIService.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDataDescription' => __DIR__ . '/../..' . '/sources/Service/Events/Description/EventDataDescription.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDescription' => __DIR__ . '/../..' . '/sources/Service/Events/Description/EventDescription.php',
'Combodo\\iTop\\Service\\Events\\EventData' => __DIR__ . '/../..' . '/sources/Service/Events/EventData.php',

View File

@@ -26,192 +26,192 @@ use Symfony\Component\Form\Extension\Core\Type\TextType;
*/
class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormBuilderInterface
{
/**
* The children of the form builder.
*
* @var FormBuilderInterface[]
*/
private array $children = [];
/**
* The children of the form builder.
*
* @var FormBuilderInterface[]
*/
private array $children = [];
/**
* The data of children who haven't been converted to form builders yet.
*/
private array $unresolvedChildren = [];
/**
* The data of children who haven't been converted to form builders yet.
*/
private array $unresolvedChildren = [];
public function __construct(?string $name, ?string $dataClass, EventDispatcherInterface $dispatcher, FormFactoryInterface $factory, array $options = [])
{
parent::__construct($name, $dataClass, $dispatcher, $options);
public function __construct(?string $name, ?string $dataClass, EventDispatcherInterface $dispatcher, FormFactoryInterface $factory, array $options = [])
{
parent::__construct($name, $dataClass, $dispatcher, $options);
$this->setFormFactory($factory);
}
$this->setFormFactory($factory);
}
public function add(FormBuilderInterface|string $child, ?string $type = null, array $options = []): static
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
public function add(FormBuilderInterface|string $child, ?string $type = null, array $options = []): static
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
if ($child instanceof FormBuilderInterface) {
$this->children[$child->getName()] = $child;
if ($child instanceof FormBuilderInterface) {
$this->children[$child->getName()] = $child;
// In case an unresolved child with the same name exists
unset($this->unresolvedChildren[$child->getName()]);
// In case an unresolved child with the same name exists
unset($this->unresolvedChildren[$child->getName()]);
return $this;
}
return $this;
}
if (!\is_string($child) && !\is_int($child)) {
throw new UnexpectedTypeException($child, 'string or Symfony\Component\Form\FormBuilderInterface');
}
if (!\is_string($child) && !\is_int($child)) {
throw new UnexpectedTypeException($child, 'string or Symfony\Component\Form\FormBuilderInterface');
}
// Add to "children" to maintain order
$this->children[$child] = null;
$this->unresolvedChildren[$child] = [$type, $options];
// Add to "children" to maintain order
$this->children[$child] = null;
$this->unresolvedChildren[$child] = [$type, $options];
return $this;
}
return $this;
}
public function create(string $name, ?string $type = null, array $options = []): FormBuilderInterface
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
public function create(string $name, ?string $type = null, array $options = []): FormBuilderInterface
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
if (null === $type && null === $this->getDataClass()) {
$type = TextType::class;
}
if (null === $type && null === $this->getDataClass()) {
$type = TextType::class;
}
if (null !== $type) {
return $this->getFormFactory()->createNamedBuilder($name, $type, null, $options);
}
if (null !== $type) {
return $this->getFormFactory()->createNamedBuilder($name, $type, null, $options);
}
return $this->getFormFactory()->createBuilderForProperty($this->getDataClass(), $name, null, $options);
}
return $this->getFormFactory()->createBuilderForProperty($this->getDataClass(), $name, null, $options);
}
public function get(string $name): FormBuilderInterface
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
public function get(string $name): FormBuilderInterface
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
if (isset($this->unresolvedChildren[$name])) {
return $this->resolveChild($name);
}
if (isset($this->unresolvedChildren[$name])) {
return $this->resolveChild($name);
}
if (isset($this->children[$name])) {
return $this->children[$name];
}
if (isset($this->children[$name])) {
return $this->children[$name];
}
throw new InvalidArgumentException(\sprintf('The child with the name "%s" does not exist.', $name));
}
throw new InvalidArgumentException(\sprintf('The child with the name "%s" does not exist.', $name));
}
public function remove(string $name): static
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
public function remove(string $name): static
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
unset($this->unresolvedChildren[$name], $this->children[$name]);
unset($this->unresolvedChildren[$name], $this->children[$name]);
return $this;
}
return $this;
}
public function has(string $name): bool
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
public function has(string $name): bool
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
return isset($this->unresolvedChildren[$name]) || isset($this->children[$name]);
}
return isset($this->unresolvedChildren[$name]) || isset($this->children[$name]);
}
public function all(): array
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
public function all(): array
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
$this->resolveChildren();
$this->resolveChildren();
return $this->children;
}
return $this->children;
}
public function count(): int
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
public function count(): int
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
return \count($this->children);
}
return \count($this->children);
}
public function getFormConfig(): FormConfigInterface
{
/** @var self $config */
$config = parent::getFormConfig();
public function getFormConfig(): FormConfigInterface
{
/** @var self $config */
$config = parent::getFormConfig();
$config->children = [];
$config->unresolvedChildren = [];
$config->children = [];
$config->unresolvedChildren = [];
return $config;
}
return $config;
}
public function getForm(): FormInterface
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
public function getForm(): FormInterface
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
$this->resolveChildren();
$this->resolveChildren();
$form = new Form($this->getFormConfig());
$form = new Form($this->getFormConfig());
foreach ($this->children as $child) {
// Automatic initialization is only supported on root forms
$form->add($child->setAutoInitialize(false)->getForm());
}
foreach ($this->children as $child) {
// Automatic initialization is only supported on root forms
$form->add($child->setAutoInitialize(false)->getForm());
}
if ($this->getAutoInitialize()) {
// Automatically initialize the form if it is configured so
$form->initialize();
}
if ($this->getAutoInitialize()) {
// Automatically initialize the form if it is configured so
$form->initialize();
}
return $form;
}
return $form;
}
/**
* @return \Traversable<string, FormBuilderInterface>
*/
public function getIterator(): \Traversable
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
/**
* @return \Traversable<string, FormBuilderInterface>
*/
public function getIterator(): \Traversable
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
return new \ArrayIterator($this->all());
}
return new \ArrayIterator($this->all());
}
/**
* Converts an unresolved child into a {@link FormBuilderInterface} instance.
*/
private function resolveChild(string $name): FormBuilderInterface
{
[$type, $options] = $this->unresolvedChildren[$name];
/**
* Converts an unresolved child into a {@link FormBuilderInterface} instance.
*/
private function resolveChild(string $name): FormBuilderInterface
{
[$type, $options] = $this->unresolvedChildren[$name];
unset($this->unresolvedChildren[$name]);
unset($this->unresolvedChildren[$name]);
return $this->children[$name] = $this->create($name, $type, $options);
}
return $this->children[$name] = $this->create($name, $type, $options);
}
/**
* Converts all unresolved children into {@link FormBuilder} instances.
*/
private function resolveChildren(): void
{
foreach ($this->unresolvedChildren as $name => $info) {
$this->children[$name] = $this->create($name, $info[0], $info[1]);
}
/**
* Converts all unresolved children into {@link FormBuilder} instances.
*/
private function resolveChildren(): void
{
foreach ($this->unresolvedChildren as $name => $info) {
$this->children[$name] = $this->create($name, $info[0], $info[1]);
}
$this->unresolvedChildren = [];
}
$this->unresolvedChildren = [];
}
}

View File

@@ -23,7 +23,7 @@ use Combodo\iTop\Application\WebPage\iTopWebPage;
use Combodo\iTop\Application\WebPage\Page;
use Combodo\iTop\DesignElement;
use Combodo\iTop\DesignDocument;
use Combodo\iTop\PropertyType\PropertyTypeDesign;
use Combodo\iTop\PropertyTree\PropertyTreeDesign;
require_once(APPROOT.'setup/setuputils.class.inc.php');
require_once(APPROOT.'setup/modelfactory.class.inc.php');
@@ -700,9 +700,9 @@ PHP;
$oModuleDesignsNode = $this->oFactory->GetNodes('/itop_design/module_designs')->item(0);
$this->CompileModuleDesigns($oModuleDesignsNode, $sTempTargetDir, $sFinalTargetDir);
// Create property types XML files
$oPropertyTypesNode = $this->oFactory->GetNodes('/itop_design/meta/property_types')->item(0);
$this->CompilePropertyTypes($oPropertyTypesNode, $sTempTargetDir, $sFinalTargetDir);
// Create property trees XML files
$oPropertyTreesNode = $this->oFactory->GetNodes('/itop_design/meta/property_trees')->item(0);
$this->CompilePropertyTrees($oPropertyTreesNode, $sTempTargetDir, $sFinalTargetDir);
// Compile the XML parameters
/** @var \MFElement $oParametersNode */
@@ -3577,17 +3577,17 @@ EOF;
}
}
protected function CompilePropertyTypes(?DOMNode $oPropertyTypes, string $sTempTargetDir, string $sFinalTargetDir): void
protected function CompilePropertyTrees(?DOMNode $oPropertyTrees, string $sTempTargetDir, string $sFinalTargetDir): void
{
if ($oPropertyTypes) {
foreach ($oPropertyTypes->GetNodes('property_type') as $oPropertyType) {
$oDoc = new PropertyTypeDesign();
$oClone = $oDoc->importNode($oPropertyType->cloneNode(true), true);
if ($oPropertyTrees) {
foreach ($oPropertyTrees->GetNodes('property_tree') as $oPropertyTree) {
$oDoc = new PropertyTreeDesign();
$oClone = $oDoc->importNode($oPropertyTree->cloneNode(true), true);
$oDoc->appendChild($oClone);
/** @var DesignElement $oPropertyType */
$sExtends = $oPropertyType->GetChildText('extends', 'Default');
SetupUtils::builddir($sTempTargetDir.'/core/property_types/'.$sExtends);
$oDoc->save($sTempTargetDir.'/core/property_types/'.$sExtends.'/'.$oPropertyType->getAttribute('id').'.xml');
/** @var DesignElement $oPropertyTree */
$sExtends = $oPropertyTree->GetChildText('extends', 'Default');
SetupUtils::builddir($sTempTargetDir.'/core/property_trees/'.$sExtends);
$oDoc->save($sTempTargetDir.'/core/property_trees/'.$sExtends.'/'.$oPropertyTree->getAttribute('id').'.xml');
}
}
}

View File

@@ -60,11 +60,6 @@ class iTopExtension
* @var bool
*/
public $bMarkedAsChosen;
/**
* If null, check if at least one module cannot be uninstalled
* @var bool|null
*/
public ?bool $bCanBeUninstalled = null;
/**
* @var bool
@@ -96,14 +91,6 @@ class iTopExtension
* @var string[]
*/
public $aMissingDependencies;
/**
* @var bool
*/
public bool $bInstalled = false;
/**
* @var bool
*/
public bool $bRemovedFromDisk = false;
public function __construct()
{
@@ -128,14 +115,13 @@ class iTopExtension
* @since 3.3.0
* @return bool
*/
public function CanBeUninstalled(): bool
public function CanBeUninstalled()
{
if (!is_null($this->bCanBeUninstalled)) {
return $this->bCanBeUninstalled;
}
foreach ($this->aModuleInfo as $sModuleCode => $aModuleInfo) {
$this->bCanBeUninstalled = $aModuleInfo['uninstallable'] === 'yes';
return $this->bCanBeUninstalled;
$bUninstallable = $aModuleInfo['uninstallable'] === 'yes';
if (!$bUninstallable) {
return false;
}
}
return true;
}
@@ -153,11 +139,6 @@ class iTopExtensionsMap
* @return void
*/
protected $aExtensions;
/**
* The list of all currently installed extensions
* @var array|null
*/
protected ?array $aInstalledExtensions = null;
/**
* The list of directories browsed using the ReadDir method when building the map
@@ -165,7 +146,7 @@ class iTopExtensionsMap
*/
protected $aScannedDirs;
public function __construct($sFromEnvironment = 'production', $aExtraDirs = [])
public function __construct($sFromEnvironment = 'production', $bNormalizeOldExtensions = true, $aExtraDirs = [])
{
$this->aExtensions = [];
$this->aScannedDirs = [];
@@ -174,6 +155,9 @@ class iTopExtensionsMap
$this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE);
}
$this->CheckDependencies($sFromEnvironment);
if ($bNormalizeOldExtensions) {
$this->NormalizeOldExtensions();
}
}
/**
@@ -229,7 +213,6 @@ class iTopExtensionsMap
$oExtension = new iTopExtension();
$oExtension->sCode = $aChoiceInfo['extension_code'];
$oExtension->sLabel = $aChoiceInfo['title'];
$oExtension->sDescription = $aChoiceInfo['description'];
if (array_key_exists('modules', $aChoiceInfo)) {
// Some wizard choices are not associated with any module
$oExtension->aModules = $aChoiceInfo['modules'];
@@ -278,7 +261,7 @@ class iTopExtensionsMap
*
* @return \iTopExtension|null
*/
public function GetFromExtensionCode(string $sExtensionCode): ?iTopExtension
public function Get(string $sExtensionCode): ?iTopExtension
{
foreach ($this->aExtensions as $oExtension) {
if ($oExtension->sCode === $sExtensionCode) {
@@ -358,7 +341,7 @@ class iTopExtensionsMap
$this->aExtensions[$sParentExtensionId]->aModuleVersion[$sModuleName] = $sModuleVersion;
$this->aExtensions[$sParentExtensionId]->aModuleInfo[$sModuleName] = $aModuleInfo[ModuleFileReader::MODULE_INFO_CONFIG];
} else {
// Not already inside a folder containing an 'extension.xml' file
// Not already inside an folder containing an 'extension.xml' file
// Ignore non-visible modules and auto-select ones, since these are never prompted
// as a choice to the end-user
@@ -449,19 +432,10 @@ class iTopExtensionsMap
return $this->aExtensions;
}
/**
* @return array All available extensions and extensions currently installed but not available due to files removal
*/
public function GetAllExtensionsWithPreviouslyInstalled(): array
{
//Mind the order, local extensions data must overwrite installed extensions data since installed extensions does not have the associated modules.
return array_merge($this->aInstalledExtensions ?? [], $this->aExtensions);
}
/**
* Mark the given extension as chosen
* @param string $sExtensionCode The code of the extension (code without version number)
* @param bool $bMark The value to set for the bMarkAsChosen flag
* @param string $sExtensionCode The code of the extension (code without verison number)
* @param bool $bMark The value to set for the bmarkAschosen flag
* @return void
*/
public function MarkAsChosen($sExtensionCode, $bMark = true)
@@ -526,55 +500,124 @@ class iTopExtensionsMap
* @return bool
*/
public function LoadChoicesFromDatabase(Config $oConfig)
{
foreach ($this->LoadInstalledExtensionsFromDatabase($oConfig) as $oExtension) {
$this->MarkAsChosen($oExtension->sCode);
$this->SetInstalledVersion($oExtension->sCode, $oExtension->sVersion);
}
return true;
}
protected function LoadInstalledExtensionsFromDatabase(Config $oConfig): array|false
{
try {
$aInstalledExtensions = [];
if (CMDBSource::DBName() === null) {
CMDBSource::InitFromConfig($oConfig);
}
$sLatestInstallationDate = CMDBSource::QueryToScalar("SELECT max(installed) FROM ".$oConfig->Get('db_subname')."priv_extension_install");
$aDBInfo = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_extension_install WHERE installed = '".$sLatestInstallationDate."'");
$this->aInstalledExtensions = [];
foreach ($aDBInfo as $aExtensionInfo) {
$oExtension = new iTopExtension();
$oExtension->sCode = $aExtensionInfo['code'];
$oExtension->sLabel = $aExtensionInfo['label'];
$oExtension->sDescription = $aExtensionInfo['description'] ?? '';
$oExtension->sVersion = $aExtensionInfo['version'];
$oExtension->sSource = $aExtensionInfo['source'];
$oExtension->bMandatory = false;
$oExtension->sMoreInfoUrl = '';
$oExtension->aModules = [];
$oExtension->aModuleVersion = [];
$oExtension->aModuleInfo = [];
$oExtension->sSourceDir = '';
$oExtension->bVisible = true;
$oExtension->bInstalled = true;
$oExtension->bCanBeUninstalled = !isset($aExtensionInfo['uninstallable']) || $aExtensionInfo['uninstallable'] === 'yes';
$oChoice = $this->GetFromExtensionCode($oExtension->sCode);
if ($oChoice) {
$oChoice->bInstalled = true;
} else {
$oExtension->bRemovedFromDisk = true;
}
$this->aInstalledExtensions[$oExtension->sCode.'/'.$oExtension->sVersion] = $oExtension;
}
return $this->aInstalledExtensions;
$aInstalledExtensions = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_extension_install WHERE installed = '".$sLatestInstallationDate."'");
} catch (MySQLException $e) {
// No database or erroneous information
return false;
}
foreach ($aInstalledExtensions as $aDBInfo) {
$this->MarkAsChosen($aDBInfo['code']);
$this->SetInstalledVersion($aDBInfo['code'], $aDBInfo['version']);
}
return true;
}
/**
* Find is a single-module extension is contained within another extension
* @param iTopExtension $oExtension
* @return NULL|iTopExtension
*/
public function IsExtensionObsoletedByAnother(iTopExtension $oExtension)
{
// Complex extensions (more than 1 module) are never considered as obsolete
if (count($oExtension->aModules) != 1) {
return null;
}
foreach ($this->GetAllExtensions() as $oOtherExtension) {
if (($oOtherExtension->sSourceDir != $oExtension->sSourceDir) && ($oOtherExtension->sSource != iTopExtension::SOURCE_WIZARD)) {
if (array_key_exists($oExtension->sCode, $oOtherExtension->aModuleVersion) &&
(version_compare($oOtherExtension->aModuleVersion[$oExtension->sCode], $oExtension->sVersion, '>='))) {
// Found another extension containing a more recent version of the extension/module
return $oOtherExtension;
}
}
}
// No match at all
return null;
}
/**
* Search for multi-module extensions that are NOT deployed as an extension (i.e. shipped with an extension.xml file)
* but as a bunch of un-related modules based on the signature of some well-known extensions. If such an extension is found,
* replace the stand-alone modules by an "extension" with the appropriate label/description/version containing the same modules.
* @param string $sInSourceOnly The source directory to scan (datamodel|extensions|data)
*/
public function NormalizeOldExtensions($sInSourceOnly = iTopExtension::SOURCE_MANUAL)
{
$aSignatures = $this->GetOldExtensionsSignatures();
foreach ($aSignatures as $sExtensionCode => $aExtensionSignatures) {
$bFound = false;
foreach ($aExtensionSignatures['versions'] as $sVersion => $aModules) {
$bInstalled = true;
foreach ($aModules as $sModuleId) {
if (!$this->ModuleIsPresent($sModuleId, $sInSourceOnly)) {
$bFound = false;
break; // One missing module is enough to determine that the extension/version is not present
} else {
$bInstalled = $bInstalled && $this->ModuleIsInstalled($sModuleId, $sInSourceOnly);
$bFound = true;
}
}
if ($bFound) {
break;
} // The current version matches the signature
}
if ($bFound) {
$oExtension = new iTopExtension();
$oExtension->sCode = $sExtensionCode;
$oExtension->sLabel = $aExtensionSignatures['label'];
$oExtension->sSource = $sInSourceOnly;
$oExtension->sDescription = $aExtensionSignatures['description'];
$oExtension->sVersion = $sVersion;
$oExtension->aModules = [];
if ($bInstalled) {
$oExtension->sInstalledVersion = $sVersion;
$oExtension->bMarkedAsChosen = true;
}
foreach ($aModules as $sModuleId) {
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
$oExtension->aModules[] = $sModuleName;
$oExtension->aModuleInfo[$sModuleName] = $this->aExtensions[$sModuleId]->aModuleInfo[$sModuleName];
}
$this->ReplaceModulesByNormalizedExtension($aExtensionSignatures['versions'][$sVersion], $oExtension);
}
}
}
/**
* Check if the given module-code/version is present on the disk
* @param string $sModuleIdToFind The module ID (code/version) to search for
* @param string $sInSourceOnly The origin (=source) to search in (datamodel|extensions|data)
* @return boolean
*/
protected function ModuleIsPresent($sModuleIdToFind, $sInSourceOnly)
{
return (array_key_exists($sModuleIdToFind, $this->aExtensions) && ($this->aExtensions[$sModuleIdToFind]->sSource == $sInSourceOnly));
}
/**
* Check if the given module-code/version is currently installed
* @param string $sModuleIdToFind The module ID (code/version) to search for
* @param string $sInSourceOnly The origin (=source) to search in (datamodel|extensions|data)
* @return boolean
*/
protected function ModuleIsInstalled($sModuleIdToFind, $sInSourceOnly)
{
return (array_key_exists($sModuleIdToFind, $this->aExtensions) &&
($this->aExtensions[$sModuleIdToFind]->sSource == $sInSourceOnly) &&
($this->aExtensions[$sModuleIdToFind]->sInstalledVersion !== ''));
}
/**
@@ -597,4 +640,657 @@ class iTopExtensionsMap
return false;
}
/**
* Replace a given set of stand-alone modules by one single "extension"
* @param string[] $aModules
* @param iTopExtension $oNewExtension
*/
protected function ReplaceModulesByNormalizedExtension($aModules, iTopExtension $oNewExtension)
{
foreach ($aModules as $sModuleId) {
unset($this->aExtensions[$sModuleId]);
}
$this->AddExtension($oNewExtension);
}
/**
* Get the list of signatures of some well-known multi-module extensions without extension.xml file (should not exist anymore)
*
* @return string[][]|string[][][][]
*/
protected function GetOldExtensionsSignatures()
{
// Generated by the Factory using the page export_component_versions_for_normalisation.php
return [
'combodo-approval-process-light' =>
[
'label' => 'Approval process light',
'description' => 'Approve a request via a simple email',
'versions' =>
[
'1.0.1' =>
[
0 => 'approval-base/2.1.0',
1 => 'combodo-approval-light/1.0.1',
],
'1.0.2' =>
[
0 => 'approval-base/2.1.1',
1 => 'combodo-approval-light/1.0.2',
],
'1.0.3' =>
[
0 => 'approval-base/2.1.2',
1 => 'combodo-approval-light/1.0.2',
],
'1.1.0' =>
[
0 => 'approval-base/2.2.2',
1 => 'combodo-approval-light/1.0.2',
],
'1.1.1' =>
[
0 => 'approval-base/2.2.3',
1 => 'combodo-approval-light/1.0.2',
],
'1.1.2' =>
[
0 => 'approval-base/2.2.6',
1 => 'combodo-approval-light/1.0.2',
],
'1.1.3' =>
[
0 => 'approval-base/2.2.6',
1 => 'combodo-approval-light/1.0.3',
],
'1.2.0' =>
[
0 => 'approval-base/2.3.0',
1 => 'combodo-approval-light/1.0.3',
],
'1.2.1' =>
[
0 => 'approval-base/2.4.0',
1 => 'combodo-approval-light/1.0.4',
],
'1.3.0' =>
[
0 => 'approval-base/2.4.2',
1 => 'combodo-approval-light/1.1.1',
],
'1.3.1' =>
[
0 => 'approval-base/2.5.0',
1 => 'combodo-approval-light/1.1.1',
],
'1.3.2' =>
[
0 => 'approval-base/2.5.0',
1 => 'combodo-approval-light/1.1.2',
],
'1.2.2' =>
[
0 => 'approval-base/2.4.2',
1 => 'combodo-approval-light/1.0.5',
],
'1.3.3' =>
[
0 => 'approval-base/2.5.1',
1 => 'combodo-approval-light/1.1.2',
],
'1.3.4' =>
[
0 => 'approval-base/2.5.2',
1 => 'combodo-approval-light/1.1.2',
],
'1.3.5' =>
[
0 => 'approval-base/2.5.3',
1 => 'combodo-approval-light/1.1.2',
],
'1.4.0' =>
[
0 => 'approval-base/2.5.3',
1 => 'combodo-approval-light/1.1.2',
2 => 'itop-approval-portal/1.0.0',
],
],
],
'combodo-approval-process-automation' =>
[
'label' => 'Approval process automation',
'description' => 'Control your approval process with predefined rules based on service catalog',
'versions' =>
[
'1.0.1' =>
[
0 => 'approval-base/2.1.0',
1 => 'combodo-approval-extended/1.0.2',
],
'1.0.2' =>
[
0 => 'approval-base/2.1.1',
1 => 'combodo-approval-extended/1.0.4',
],
'1.0.3' =>
[
0 => 'approval-base/2.1.2',
1 => 'combodo-approval-extended/1.0.4',
],
'1.1.0' =>
[
0 => 'approval-base/2.2.2',
1 => 'combodo-approval-extended/1.0.4',
],
'1.1.1' =>
[
0 => 'approval-base/2.2.3',
1 => 'combodo-approval-extended/1.0.4',
],
'1.1.2' =>
[
0 => 'approval-base/2.2.6',
1 => 'combodo-approval-extended/1.0.5',
],
'1.1.3' =>
[
0 => 'approval-base/2.2.6',
1 => 'combodo-approval-extended/1.0.6',
],
'1.2.0' =>
[
0 => 'approval-base/2.3.0',
1 => 'combodo-approval-extended/1.0.7',
],
'1.2.1' =>
[
0 => 'approval-base/2.4.0',
1 => 'combodo-approval-extended/1.0.8',
],
'1.3.0' =>
[
0 => 'approval-base/2.4.2',
1 => 'combodo-approval-extended/1.2.1',
],
'1.3.1' =>
[
0 => 'approval-base/2.5.0',
1 => 'combodo-approval-extended/1.2.1',
],
'1.3.2' =>
[
0 => 'approval-base/2.5.0',
1 => 'combodo-approval-extended/1.2.2',
],
'1.2.2' =>
[
0 => 'approval-base/2.4.2',
1 => 'combodo-approval-extended/1.0.9',
],
'1.3.3' =>
[
0 => 'approval-base/2.5.1',
1 => 'combodo-approval-extended/1.2.3',
],
'1.3.4' =>
[
0 => 'approval-base/2.5.2',
1 => 'combodo-approval-extended/1.2.3',
],
'1.3.5' =>
[
0 => 'approval-base/2.5.3',
1 => 'combodo-approval-extended/1.2.3',
],
'1.4.0' =>
[
0 => 'approval-base/2.5.3',
1 => 'combodo-approval-extended/1.2.3',
3 => 'itop-approval-portal/1.0.0',
],
],
],
'combodo-predefined-response-models' =>
[
'label' => 'Predefined response models',
'description' => 'Pick common answers from a list of predefined replies grouped by categories to update tickets log',
'versions' =>
[
'1.0.0' =>
[
0 => 'precanned-replies/1.0.0',
1 => 'precanned-replies-pro/1.0.0',
],
'1.0.1' =>
[
0 => 'precanned-replies/1.0.1',
1 => 'precanned-replies-pro/1.0.1',
],
'1.0.2' =>
[
0 => 'precanned-replies/1.0.2',
1 => 'precanned-replies-pro/1.0.1',
],
'1.0.3' =>
[
0 => 'precanned-replies/1.0.3',
1 => 'precanned-replies-pro/1.0.1',
],
'1.0.4' =>
[
0 => 'precanned-replies/1.0.3',
1 => 'precanned-replies-pro/1.0.2',
],
'1.0.5' =>
[
0 => 'precanned-replies/1.0.4',
1 => 'precanned-replies-pro/1.0.2',
],
'1.1.0' =>
[
0 => 'precanned-replies/1.1.0',
1 => 'precanned-replies-pro/1.0.2',
],
'1.1.1' =>
[
0 => 'precanned-replies/1.1.1',
1 => 'precanned-replies-pro/1.0.2',
],
],
],
'combodo-customized-request-forms' =>
[
'label' => 'Customized request forms',
'description' => 'Define personalized request forms based on the service catalog. Add extra fields for a given type of request.',
'versions' =>
[
'1.0.1' =>
[
0 => 'templates-base/2.1.1',
1 => 'itop-request-template/1.0.0',
],
'1.0.2' =>
[
0 => 'templates-base/2.1.2',
1 => 'itop-request-template/1.0.0',
],
'1.0.3' =>
[
0 => 'templates-base/2.1.2',
1 => 'itop-request-template/1.0.1',
],
'1.0.4' =>
[
0 => 'templates-base/2.1.3',
1 => 'itop-request-template/1.0.1',
],
'1.0.5' =>
[
0 => 'templates-base/2.1.4',
1 => 'itop-request-template/1.0.1',
],
'2.0.0' =>
[
0 => 'templates-base/3.0.0',
1 => 'itop-request-template/2.0.0',
2 => 'itop-request-template-portal/1.0.0',
],
'2.0.1' =>
[
0 => 'templates-base/3.0.1',
1 => 'itop-request-template/2.0.0',
2 => 'itop-request-template-portal/1.0.0',
],
'2.0.2' =>
[
0 => 'templates-base/3.0.2',
1 => 'itop-request-template/2.0.0',
2 => 'itop-request-template-portal/1.0.0',
],
'2.0.3' =>
[
0 => 'templates-base/3.0.4',
1 => 'itop-request-template/2.0.0',
2 => 'itop-request-template-portal/1.0.0',
],
'2.0.4' =>
[
0 => 'templates-base/3.0.5',
1 => 'itop-request-template/2.0.0',
2 => 'itop-request-template-portal/1.0.0',
],
'2.0.5' =>
[
0 => 'templates-base/3.0.6',
1 => 'itop-request-template/2.0.0',
2 => 'itop-request-template-portal/1.0.0',
],
'2.0.6' =>
[
0 => 'templates-base/3.0.8',
1 => 'itop-request-template/2.0.0',
2 => 'itop-request-template-portal/1.0.0',
],
'2.0.7' =>
[
0 => 'templates-base/3.0.9',
1 => 'itop-request-template/2.0.0',
2 => 'itop-request-template-portal/1.0.0',
],
'2.0.8' =>
[
0 => 'templates-base/3.0.12',
1 => 'itop-request-template/2.0.0',
2 => 'itop-request-template-portal/1.0.0',
],
],
],
'combodo-sla-considering-business-hours' =>
[
'label' => 'SLA considering business hours',
'description' => 'Compute SLAs taking into account service coverage window and holidays',
'versions' =>
[
'2.0.1' =>
[
0 => 'combodo-sla-computation/2.0.1',
1 => 'combodo-coverage-windows-computation/2.0.0',
],
'2.1.0' =>
[
0 => 'combodo-sla-computation/2.1.0',
1 => 'combodo-coverage-windows-computation/2.0.0',
],
'2.1.1' =>
[
0 => 'combodo-sla-computation/2.1.1',
1 => 'combodo-coverage-windows-computation/2.0.0',
],
'2.1.2' =>
[
0 => 'combodo-sla-computation/2.1.2',
1 => 'combodo-coverage-windows-computation/2.0.0',
],
'2.1.3' =>
[
0 => 'combodo-sla-computation/2.1.2',
1 => 'combodo-coverage-windows-computation/2.0.1',
],
'2.0.2' =>
[
0 => 'combodo-sla-computation/2.0.1',
1 => 'combodo-coverage-windows-computation/2.0.1',
],
'2.1.4' =>
[
0 => 'combodo-sla-computation/2.1.3',
1 => 'combodo-coverage-windows-computation/2.0.1',
],
'2.1.5' =>
[
0 => 'combodo-sla-computation/2.1.5',
1 => 'combodo-coverage-windows-computation/2.0.1',
],
'2.1.6' =>
[
0 => 'combodo-sla-computation/2.1.5',
1 => 'combodo-coverage-windows-computation/2.0.2',
],
'2.1.7' =>
[
0 => 'combodo-sla-computation/2.1.6',
1 => 'combodo-coverage-windows-computation/2.0.2',
],
'2.1.8' =>
[
0 => 'combodo-sla-computation/2.1.7',
1 => 'combodo-coverage-windows-computation/2.0.2',
],
'2.1.9' =>
[
0 => 'combodo-sla-computation/2.1.8',
1 => 'combodo-coverage-windows-computation/2.0.2',
],
],
],
'combodo-mail-to-ticket-automation' =>
[
'label' => 'Mail to ticket automation',
'description' => 'Scan several mailboxes to create or update tickets.',
'versions' =>
[
'2.6.0' =>
[
0 => 'combodo-email-synchro/2.6.0',
1 => 'itop-standard-email-synchro/2.6.0',
],
'2.6.1' =>
[
0 => 'combodo-email-synchro/2.6.1',
1 => 'itop-standard-email-synchro/2.6.0',
],
'2.6.2' =>
[
0 => 'combodo-email-synchro/2.6.2',
1 => 'itop-standard-email-synchro/2.6.0',
],
'2.6.3' =>
[
0 => 'combodo-email-synchro/2.6.2',
1 => 'itop-standard-email-synchro/2.6.1',
],
'2.6.4' =>
[
0 => 'combodo-email-synchro/2.6.3',
1 => 'itop-standard-email-synchro/2.6.2',
],
'2.6.5' =>
[
0 => 'combodo-email-synchro/2.6.4',
1 => 'itop-standard-email-synchro/2.6.2',
],
'2.6.6' =>
[
0 => 'combodo-email-synchro/2.6.5',
1 => 'itop-standard-email-synchro/2.6.3',
],
'2.6.7' =>
[
0 => 'combodo-email-synchro/2.6.6',
1 => 'itop-standard-email-synchro/2.6.4',
],
'2.6.8' =>
[
0 => 'combodo-email-synchro/2.6.7',
1 => 'itop-standard-email-synchro/2.6.4',
],
'2.6.9' =>
[
0 => 'combodo-email-synchro/2.6.8',
1 => 'itop-standard-email-synchro/2.6.5',
],
'2.6.10' =>
[
0 => 'combodo-email-synchro/2.6.9',
1 => 'itop-standard-email-synchro/2.6.6',
],
'2.6.11' =>
[
0 => 'combodo-email-synchro/2.6.10',
1 => 'itop-standard-email-synchro/2.6.6',
],
'2.6.12' =>
[
0 => 'combodo-email-synchro/2.6.11',
1 => 'itop-standard-email-synchro/2.6.6',
],
'3.0.0' =>
[
0 => 'combodo-email-synchro/3.0.0',
1 => 'itop-standard-email-synchro/3.0.0',
],
'3.0.1' =>
[
0 => 'combodo-email-synchro/3.0.1',
1 => 'itop-standard-email-synchro/3.0.1',
],
'3.0.2' =>
[
0 => 'combodo-email-synchro/3.0.2',
1 => 'itop-standard-email-synchro/3.0.1',
],
'3.0.3' =>
[
0 => 'combodo-email-synchro/3.0.3',
1 => 'itop-standard-email-synchro/3.0.3',
],
'3.0.4' =>
[
0 => 'combodo-email-synchro/3.0.3',
1 => 'itop-standard-email-synchro/3.0.4',
],
'3.0.5' =>
[
0 => 'combodo-email-synchro/3.0.4',
1 => 'itop-standard-email-synchro/3.0.4',
],
'3.0.6' =>
[
0 => 'combodo-email-synchro/3.0.5',
1 => 'itop-standard-email-synchro/3.0.4',
],
'3.0.7' =>
[
0 => 'combodo-email-synchro/3.0.5',
1 => 'itop-standard-email-synchro/3.0.5',
],
],
],
'combodo-configurator-for-automatic-object-creation' =>
[
'label' => 'Configurator for automatic object creation',
'description' => 'Templating based on existing objects.',
'versions' =>
[
'1.0.13' =>
[
1 => 'itop-stencils/1.0.6',
],
],
],
'combodo-user-actions-configurator' =>
[
'label' => 'User actions configurator',
'description' => 'Configure user actions to simplify and automate processes (e.g. create an incident from a CI).',
'versions' =>
[
'1.0.0' =>
[
0 => 'itop-object-copier/1.0.0',
],
'1.0.1' =>
[
0 => 'itop-object-copier/1.0.1',
],
'1.0.2' =>
[
0 => 'itop-object-copier/1.0.2',
],
'1.0.3' =>
[
0 => 'itop-object-copier/1.0.3',
],
'1.1.0' =>
[
0 => 'itop-object-copier/1.1.0',
],
'1.1.1' =>
[
0 => 'itop-object-copier/1.1.1',
],
'1.1.2' =>
[
0 => 'itop-object-copier/1.1.2',
],
'1.1.3' =>
[
0 => 'itop-object-copier/1.1.3',
],
'1.1.4' =>
[
0 => 'itop-object-copier/1.1.4',
],
'1.1.5' =>
[
0 => 'itop-object-copier/1.1.5',
],
'1.1.6' =>
[
0 => 'itop-object-copier/1.1.6',
],
'1.1.7' =>
[
0 => 'itop-object-copier/1.1.7',
],
'1.1.8' =>
[
0 => 'itop-object-copier/1.1.8',
],
],
],
'combodo-send-updates-by-email' =>
[
'label' => 'Send updates by email',
'description' => 'Send an email to pre-configured contacts when a ticket log is updated.',
'versions' =>
[
'1.0.1' =>
[
0 => 'email-reply/1.0.1',
],
'1.0.3' =>
[
0 => 'email-reply/1.0.3',
],
'1.1.1' =>
[
0 => 'email-reply/1.1.1',
],
'1.1.2' =>
[
0 => 'email-reply/1.1.2',
],
'1.1.3' =>
[
0 => 'email-reply/1.1.3',
],
'1.1.4' =>
[
0 => 'email-reply/1.1.4',
],
'1.1.5' =>
[
0 => 'email-reply/1.1.5',
],
'1.1.6' =>
[
0 => 'email-reply/1.1.6',
],
'1.1.7' =>
[
0 => 'email-reply/1.1.7',
],
// 1.1.8 was never released
'1.1.9' =>
[
0 => 'email-reply/1.1.9',
],
'1.1.10' =>
[
0 => 'email-reply/1.1.10',
],
],
],
];
}
}

View File

@@ -1,155 +0,0 @@
<?php
namespace Combodo\iTop\Setup\ModuleDependency;
require_once(APPROOT.'/setup/runtimeenv.class.inc.php');
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
use ModuleFileReaderException;
use RunTimeEnvironment;
/**
* Class that handles a module dependency
* Dependency expression example : (moduleA/123 || moduleB>456)
*/
class DependencyExpression
{
private static PhpExpressionEvaluator $oPhpExpressionEvaluator;
private string $sDependencyExpression;
private bool $bValid = true;
private bool $bResolved = false;
/**
* @var array<string, bool> $aRemainingModuleNamesToResolve
*/
private array $aRemainingModuleNamesToResolve;
/**
* @var array<string, array> $aParamsPerModuleId
*/
private array $aParamsPerModuleId;
public function __construct(string $sDependencyExpression)
{
$this->sDependencyExpression = $sDependencyExpression;
$this->aParamsPerModuleId = [];
$this->aRemainingModuleNamesToResolve = [];
if (preg_match_all('/([^\(\)&| ]+)/', $sDependencyExpression, $aMatches)) {
foreach ($aMatches as $aMatch) {
foreach ($aMatch as $sModuleId) {
if (!array_key_exists($sModuleId, $this->aParamsPerModuleId)) {
// $sModuleId in the dependency string is made of a <name>/<optional_operator><version>
// where the operator is < <= = > >= (by default >=)
$aModuleMatches = [];
if (preg_match('|^([^/]+)/(<?>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches)) {
$sModuleName = $aModuleMatches[1];
$this->aRemainingModuleNamesToResolve[$sModuleName] = true;
$sOperator = $aModuleMatches[2];
if ($sOperator == '') {
$sOperator = '>=';
}
$sExpectedVersion = $aModuleMatches[3];
$this->aParamsPerModuleId[$sModuleId] = [$sModuleName, $sOperator, $sExpectedVersion];
}
}
}
}
} else {
$this->bValid = false;
}
}
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
{
if (!isset(static::$oPhpExpressionEvaluator)) {
static::$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
}
return static::$oPhpExpressionEvaluator;
}
/**
* Return module names potentially required by current dependency
*
* @return array
*/
public function GetRemainingModuleNamesToResolve(): array
{
return array_keys($this->aRemainingModuleNamesToResolve);
}
public function IsResolved(): bool
{
return $this->bResolved;
}
/**
* Check if dependency is resolved with current list of module versions
*
* @param array $aResolvedModuleVersions : versions by module names dict
* @param array $aAllModuleNames : modules names dict
*
* @return void
*/
public function UpdateModuleResolutionState(array $aResolvedModuleVersions, array $aAllModuleNames): void
{
if (!$this->bValid) {
return;
}
$aReplacements = [];
$bDelayEvaluation = false;
foreach ($this->aParamsPerModuleId as $sModuleId => list($sModuleName, $sOperator, $sExpectedVersion)) {
if (array_key_exists($sModuleName, $aResolvedModuleVersions)) {
// module is resolved, check the version
$sCurrentVersion = $aResolvedModuleVersions[$sModuleName];
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) {
if (array_key_exists($sModuleName, $this->aRemainingModuleNamesToResolve)) {
unset($this->aRemainingModuleNamesToResolve[$sModuleName]);
}
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
} else {
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
}
} else {
// module is not resolved yet
if (array_key_exists($sModuleName, $aAllModuleNames)) {
//Weird piece of code that covers below usecase:
//module B dependency: 'moduleA || true'
// if moduleA not present on disk, whole expression can be evaluated and may be resolved
// if moduleA present on disk, we need to sort moduleB after moduleA. expression cannot be resolved yet
$bDelayEvaluation = true;
} else {
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
}
}
}
if ($bDelayEvaluation) {
return;
}
$bResult = false;
$sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $this->sDependencyExpression);
try {
$bResult = self::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($sBooleanExpr);
} catch (ModuleFileReaderException $e) {
//logged already
echo "Failed to parse the boolean Expression = '$sBooleanExpr'<br/>";
}
$this->bResolved = $bResult;
}
public function IsValid(): bool
{
return $this->bValid;
}
}

View File

@@ -1,129 +0,0 @@
<?php
namespace Combodo\iTop\Setup\ModuleDependency;
require_once(__DIR__.'/dependencyexpression.class.inc.php');
use ModuleDiscovery;
/**
* Class that handles a modules and all its dependencies
*/
class Module
{
private string $sModuleId;
private string $sModuleName;
private string $sVersion;
/**
* @var array<string> $aInitialDependencyExpressions
*/
private array $aInitialDependencyExpressions;
/**
* @var array<string, DependencyExpression> $aRemainingDependenciesToResolve
*/
public array $aRemainingDependenciesToResolve;
public function __construct(string $sModuleId)
{
$this->sModuleId = $sModuleId;
list($this->sModuleName, $this->sVersion) = ModuleDiscovery::GetModuleName($sModuleId);
}
public function IsDependencyExpressionResolved(string $sDependencyExpression): bool
{
return ! array_key_exists($sDependencyExpression, $this->aRemainingDependenciesToResolve);
}
public function GetDependencyResolutionFeedback(): array
{
$aDepsWithIcons = [];
foreach ($this->aInitialDependencyExpressions as $sDependencyExpression) {
if (! $this->IsDependencyExpressionResolved($sDependencyExpression)) {
$aDepsWithIcons[] = '❌ '.$sDependencyExpression;
}
}
return $aDepsWithIcons;
}
/**
* @return string
*/
public function GetModuleName()
{
return $this->sModuleName;
}
/**
* @return string
*/
public function GetVersion()
{
return $this->sVersion;
}
/**
* @return string
*/
public function GetModuleId()
{
return $this->sModuleId;
}
/**
* @param array $aAllDependencyExpressions: list of dependencies (string)
*
* @return void
*/
public function SetDependencies(array $aAllDependencyExpressions): void
{
$this->aInitialDependencyExpressions = $aAllDependencyExpressions;
$this->aRemainingDependenciesToResolve = [];
foreach ($aAllDependencyExpressions as $sDependencyExpression) {
$this->aRemainingDependenciesToResolve[$sDependencyExpression] = new DependencyExpression($sDependencyExpression);
}
}
public function IsResolved(): bool
{
return (0 === count($this->aRemainingDependenciesToResolve));
}
/**
* Check if module dependencies are resolved with current list of module versions
* @param array<string, string> $aResolvedModuleVersions : versions by module names dict
* @param array<string> $aAllModuleNames : resolved modules names
*
* @return void
*/
public function UpdateModuleResolutionState(array $aResolvedModuleVersions, array $aAllModuleNames): void
{
$aNextDependencies = [];
foreach ($this->aRemainingDependenciesToResolve as $sDependencyExpression => $oModuleDependency) {
/** @var DependencyExpression $oModuleDependency*/
$oModuleDependency->UpdateModuleResolutionState($aResolvedModuleVersions, $aAllModuleNames);
if (!$oModuleDependency->IsResolved()) {
$aNextDependencies[$sDependencyExpression] = $oModuleDependency;
}
}
$this->aRemainingDependenciesToResolve = $aNextDependencies;
}
/**
* @return array: list of unique module names
*/
public function GetUnresolvedDependencyModuleNames(): array
{
$aRes = [];
foreach ($this->aRemainingDependenciesToResolve as $sDependencyExpression => $oModuleDependency) {
/** @var DependencyExpression $oModuleDependency */
$aRes = array_merge($aRes, $oModuleDependency->GetRemainingModuleNamesToResolve());
}
return array_unique($aRes);
}
}

View File

@@ -1,201 +0,0 @@
<?php
namespace Combodo\iTop\Setup\ModuleDependency;
require_once(__DIR__.'/module.class.inc.php');
use MissingDependencyException;
/**
* Class that sorts module dependencies
*/
class ModuleDependencySort
{
private static ModuleDependencySort $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): ModuleDependencySort
{
if (!isset(static::$oInstance)) {
static::$oInstance = new static();
}
return static::$oInstance;
}
final public static function SetInstance(?ModuleDependencySort $oInstance): void
{
static::$oInstance = $oInstance;
}
/**
* Sort a list of modules, based on their (inter) dependencies
*
* @param array $aModules The list of modules to process: 'id' => $aModuleInfo
* @param bool $bAbortOnMissingDependency ...
*
* @return array
* @throws \MissingDependencyException
*/
public function GetModulesOrderedForInstallation($aModules, $bAbortOnMissingDependency = false)
{
// Filter modules to compute
$aUnresolvedDependencyModules = [];
$aAllModuleNames = [];
foreach ($aModules as $sModuleId => $aModule) {
$oModule = new Module($sModuleId);
$sModuleName = $oModule->GetModuleName();
$oModule->SetDependencies($aModule['dependencies']);
$aUnresolvedDependencyModules[$sModuleId] = $oModule;
$aAllModuleNames[$sModuleName] = true;
}
// Make sure order is deterministic (alphabtical order)
ksort($aUnresolvedDependencyModules);
//Attempt to resolve module dependencies
$aOrderedModules = [];
$aResolvedModuleVersions = [];
$iPreviousUnresolvedCount = -1;
//loop until no dependency is resolved
while ($iPreviousUnresolvedCount !== count($aUnresolvedDependencyModules)) {
$iPreviousUnresolvedCount = count($aUnresolvedDependencyModules);
if ($iPreviousUnresolvedCount === 0) {
break;
}
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
/** @var Module $oModule */
$oModule->UpdateModuleResolutionState($aResolvedModuleVersions, $aAllModuleNames);
if ($oModule->IsResolved()) {
$aOrderedModules[] = $sModuleId;
$aResolvedModuleVersions[$oModule->GetModuleName()] = $oModule->GetVersion();
unset($aUnresolvedDependencyModules[$sModuleId]);
}
}
}
// Report unresolved dependencies
if ($bAbortOnMissingDependency && count($aUnresolvedDependencyModules) > 0) {
$this->SortModulesByCountOfDepencenciesDescending($aUnresolvedDependencyModules);
$aUnresolvedModulesInfo = [];
$aModuleDeps = [];
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
$aModule = $aModules[$sModuleId];
$aDepsWithIcons = $oModule->GetDependencyResolutionFeedback();
$aModuleDeps[] = "{$aModule['label']} (id: $sModuleId) depends on: ".implode(' + ', $aDepsWithIcons);
$aUnresolvedModulesInfo[$sModuleId] = ['module' => $aModule, 'dependencies' => $aDepsWithIcons];
}
$sMessage = "The following modules have unmet dependencies:\n".implode(",\n", $aModuleDeps);
$oException = new MissingDependencyException($sMessage);
$oException->aModulesInfo = $aUnresolvedModulesInfo;
throw $oException;
}
// Return the ordered list, so that the dependencies are met...
$aResult = [];
foreach ($aOrderedModules as $sId) {
$aResult[$sId] = $aModules[$sId];
}
return $aResult;
}
/**
* This method is key as it sorts modules by their dependencies (topological sort).
* Modules with less dependencies are first.
* When module A depends from module B with same amount of dependencies, moduleB is first.
* This order can deal with
* - cyclic dependencies
* - further versions of same module (name)
*
* @param array $aUnresolvedDependencyModules : dict of Module objects by moduleId key
*
* @return void
*/
protected function SortModulesByCountOfDepencenciesDescending(array &$aUnresolvedDependencyModules): void
{
$aCountDepsByModuleId = [];
$aDependsOnModuleName = [];
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
/** @var Module $oModule */
$aDependsOnModuleName[$oModule->GetModuleName()] = [];
}
foreach ($aUnresolvedDependencyModules as $sModuleId => $oModule) {
$iInDegreeCounter = 0;
/** @var Module $oModule */
$aUnresolvedDependencyModuleNames = $oModule->GetUnresolvedDependencyModuleNames();
foreach ($aUnresolvedDependencyModuleNames as $sModuleName) {
if (array_key_exists($sModuleName, $aDependsOnModuleName)) {
$aDependsOnModuleName[$sModuleName][] = $sModuleId;
$iInDegreeCounter++;
}
}
//include all modules
$iInDegreeCounterIncludingOutsideModules = count($oModule->GetUnresolvedDependencyModuleNames());
$aCountDepsByModuleId[$sModuleId] = [$iInDegreeCounter, $iInDegreeCounterIncludingOutsideModules, $sModuleId];
}
$aRes = [];
while (count($aUnresolvedDependencyModules) > 0) {
asort($aCountDepsByModuleId);
uasort($aCountDepsByModuleId, function (array $aDeps1, array $aDeps2) {
//compare $iInDegreeCounter
$res = $aDeps1[0] - $aDeps2[0];
if ($res != 0) {
return $res;
}
//compare $iInDegreeCounterIncludingOutsideModules
$res = $aDeps1[1] - $aDeps2[1];
if ($res != 0) {
return $res;
}
//alphabetical order at least
return strcmp($aDeps1[2], $aDeps2[2]);
});
$bOneLoopAtLeast = false;
foreach ($aCountDepsByModuleId as $sModuleId => $iInDegreeCounter) {
$oModule = $aUnresolvedDependencyModules[$sModuleId];
if ($bOneLoopAtLeast && $iInDegreeCounter > 0) {
break;
}
unset($aUnresolvedDependencyModules[$sModuleId]);
unset($aCountDepsByModuleId[$sModuleId]);
$aRes[$sModuleId] = $oModule;
//when 2 versions of the same module (name) below array has been removed already
if (array_key_exists($oModule->GetModuleName(), $aDependsOnModuleName)) {
foreach ($aDependsOnModuleName[$oModule->GetModuleName()] as $sModuleId2) {
if (!array_key_exists($sModuleId2, $aCountDepsByModuleId)) {
continue;
}
$aDepCount = $aCountDepsByModuleId[$sModuleId2];
$iInDegreeCounter = $aDepCount[0] - 1;
$iInDegreeCounterIncludingOutsideModules = $aDepCount[1];
$aCountDepsByModuleId[$sModuleId2] = [$iInDegreeCounter, $iInDegreeCounterIncludingOutsideModules, $sModuleId2];
}
unset($aDependsOnModuleName[$oModule->GetModuleName()]);
}
$bOneLoopAtLeast = true;
}
}
$aUnresolvedDependencyModules = $aRes;
}
}

148
setup/modulediscovery.class.inc.php Executable file → Normal file
View File

@@ -21,14 +21,10 @@
*/
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
use Combodo\iTop\Setup\ModuleDependency\Module;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
require_once(APPROOT.'setup/modulediscovery/ModuleFileReader.php');
require_once(__DIR__.'/moduledependency/moduledependencysort.class.inc.php');
use Combodo\iTop\Setup\ModuleDependency\ModuleDependencySort;
class MissingDependencyException extends CoreException
{
@@ -215,23 +211,76 @@ class ModuleDiscovery
* @param array $aModulesToLoad List of modules to search for, defaults to all if omitted
* @return array
* @throws \MissingDependencyException
*/
*/
public static function OrderModulesByDependencies($aModules, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
{
if (is_null($aModulesToLoad)) {
$aFilteredModules = $aModules;
} else {
$aFilteredModules = [];
foreach ($aModules as $sModuleId => $aModule) {
$oModule = new Module($sModuleId);
$sModuleName = $oModule->GetModuleName();
if (in_array($sModuleName, $aModulesToLoad)) {
$aFilteredModules[$sModuleId] = $aModule;
}
// Order the modules to take into account their inter-dependencies
$aDependencies = [];
$aSelectedModules = [];
foreach ($aModules as $sId => $aModule) {
list($sModuleName, ) = self::GetModuleName($sId);
if (is_null($aModulesToLoad) || in_array($sModuleName, $aModulesToLoad)) {
$aDependencies[$sId] = $aModule['dependencies'];
$aSelectedModules[$sModuleName] = true;
}
}
ksort($aDependencies);
$aOrderedModules = [];
$iLoopCount = 0;
while (($iLoopCount < count($aModules)) && (count($aDependencies) > 0)) {
foreach ($aDependencies as $sId => $aRemainingDeps) {
$bDependenciesSolved = true;
foreach ($aRemainingDeps as $sDepId) {
if (!self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) {
$bDependenciesSolved = false;
}
}
if ($bDependenciesSolved) {
$aOrderedModules[] = $sId;
unset($aDependencies[$sId]);
}
}
$iLoopCount++;
}
if ($bAbortOnMissingDependency && count($aDependencies) > 0) {
$aModulesInfo = [];
$aModuleDeps = [];
foreach ($aDependencies as $sId => $aDeps) {
$aModule = $aModules[$sId];
$aDepsWithIcons = [];
foreach ($aDeps as $sIndex => $sDepId) {
if (self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) {
$aDepsWithIcons[$sIndex] = '✅ '.$sDepId;
} else {
$aDepsWithIcons[$sIndex] = '❌ '.$sDepId;
}
}
$aModuleDeps[] = "{$aModule['label']} (id: $sId) depends on: ".implode(' + ', $aDepsWithIcons);
$aModulesInfo[$sId] = ['module' => $aModule, 'dependencies' => $aDepsWithIcons];
}
$sMessage = "The following modules have unmet dependencies:\n".implode(",\n", $aModuleDeps);
$oException = new MissingDependencyException($sMessage);
$oException->aModulesInfo = $aModulesInfo;
throw $oException;
}
// Return the ordered list, so that the dependencies are met...
$aResult = [];
foreach ($aOrderedModules as $sId) {
$aResult[$sId] = $aModules[$sId];
}
return $aResult;
}
return ModuleDependencySort::GetInstance()->GetModulesOrderedForInstallation($aFilteredModules, $bAbortOnMissingDependency);
/**
* Remove the duplicate modules (i.e. modules with the same name but with a different version) from the supplied list of modules
* @param array $aModules
* @return array The ordered modules as a duplicate-free list of modules
*/
public static function RemoveDuplicateModules($aModules)
{
// No longer needed, kept only for compatibility
// The de-duplication is now done directly by the AddModule method
return $aModules;
}
private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator
@@ -243,6 +292,73 @@ class ModuleDiscovery
return static::$oPhpExpressionEvaluator;
}
protected static function DependencyIsResolved($sDepString, $aOrderedModules, $aSelectedModules)
{
$bResult = false;
$aModuleVersions = [];
// Separate the module names from their version for an easier comparison later
foreach ($aOrderedModules as $sModuleId) {
list($sModuleName, $sVersion) = self::GetModuleName($sModuleId);
$aModuleVersions[$sModuleName] = $sVersion;
}
if (preg_match_all('/([^\(\)&| ]+)/', $sDepString, $aMatches)) {
$aReplacements = [];
$aPotentialPrerequisites = [];
foreach ($aMatches as $aMatch) {
foreach ($aMatch as $sModuleId) {
// $sModuleId in the dependency string is made of a <name>/<optional_operator><version>
// where the operator is < <= = > >= (by default >=)
$aModuleMatches = [];
if (preg_match('|^([^/]+)/(<?>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches)) {
$sModuleName = $aModuleMatches[1];
$aPotentialPrerequisites[$sModuleName] = true;
$sOperator = $aModuleMatches[2];
if ($sOperator == '') {
$sOperator = '>=';
}
$sExpectedVersion = $aModuleMatches[3];
if (array_key_exists($sModuleName, $aModuleVersions)) {
// module is present, check the version
$sCurrentVersion = $aModuleVersions[$sModuleName];
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) {
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
} else {
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
}
} else {
// module is not present
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
}
}
}
}
$bMissingPrerequisite = false;
foreach (array_keys($aPotentialPrerequisites) as $sModuleName) {
if (array_key_exists($sModuleName, $aSelectedModules)) {
// This module is actually a prerequisite
if (!array_key_exists($sModuleName, $aModuleVersions)) {
$bMissingPrerequisite = true;
}
}
}
if ($bMissingPrerequisite) {
$bResult = false;
} else {
$sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $sDepString);
try {
$bResult = self::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($sBooleanExpr);
} catch (ModuleFileReaderException $e) {
//logged already
echo "Failed to parse the boolean Expression = '$sBooleanExpr'<br/>";
}
}
}
return $bResult;
}
/**
* Search (on the disk) for all defined iTop modules, load them and returns the list (as an array)
* of the possible iTop modules to install

View File

@@ -89,7 +89,6 @@ class ExtensionInstallation extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeString("source", ["allowed_values" => null, "sql" => "source", "default_value" => null, "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeEnum("uninstallable", ["allowed_values" => new ValueSetEnum('yes,no,maybe'), "sql" => "uninstallable", "default_value" => 'yes', "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", ["allowed_values" => null, "sql" => "installed", "default_value" => 'NOW()', "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeText("description", ["allowed_values" => null, "sql" => "description", "default_value" => null, "is_null_allowed" => true, "depends_on" => []]));
// Display lists
MetaModel::Init_SetZListItems('details', ['code', 'label', 'version', 'installed', 'source']); // Attributes to be displayed for the complete details

View File

@@ -1,5 +1,4 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
@@ -17,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Manage a runtime environment
*
@@ -33,16 +33,18 @@ require_once APPROOT.'setup/modelfactory.class.inc.php';
require_once APPROOT.'setup/compiler.class.inc.php';
require_once APPROOT.'setup/extensionsmap.class.inc.php';
define('MODULE_ACTION_OPTIONAL', 1);
define('MODULE_ACTION_MANDATORY', 2);
define('MODULE_ACTION_IMPOSSIBLE', 3);
define('ROOT_MODULE', '_Root_'); // Convention to store IN MEMORY the name/version of the root module i.e. application
define('DATAMODEL_MODULE', 'datamodel'); // Convention to store the version of the datamodel
define ('MODULE_ACTION_OPTIONAL', 1);
define ('MODULE_ACTION_MANDATORY', 2);
define ('MODULE_ACTION_IMPOSSIBLE', 3);
define ('ROOT_MODULE', '_Root_'); // Convention to store IN MEMORY the name/version of the root module i.e. application
define ('DATAMODEL_MODULE', 'datamodel'); // Convention to store the version of the datamodel
class RunTimeEnvironment
{
public const STATIC_CALL_AUTOSELECT_WHITELIST = [
"SetupInfo::ModuleIsSelected",
const STATIC_CALL_AUTOSELECT_WHITELIST=[
"SetupInfo::ModuleIsSelected"
];
/**
@@ -72,10 +74,13 @@ class RunTimeEnvironment
public function __construct($sEnvironment = 'production', $bAutoCommit = true)
{
$this->sFinalEnv = $sEnvironment;
if ($bAutoCommit) {
if ($bAutoCommit)
{
// Build directly onto the requested environment
$this->sTargetEnv = $sEnvironment;
} else {
}
else
{
// Build into a temporary target
$this->sTargetEnv = $sEnvironment.'-build';
}
@@ -116,20 +121,25 @@ class RunTimeEnvironment
require_once APPROOT.'/setup/moduleinstallation.class.inc.php';
$sConfigFile = $oConfig->GetLoadedFile();
if (strlen($sConfigFile) > 0) {
if (strlen($sConfigFile) > 0)
{
$this->log_info("MetaModel::Startup from $sConfigFile (ModelOnly = $bModelOnly)");
} else {
}
else
{
$this->log_info("MetaModel::Startup (ModelOnly = $bModelOnly)");
}
if (!$bUseCache) {
if (!$bUseCache)
{
// Reset the cache for the first use !
MetaModel::ResetAllCaches($this->sTargetEnv);
}
MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false /* $bTraceSourceFiles */, $this->sTargetEnv);
if ($this->oExtensionsMap === null) {
if ($this->oExtensionsMap === null)
{
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv);
}
}
@@ -168,23 +178,26 @@ class RunTimeEnvironment
*/
public function AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
{
$aRes = [
ROOT_MODULE => [
$aRes = array(
ROOT_MODULE => array(
'version_db' => '',
'name_db' => '',
'version_code' => ITOP_VERSION_FULL,
'name_code' => ITOP_APPLICATION,
],
];
)
);
$aDirs = is_array($modulesPath) ? $modulesPath : [$modulesPath];
$aDirs = is_array($modulesPath) ? $modulesPath : array($modulesPath);
$aModules = ModuleDiscovery::GetAvailableModules($aDirs, $bAbortOnMissingDependency, $aModulesToLoad);
foreach ($aModules as $sModuleId => $aModuleInfo) {
foreach($aModules as $sModuleId => $aModuleInfo)
{
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
if ($sModuleName == '') {
if ($sModuleName == '')
{
throw new Exception("Missing name for the module: '$sModuleId'");
}
if ($sModuleVersion == '') {
if ($sModuleVersion == '')
{
// The version must not be empty (it will be used as a criteria to determine wether a module has been installed or not)
//throw new Exception("Missing version for the module: '$sModuleId'");
$sModuleVersion = '1.0.0';
@@ -194,76 +207,95 @@ class RunTimeEnvironment
$aModuleInfo['version_db'] = '';
$aModuleInfo['version_code'] = $sModuleVersion;
if (!in_array($sModuleAppVersion, ['1.0.0', '1.0.1', '1.0.2'])) {
if (!in_array($sModuleAppVersion, array('1.0.0', '1.0.1', '1.0.2')))
{
// This module is NOT compatible with the current version
$aModuleInfo['install'] = [
$aModuleInfo['install'] = array(
'flag' => MODULE_ACTION_IMPOSSIBLE,
'message' => 'the module is not compatible with the current version of the application',
];
} elseif ($aModuleInfo['mandatory']) {
$aModuleInfo['install'] = [
'message' => 'the module is not compatible with the current version of the application'
);
}
elseif ($aModuleInfo['mandatory'])
{
$aModuleInfo['install'] = array(
'flag' => MODULE_ACTION_MANDATORY,
'message' => 'the module is part of the application',
];
} else {
$aModuleInfo['install'] = [
'message' => 'the module is part of the application'
);
}
else
{
$aModuleInfo['install'] = array(
'flag' => MODULE_ACTION_OPTIONAL,
'message' => '',
];
'message' => ''
);
}
$aRes[$sModuleName] = $aModuleInfo;
}
try {
$aSelectInstall = [];
try
{
$aSelectInstall = array();
if (! is_null($oConfig)) {
CMDBSource::InitFromConfig($oConfig);
$aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install");
}
} catch (MySQLException $e) {
}
catch (MySQLException $e)
{
// No database or erroneous information
}
// Build the list of installed module (get the latest installation)
//
$aInstallByModule = []; // array of <module> => array ('installed' => timestamp, 'version' => <version>)
$aInstallByModule = array(); // array of <module> => array ('installed' => timestamp, 'version' => <version>)
$iRootId = 0;
foreach ($aSelectInstall as $aInstall) {
if (($aInstall['parent_id'] == 0) && ($aInstall['name'] != 'datamodel')) {
foreach ($aSelectInstall as $aInstall)
{
if (($aInstall['parent_id'] == 0) && ($aInstall['name'] != 'datamodel'))
{
// Root module, what is its ID ?
$iId = (int) $aInstall['id'];
if ($iId > $iRootId) {
if ($iId > $iRootId)
{
$iRootId = $iId;
}
}
}
foreach ($aSelectInstall as $aInstall) {
foreach ($aSelectInstall as $aInstall)
{
//$aInstall['comment']; // unsused
$iInstalled = strtotime($aInstall['installed']);
$sModuleName = $aInstall['name'];
$sModuleVersion = $aInstall['version'];
if ($sModuleVersion == '') {
if ($sModuleVersion == '')
{
// Though the version cannot be empty in iTop 2.0, it used to be possible
// therefore we have to put something here or the module will not be considered
// as being installed
$sModuleVersion = '0.0.0';
}
if ($aInstall['parent_id'] == 0) {
if ($aInstall['parent_id'] == 0)
{
$sModuleName = ROOT_MODULE;
} elseif ($aInstall['parent_id'] != $iRootId) {
}
else if($aInstall['parent_id'] != $iRootId)
{
// Skip all modules belonging to previous installations
continue;
}
if (array_key_exists($sModuleName, $aInstallByModule)) {
if ($iInstalled < $aInstallByModule[$sModuleName]['installed']) {
if (array_key_exists($sModuleName, $aInstallByModule))
{
if ($iInstalled < $aInstallByModule[$sModuleName]['installed'])
{
continue;
}
}
if ($aInstall['parent_id'] == 0) {
if ($aInstall['parent_id'] == 0)
{
$aRes[$sModuleName]['version_db'] = $sModuleVersion;
$aRes[$sModuleName]['name_db'] = $aInstall['name'];
}
@@ -274,33 +306,37 @@ class RunTimeEnvironment
// Adjust the list of proposed modules
//
foreach ($aInstallByModule as $sModuleName => $aModuleDB) {
if ($sModuleName == ROOT_MODULE) {
continue;
} // Skip the main module
foreach ($aInstallByModule as $sModuleName => $aModuleDB)
{
if ($sModuleName == ROOT_MODULE) continue; // Skip the main module
if (!array_key_exists($sModuleName, $aRes)) {
if (!array_key_exists($sModuleName, $aRes))
{
// A module was installed, it is not proposed in the new build... skip
continue;
}
$aRes[$sModuleName]['version_db'] = $aModuleDB['version'];
if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY) {
$aRes[$sModuleName]['uninstall'] = [
if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY)
{
$aRes[$sModuleName]['uninstall'] = array(
'flag' => MODULE_ACTION_IMPOSSIBLE,
'message' => 'the module is part of the application',
];
} else {
$aRes[$sModuleName]['uninstall'] = [
'message' => 'the module is part of the application'
);
}
else
{
$aRes[$sModuleName]['uninstall'] = array(
'flag' => MODULE_ACTION_OPTIONAL,
'message' => '',
];
'message' => ''
);
}
}
return $aRes;
}
/**
* @param Config $oConfig
*
@@ -323,10 +359,10 @@ class RunTimeEnvironment
* Return an array with extra directories to scan for extensions/modules to install
* @return string[]
*/
protected function GetExtraDirsToScan($aDirs = [])
protected function GetExtraDirsToScan($aDirs = array())
{
// Do nothing, overload this method if needed
return [];
return array();
}
/**
@@ -345,22 +381,25 @@ class RunTimeEnvironment
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
{
$sSourceDirFull = APPROOT.$sSourceDir;
if (!is_dir($sSourceDirFull)) {
if (!is_dir($sSourceDirFull))
{
throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
}
$aDirsToCompile = [$sSourceDirFull];
if (is_dir(APPROOT.'extensions')) {
$aDirsToCompile = array($sSourceDirFull);
if (is_dir(APPROOT.'extensions'))
{
$aDirsToCompile[] = APPROOT.'extensions';
}
$sExtraDir = utils::GetDataPath().$this->sTargetEnv.'-modules/';
if (is_dir($sExtraDir)) {
if (is_dir($sExtraDir))
{
$aDirsToCompile[] = $sExtraDir;
}
$aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile);
$aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs);
$aRet = [];
$aRet = array();
// Determine the installed modules and extensions
//
@@ -373,10 +412,12 @@ class RunTimeEnvironment
// mark as (automatically) chosen alll the "remote" modules present in the
// target environment (data/<target-env>-modules)
// The actual choices will be recorded by RecordInstallation below
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv, $aExtraDirs);
$this->oExtensionsMap = new iTopExtensionsMap($this->sTargetEnv, true, $aExtraDirs);
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
if ($this->IsExtensionSelected($oExtension)) {
foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
{
if($this->IsExtensionSelected($oExtension))
{
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
}
}
@@ -388,23 +429,28 @@ class RunTimeEnvironment
$oFactory = new ModelFactory($aDirsToCompile);
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
if (file_exists($sDeltaFile)) {
if (file_exists($sDeltaFile))
{
$oCoreModule = new MFCoreModule('core', 'Core Module', $sDeltaFile);
$aRet[$oCoreModule->GetName()] = $oCoreModule;
}
$sDeltaFile = APPROOT.'application/datamodel.application.xml';
if (file_exists($sDeltaFile)) {
if (file_exists($sDeltaFile))
{
$oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile);
$aRet[$oApplicationModule->GetName()] = $oApplicationModule;
}
$aModules = $oFactory->FindModules();
foreach ($aModules as $oModule) {
foreach($aModules as $oModule)
{
$sModule = $oModule->GetName();
$sModuleRootDir = $oModule->GetRootDir();
$bIsExtra = $this->oExtensionsMap->ModuleIsChosenAsPartOfAnExtension($sModule, iTopExtension::SOURCE_REMOTE);
if (array_key_exists($sModule, $aAvailableModules)) {
if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) { //Extra modules are always unless they are 'AutoSelect'
if (array_key_exists($sModule, $aAvailableModules))
{
if (($aAvailableModules[$sModule]['version_db'] != '') || $bIsExtra && !$oModule->IsAutoSelect()) //Extra modules are always unless they are 'AutoSelect'
{
$aRet[$oModule->GetName()] = $oModule;
}
}
@@ -413,27 +459,33 @@ class RunTimeEnvironment
$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST);
// Now process the 'AutoSelect' modules
do {
do
{
// Loop while new modules are added...
$bModuleAdded = false;
foreach ($aModules as $oModule) {
if (!array_key_exists($oModule->GetName(), $aRet) && $oModule->IsAutoSelect()) {
foreach($aModules as $oModule)
{
if (!array_key_exists($oModule->GetName(), $aRet) && $oModule->IsAutoSelect())
{
SetupInfo::SetSelectedModules($aRet);
try {
try{
$bSelected = $oPhpExpressionEvaluator->ParseAndEvaluateBooleanExpression($oModule->GetAutoSelect());
if ($bSelected) {
if ($bSelected)
{
$aRet[$oModule->GetName()] = $oModule; // store the Id of the selected module
$bModuleAdded = true;
}
} catch (ModuleFileReaderException $e) {
} catch(ModuleFileReaderException $e){
//do nothing. logged already
}
}
}
} while ($bModuleAdded);
}
while($bModuleAdded);
$sDeltaFile = utils::GetDataPath().$this->sTargetEnv.'.delta.xml';
if (file_exists($sDeltaFile)) {
if (file_exists($sDeltaFile))
{
$oDelta = new MFDeltaModule($sDeltaFile);
$aRet[$oDelta->GetName()] = $oDelta;
}
@@ -462,8 +514,10 @@ class RunTimeEnvironment
//
$oFactory = new ModelFactory($sSourceDirFull);
$aModulesToCompile = $this->GetMFModulesToCompile($sSourceEnv, $sSourceDir);
foreach ($aModulesToCompile as $oModule) {
if ($oModule instanceof MFDeltaModule) {
foreach ($aModulesToCompile as $oModule)
{
if ($oModule instanceof MFDeltaModule)
{
// Just before loading the delta, let's save an image of the datamodel
// in case there is no delta the operation will be done after the end of the loop
$oFactory->SaveToFile(utils::GetDataPath().'datamodel-'.$this->sTargetEnv.'.xml');
@@ -471,6 +525,7 @@ class RunTimeEnvironment
$oFactory->LoadModule($oModule);
}
if ($oModule instanceof MFDeltaModule) {
// A delta was loaded, let's save a second copy of the datamodel
$oFactory->SaveToFile(utils::GetDataPath().'datamodel-'.$this->sTargetEnv.'-with-delta.xml');
@@ -505,32 +560,45 @@ class RunTimeEnvironment
*/
public function CreateDatabaseStructure(Config $oConfig, $sMode)
{
if (strlen($oConfig->Get('db_subname')) > 0) {
if (strlen($oConfig->Get('db_subname')) > 0)
{
$this->log_info("Creating the structure in '".$oConfig->Get('db_name')."' (table names prefixed by '".$oConfig->Get('db_subname')."').");
} else {
}
else
{
$this->log_info("Creating the structure in '".$oConfig->Get('db_name')."'.");
}
//MetaModel::CheckDefinitions();
if ($sMode == 'install') {
if (!MetaModel::DBExists(/* bMustBeComplete */ false)) {
MetaModel::DBCreate([$this, 'LogQueryCallback']);
if ($sMode == 'install')
{
if (!MetaModel::DBExists(/* bMustBeComplete */ false))
{
MetaModel::DBCreate(array($this, 'LogQueryCallback'));
$this->log_ok("Database structure successfully created.");
} else {
if (strlen($oConfig->Get('db_subname')) > 0) {
}
else
{
if (strlen($oConfig->Get('db_subname')) > 0)
{
throw new Exception("Error: found iTop tables into the database '".$oConfig->Get('db_name')."' (prefix: '".$oConfig->Get('db_subname')."'). Please, try selecting another database instance or specify another prefix to prevent conflicting table names.");
} else {
}
else
{
throw new Exception("Error: found iTop tables into the database '".$oConfig->Get('db_name')."'. Please, try selecting another database instance or specify a prefix to prevent conflicting table names.");
}
}
} else {
if (MetaModel::DBExists(/* bMustBeComplete */ false)) {
}
else
{
if (MetaModel::DBExists(/* bMustBeComplete */ false))
{
// Have it work fine even if the DB has been set in read-only mode for the users
// (fix copied from RunTimeEnvironment::RecordInstallation)
$iPrevAccessMode = $oConfig->Get('access_mode');
$oConfig->Set('access_mode', ACCESS_FULL);
MetaModel::DBCreate([$this, 'LogQueryCallback']);
MetaModel::DBCreate(array($this, 'LogQueryCallback'));
$this->log_ok("Database structure successfully updated.");
// Check (and update only if it seems needed) the hierarchical keys
@@ -558,10 +626,15 @@ class RunTimeEnvironment
// Restore the previous access mode
$oConfig->Set('access_mode', $iPrevAccessMode);
} else {
if (strlen($oConfig->Get('db_subname')) > 0) {
}
else
{
if (strlen($oConfig->Get('db_subname')) > 0)
{
throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->Get('db_name')."' (prefix: '".$oConfig->Get('db_subname')."'). Please, try selecting another database instance.");
} else {
}
else
{
throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->Get('db_name')."'. Please, try selecting another database instance.");
}
}
@@ -578,36 +651,46 @@ class RunTimeEnvironment
// Constant classes (e.g. User profiles)
//
foreach (MetaModel::GetClasses() as $sClass) {
$aPredefinedObjects = call_user_func([
foreach (MetaModel::GetClasses() as $sClass)
{
$aPredefinedObjects = call_user_func(array(
$sClass,
'GetPredefinedObjects',
]);
if ($aPredefinedObjects != null) {
$this->log_info("$sClass::GetPredefinedObjects() returned ".count($aPredefinedObjects)." elements.");
'GetPredefinedObjects'
));
if ($aPredefinedObjects != null)
{
$this->log_info("$sClass::GetPredefinedObjects() returned " . count($aPredefinedObjects) . " elements.");
// Create/Delete/Update objects of this class,
// according to the given constant values
//
$aDBIds = [];
$aDBIds = array();
$oAll = new DBObjectSet(new DBObjectSearch($sClass));
while ($oObj = $oAll->Fetch()) {
if (array_key_exists($oObj->GetKey(), $aPredefinedObjects)) {
while ($oObj = $oAll->Fetch())
{
if (array_key_exists($oObj->GetKey(), $aPredefinedObjects))
{
$aObjValues = $aPredefinedObjects[$oObj->GetKey()];
foreach ($aObjValues as $sAttCode => $value) {
foreach ($aObjValues as $sAttCode => $value)
{
$oObj->Set($sAttCode, $value);
}
$oObj->DBUpdate();
$aDBIds[$oObj->GetKey()] = true;
} else {
}
else
{
$oObj->DBDelete();
}
}
foreach ($aPredefinedObjects as $iRefId => $aObjValues) {
if (! array_key_exists($iRefId, $aDBIds)) {
foreach ($aPredefinedObjects as $iRefId => $aObjValues)
{
if (! array_key_exists($iRefId, $aDBIds))
{
$oNewObj = MetaModel::NewObject($sClass);
$oNewObj->SetKey($iRefId);
foreach ($aObjValues as $sAttCode => $value) {
foreach ($aObjValues as $sAttCode => $value)
{
$oNewObj->Set($sAttCode, $value);
}
$oNewObj->DBInsert();
@@ -627,20 +710,22 @@ class RunTimeEnvironment
MetaModel::GetConfig()->Set('access_mode', ACCESS_FULL);
//$oConfig->Set('access_mode', ACCESS_FULL);
if (CMDBSource::DBName() == '') {
if (CMDBSource::DBName() == '')
{
// In case this has not yet been done
CMDBSource::InitFromConfig($oConfig);
}
if ($sShortComment === null) {
if ($sShortComment === null)
{
$sShortComment = 'Done by the setup program';
}
$sMainComment = $sShortComment."\nBuilt on ".ITOP_BUILD_DATE;
// Record datamodel version
$aData = [
$aData = array(
'source_dir' => $oConfig->Get('source_dir'),
];
);
$iInstallationTime = time(); // Make sure that all modules record the same installation time
$oInstallRec = new ModuleInstallation();
$oInstallRec->Set('name', DATAMODEL_MODULE);
@@ -659,9 +744,10 @@ class RunTimeEnvironment
$oInstallRec->Set('installed', $iInstallationTime);
$iMainItopRecord = $oInstallRec->DBInsertNoReload();
// Record installed modules and extensions
//
$aAvailableExtensions = [];
$aAvailableExtensions = array();
$aAvailableModules = $this->AnalyzeInstallation($oConfig, $this->GetBuildDir());
foreach ($aSelectedModuleCodes as $sModuleId) {
if (!array_key_exists($sModuleId, $aAvailableModules)) {
@@ -671,7 +757,7 @@ class RunTimeEnvironment
$sName = $sModuleId;
$sVersion = $aModuleData['version_code'];
$sUninstallable = $aModuleData['uninstallable'] ?? 'yes';
$aComments = [];
$aComments = array();
$aComments[] = $sShortComment;
if ($aModuleData['mandatory']) {
$aComments[] = 'Mandatory';
@@ -702,24 +788,27 @@ class RunTimeEnvironment
$oInstallRec->DBInsertNoReload();
}
if ($this->oExtensionsMap) {
if ($this->oExtensionsMap)
{
// Mark as chosen the selected extensions code passed to us
// Note: some other extensions may already be marked as chosen
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
if (in_array($oExtension->sCode, $aSelectedExtensionCodes)) {
foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension)
{
if (in_array($oExtension->sCode, $aSelectedExtensionCodes))
{
$this->oExtensionsMap->MarkAsChosen($oExtension->sCode);
}
}
foreach ($this->oExtensionsMap->GetChoices() as $oExtension) {
foreach($this->oExtensionsMap->GetChoices() as $oExtension)
{
$oInstallRec = new ExtensionInstallation();
$oInstallRec->Set('code', $oExtension->sCode);
$oInstallRec->Set('label', $oExtension->sLabel);
$oInstallRec->Set('version', $oExtension->sVersion);
$oInstallRec->Set('source', $oExtension->sSource);
$oInstallRec->Set('source', $oExtension->sSource);
$oInstallRec->Set('uninstallable', $oExtension->CanBeUninstalled() ? 'yes' : 'no');
$oInstallRec->Set('installed', $iInstallationTime);
$oInstallRec->Set('description', $oExtension->sDescription);
$oInstallRec->DBInsertNoReload();
}
}
@@ -738,11 +827,14 @@ class RunTimeEnvironment
*/
public function GetApplicationVersion(Config $oConfig)
{
try {
try
{
CMDBSource::InitFromConfig($oConfig);
$sSQLQuery = "SELECT * FROM ".$oConfig->Get('db_subname')."priv_module_install";
$aSelectInstall = CMDBSource::QueryToArray($sSQLQuery);
} catch (MySQLException $e) {
}
catch (MySQLException $e)
{
// No database or erroneous information
$this->log_error('Can not connect to the database: host: '.$oConfig->Get('db_host').', user:'.$oConfig->Get('db_user').', pwd:'.$oConfig->Get('db_pwd').', db name:'.$oConfig->Get('db_name'));
$this->log_error('Exception '.$e->getMessage());
@@ -751,29 +843,37 @@ class RunTimeEnvironment
$aResult = [];
// Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version
foreach ($aSelectInstall as $aInstall) {
foreach ($aSelectInstall as $aInstall)
{
$sModuleVersion = $aInstall['version'];
if ($sModuleVersion == '') {
if ($sModuleVersion == '')
{
// Though the version cannot be empty in iTop 2.0, it used to be possible
// therefore we have to put something here or the module will not be considered
// as being installed
$sModuleVersion = '0.0.0';
}
if ($aInstall['parent_id'] == 0) {
if ($aInstall['name'] == DATAMODEL_MODULE) {
if ($aInstall['parent_id'] == 0)
{
if ($aInstall['name'] == DATAMODEL_MODULE)
{
$aResult['datamodel_version'] = $sModuleVersion;
$aComments = json_decode($aInstall['comment'], true);
if (is_array($aComments)) {
if (is_array($aComments))
{
$aResult = array_merge($aResult, $aComments);
}
} else {
}
else
{
$aResult['product_name'] = $aInstall['name'];
$aResult['product_version'] = $sModuleVersion;
}
}
}
if (!array_key_exists('datamodel_version', $aResult)) {
if (!array_key_exists('datamodel_version', $aResult))
{
// Versions prior to 2.0 did not record the version of the datamodel
// so assume that the datamodel version is equal to the application version
$aResult['datamodel_version'] = $aResult['product_version'];
@@ -784,8 +884,10 @@ class RunTimeEnvironment
public static function MakeDirSafe($sDir)
{
if (!is_dir($sDir)) {
if (!@mkdir($sDir)) {
if (!is_dir($sDir))
{
if (!@mkdir($sDir))
{
throw new Exception("Failed to create directory '$sDir', please check that the web server process has enough rights to create the directory.");
}
@chmod($sDir, 0770); // RWX for owner and group, nothing for others
@@ -824,7 +926,8 @@ class RunTimeEnvironment
{
$sSetupQueriesFilePath = SetupUtils::GetSetupQueriesFilePath();
$hSetupQueriesFile = @fopen($sSetupQueriesFilePath, 'a');
if ($hSetupQueriesFile !== false) {
if ($hSetupQueriesFile !== false)
{
fwrite($hSetupQueriesFile, "$sQuery\n");
fclose($hSetupQueriesFile);
}
@@ -833,9 +936,10 @@ class RunTimeEnvironment
public function GetCurrentDataModelVersion()
{
$oSearch = DBObjectSearch::FromOQL("SELECT ModuleInstallation WHERE name='".DATAMODEL_MODULE."'");
$oSet = new DBObjectSet($oSearch, ['installed' => false]);
$oSet = new DBObjectSet($oSearch, array('installed' => false));
$oLatestDM = $oSet->Fetch();
if ($oLatestDM == null) {
if ($oLatestDM == null)
{
return '0.0.0';
}
return $oLatestDM->Get('version');
@@ -843,9 +947,12 @@ class RunTimeEnvironment
public function Commit()
{
if ($this->sFinalEnv != $this->sTargetEnv) {
if (file_exists(utils::GetDataPath().$this->sTargetEnv.'.delta.xml')) {
if (file_exists(utils::GetDataPath().$this->sFinalEnv.'.delta.xml')) {
if ($this->sFinalEnv != $this->sTargetEnv)
{
if (file_exists(utils::GetDataPath().$this->sTargetEnv.'.delta.xml'))
{
if (file_exists(utils::GetDataPath().$this->sFinalEnv.'.delta.xml'))
{
// Make a "previous" file
copy(
utils::GetDataPath().$this->sFinalEnv.'.delta.xml',
@@ -906,24 +1013,34 @@ class RunTimeEnvironment
*/
protected function CommitFile($sSource, $sDest, $bSourceMustExist = true)
{
if (file_exists($sSource)) {
if (file_exists($sSource))
{
SetupUtils::builddir(dirname($sDest));
if (file_exists($sDest)) {
if (file_exists($sDest))
{
$bRes = @unlink($sDest);
if (!$bRes) {
if (!$bRes)
{
throw new Exception('Commit - Failed to cleanup destination file: '.$sDest);
}
}
rename($sSource, $sDest);
} else {
}
else
{
// The file does not exist
if ($bSourceMustExist) {
if ($bSourceMustExist)
{
throw new Exception('Commit - Missing file: '.$sSource);
} else {
}
else
{
// Align the destination with the source... make sure there is NO file
if (file_exists($sDest)) {
if (file_exists($sDest))
{
$bRes = @unlink($sDest);
if (!$bRes) {
if (!$bRes)
{
throw new Exception('Commit - Failed to cleanup destination file: '.$sDest);
}
}
@@ -942,15 +1059,22 @@ class RunTimeEnvironment
*/
protected function CommitDir($sSource, $sDest, $bSourceMustExist = true, $bRemoveSource = true)
{
if (file_exists($sSource)) {
if (file_exists($sSource))
{
SetupUtils::movedir($sSource, $sDest, $bRemoveSource);
} else {
}
else
{
// The file does not exist
if ($bSourceMustExist) {
if ($bSourceMustExist)
{
throw new Exception('Commit - Missing directory: '.$sSource);
} else {
}
else
{
// Align the destination with the source... make sure there is NO file
if (file_exists($sDest)) {
if (file_exists($sDest))
{
SetupUtils::rrmdir($sDest);
}
}
@@ -959,7 +1083,8 @@ class RunTimeEnvironment
public function Rollback()
{
if ($this->sFinalEnv != $this->sTargetEnv) {
if ($this->sFinalEnv != $this->sTargetEnv)
{
SetupUtils::tidydir(APPROOT.'env-'.$this->sTargetEnv);
}
}
@@ -973,8 +1098,10 @@ class RunTimeEnvironment
*/
public function CallInstallerHandlers($aAvailableModules, $aSelectedModules, $sHandlerName)
{
foreach ($aAvailableModules as $sModuleId => $aModule) {
if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules)) {
foreach($aAvailableModules as $sModuleId => $aModule)
{
if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules))
{
$aArgs = [MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code']];
RunTimeEnvironment::CallInstallerHandler($aAvailableModules[$sModuleId], $sHandlerName, $aArgs);
}
@@ -993,13 +1120,14 @@ class RunTimeEnvironment
public static function CallInstallerHandler(array $aModuleInfo, $sHandlerName, array $aArgs)
{
$sModuleInstallerClass = ModuleFileReader::GetInstance()->GetAndCheckModuleInstallerClass($aModuleInfo);
if (is_null($sModuleInstallerClass)) {
if (is_null($sModuleInstallerClass)){
return;
}
SetupLog::Debug("Calling Module Handler: $sModuleInstallerClass::$sHandlerName");
SetupLog::Info("Calling Module Handler: $sModuleInstallerClass::$sHandlerName", null, $aArgs);
$aCallSpec = [$sModuleInstallerClass, $sHandlerName];
if (is_callable($aCallSpec)) {
if (is_callable($aCallSpec))
{
try {
call_user_func_array($aCallSpec, $aArgs);
} catch (Exception $e) {
@@ -1032,8 +1160,8 @@ class RunTimeEnvironment
SetupLog::Info("starting data load session");
$oDataLoader->StartSession($oMyChange);
$aFiles = [];
$aPreviouslyLoadedFiles = [];
$aFiles = array();
$aPreviouslyLoadedFiles = array();
foreach ($aAvailableModules as $sModuleId => $aModule) {
if (($sModuleId != ROOT_MODULE)) {
$sRelativePath = 'env-'.$this->sTargetEnv.'/'.basename($aModule['root_dir']);
@@ -1044,15 +1172,22 @@ class RunTimeEnvironment
if ($bSampleData) {
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']);
} else {
}
else
{
// Load only structural data
$aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
}
} else {
if ($bSampleData) {
}
else
{
if ($bSampleData)
{
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']);
} else {
}
else
{
// Load only structural data
$aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']);
}
@@ -1064,10 +1199,12 @@ class RunTimeEnvironment
// Simulate the load of the previously loaded files, in order to initialize
// the mapping between the identifiers in the XML and the actual identifiers
// in the current database
foreach ($aPreviouslyLoadedFiles as $sFileRelativePath) {
foreach($aPreviouslyLoadedFiles as $sFileRelativePath)
{
$sFileName = APPROOT.$sFileRelativePath;
SetupLog::Info("Loading file: $sFileName (just to get the keys mapping)");
if (empty($sFileName) || !file_exists($sFileName)) {
if (empty($sFileName) || !file_exists($sFileName))
{
throw(new Exception("File $sFileName does not exist"));
}
@@ -1076,10 +1213,12 @@ class RunTimeEnvironment
SetupLog::Info($sResult);
}
foreach ($aFiles as $sFileRelativePath) {
foreach($aFiles as $sFileRelativePath)
{
$sFileName = APPROOT.$sFileRelativePath;
SetupLog::Info("Loading file: $sFileName");
if (empty($sFileName) || !file_exists($sFileName)) {
if (empty($sFileName) || !file_exists($sFileName))
{
throw(new Exception("File $sFileName does not exist"));
}
@@ -1101,8 +1240,9 @@ class RunTimeEnvironment
*/
protected static function MergeWithRelativeDir($aSourceArray, $sBaseDir, $aFilesToMerge)
{
$aToMerge = [];
foreach ($aFilesToMerge as $sFile) {
$aToMerge = array();
foreach($aFilesToMerge as $sFile)
{
$aToMerge[] = $sBaseDir.'/'.$sFile;
}
return array_merge($aSourceArray, $aToMerge);
@@ -1118,8 +1258,10 @@ class RunTimeEnvironment
{
$iCount = 0;
$fStart = microtime(true);
foreach (MetaModel::GetClasses() as $sClass) {
if (false == MetaModel::HasTable($sClass) && MetaModel::IsAbstract($sClass)) {
foreach(MetaModel::GetClasses() as $sClass)
{
if (false == MetaModel::HasTable($sClass) && MetaModel::IsAbstract($sClass))
{
//if a class is not persisted and is abstract, the code below would crash. Needed by the class AbstractRessource. This is tolerable to skip this because we check the setup process integrity, not the datamodel integrity.
continue;
}
@@ -1128,21 +1270,24 @@ class RunTimeEnvironment
$oSearch->SetShowObsoleteData(false);
$oSQLQuery = $oSearch->GetSQLQueryStructure(null, false);
$sViewName = MetaModel::DBGetView($sClass);
if (strlen($sViewName) > 64) {
if (strlen($sViewName) > 64)
{
throw new Exception("Class name too long for class: '$sClass'. The name of the corresponding view ($sViewName) would exceed MySQL's limit for the name of a table (64 characters).");
}
$sTableName = MetaModel::DBGetTable($sClass);
if (strlen($sTableName) > 64) {
if (strlen($sTableName) > 64)
{
throw new Exception("Table name too long for class: '$sClass'. The name of the corresponding MySQL table ($sTableName) would exceed MySQL's limit for the name of a table (64 characters).");
}
$iTableCount = $oSQLQuery->CountTables();
if ($iTableCount > 61) {
if ($iTableCount > 61)
{
throw new Exception("Class requiring too many tables: '$sClass'. The structure of the class ($sClass) would require a query with more than 61 JOINS (MySQL's limitation).");
}
$iCount++;
}
$fDuration = microtime(true) - $fStart;
return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration * 1000.0);
return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration*1000.0);
}
} // End of class

View File

@@ -79,7 +79,7 @@ class InstallationFileService
public function GetItopExtensionsMap(): ItopExtensionsMap
{
if (is_null($this->oItopExtensionsMap)) {
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment);
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment, true);
}
return $this->oItopExtensionsMap;
}

View File

@@ -210,12 +210,12 @@ HTML;
}
}
$oPage->LinkScriptFromAppRoot('setup/setup.js');
$oPage->add_script("function CanMoveForward()\n{\n".$oStep->JSCanMoveForward()."\n}\n");
$oPage->add_script("function CanMoveBackward()\n{\n".$oStep->JSCanMoveBackward()."\n}\n");
$oPage->add('<form id="wiz_form" class="ibo-setup--wizard" method="post">');
$oPage->add('<div class="ibo-setup--wizard--content">');
$oStep->Display($oPage);
$oPage->add('</div>');
$oPage->add_script("function CanMoveForward()\n{\n".$oStep->JSCanMoveForward()."\n}\n");
$oPage->add_script("function CanMoveBackward()\n{\n".$oStep->JSCanMoveBackward()."\n}\n");
// Add the back / next buttons and the hidden form
// to store the parameters

View File

@@ -1310,16 +1310,14 @@ EOF
*/
class WizStepModulesChoice extends WizardStep
{
protected static string $SEP = '_';
protected bool $bUpgrade = false;
protected bool $bCanMoveForward = true;
protected ?Config $oConfig = null;
protected static $SEP = '_';
protected $bUpgrade = false;
/**
*
* @var iTopExtensionsMap
*/
protected iTopExtensionsMap $oExtensionsMap;
protected $oExtensionsMap;
protected PhpExpressionEvaluator $oPhpExpressionEvaluator;
@@ -1327,7 +1325,7 @@ class WizStepModulesChoice extends WizardStep
* Whether we were able to load the choices from the database or not
* @var bool
*/
protected bool $bChoicesFromDatabase;
protected $bChoicesFromDatabase;
public function __construct(WizardController $oWizard, $sCurrentState)
{
@@ -1345,13 +1343,12 @@ class WizStepModulesChoice extends WizardStep
// only called if the config file exists : we are updating a previous installation !
// WARNING : we can't load this config directly, as it might be from another directory with a different approot_url (N°2684)
if ($sConfigPath !== null) {
$this->oConfig = new Config($sConfigPath);
$oConfig = new Config($sConfigPath);
$aParamValues = $oWizard->GetParamForConfigArray();
$this->oConfig->UpdateFromParams($aParamValues);
$oConfig->UpdateFromParams($aParamValues);
$this->oExtensionsMap->LoadChoicesFromDatabase($this->oConfig);
$this->bChoicesFromDatabase = true;
$this->bChoicesFromDatabase = $this->oExtensionsMap->LoadChoicesFromDatabase($oConfig);
}
}
@@ -1455,7 +1452,7 @@ class WizStepModulesChoice extends WizardStep
}
$oPage->add('<img src="'.$sBannerUrl.'"/>');
}
$sDescription = $aStepInfo['description'] ?? '';
$sDescription = isset($aStepInfo['description']) ? $aStepInfo['description'] : '';
$oPage->add('<span>'.$sDescription.'</span>');
$oPage->add('</div>');
@@ -1849,7 +1846,6 @@ EOF
}
return $index;
}
protected function GetStepInfo($idx = null)
{
$aStepInfo = null;
@@ -1870,12 +1866,12 @@ EOF
// Additional step for the "extensions"
$aStepDefinition = [
'title' => 'Extensions',
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions or remove installed ones.</h2>',
'description' => '<h2>Select additional extensions to install. You can launch the installation again to install new extensions, but you cannot remove already installed extensions.</h2>',
'banner' => '/images/icons/icons8-puzzle.svg',
'options' => [],
];
foreach ($this->oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) {
foreach ($this->oExtensionsMap->GetAllExtensions() as $oExtension) {
if (($oExtension->sSource !== iTopExtension::SOURCE_WIZARD) && ($oExtension->bVisible) && (count($oExtension->aMissingDependencies) == 0)) {
$aStepDefinition['options'][] = [
'extension_code' => $oExtension->sCode,
@@ -1886,19 +1882,16 @@ EOF
'modules' => $oExtension->aModules,
'mandatory' => $oExtension->bMandatory || ($oExtension->sSource === iTopExtension::SOURCE_REMOTE),
'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource),
'uninstallable' => $oExtension->CanBeUninstalled(),
'missing' => $oExtension->bRemovedFromDisk,
];
}
}
// Display this step of the wizard only if there is something to display
if (count($aStepDefinition['options']) !== 0) {
$aSteps[] = $aStepDefinition;
$this->oWizard->SetParameter('additional_extensions_modules', json_encode($aStepDefinition['options']));
}
} else {
// No wizard configuration provided, build a standard one with just one big list. All items are mandatory, only works when there are no conflicted modules.
// No wizard configuration provided, build a standard one with just one big list
$aStepDefinition = [
'title' => 'Modules Selection',
'description' => '<h2>Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.</h2>',
@@ -1965,41 +1958,18 @@ EOF
$sId = utils::EscapeHtml($aChoice['extension_code']);
$bIsDefault = array_key_exists($sChoiceId, $aDefaults);
$oITopExtension = $this->oExtensionsMap->GetFromExtensionCode($aChoice['extension_code']);
$bCanBeUninstalled = isset($aChoice['uninstallable']) ? $aChoice['uninstallable'] : $oITopExtension->CanBeUninstalled();
$bCanBeUninstalled = $this->oExtensionsMap->Get($aChoice['extension_code'])->CanBeUninstalled();
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId);
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || $this->bUpgrade && $bIsDefault && !$bCanBeUninstalled && !$bDisableUninstallCheck;
;
$bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true;
$bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled;
$bDisabled = $bMandatory || $bAllDisabled || $bMissingFromDisk;
$bDisabled = $bMandatory || $bAllDisabled;
$bChecked = $bMandatory || $bSelected;
$sTooltip = '';
$sUnremovable = '';
if ($bMissingFromDisk) {
$sTooltip .= '<span class="setup-extension-tag removed">source removed</span>';
}
if ($bInstalled) {
$sTooltip .= '<span class="setup-extension-tag checked installed">installed</span>';
$sTooltip .= '<span class="setup-extension-tag unchecked tobeuninstalled">to be uninstalled</span>';
} else {
$sTooltip .= '<span class="setup-extension-tag checked tobeinstalled">to be installed</span>';
$sTooltip .= '<span class="setup-extension-tag unchecked notinstalled">not installed</span>';
}
if (!$bCanBeUninstalled) {
$sTooltip .= '<span class="setup-extension-tag notuninstallable">cannot be uninstalled</span>';
}
if ($bDisabled && !$bChecked && !$bCanBeUninstalled && !$bDisableUninstallCheck) {
$this->bCanMoveForward = false;//Disable "Next"
}
$sChecked = $bChecked ? ' checked ' : '';
$sDisabled = $bDisabled ? ' disabled data-disabled="disabled" ' : '';
$sMissingModule = $bMissingFromDisk ? 'setup-extension--missing' : '';
$sUnremovable = !$bCanBeUninstalled ? ' unremovable ' : '';
$sHiddenInput = $bDisabled && $bChecked ? '<input type="hidden" name="choice['.$sChoiceId.']" value="'.$sChoiceId.'"/>' : '';
$oPage->add('<div class="choice '.$sMissingModule.'" '.$sDataId.'><input class="wiz-choice '.$sUnremovable.'" id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'" '.$sDisabled.$sChecked.'/>'.$sHiddenInput.'&nbsp;');
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled, $sTooltip);
$oPage->add('<div class="choice" '.$sDataId.'><input class="wiz-choice '.$sUnremovable.'" id="'.$sId.'" name="choice['.$sChoiceId.']" type="checkbox" value="'.$sChoiceId.'" '.$sDisabled.$sChecked.'/>'.$sHiddenInput.'&nbsp;');
$this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled, $bCanBeUninstalled);
$oPage->add('</div>');
}
$sChoiceName = null;
@@ -2060,13 +2030,14 @@ EOF
}
}
protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false, $sTooltip = '')
protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled = false, $bUninstallable = true)
{
$sMoreInfo = (isset($aChoice['more_info']) && ($aChoice['more_info'] != '')) ? '<a class="setup--wizard-choice--more-info" target="_blank" href="'.$aChoice['more_info'].'">More information</a>' : '';
$sSourceLabel = $aChoice['source_label'] ?? '';
$sSourceLabel = isset($aChoice['source_label']) ? $aChoice['source_label'] : '';
$sId = utils::EscapeHtml($aChoice['extension_code']);
$sUninstallationWarning = $bUninstallable ? '' : '<span style="color:orangered" title="Once this extension has been installed, it cannot be removed">(!)</span>';
$oPage->add('<label class="setup--wizard-choice--label" for="'.$sId.'">'.$sSourceLabel.'<b>'.utils::EscapeHtml($aChoice['title']).'</b>'.'&nbsp;'.$sTooltip.'</label> '.$sMoreInfo.'');
$oPage->add('<label class="setup--wizard-choice--label" for="'.$sId.'">'.$sSourceLabel.'<b>'.utils::EscapeHtml($aChoice['title']).'</b>'.'</label>&nbsp;'.$sUninstallationWarning.' '.$sMoreInfo.'');
$sDescription = isset($aChoice['description']) ? utils::EscapeHtml($aChoice['description']) : '';
$oPage->add('<div class="setup--wizard-choice--description description">'.$sDescription.'<span id="sub_choices'.$sId.'">');
if (isset($aChoice['sub_options'])) {
@@ -2081,22 +2052,6 @@ EOF
return $sSourceDir.'/installation.xml';
}
public function CanMoveForward()
{
return true;
}
public function JSCanMoveForward()
{
return $this->bCanMoveForward ? 'return true;' : 'return false;';
}
public function GetNextButtonLabel()
{
return $this->bCanMoveForward ? 'Next' : 'Non-uninstallable extension missing';
}
}
/**

View File

@@ -15,6 +15,15 @@ class TurboForm extends UIContentBlock
// Overloaded constants
public const BLOCK_CODE = 'ibo-form';
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/turbo-form/layout';
public const DEFAULT_JS_MODULES_CONFIG = [
[
'sModuleName' => 'session',
'sJsModuleSrc' => '/node_modules/@hotwired/turbo/dist/turbo.es2017-esm.js',
'sModuleConfigJs' => <<<JS
session.drive = false;
JS
],
];
/** @var string|null */
protected ?string $sOnSubmitJsCode;

View File

@@ -65,6 +65,11 @@ abstract class UIBlock implements iUIBlock
public const DEFAULT_JS_FILES_REL_PATH = [
'js/ui-block.js',
];
/**
* @var array
* @see static::$aJsModulesConfig
*/
public const DEFAULT_JS_MODULES_CONFIG = [];
/**
* @var string|null Relative path (from <ITOP>/templates/) to the "on init" JS template
* @see static::$aJsTemplatesRelPath
@@ -138,6 +143,11 @@ abstract class UIBlock implements iUIBlock
* and not in {@see static::DEFAULT_JS_TEMPLATE_REL_PATH} ! Indeed the later is output before external files loading.
*/
protected $aJsFilesRelPath = [];
/**
* @var array Relative paths (from <ITOP>/) to the external JS module files to include in the page.
* @description Used to include JS file that contains modules + the JS code assocciated to these modules. Even if you use only a few modules, the whole JS file will be loaded.
*/
protected $aJsModulesConfig = [];
/**
* @var array
* @see iUIBlock::GetCssFilesRelPaths()
@@ -182,6 +192,9 @@ abstract class UIBlock implements iUIBlock
$this->AddMultipleJsFilesRelPaths(static::DEFAULT_JS_FILES_REL_PATH);
}
$this->AddMultipleJsModulesFilesRelPaths(static::DEFAULT_JS_MODULES_CONFIG);
// Add external CSS files
// 1) From ancestors if they are required
if (static::REQUIRES_ANCESTORS_DEFAULT_CSS_FILES) {
@@ -253,6 +266,15 @@ abstract class UIBlock implements iUIBlock
return $this->aJsFilesRelPath;
}
/**
* @inheritDoc
*/
public function GetJsModuleConfigs(): array
{
return $this->aJsModulesConfig;
}
/**
* @inheritDoc
*/
@@ -316,6 +338,31 @@ abstract class UIBlock implements iUIBlock
return $this->GetFilesUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_JS, $bAbsoluteUrl);
}
/**
* @inheritDoc
* @throws \Exception
*/
public function GetJsModulesRecursively(bool $bAbsoluteUrl = false): array
{
$aJsModulesConfigs = [];
// Files from the block itself
foreach ($this->GetJsModuleConfigs() as $sJsCurrentModuleBlockConfig) {
$aJsModulesConfigs[] = $sJsCurrentModuleBlockConfig;
}
// Files from its sub blocks
foreach ($this->GetSubBlocks() as $sSubBlockName => $oSubBlock) {
/** @noinspection SlowArrayOperationsInLoopInspection */
$aJsModulesConfigs = array_merge(
$aJsModulesConfigs,
$oSubBlock->GetJsModulesRecursively($bAbsoluteUrl)
);
}
return $aJsModulesConfigs;
}
/**
* @inheritDoc
* @throws \Exception
@@ -354,6 +401,16 @@ abstract class UIBlock implements iUIBlock
return $this;
}
/**
* @inheritDoc
*/
public function AddJsModuleConfigs(array $aModuleConfig)
{
$this->aJsModulesConfig[] = $aModuleConfig;
return $this;
}
/**
* @inheritDoc
*/
@@ -366,6 +423,18 @@ abstract class UIBlock implements iUIBlock
return $this;
}
/**
* @inheritDoc
*/
public function AddMultipleJsModulesFilesRelPaths(array $aModulesConfig)
{
foreach ($aModulesConfig as $aModuleConfig) {
$this->AddJsModuleConfigs($aModuleConfig);
}
return $this;
}
/**
* @inheritDoc
*/

View File

@@ -135,6 +135,15 @@ interface iUIBlock
*/
public function AddJsFileRelPath(string $sPath);
/**
* Add a JS module file (whole files inclusion + module configuration) to a block
*
* @param array $aModuleConfig relative path of a JS file to add
*
* @return $this
*/
public function AddJsModuleConfigs(array $aModuleConfig);
/**
* Add several JS files to a block.
* Duplicates will not be added.
@@ -145,6 +154,15 @@ interface iUIBlock
*/
public function AddMultipleJsFilesRelPaths(array $aPaths);
/**
* Add several JS modules files to a block.
*
* @param string[] $aModulesConfig
*
* @return mixed
*/
public function AddMultipleJsModulesFilesRelPaths(array $aModulesConfig);
/**
* Add a CSS file to a block (if not already present)
*

View File

@@ -215,6 +215,7 @@ class AjaxPage extends WebPage implements iTabbedPage
'aCssFiles' => $this->a_linked_stylesheets,
'aCssInline' => $this->a_styles,
'aJsFiles' => $this->a_linked_scripts,
'aJsFilesModules' => $this->a_linked_modules_config,
'aJsInlineLive' => $this->a_scripts,
'aJsInlineOnDomReady' => $this->GetReadyScripts(),
'aJsInlineOnInit' => $this->a_init_scripts,

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