Compare commits

...

382 Commits

Author SHA1 Message Date
odain
18dae7b94a N°8806 - code cleanup + test cover 2026-04-10 11:08:53 +02:00
odain
2adfd8552a N°8806 - add log when loading MFModule 2026-04-09 16:31:44 +02:00
odain
3e57765607 N°8806 - improve GetMFModulesToCompile to handle extensions and installation choices on all setup that use compileFrom (MTP, CoreUpdate, Hub, DryRemoval) 2026-04-09 14:13:16 +02:00
odain
20e0d2dec6 N°8806 - fix tests 2026-04-09 07:53:52 +02:00
odain
fcb497f9a6 N°8806 - Installation multiple extension with dependence via ITSM Designer 2026-04-09 07:53:52 +02:00
odain
b133f52d83 fix merge
fix merge
2026-04-08 21:10:32 +02:00
odain
e28addfd6d Merge branch 'develop' into odain 2026-04-08 20:24:22 +02:00
odain
2ec0931896 N°8955 - fix Implicitly... again 2026-04-08 20:18:52 +02:00
odain
ca30645d36 N°9144 - fix rebase 2026-04-08 16:03:15 +02:00
odain
bd14a84677 N°8955 - fix Implicitly marking parameter as nullable is deprecated 2026-04-08 15:26:11 +02:00
odain
ad0234a94f N°9144 - setup wizard sequencers test coverage + fixes
N°9144 - ci: fix hub setup test

N°9144 - code style

N°9144 - tests and fixes

N°9144 - ci: fix tests and code style
2026-04-08 15:26:09 +02:00
odain
611f1e66f2 N°9144 - ci revert ITOP_DEFAULT_ENV 2026-04-08 15:24:47 +02:00
odain
82a20c54a9 N°9144 - fix both UI setup wizard and CLI unattended + replace production by ITOP_DEFAULT_ENV 2026-04-08 15:24:42 +02:00
Eric Espie
d36f68e3b0 N°9144 - Fix sessions 2026-04-08 15:24:40 +02:00
odain
9ca0dd643f N°9144 - fix fresh install 2026-04-08 15:24:37 +02:00
odain
e27aca728c N°9144 - WIP: fix unattended 2026-04-08 15:24:35 +02:00
odain
c3824f421b N°9144 - bypass audit in setup wizard sometimes 2026-04-08 15:24:32 +02:00
Eric Espie
ecc07f4575 N°9144 - WIP symbolic links 2026-04-08 15:24:29 +02:00
Eric Espie
bcf2123df0 N°9144 - Fix unattended install 2026-04-08 15:24:27 +02:00
Eric Espie
8dca03f557 N°9144 - Fix runtime env 2026-04-08 15:24:24 +02:00
Eric Espie
2129007512 N°9144 - Setup sequencer side B 2026-04-08 15:24:22 +02:00
Eric Espie
14d01b89a7 N°9144 - Setup sequencer side A 2026-04-08 15:24:12 +02:00
Eric Espie
e2bda81414 N°9144 - Split setup in 2 different step - refactor RuntimeEnv 2026-04-08 15:20:52 +02:00
Eric Espie
6b7d306006 N°9409 - Fix CI 2026-04-08 15:20:49 +02:00
Eric Espie
40feabb866 N°9409 - Fix CI 2026-04-08 15:20:46 +02:00
Eric Espie
e17eaa7430 N°9409 - Fix CI 2026-04-08 15:20:44 +02:00
Eric Espie
deeee08e8c N°9409 - Designer 3.3.0 - PHPStan level 2 2026-04-08 15:20:42 +02:00
Molkobain
b7b01c94e7 N°9161 - Fix unit test which missed a markup change 2026-04-08 15:20:42 +02:00
odain
ba29a41d98 N°9409 - Designer 3.3.0: be able to extend nstallationChoicesToModuleConverter 2026-04-08 15:20:39 +02:00
Eric Espie
073d4f15f5 N°9409 - Designer 3.3.0 - PHPStan level 1 2026-04-08 15:20:36 +02:00
Eric Espie
91915f001b N°9409 - Designer 3.3.0 - PHPStan level 1 wip 2026-04-08 15:20:34 +02:00
Molkobain
9b7c7c0b48 N°9161 - Change UIBlock badges markup from span to div to improve E2E testing 2026-04-08 15:20:34 +02:00
Eric Espie
85b20f34a3 N°9409 - Designer 3.3.0 - edit dashboard fix 2026-04-08 15:20:31 +02:00
odain
7a153c3698 N°8760 - fix setup/cleanup audit on legacy 1.x packages without any
installation.xml
2026-04-08 15:19:34 +02:00
Eric Espie
1be0e61971 N°9409 - Designer 3.3.0 2026-04-08 15:19:32 +02:00
Eric Espie
d53433e62d N°9409 - Designer 3.3.0 2026-04-08 15:19:29 +02:00
Eric Espie
51f4dd7948 N°9409 - Designer 3.3.0 2026-04-08 15:19:25 +02:00
Eric Espie
d2384bb274 N°9409 - Create a package designer 3.3.0 - PHP 8.4 2026-04-08 15:19:16 +02:00
Eric Espie
cedc1ec711 N°9370 - autoload 2026-04-08 15:18:24 +02:00
odain
9cb2d78b48 PR review - move Prepare in DryRemovalRuntimeEnvironment constr 2026-04-08 15:18:24 +02:00
odain
9c32e29e77 code style 2026-04-08 15:18:24 +02:00
odain
c6dd16549c N°9370 - use env-production-build in setup audit sequencer (no more dry-production) 2026-04-08 15:18:24 +02:00
odain
f2b15554bb N°9370 - use env-production-build for dry removal audit
wip
2026-04-08 15:18:24 +02:00
Stephen Abello
4afe06d370 Merge branch 'support/3.2' into develop
# Conflicts:
#	core/config.class.inc.php
2026-04-08 14:21:51 +02:00
Stephen Abello
9dc3c56689 N°9448 - Fix external auth variable value (#871) 2026-04-08 14:19:24 +02:00
Stephen Abello
effd35c3e6 N°8758 - Fix mandatory caselog in transition requiring double confirmation (#868) 2026-04-08 14:19:09 +02:00
lenaick.moreira
587d7fcf72 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-04-08 10:30:35 +02:00
lenaick.moreira
f1735767c3 💚 Fix CI 2026-04-08 10:19:31 +02:00
lenaick.moreira
bfdc8a68e6 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-04-08 09:49:34 +02:00
lenaick.moreira
00735f0c54 N°9483 - The search suggestion message is not displaying correctly 2026-04-08 09:48:15 +02:00
lenaick.moreira
882390e8d6 N°9043 - Fix automatic search on direct linkset block 2026-04-08 09:48:15 +02:00
Lenaick
5d0da47f21 N°8178 - Respect "high_cardinality_classes" parameter on search operation (#870) 2026-04-08 09:47:46 +02:00
lenaick.moreira
684eaac278 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-04-08 09:43:20 +02:00
lenaick.moreira
4eadff7f3b ♻️ Refactor BODY_DATA_GUI_TYPE constants to public visibility (N°9101) 2026-04-08 09:38:40 +02:00
Molkobain
4ce8f70382 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-04-07 21:10:09 +02:00
Molkobain
f66ce1c956 Fix cURL calls to iTop within Docker dev. environment with PHP version autodetection (#869) 2026-04-07 19:06:30 +02:00
jf-cbd
868eea9b43 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-04-07 17:38:21 +02:00
jf-cbd
802f9f3e08 N°8576 - [RequestTemplate] Slowness during the selection of drop-down list field (#872) 2026-04-07 17:06:21 +02:00
v-dumas
70a8bdf427 N°9450 - Add Tape, NAS file System, Fiber Channel Interface in overview dashboard 2026-04-03 09:43:30 +02:00
Vincent Dumas
38cda442e3 N°9138 - Presentation details step2 + move sample data (#866)
N°9471 - Rename in FR Rack as Baie - Switch Baie and Chassis icons
2026-04-02 18:23:11 +02:00
Vincent Dumas
20c8b7c0ce N°9137 logo by network device type (#864)
Add Logo on NetworkDeviceType used as object icon on all NetworkDevice of a given type
Add Brand, OSFamily and NetworkDeviceType icons as dashlet ressources
2026-04-02 17:23:08 +02:00
lenaick.moreira
b58a848cc9 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-04-01 16:25:36 +02:00
lenaick.moreira
5c5d98bb78 Enable the retrieval of all data for event notifications in the activity panel 2026-04-01 16:12:36 +02:00
Stephen Abello
a08a9b43f3 N°8737 - Fix search criteria dropdown vertical overflow (#867)
* N°8737 - Fix search criteria dropdown vertical overflow

* Update css/backoffice/components/_search-form.scss
2026-04-01 14:57:10 +02:00
jf-cbd
d9433fead5 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-04-01 10:57:23 +02:00
jf-cbd
abd85ff4db Merge remote-tracking branch 'origin/support/3.2' into support/3.2 2026-04-01 10:37:59 +02:00
jf-cbd
81f328b26e N°8543 - rename security parameter with new convention 2026-04-01 10:37:46 +02:00
lenaick.moreira
9a2c8f10bf Fix CliResetSessionTest by allowing empty POST fields to be defined in the curl options 2026-04-01 10:35:59 +02:00
jf-cbd
1a9e4bd5ad Reformat ormDocumentTest.php 2026-03-31 17:51:06 +02:00
Lenaick
3cdadf3c6e Add tests for lock acquisition functionality (#865) 2026-03-31 17:01:22 +02:00
jf-cbd
36891e441b Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	datamodels/2.x/itop-hub-connector/ajax.php
2026-03-31 16:43:48 +02:00
jf-cbd
a6295f1b14 Duplicate try-catch to avoid finally statement in case of uncorrrecth auth 2026-03-31 16:39:05 +02:00
Stephen Abello
ba6b3da238 N°9231 - Fix unit test for 3.3.0 2026-03-31 16:28:19 +02:00
Stephen Abello
2674e9c47f Merge branch 'support/3.2' into develop
# Conflicts:
#	tests/php-unit-tests/unitary-tests/core/ormDocumentTest.php
2026-03-31 15:51:27 +02:00
Stephen Abello
e467ca83cf N°8532 - Apply filters on all DBSearch classes (#848)
Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>
2026-03-31 15:41:28 +02:00
Anne-Cath
f7b73717b4 Rollback N°2364 - API : remove old linkedset persistance 2026-03-31 10:37:36 +02:00
v-dumas
0d9b34a879 N°9063 - Add ev_autoresolve on Approved UserRequest in Simple Ticket 2026-03-30 17:13:14 +02:00
Stephen Abello
7791585387 N°9231 - Make OrmDocument apply same safety to attachments and regular documents (#860) 2026-03-30 15:25:52 +02:00
v-dumas
a4a0b3c18c Merge remote-tracking branch 'origin/support/3.2' into develop 2026-03-30 14:31:02 +02:00
Molkobain
3406ca79de N°9361 - Update PHPDoc 2026-03-30 13:49:30 +02:00
Stephen Abello
91ad01055e N°5228 - Allow themes variable imports to be overloaded by variable entries (#858) 2026-03-27 15:46:15 +01:00
v-dumas
443fa60459 N°8515 - Access rights & relation labels for container classes 2026-03-27 15:19:33 +01:00
Lenaick
804cdffe42 N°8234 - Fix permission checks to conditionally allow display of unauthorized objects (#859) 2026-03-27 15:09:31 +01:00
v-dumas
63e40fc0a2 N°9347 - Make the icon of ContainerImages using the one of Software class 2026-03-27 14:17:41 +01:00
v-dumas
176bd51afb N°8515 - Add ContainerImages tab on Software class 2026-03-27 11:35:18 +01:00
v-dumas
e8a1f8c3b6 N°8994 - Classes description fix (FR, EN) 2026-03-27 10:25:35 +01:00
v-dumas
5f4affc896 N°9057 - Fix tests broken due to ModuleInstallation given grant_by_profile (2) 2026-03-26 17:56:52 +01:00
v-dumas
042fee2360 N°9057 - Fix tests broken due to ModuleInstallation given grant_by_profile 2026-03-26 17:37:18 +01:00
v-dumas
447e7d01d5 N°9063 - Fix testEnumTransition as ev_autoresolve is no more available in resolve state 2026-03-26 17:02:50 +01:00
lenaick.moreira
d4a9d1e831 N°8834 - Updated the PHP version to 8.4 for the CI in the commit 2026-03-26 15:59:52 +01:00
lenaick.moreira
be3726623f Refactor constructor in AttributeExist to require sOqlPropertyPath parameter
* https://github.com/Combodo/iTop/pull/857#discussion_r2993953031
2026-03-26 15:57:39 +01:00
Lenaick
aae6d324f9 N°8851 - Explicit nullable in functions parameters (#857) 2026-03-26 15:52:57 +01:00
Vincent Dumas
398b47d5d1 N°9356 Improve service classes tooltips (#849) 2026-03-26 15:30:47 +01:00
Vincent Dumas
b85f4a5161 N°9086 - Add uniqueness rules on OSFamily, OSVersion and IOSVersion (#794) 2026-03-26 12:20:07 +01:00
Vincent Dumas
3c17b1bdea N°9063 - Add missing ev_autoresolve transitions on UserRequest and Incident (#798) 2026-03-26 12:14:41 +01:00
Vincent Dumas
7f8ec25977 N°9057 - Enable SuperUser to execute collectors (#799) 2026-03-26 12:13:45 +01:00
Lenaick
41f8437c23 N°8234 - Allow display of unauthorized objects in notifications and event queries (#853)
* N°8234 - Allow display of unauthorized objects in notifications and event queries

* Refactor EventNotificationNewsroom class usage in iTopNewsroomController
2026-03-26 11:44:06 +01:00
Anne-Catherine
df8b25d4b4 N°9223 - Portal - AttributeExternalKey or AttributeEnum are not displayed after adding a link. (#802) 2026-03-26 10:34:41 +01:00
Lenaick
9cc80a8946 Temporarily rollback the PHP version to 8.3 for the CI in the commit 2026-03-25 18:02:20 +01:00
lenaick.moreira
11afd23087 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-03-25 17:56:19 +01:00
Lenaick
511dabe2b0 N°8834 - Updated the PHP version to 8.4 for the CI in the commit 2026-03-25 17:43:39 +01:00
Lenaick
0c517f254c N°9101 - Improve HTML markup for end-to-end tests automation (#856) 2026-03-25 17:20:28 +01:00
Stephen Abello
347663f8f7 Merge branch 'support/3.2' into develop
# Conflicts:
#	composer.json
#	composer.lock
#	core/attributedef.class.inc.php
#	sources/Controller/AjaxRenderController.php
2026-03-25 10:30:30 +01:00
Stephen Abello
c56c7a1f9d Fix CI by fixing code style 2026-03-25 10:25:37 +01:00
v-dumas
3d21ff1cd7 Fix SK dico misaligned 2026-03-25 10:20:00 +01:00
Stephen Abello
fb2f0f1447 N°9328 - Add scssphp compatibility with PHP 8.4 (#851) 2026-03-25 09:58:05 +01:00
v-dumas
4847bb563a N°8958 - Fix class name in french 2026-03-24 17:10:50 +01:00
Vincent Dumas
8b664ff644 N°8958 - Fix FR class names, add EN lnk descriptions (#852) 2026-03-24 15:40:10 +01:00
v-dumas
902a05cc84 N°3961 - Service Catalog: fix icons and dico entries 2026-03-24 15:33:36 +01:00
Lenaick
b3223eb9b6 N°8606 - Check user permissions in search operation of ajax.render.php (#836) 2026-03-24 08:52:22 +01:00
v-dumas
19d6052460 Merge remote-tracking branch 'origin/develop' into develop 2026-03-23 18:20:36 +01:00
v-dumas
70eaa30e10 N°8515 - Container SummaryCards + containerhosts_list on Server and VM 2026-03-23 18:19:49 +01:00
Benjamin DALSASS
458a996c29 N°8612 - force authentication for inline image endpoints
- ajax.render dict route needs to be reached without login authentication
2026-03-23 15:50:47 +01:00
Anne-Cath
a6de103b4a N°8692 - Notification: Fix AttributeSubitem placeholder behavior - fix for 3.3 2026-03-23 15:45:09 +01:00
Anne-Cath
9328f6d916 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	core/attributedef.class.inc.php
2026-03-23 15:39:25 +01:00
Anne-Catherine
c61b21559c N°8692 - Notification - placeholder attributesubitem (#778) 2026-03-23 15:33:50 +01:00
v-dumas
0d18572fbe N°9357 - Hide in User Portal, Service Subcategory in implementation when in full ITIL 2026-03-23 13:44:52 +01:00
jf-cbd
9db21ab860 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-03-20 16:32:59 +01:00
jf-cbd
ed33238750 Merge remote-tracking branch 'origin/support/3.2' into support/3.2 2026-03-20 16:31:44 +01:00
jf-cbd
272678b8cd N°9361 - Indicate to itop admin concerned by 8543 that changes could be required in extension 2026-03-20 16:30:53 +01:00
Benjamin DALSASS
3a435eba7d Merge remote-tracking branch 'origin/support/3.2' into develop 2026-03-20 14:37:01 +01:00
Benjamin Dalsass
170014e8f0 N°9232 - Information Disclosure (#850) 2026-03-20 14:35:05 +01:00
Stephen Abello
df05a4688e Merge branch 'support/3.2' into develop
# Conflicts:
#	js/searchformforeignkeys.js
2026-03-19 09:24:10 +01:00
Molkobain
006f666089 N°8554 - Fix impossible installation of portal new look via iTop Hub (#846) 2026-03-18 19:59:42 +01:00
v-dumas
960990d47d N°8994 - fix duplicate key in EN dico 2026-03-18 17:42:15 +01:00
Vincent Dumas
99d39732a5 N°8994 - Add description on all iTop classes (#789)
* N°8994 - Add description on all iTop classes 

---------

Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>
2026-03-18 17:16:13 +01:00
Vincent Dumas
5e916510fb N°9138 - Add semantic logo on some CMDB classes (#841)
* N°9138 - Align presentation details of CMDB classes
* N°9138 - Align presentation details of Software classes
* N°9138 - fix greptile feedbacks
* N°9347 - Enable icon of some CIs by instance
* N°9347 - Get Software logo as SoftwareInstance icon
* N°9347 - Change Cloud class icon location
* N°9347 - Fix logo dictionary entries missing
* N°9347 - Fix 'impact analysis' spelling
* N°9347 - fix external key labels for PhysicalInterface
2026-03-18 16:26:08 +01:00
Stephen Abello
2a16143e53 N°9229 - Modernize search foreign keys code with built in JS tools (#847)
* N°9229 - Modernize search foreign keys code with built in JS tools

* N°9229 - Allow modals to have button id specified

* N°9229 - Remove the modal instead of only destroying it

* N°9229 - Remove dead code

* Update js/searchformforeignkeys.js

* Add robustness to modals button id
2026-03-18 15:23:52 +01:00
Eric Espie
358c4383f8 Add explanation messages 2026-03-18 14:16:55 +01:00
Eric Espie
890db04fa3 Fix CI 2026-03-18 14:16:54 +01:00
Eric Espie
e1e2da2881 N°8761 - Assist in cleaning up data prior to uninstalling extensions 2026-03-18 14:16:54 +01:00
Eric Espie
cb47ea4316 N°8761 - Assist in cleaning up data prior to uninstalling extensions 2026-03-18 14:16:54 +01:00
Eric Espié
a3e8abe520 N°8761 - Assist in cleaning up data prior to uninstalling extensions (#838)
* N°8761 - Assist in cleaning up data prior to uninstalling extensions - handle transaction ID + add deletion plan screen

* N°8761 - poc of deletion plan screen

* code style

* N°8761 - WIP deletion plan execution

* Delete all parent classes objects + cleanup

* 🌐 translation (EN only)

* remove history

* In case of no leaf class to remove, delete also the child classes

* 🎨 refactor & fix typo

* Analysis not stored anymore in DB

* Analysis for removed modules

* 🌐 dico

* Add spinner to setup button "Go to backoffice"

* Fix count after PR review

* Fix after PR review

* Fix Number of elements to remove

* Fix arrays

---------

Co-authored-by: odain <olivier.dain@combodo.com>
2026-03-18 14:16:54 +01:00
Timmy38
48e58f4323 * N°8955 Add UIBlocks markup in the setup 2026-03-18 14:16:54 +01:00
Timothee
337ccbb921 N°9141 Fix missing bCanBeUninstalled flag for installation.xml choices 2026-03-18 14:16:54 +01:00
Timothee
34930a93cb N°9327 Fix phpunit tests 2026-03-18 14:16:54 +01:00
Timmy38
5f731d9f97 N°9327 Do not enter readonly or maintenance mode when doing data audit 2026-03-18 14:16:54 +01:00
Timmy38
9b7fd7b398 N°9141 Log when extension uninstallation is forced 2026-03-18 14:16:54 +01:00
Timmy38
e0eee6798d N°9088 Allow extension uninstallation from production-modules folder 2026-03-18 14:16:54 +01:00
Timothee
a8f1b6ea35 N°9161 Fix missing href 2026-03-18 14:16:54 +01:00
odain
d49a50da5b ci: code style 2026-03-18 14:16:54 +01:00
odain
6a34fb9a15 phpstan cleanup 2026-03-18 14:16:54 +01:00
Timmy38
3befe469e8 N°9161 Add markup for behat in setup recap screen 2026-03-18 14:16:54 +01:00
Timmy38
add7e7a677 N°8955 add UI block for uninstallation 2026-03-18 14:16:54 +01:00
odain
f80a074135 cleanup 2026-03-18 14:16:53 +01:00
Timothee
113e3bc110 Fix Code Style 2026-03-18 14:16:53 +01:00
Timmy38
c20c4644b1 N°9009 Add phpunit test to GetSelectedModules 2026-03-18 14:16:53 +01:00
Timothee
b77a0e3297 N°9144 Fix backup option presence during install 2026-03-18 14:16:53 +01:00
odain
a237b32115 N°9168 - fix ci 2026-03-18 14:16:53 +01:00
odain
681abc84b9 Merge branch 'feature/9168-moveintocore' into feature/uninstallation 2026-03-18 14:16:53 +01:00
Timothee
7775cfeccc N°9144 Small fixes
> Prevent verification when some dependencies are missing
> Close unclosed div
> Change progress bar title depending on step
> Fix ignored "check uninstall check" flag
> Added phpunit tests to cover "check uninstall check" flag
> Progress bar appropriately reflect error status (red & not animated)
2026-03-18 14:16:53 +01:00
odain
222e08e29b N°9168 - merge data feature removal to iTop core code
N°9168 - phpstan fix

N°9168 - align module version with itop core
2026-03-18 14:16:53 +01:00
Timothee
773501baed N°9144 Fix back from module choice 2026-03-18 14:16:53 +01:00
Timothee
150094341f N°9010 Fix Code Style 2026-03-18 14:16:53 +01:00
Timmy38
e1215733e9 N°9010 fix flags when extension is missing 2026-03-18 14:16:53 +01:00
Timmy38
2b2e431d0a N°9144 Add data audit setup step
N°8864 Fix unneeded char
2026-03-18 14:16:53 +01:00
odain
98312d1c15 N°8760 - fix installation choices db query - read code instead of title + add log 2026-03-18 14:16:53 +01:00
odain
fc08bed218 Merge branch 'feature/8760-audit-squashed' into feature/uninstallation 2026-03-18 14:16:52 +01:00
odain
eb01828f06 N°8764 - enhance setup wizards transition computation/tests 2026-03-18 14:16:52 +01:00
odain
4c0b54a0f7 N°8760 - Audit uninstall of extensions that declare final classes
N°8760 - be able to list modules based on extension choices
refactoring: move some classes in a moduleinstallation folder (coming
namespace)

N°8760 - module dependency check applied before audit

N°8760 - make dependency check work during audit

N°8760 - fix ci

N°8760 - fix ci

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

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

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

temp review 1

review: renaming InstallationChoicesToModuleConverter

review: renaming InstallationChoicesToModuleConverter

review: ModuleDiscovery:GetModulesOrderedByDependencies replacing deprecated GetAvailableModules method

ci: fix typo

cleanup

review: rework InstallationChoicesToModuleConverter

N°8760 - review tests
2026-03-18 14:16:52 +01:00
Molkobain
8074643231 N°9106 - Adapt extensions pills so they can be read correctly by Behat 2026-03-18 14:16:52 +01:00
odain
1fb264e630 N°8764 - fix setup wizards transitions - damned missing file 2026-03-18 14:16:52 +01:00
odain
2bfafeffe6 N°8764 - fix setup wizards transitions 2026-03-18 14:16:52 +01:00
odain
324a68df32 N°8764 - fix missing setup wizards titles + cache step computation 2026-03-18 14:16:52 +01:00
odain
14c3dafa4d code style 2026-03-18 14:16:52 +01:00
odain
7e78bd8519 N°8764 - use last working model in case setup wizard to do setup audit - skip if no model available to make setup work 2026-03-18 14:16:52 +01:00
Timothee
54aa56c5a8 N°8763 Add phpunit tests for iTopExtension::CanBeUninstalled 2026-03-18 14:16:52 +01:00
odain
62bd1b8f49 N°8864 - code readability 2026-03-18 14:16:52 +01:00
odain
b5e95f297c N°8760 - Audit uninstall of extensions that declare final classes - refactor extension computations and move them in ExtensionMap
N°8760 - Audit uninstall of extensions that declare final classes - refactor extension computations and move them in ExtensionMap
2026-03-18 14:16:52 +01:00
odain
f89cb71f61 simplify WizardStep::GetTitle 2026-03-18 14:16:52 +01:00
odain
b30ff8b832 N°8981 - reduce log verbosity during setup 2026-03-18 14:16:52 +01:00
Timothee
5254a83327 N°8763 Fix non-uninstallable check in multi-modules extensions 2026-03-18 14:16:52 +01:00
odain
b1f49a1568 Merge branch 'feature/8981-prepare' into feature/uninstallation 2026-03-18 14:16:52 +01:00
odain
336898da98 Merge branch 'feature/8981-prepare' into feature/uninstallation 2026-03-18 14:16:51 +01:00
odain
84803abd36 N°8764 - skip audit when itop reinstalled on top of a backup without env-production to read previous datamodel + phpdoc fix 2026-03-18 14:16:51 +01:00
odain
9370c83a69 setup: phpstan level 2
setup: phpstan level 2
2026-03-18 14:16:51 +01:00
odain
f7dfeafa3f setup: phpstan level 1 2026-03-18 14:16:48 +01:00
odain
5b4030a95f setup: phpstan level 0 2026-03-18 14:16:13 +01:00
odain
fe6ba35ada N°8764 halt setup wizard at data issue - 2nd review 2026-03-18 14:16:13 +01:00
odain
789c4bb5ea N°8764 halt setup wizard at data issue - review
- 2 types of SetupAudit constructors
- setup wizard new step management enhancement
- change SetupAudit GetIssue API behaviour
2026-03-18 14:16:13 +01:00
odain
f5f5334e5f ci: test enhancement => use GetTemporaryFilePath 2026-03-18 14:16:13 +01:00
odain
51f0eea8e3 enhance test sdk: add GetTemporaryFilePath
# Conflicts:
#	tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
2026-03-18 14:16:10 +01:00
odain
875b2f1f29 N°7629: log changed 2026-03-18 14:15:00 +01:00
odain
36fc0fb0ea N°8981: test ModuleDiscovery filtered by removed extensions 2026-03-18 14:15:00 +01:00
odain
4f9a4dcfa5 N°8764 - fix first install 2026-03-18 14:15:00 +01:00
odain
dfc6189cef N°8764 - stop setup and display data to cleanup message 2026-03-18 14:15:00 +01:00
odain
ad33453860 N°7629: improve InterfaceDiscovery::GetCandidateClasses robustness 2026-03-18 14:15:00 +01:00
odain
f787cf0950 N°8764 - Halt setup if database is not compatible with an uninstallation 2026-03-18 14:15:00 +01:00
odain
cf7a193f7b N°8981: fix broken setup wizard due to invalid array passed 2026-03-18 14:14:59 +01:00
odain
8bbd1ab621 Merge branch 'feature/8981-prepare' into feature/uninstallation 2026-03-18 14:14:59 +01:00
odain
20ae350286 N°8981: ModuleInstallationRepository dedicated to module installation queries
N°8981: ModuleInstallationRepository dedicated to module installation queries

fix renaming
2026-03-18 14:14:59 +01:00
odain
df9d69c70f N°8981: review cleanup on module filtering due to extensoin removal 2026-03-18 14:14:59 +01:00
odain
ba09c624f0 N°8981: be able to remove extension during setup even when present on disk 2026-03-18 14:14:59 +01:00
odain
7611a0e5c8 N°8981 setup wizard: reuse AnalyzeInstallation result in wizard module choice step 2026-03-18 14:14:59 +01:00
odain
19e60ea628 N°8981 setup wizard: optimize IsConnectableToITopHub use (perf)
N°8981: revert IsConnectableToITopHub
2026-03-18 14:14:59 +01:00
odain
5aaae3ad9c N°8981 setup wizard: static removal + cleanup 2026-03-18 14:14:59 +01:00
odain
3b6ead1307 N°8981 : add type in function 2026-03-18 14:14:59 +01:00
odain
4678684ce6 N°8981 setup wizard: cache GetParamForConfigArray 2026-03-18 14:14:59 +01:00
odain
c008483625 N°8981 setup wizard: cleanup main actions + remove static when possible 2026-03-18 14:14:59 +01:00
odain
bfef10d636 N°8981 setup wizard: cleanup config use in setup wizard 2026-03-18 14:14:59 +01:00
odain
d84959ce6c test SDK enhancement: be able to compare moduleinstallation
ci: add log and hopefully fix hub test

ci: fix all test that rely on CustomDataTC
2026-03-18 14:14:59 +01:00
odain
b230623a32 N°8981: repair previous setup cleanup (broken setups) 2026-03-18 14:14:59 +01:00
odain
375c1f3a73 N°8981: prepare hub connector test cover
sdk test enhancement : add call itop api

fix ci

ci: fix broken tests

ci: cover hub setup on compile and launch steps

code style

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

ci: fix log during setup tests
2026-03-18 14:14:59 +01:00
odain
ab7a737512 N°4789 - do not call empty ModuleInstallerAPI class + logs
code style
2026-03-18 14:14:59 +01:00
Timothee
371819f13f N°8864 Passing array instead of html 2026-03-18 14:14:58 +01:00
odain
56cb3cae4f N°8981 - cleanup/simplify setup
fix RecordInstallation due to previous refactoring
2026-03-18 14:14:58 +01:00
Timothee
11b985aaa7 N°9010 Improve tests readability 2026-03-18 14:14:58 +01:00
odain
59fe11b96e ci: fix broken LoginTest 2026-03-18 14:14:58 +01:00
Timmy38
55b03941e8 N°8864 list extensions installation in setup recap 2026-03-18 14:14:58 +01:00
Timothee
2fcd224ffd N°9010 Setup wizard : manage multiple level extension choice 2026-03-18 14:14:58 +01:00
odain
b26e0c8a90 Merge branch 'develop' into feature/uninstallation 2026-03-18 14:14:58 +01:00
jf-cbd
0be4f52b04 N°8543 - best practices 2026-03-18 11:36:13 +01:00
Molkobain
f7d41d3650 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	datamodels/2.x/itop-portal-base/portal/templates/bricks/browse/mode_tree.html.twig
2026-03-18 11:33:29 +01:00
jf-cbd
eabbe2f00b N°8543 - best practices 2026-03-18 10:09:25 +01:00
jf-cbd
52e303cb37 N°8543 - develop branch adaptation to use new method to get module info 2026-03-17 17:22:06 +01:00
Molkobain
58790bc352 N°8597 - Fix special characters being escaped for BrowseBrick items in "Tree" mode (#845)
* N°8597 - Fix special characters being escaped for BrowseBrick items in "Tree" mode

* N°8597 - Fix forgotten use case for "Tree" mode for intermediate levels
2026-03-17 15:02:20 +01:00
Benjamin DALSASS
0b19333cd7 Dump autoload 2026-03-17 08:08:17 +01:00
Benjamin DALSASS
da09c701cc N°8612 inline images to base64
- AttributeText standalone file modification
2026-03-17 08:07:15 +01:00
Benjamin DALSASS
7c8670b57c Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	core/attributedef.class.inc.php
#	setup/extensionsmap.class.inc.php
#	tests/php-unit-tests/composer.lock
2026-03-17 08:05:10 +01:00
Vincent Dumas
1c1f01aed5 N°9357 - Status mandatory on Service(Subcategory) (#843)
* N°9357 - Status mandatory on Service(Subcategory)

* N°9357 - Fix greptile feedbacks
2026-03-16 17:35:20 +01:00
Vincent Dumas
5d6e2cc9f7 N°3961 Reorganize Service menu (#842)
* N°3961 - Reorganize Service menus

* N°3961 - Dashboard Service Catalog

* N°3961 - dictionaries

* N°3961 - Suppress useless dictionary entries

* N°3961 - Align dictionary entries

* N°3961 - Missing FR translation

* N°3961 - Translation issue

* N°3961 - Remove Rules & Workflow automation menu
2026-03-16 17:14:01 +01:00
Lenaick
28db230697 N°9233 - Check user access before acquiring lock on object (#844) 2026-03-16 17:07:26 +01:00
jf-cbd
4fe61cbdc7 N°8543 - Add checks on exec.php (#835) 2026-03-16 17:06:37 +01:00
v-dumas
3c5bf8a134 N°9138 - Fix typo in dict entry code ConfigMgmt 2026-03-16 16:51:50 +01:00
Benjamin Dalsass
e2994b645b N°8612 inline images to base64 (#826) 2026-03-16 08:36:37 +01:00
Vincent Dumas
32ddf1c980 Feature/9138 display details cmdb (#839)
* N°9138 - Align presentation details of CMDB classes
2026-03-13 17:19:34 +01:00
jf-cbd
0f7540dec8 Create French counterpart of English dictionaries 2026-03-11 14:28:59 +01:00
Thomas Casteleyn
a36a7cc832 fix: Align FiberChannelInterface and LogicalInterface with PhysicalInterface naming (#784) 2026-03-11 14:10:42 +01:00
v-dumas
3dd1c11d3b N°9304 - Add quick links to access impact analysis by default 2026-03-10 13:28:04 +01:00
lenaick.moreira
9fca81cc32 N°9366 - Update PHPUnit to be compatible with PHP 8.4 2026-03-10 10:59:54 +01:00
lenaick.moreira
9792358aea Remove debug OQL filter in HTML comments of the universal search page 2026-03-09 17:06:00 +01:00
lenaick.moreira
7bfa14a874 N°9235 - Sanitize oql_clause query parameter in universal search page 2026-03-09 17:06:00 +01:00
Lenaick
9236449b21 N°9238 - Sanitize data_source_id query parameter in synchro_import script (#831) 2026-03-09 09:02:17 +01:00
Benjamin DALSASS
b3613b6c4b Merge remote-tracking branch 'origin/support/3.2' into develop 2026-03-09 08:42:39 +01:00
Benjamin Dalsass
ab8e7bd15e N°9236 - tag admin fix (#832) 2026-03-09 08:30:39 +01:00
lenaick.moreira
307c308eb0 Fix unit test testPhpMinVersionConsistency 2026-03-05 16:15:18 +01:00
Lenaick
61e5536b50 N°9234 - Sanitize query expression parameter in suggested OQL on run query page (#829) 2026-03-05 16:02:30 +01:00
Lenaick
104dd1970f N°9230 - Sanitize dashboard_id parameter in "revert_dashboard" operation of AJAX render function (#828) 2026-03-05 15:55:28 +01:00
Stephen Abello
884d64a42a Remove hardcoded colors from non theme file and replace hardcoded value with variable 2026-03-05 15:35:49 +01:00
Stephen Abello
44c0a025a8 Merge branch 'support/3.2' into develop
# Conflicts:
#	datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig
2026-03-05 15:32:35 +01:00
Stephen Abello
929b8b9eca Fix CI by fixing code style 2026-03-05 15:28:27 +01:00
Stephen Abello
3b8e079cf1 N°6977 - Sanitize Excel formulas in exports (#818)
* N°6977 - Sanitize Excel formulas in export in the backoffice
---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2026-03-05 15:07:27 +01:00
lenaick.moreira
36247ba0ee Fix unit test testPhpMinVersionConsistency 2026-03-02 15:30:30 +01:00
lenaick.moreira
091d99b08f PHP CS Fixer 2026-03-02 15:19:31 +01:00
lenaick.moreira
3c60a80f9b Fix error on CI
* Call to undefined method PhpParser\ConstExprEvaluator::setStaticcallsWhitelist()
2026-03-02 14:55:12 +01:00
lenaick.moreira
ae633111c0 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-03-02 14:50:14 +01:00
Molkobain
52a1d8d626 📝 Update developers.md to fix php static analysis link 2026-03-02 09:55:05 +01:00
Molkobain
3a64e3bb9e 📝 Update README.md to point to a new "Developers" page 2026-03-02 09:52:36 +01:00
Lenaick
fc967c06ce N°8834 - Add compatibility with PHP 8.4 (#819)
* N°8834 - Add compatibility with PHP 8.4

* Rollback of scssphp/scssphp version upgrade due to compilation error
2026-02-26 10:36:32 +01:00
Eric Espie
d4821b7edc remove log level config 2026-02-26 10:19:34 +01:00
Molkobain
94a36c0066 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-02-26 09:30:14 +01:00
Molkobain
62e09f1224 N°8604 N°8605 - Add authentication to combodo-db-tools binaries (#817)
* N°8604 N°8605 - Add autoloader and dedicated classes for binaries utils

* N°8605 - Harden security

* N°8604 - Harden security

* N°8604 N°8605 - Fixes from code review

* N°8604 N°8605 - Improve robustness whether module is in datamodels/2.x or env-xxx folder
2026-02-26 09:29:20 +01:00
Eric Espie
57a0b5691f Merge support/3.2 into develop 2026-02-25 10:44:19 +01:00
Eric Espié
f82389d156 N°8632 - Various fixes (#814)
* N°8632 - Check existence of parameter file within iTop

* N°8632 - block parameter file from request

* log on error

* PHP CS fixer

* N°8632 - param files must be outside iTop

* PHP CS fixer

* Update webservices/export.php

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

* Update tests/php-unit-tests/unitary-tests/application/utilsTest.php

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

*  Fix CI

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-25 10:42:04 +01:00
Molkobain
9e21976424 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-02-24 21:10:57 +01:00
Molkobain
f558093f5d N°8796 - Add PHP-CS-Fixer cache file to .gitignore file 2026-02-24 21:10:06 +01:00
Eric Espie
5201a1ed3b PHP CS fixer 2026-02-24 16:38:21 +01:00
Eric Espie
dad39c3ebe Merge support/3.2 into develop 2026-02-24 13:55:55 +01:00
odain
29920bfeb7 cleanup: log level in setUp + remove ls cli 2026-02-24 11:26:48 +01:00
odain
2313ee2bbd cleanup: log level in setUp + remove ls cli 2026-02-24 11:22:24 +01:00
jf-cbd
22b0c431a0 🌐 Add French translations in fr.dict.itop-config-mgmt.php 2026-02-23 18:51:57 +01:00
Thomas Casteleyn
499b3bca88 N°7570 - feat(Typology): Add useful tabs to certain typology classes (#756) 2026-02-23 17:28:56 +01:00
Stephen Abello
aede5ea7b8 Fix CI by updating files code style 2026-02-23 16:25:08 +01:00
Stephen Abello
da6c443a35 Fix CI by updating files code style 2026-02-23 16:08:20 +01:00
Stephen Abello
9c39efd9af N°8549 - Update inline images secret (#815) 2026-02-23 15:42:21 +01:00
jf-cbd
d5f2303ed2 N°8673 - Avoid server overload when user access news page with a lot of unread news (#816) 2026-02-23 15:35:31 +01:00
Eric Espié
48e584503e N°8782 - Union queries allow all data (#807)
* 8782 - Union queries allow all data

* N°8782 - Union queries allow all data
2026-02-23 15:10:09 +01:00
Benjamin DALSASS
454a1b26eb N°8603 N°8601 - Remove unnecessary quotes in reload url injection 2026-02-23 10:22:10 +01:00
Benjamin Dalsass
4853ca444e N°8910 - Upgrade Symfony packages (#811) 2026-02-23 06:54:26 +01:00
Benjamin Dalsass
330539abd2 N°8601 N°8603 dashboards issues (#813) 2026-02-23 06:51:40 +01:00
Stephen Abello
5357a0c060 Fix code style 2026-02-21 14:26:21 +01:00
lenaick.moreira
fc22cce037 N°8854 - Replace trigger_error() usage with E_USER_ERROR 2026-02-20 10:07:30 +01:00
lenaick.moreira
34c8a57814 N°8851 - Explicit nullable in functions parameters 2026-02-20 10:07:30 +01:00
Stephen Abello
b91e6c384a Merge branch 'support/3.2' into develop 2026-02-19 17:39:47 +01:00
Stephen Abello
2247691e58 Fix CI by updating files code style 2026-02-19 16:59:25 +01:00
Stephen Abello
f014b43761 Merge branch 'support/3.2' into develop 2026-02-19 16:16:56 +01:00
Stephen Abello
076d49abc2 Dump autoloader 2026-02-19 16:15:58 +01:00
Stephen Abello
9fd0ffd84e N°8545 - Standardize return message from password reset (#812)
* N°8545 - Standardize return message from password reset

* N°8545 - Change log severity depending on the error source

* Add copyrights

* Update application/loginwebpage.class.inc.php

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

* Update application/loginwebpage.class.inc.php

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

* Avoid using dictionary entries in logs

* Update application/loginwebpage.class.inc.php

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-19 16:12:34 +01:00
Benjamin DALSASS
0f11fd9919 N°8910 - Upgrade Symfony packages
- update composer packages
2026-02-19 09:52:18 +01:00
Benjamin DALSASS
2b828f8a22 Merge branch 'support/3.2' into develop
# Conflicts:
#	composer.lock
#	datamodels/2.x/authent-local/dictionaries/pt_br.dict.authent-local.php
#	dictionaries/fr.dictionary.itop.ui.php
#	dictionaries/it.dictionary.itop.ui.php
#	dictionaries/pt_br.dictionary.itop.ui.php
#	lib/composer/autoload_classmap.php
#	lib/composer/autoload_static.php
#	lib/composer/installed.json
#	lib/composer/installed.php
#	lib/symfony/cache/Traits/RelayProxy.php
#	lib/symfony/form/Exception/AccessException.php
#	lib/symfony/form/Exception/ErrorMappingException.php
#	lib/symfony/form/Exception/StringCastException.php
#	lib/symfony/http-foundation/Request.php
#	lib/symfony/polyfill-intl-icu/composer.json
#	lib/symfony/property-access/LICENSE
#	lib/symfony/security-core/Event/AuthenticationSuccessEvent.php
2026-02-19 09:22:08 +01:00
Benjamin Dalsass
d2f67dcb3c N°8910 - Upgrade Symfony packages 2026-02-19 09:18:56 +01:00
jf-cbd
d5706fcbef 🧑‍💻 Add auto-assign for Combodo members 2026-02-18 14:39:25 +01:00
Benjamin Dalsass
807f2a88bc N°8933 - Change password default length 2026-02-18 14:36:38 +01:00
Stephen Abello
9d3311e623 Fix CI by updating new dictionary files code style again 2026-02-18 12:18:15 +01:00
Stephen Abello
9bf2cb7e1d Fix CI by updating new dictionary files code style 2026-02-18 11:50:38 +01:00
Vincent Dumas
0134ead5dd remove installer in itop-container-mgmt 2026-02-18 11:49:40 +01:00
Stephen Abello
54909520e9 N°8544 - Update comparison method (#808) 2026-02-18 11:30:34 +01:00
Stephen Abello
d124f8ee58 N°8541 - Replace login logo title with an alt customizable through dictionary entry (#805) 2026-02-18 09:53:57 +01:00
Molkobain
3fdbcbc0fb N°8681 - Enforce type hint for parameters and return value 2026-02-17 20:55:55 +01:00
Molkobain
a5296e11e1 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	core/attributedef.class.inc.php
2026-02-17 20:54:47 +01:00
Molkobain
3b62597092 Update PHP version to 8.3 and DB version to MariaDB latest for CI jobs on commit 2026-02-17 20:39:58 +01:00
v-dumas
b085147f23 N°8844 - Datamodel Cloud moved in Virtualization 2026-02-17 17:49:55 +01:00
Vincent Dumas
38fccf85e3 N°8515 - Add CMDB datamodel for Docker and Kubernetes (#787)
* N°8515 - Add CMDB datamodel for Docker and Kubernetes
* N°8515 - Add Cloud class under Virtualization

* N°8515 - Add lnkContainerApplicationToImage and more fields on Image
* N°8515 - Move ContainerApplication under SoftwareInstance
* N°8515 - Use structural data instead of explicit load
--------

Co-authored-by: Stephen Abello <stephen.abello@combodo.com>
Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>
2026-02-17 12:20:43 +01:00
Molkobain
c0a2771d4e N°8681 - Fix PHP code styles 2026-02-16 16:46:00 +01:00
Molkobain
6bd5a7b634 N°8681 - PHP 8.1: Fix deprecated notice for null value passed to preg_match_all() (#803)
* N°8681 - PHP 8.1: Fix deprecated notice for null value passed to preg_match_all()

* N°8681 - Add unit tests

* N°8681 - Add unit tests
2026-02-16 16:18:04 +01:00
lenaick.moreira
82b7ef86c7 N°8796 - Refactor PHP CS Fixer configuration for improved readability
* `'indentation_type'` already included in @PSR12 rule set
2026-02-16 15:35:46 +01:00
Timothee
f8cf14cbad N°3124 Add deprecation version 2026-02-09 14:17:35 +01:00
Eric Espie
a5dededfb4 Merge remote-tracking branch 'origin/support/3.2' into develop 2026-02-05 17:06:26 +01:00
Eric Espie
4f878536a8 Fix CI 2026-02-05 16:53:02 +01:00
Eric Espie
d937ec0350 Fix CI 2026-02-05 16:24:27 +01:00
Eric Espie
1cdcaac3d0 Merge remote-tracking branch 'origin/support/3.2' into develop
# Conflicts:
#	sources/Core/Kpi/KpiLogData.php
2026-02-05 15:33:25 +01:00
Eric Espie
985db46960 N°9193 - Start the KPI logs at the beginning of the http request 2026-02-05 14:57:54 +01:00
v-dumas
01adaadfad N°8492 - Missing accent for 'Categorie' 2026-02-02 14:56:53 +01:00
Molkobain
3f807a64bb N°9154 - Update Jimmy avatar to reduce size 2026-01-28 11:54:56 +01:00
Molkobain
1d4a155e8f N°9154 - Add Amine, Gaëlle & Lorraine to sample data 2026-01-28 11:53:23 +01:00
v-dumas
643752f8e7 N°8378 - Missing rights on incident for SuperUser 2026-01-23 16:24:19 +01:00
v-dumas
0e0c09c420 N°9027 - Add right on WorkOrder transition to SuperUser 2026-01-23 15:55:59 +01:00
v-dumas
2b6fa8b381 N°9080 - Fix duplicate 'Model' in class labels in EN and FR 2026-01-23 15:07:49 +01:00
v-dumas
7d2dc5e36a N°9076 - Fix typos and cosmetics in FR initial queries 2026-01-23 14:36:38 +01:00
v-dumas
f87df8f28b N°9087 - Add logo on Brand and OS Family, with structural data 2026-01-21 17:13:32 +01:00
Anne-Cath
cc9e64616f N°8786 - configuration: allow conditions on the allowed_login_types field - cleanup 2026-01-16 16:29:15 +01:00
Stephen Abello
f34373be6d N°7909 - Missing spacing between fields when columns collapse 2026-01-16 15:30:32 +01:00
v-dumas
a39234f438 N°4032 - Restore explicit dependency used in filter 2026-01-14 16:06:14 +01:00
v-dumas
ac8937105d N°8873 - Modify Tooltip on 'filter' field of TriggerOnObjectUpdate class 2026-01-12 16:33:36 +01:00
odain
fb6f892244 N°9085 - Cannot install itop without a portal 2026-01-12 15:43:56 +01:00
v-dumas
0a04c83c7b N°9045 - Add a tooltip for Service Family in Service details 2026-01-12 15:20:14 +01:00
v-dumas
cc8252bebf N°9046 - Tooltip mentions that a team is required on a support model 2026-01-12 14:52:57 +01:00
Vincent Dumas
3e879c64a7 N°4032 - On UserRequest, change proposed service subcategories (#786)
* N°4032 - On UserRequest, service subcategory no more limited by request_type
2026-01-08 10:08:52 +01:00
Eric Espie
5c6369b9b8 N°9065 - XML Definition for Dashlet properties 2026-01-07 17:16:50 +01:00
Eric Espie
154fb5c737 N°9066 - Serialization/Unserialization from XML to Forms 2026-01-07 17:09:47 +01:00
Eric Espie
efb1bd765b N°9065 - XML Definition for Dashlet properties 2026-01-07 17:09:20 +01:00
Eric Espie
b39af74d07 Fix CI 2026-01-07 09:04:56 +01:00
Eric Espie
904cd0b518 N°8772 - Move DIService into PSR-11 Service Locator 2025-12-30 14:27:28 +01:00
Benjamin Dalsass
4c1ad0f4f2 N°8772 - Form dependencies manager implementation
- Form SDK implementation
- Basic Forms
- Dynamics Forms
- Basic Blocks + Data Model Block
- Form Compilation
- Turbo integration
2025-12-30 11:42:55 +01:00
v-dumas
3955b4eb22 N°8534 - Prevent ending on Portal 2025-12-22 17:49:47 +01:00
odain
3d46fe6ef1 Merge branch 'support/3.2' into develop 2025-12-19 18:44:55 +01:00
odain
4dba47798c ci: fix broken test due to POST CURL file not sent 2025-12-19 18:35:42 +01:00
Anne-Cath
9480c4053d N°8786 - configuration: allow conditions on the allowed_login_types field - fix tests 2025-12-19 16:37:47 +01:00
odain
b967fb7f20 Merge branch 'develop' into feature/uninstallation 2025-12-19 16:17:12 +01:00
odain
9ea197148c Merge branch 'support/3.2' into develop 2025-12-19 16:17:00 +01:00
odain
49a7f3118d ci: fix Array to String Conversion in CallUrl with POST array of array 2025-12-19 16:12:53 +01:00
odain
195038c941 Merge branch 'develop' into feature/uninstallation 2025-12-19 15:48:56 +01:00
odain
bf80b5dca2 Merge branch 'support/3.2' into develop 2025-12-19 15:46:00 +01:00
odain
09afcb229c ci: fix callUrl with posted params 2025-12-19 11:11:03 +01:00
odain
92f843f676 Merge branch 'support/3.2' into develop 2025-12-19 10:39:24 +01:00
odain
3df4ddc696 ci: fix code style 2025-12-19 10:39:15 +01:00
odain
d552727c55 ci: fix code style 2025-12-19 10:38:48 +01:00
odain
1fe401c102 ci: rename CallItopUrl by CallUrl + cleanup 2025-12-19 10:01:37 +01:00
odain
a4b0b6e855 ci: rename CallItopUrl by CallUrl + cleanup 2025-12-19 09:54:43 +01:00
odain
9a8d87e2b5 Merge branch 'develop' into feature/uninstallation 2025-12-18 14:02:45 +01:00
odain
545028d68a Merge branch 'support/3.2' into develop 2025-12-18 13:51:54 +01:00
odain-cbd
6cb08ba361 Add few APIs to ease tests (#788)
* add few APIs to ease tests

* code style

* log testname via IssueLog only through ItopDataTestCase

* code style

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

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

be able to simulate SetupAudit errors in Setups for integration tests

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

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

N°8760 - provide a service dedicated to extension removal

N°8760 - compute all rules by default

add comment

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

fix ci call to ModuleInstallationService

ci: fix code style

get rid of itop_version

refactoring: make code simpler

get rid of unused name_db

refactoring setup: rename version_db by installed_version

refactoring setup: rename version_code by available_version

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

last test cleanup

review + enhance UI output and display only failed module dependencies

real life test cleanup

review: add more tests + refacto

code review: enhance algo and APIs

review: renaming

enhance test coverage

refactoring

renaming + reorder functions/tests

compute GetDependencyResolutionFeedback in Module class

review2 : renaming things

fix rebase + code formatting

fix code formatting

review changes

refactoring: code cleanup/standardization/remove all prototype stuffs

refactoring: code cleanup/standardization/remove all prototype stuffs

add deps validation to extension ci job

fix ci

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

fix tests

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

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

rebase on develop + split new sort computation apart from modulediscovery

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

make validation work (dirty way) + cleanup

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

final deps validation bases on DM and PHP classes

init in beforeclass + read defined classes/interfaces by module

module discovery classes renaming to avoid collision with customer DM definitions

read module file data apart from ModuleDiscovery

cleanup

cleanup

fix inconsistent module dependencies

fix integration check

save tmp work before trying to fetch other wml deps

fix module dependencies

fix DM filename typo

rename ModuleXXX classes by iTopCoreModuleXXX to reduce collisions with extensions

add phpdoc + add more tests

module dependency optimization - refacto + dependency new sort order

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

enhance module dependency computation for optimization and admin feedback
2025-11-26 19:23:26 +01:00
odain
d8121b563a synchro code: fix Deprecated: strlen(): Passing null to parameter 2025-11-21 10:38:26 +01:00
Anne-Cath
f266f5ff36 N°8911 - Attachment visualisation broken 2025-11-20 08:06:00 +01:00
odain
4a6b129eb8 Merge branch 'support/3.2' into develop 2025-11-18 15:36:47 +01:00
odain
4187f552a9 Merge branch 'support/3.2.1' into support/3.2 2025-11-18 15:36:32 +01:00
odain
7f7ce0837e Merge branch 'designer-3.2.1' into support/3.2.1 2025-11-18 15:35:40 +01:00
odain
7df09541ac N°8306 - ci fixes 2025-11-16 08:11:34 +01:00
1856 changed files with 169279 additions and 15398 deletions

9
.doc/developers.md Normal file
View File

@@ -0,0 +1,9 @@
# Developers
## PHP Code Styles
We use `PHP CS Fixer` to ensure code formating consistency across PHP codebase.
You can find the configuration and instructions to run it [here](../tests/php-code-style/README.md).
## PHP Static Analysis
We use `PHPStan` to ensure code quality and to detect potential bugs in our PHP codebase.
You can find the configuration and instructions to run it [here](../tests/php-static-analysis/README.md).

View File

@@ -26,13 +26,23 @@ jobs:
fi
- name: Add internal tag if member
- name: Add internal tag if member of the organization
if: env.is_member == 'true'
run: |
curl -X POST -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/Combodo/iTop/issues/${{ github.event.pull_request.number }}/labels \
-d '{"labels":["internal"]}'
- name: Set PR author as assignee if member of the organization
if: env.is_member == 'true'
run: |
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
https://api.github.com/repos/Combodo/iTop/issues/${{ github.event.pull_request.number }}/assignees \
-d '{"assignees":["${{ github.event.pull_request.user.login }}"]}'
env:
is_member: ${{ env.is_member }}
@@ -40,4 +50,4 @@ jobs:
uses: actions/add-to-project@v1.0.2
with:
project-url: ${{ env.project_url }}
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}

3
.gitignore vendored
View File

@@ -58,6 +58,9 @@ tests/*/vendor/*
/tests/php-unit-tests/phpunit.xml
/tests/php-unit-tests/postbuild_integration.xml
# PHP CS Fixer: Cache file
/.php-cs-fixer.cache
# Jetbrains
/.idea/**

16
.phpstorm.meta.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
/*
* @copyright Copyright (C) 2010-2025 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace PHPSTORM_META
{
override(\MetaModel::NewObject(0), map([
'' => '@',
]));
override(\MetaModel::GetObject(0), map([
'' => '@',
]));
}

View File

@@ -73,6 +73,9 @@ iTop development is sponsored, led, and supported by [Combodo][0].
[0]: https://www.combodo.com
## Developers
You can find information and instructions about our quality tools and how to run them [here](.doc/developers.md).
## Contributors

View File

@@ -16,5 +16,5 @@ require_once(APPROOT.'/application/audit.category.class.inc.php');
require_once(APPROOT.'/application/audit.domain.class.inc.php');
require_once(APPROOT.'/application/audit.rule.class.inc.php');
require_once(APPROOT.'/application/query.class.inc.php');
require_once(APPROOT.'/setup/moduleinstallation.class.inc.php');
require_once(APPROOT.'/setup/moduleinstallation/moduleinstallation.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');

View File

@@ -819,6 +819,7 @@ HTML
foreach ($aNotificationClasses as $sNotifClass) {
$aNotifSearches[$sNotifClass] = DBObjectSearch::FromOQL("SELECT $sNotifClass AS Ev WHERE Ev.object_id = :id AND Ev.object_class = :class");
$aNotifSearches[$sNotifClass]->SetInternalParams($aParams);
$aNotifSearches[$sNotifClass]->AllowAllData();
$oNotifSet = new DBObjectSet($aNotifSearches[$sNotifClass], []);
$iNotifsCount += $oNotifSet->Count();
}
@@ -832,6 +833,7 @@ HTML
'menu' => false,
'panel_title' => MetaModel::GetName($sNotifClass),
'panel_icon' => MetaModel::GetClassIcon($sNotifClass, false),
'display_unauthorized_objects' => true,
]);
}
}
@@ -5632,7 +5634,7 @@ JS
* @return void
* @since 3.1.0
*/
final public function AddAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', string $sReason = null): void
final public function AddAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', ?string $sReason = null): void
{
if (!isset($this->aAttributesFlags[$sTargetState])) {
$this->aAttributesFlags[$sTargetState] = [];
@@ -5655,7 +5657,7 @@ JS
* @return void
* @since 3.1.0
*/
final public function ForceAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', string $sReason = null): void
final public function ForceAttributeFlags(string $sAttCode, int $iFlags, string $sTargetState = '', ?string $sReason = null): void
{
if (!isset($this->aAttributesFlags[$sTargetState])) {
$this->aAttributesFlags[$sTargetState] = [];
@@ -5696,7 +5698,7 @@ JS
* @return void
* @since 3.1.0
*/
final public function AddInitialAttributeFlags(string $sAttCode, int $iFlags, string $sReason = null)
final public function AddInitialAttributeFlags(string $sAttCode, int $iFlags, ?string $sReason = null)
{
if (!isset($this->aInitialAttributesFlags)) {
$this->aInitialAttributesFlags = [];
@@ -5718,7 +5720,7 @@ JS
* @return void
* @since 3.1.0
*/
final public function ForceInitialAttributeFlags(string $sAttCode, int $iFlags, string $sReason = null)
final public function ForceInitialAttributeFlags(string $sAttCode, int $iFlags, ?string $sReason = null)
{
if (!isset($this->aInitialAttributesFlags)) {
$this->aInitialAttributesFlags = [];

View File

@@ -497,7 +497,7 @@ EOF
* @param array $aExtraParams
* @param bool $bCanEdit
*
* @return \Combodo\iTop\Application\UI\Base\Layout\Dashboard\DashboardLayout
* @return null|\Combodo\iTop\Application\UI\Base\Layout\Dashboard\DashboardLayout
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = [], $bCanEdit = true)
{
@@ -1029,7 +1029,7 @@ EOF
var dashboard = $('.ibo-dashboard#$sDivId')
dashboard.block();
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
{ operation: 'toggle_dashboard', dashboard_id: '$sId', file: '$sFile', extra_params: $sExtraParams, reload_url: '$sReloadURL' },
{ operation: 'toggle_dashboard', dashboard_id: '$sId', file: '$sFile', extra_params: $sExtraParams, reload_url: $sReloadURL },
function(data) {
dashboard.html(data);
dashboard.unblock();

View File

@@ -1,10 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://www.combodo.com/itop-schema/3.3"
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. Generaly controlled using UR_ACTION_MODIFY access right. */</comment>
<comment>/* Resource access control abstraction. Can be herited by abstract resource access control classes. Generally controlled using UR_ACTION_MODIFY access right. */</comment>
<abstract>true</abstract>
</properties>
<presentation/>
@@ -552,7 +554,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">
@@ -849,5 +851,168 @@ 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">
<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>
<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>
<values>
<value id="bars">
<label>UI:DashletGroupByBars:Label</label>
</value>
<value id="pie">
<label>UI:DashletGroupByPie:Label</label>
</value>
<value id="table">
<label>UI:DashletGroupByTable:Label</label>
</value>
</values>
</node>
<node id="aggregation_function" xsi:type="Combodo-ValueType-AggregateFunction">
<label>UI:DashletGroupBy:Prop-Function</label>
<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>
<class>{{query.selected_class}}</class>
<category>numeric</category>
</node>
<node id="order_by" xsi:type="Combodo-ValueType-ChoiceFromInput">
<label>UI:DashletGroupBy:Prop-OrderField</label>
<values>
<value id="attribute">
<label>{{aggregation_attribute.label}}</label>
</value>
<value id="function">
<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>
<values>
<value id="asc">
<label>UI:DashletGroupBy:Order:asc</label>
</value>
<value id="desc">
<label>UI:DashletGroupBy:Order:desc</label>
</value>
</values>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletBadge" xsi:type="Combodo-PropertyType">
<extends>Dashlet</extends>
<definition xsi:type="Combodo-ValueType-PropertyTree">
<nodes>
<node id="class" xsi:type="Combodo-ValueType-Class">
<label>UI:DashletBadge:Prop-Class</label>
<categories-csv>bizmodel</categories-csv>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletHeaderDynamic" xsi:type="Combodo-PropertyType">
<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>
<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">
<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">
<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>
<on>
<!-- not so cute, but matches exactly 3.2 implementation of boolean fields -->
<label>UI:UserManagement:ActionAllowed:Yes</label>
<value>true</value>
</on>
<off>
<label>UI:UserManagement:ActionAllowed:No</label>
<value>false</value>
</off>
</node>
</nodes>
</definition>
</property_type>
<property_type id="DashletPlainText" xsi:type="Combodo-PropertyType">
<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>
</meta>
</itop_design>

View File

@@ -726,6 +726,10 @@ class DisplayBlock
}
}
if (!$this->m_oFilter->IsAllDataAllowed() && ($aExtraParams['display_unauthorized_objects'] ?? false) === true) {
$this->m_oFilter->AllowAllData();
}
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
}
@@ -1379,7 +1383,10 @@ JS
// Check the classes that can be read (i.e authorized) by this user...
foreach ($aClasses as $sAlias => $sClassName) {
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) != UR_ALLOWED_NO) {
if (
(UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) !== UR_ALLOWED_NO)
|| ($aExtraParams['display_unauthorized_objects'] ?? false) === true
) {
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}

View File

@@ -0,0 +1,10 @@
<?php
/**
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ForgotPasswordApplicationException extends Exception
{
}

View File

@@ -0,0 +1,10 @@
<?php
/**
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ForgotPasswordUserInputException extends Exception
{
}

View File

@@ -1387,7 +1387,7 @@ class DesignerIconSelectionField extends DesignerFormField
public function AddAllowedValue($aValue)
{
// Add a null value to the list of allowed values
$this->aAllowedValues = array_merge([$aValue], $this->aAllowedValues);
$this->aAllowedValues = array_merge([$aValue], $this->aAllowedValues ?? [null]);
}
public function EnableUpload($sIconUploadUrl)
{

View File

@@ -75,13 +75,10 @@ class LoginExternal extends AbstractLoginFSMExtension
}
/**
* @return bool
* @return bool|mixed
*/
private function GetAuthUser()
{
$sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ?
eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value
/** @var string $sAuthUser */
return $sAuthUser; // Retrieve the value
return MetaModel::GetConfig()->GetExternalAuthenticationVariable();
}
}

View File

@@ -243,15 +243,12 @@ class LoginTwigRenderer
public function GetDefaultVars()
{
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl();
$aVars = [
'sAppRootUrl' => utils::GetAbsoluteUrlAppRoot(),
'aPluginFormData' => $this->GetPluginFormData(),
'sItopVersion' => ITOP_VERSION,
'sVersionShort' => $sVersionShort,
'sIconUrl' => $sIconUrl,
'sDisplayIcon' => $sDisplayIcon,
];

View File

@@ -221,15 +221,15 @@ class LoginWebPage extends NiceWebPage
if ($oUser != null) {
if (!MetaModel::IsValidAttCode(get_class($oUser), 'reset_pwd_token')) {
throw new Exception(Dict::S('UI:ResetPwd-Error-NotPossible'));
throw new ForgotPasswordUserInputException('External accounts do not allow password reset');
}
if (!$oUser->CanChangePassword()) {
throw new Exception(Dict::S('UI:ResetPwd-Error-FixedPwd'));
throw new ForgotPasswordUserInputException('The account does not allow password reset');
}
$sTo = $oUser->GetResetPasswordEmail(); // throws Exceptions if not allowed
if ($sTo == '') {
throw new Exception(Dict::S('UI:ResetPwd-Error-NoEmail'));
throw new ForgotPasswordUserInputException('Missing email address for this account');
}
// This token allows the user to change the password without knowing the previous one
@@ -255,17 +255,21 @@ class LoginWebPage extends NiceWebPage
case EMAIL_SEND_ERROR:
default:
IssueLog::Error('Failed to send the email with the NEW password for '.$oUser->Get('friendlyname').': '.implode(', ', $aIssues));
throw new Exception(Dict::S('UI:ResetPwd-Error-Send'));
throw new ForgotPasswordApplicationException('Failed to send the password reset email for '.$oUser->Get('friendlyname').': '.implode(', ', $aIssues));
}
}
$oTwigContext = new LoginTwigRenderer();
$aVars = $oTwigContext->GetDefaultVars();
$oTwigContext->Render($this, 'forgotpwdsent.html.twig', $aVars);
} catch (Exception $e) {
$this->DisplayForgotPwdForm(true, $e->getMessage());
} catch (ForgotPasswordApplicationException $e) {
IssueLog::Error('Failed to process the forgot password request for user "'.$sAuthUser.'" [reason='.get_class($e).']: '.$e->getMessage());
} catch (ForgotPasswordUserInputException $e) {
IssueLog::Info('Failed to process the forgot password request for user "'.$sAuthUser.'" [reason='.get_class($e).']: '.$e->getMessage());
} catch (\Throwable $e) {
IssueLog::Error('Unexpected error while processing the forgot password request for user "'.$sAuthUser.'": '.$e->getMessage());
}
$oTwigContext = new LoginTwigRenderer();
$aVars = $oTwigContext->GetDefaultVars();
$oTwigContext->Render($this, 'forgotpwdsent.html.twig', $aVars);
}
public function DisplayResetPwdForm($sErrorMessage = null)

View File

@@ -34,7 +34,7 @@ interface iNewsroomProvider
* @param User $oUser The user for who to check if the provider is applicable.
* return bool
*/
public function IsApplicable(User $oUser = null);
public function IsApplicable(?User $oUser = null);
/**
* The human readable (localized) label for this provider
@@ -139,7 +139,7 @@ abstract class NewsroomProviderBase implements iNewsroomProvider
*/
abstract public function GetViewAllURL();
public function IsApplicable(User $oUser = null)
public function IsApplicable(?User $oUser = null)
{
return false;
}

View File

@@ -151,7 +151,7 @@ abstract class Query extends cmdbAbstractObject
* @return string|null
* @since 3.1.0
*/
abstract public function GetExportUrl(array $aValues = null): ?string;
abstract public function GetExportUrl(?array $aValues = null): ?string;
/**
* Update last export information.
@@ -227,7 +227,7 @@ class QueryOQL extends Query
}
/** @inheritdoc */
public function GetExportUrl(array $aValues = null): ?string
public function GetExportUrl(?array $aValues = null): ?string
{
try {
// retrieve attributes

View File

@@ -924,11 +924,6 @@ CSS;
public static function CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp, $aImportsPaths)
{
$aThemeParametersVariable = [];
if (array_key_exists('variables', $aThemeParameters)) {
if (is_array($aThemeParameters['variables'])) {
$aThemeParametersVariable = array_merge([], $aThemeParameters['variables']);
}
}
if (array_key_exists('variable_imports', $aThemeParameters)) {
if (is_array($aThemeParameters['variable_imports'])) {
@@ -936,6 +931,14 @@ CSS;
}
}
// Variables defined in theme XML have the priority over variables defined in XML imports files
// They're defined after so they overwrite previous parameters
if (array_key_exists('variables', $aThemeParameters)) {
if (is_array($aThemeParameters['variables'])) {
$aThemeParametersVariable = array_merge($aThemeParametersVariable, $aThemeParameters['variables']);
}
}
$aThemeParametersVariable['$version'] = $bSetupCompilationTimestamp;
return $aThemeParametersVariable;
}
@@ -967,7 +970,9 @@ CSS;
}
}
}
array_map(function ($sVariableValue) { return ltrim($sVariableValue); }, $aVariablesResults);
array_map(function ($sVariableValue) {
return ltrim($sVariableValue);
}, $aVariablesResults);
return $aVariablesResults;
}

View File

@@ -228,7 +228,7 @@ JS
<<<HTML
<form id="ObjectsAddForm_{$this->sInputid}">
<div id="SearchResultsToAdd_{$this->sInputid}">
<div style="background: #fff; border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
<div style="border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
</div>
<input type="hidden" id="count_{$this->sInputid}" value="0"/>
</form>

View File

@@ -27,6 +27,9 @@ require_once(APPROOT.'/application/displayblock.class.inc.php');
class UISearchFormForeignKeys
{
private $m_sRemoteClass;
private $m_iInputId;
public function __construct($sTargetClass, $iInputId = null)
{
$this->m_sRemoteClass = $sTargetClass;
@@ -40,7 +43,7 @@ class UISearchFormForeignKeys
*
* @throws \Exception
*/
public function ShowModalSearchForeignKeys($oPage, $sTitle)
public function ShowModalSearchForeignKeys($oPage)
{
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
@@ -60,52 +63,17 @@ class UISearchFormForeignKeys
]
));
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sCancel = Dict::S('UI:Button:Cancel');
$sAdd = Dict::S('UI:Button:Add');
$oPage->add(
<<<HTML
<form id="ObjectsAddForm_{$this->m_iInputId}">
<div id="SearchResultsToAdd_{$this->m_iInputId}" style="vertical-align:top;height:100%;overflow:auto;padding:0;border:0;">
<div style="background: #fff; border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
<div style="border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
</div>
<input type="hidden" id="count_{$this->m_iInputId}" value="0"/>
</form>
HTML
);
$oPage->add_ready_script(
<<<JS
$('#dlg_{$this->m_iInputId}').dialog({
width: $(window).width()*0.8,
height: $(window).height()*0.8,
autoOpen: false,
modal: true,
resizeStop: oForeignKeysWidget{$this->m_iInputId}.UpdateSizes,
buttons: [
{
text: Dict.S('UI:Button:Cancel'),
class: "cancel ibo-is-alternative ibo-is-neutral",
click: function() {
$('#dlg_{$this->m_iInputId}').dialog('close');
}
},
{
text: Dict.S('UI:Button:Add'),
id: 'btn_ok_{$this->m_iInputId}',
class: "ok ibo-is-regular ibo-is-primary",
click: function() {
oForeignKeysWidget{$this->m_iInputId}.DoAddObjects(this.id);
}
},
],
});
$('#dlg_{$this->m_iInputId}').dialog('option', {title:'$sTitle'});
$('#SearchFormToAdd_{$this->m_iInputId} form').on('submit.uilinksWizard', oForeignKeysWidget{$this->m_iInputId}.SearchObjectsToAdd);
$('#SearchFormToAdd_{$this->m_iInputId}').on('resize', oForeignKeysWidget{$this->m_iInputId}.UpdateSizes);
JS
);
}
public function GetFullListForeignKeysFromSelection($oPage, $oFullSetFilter)
@@ -119,31 +87,4 @@ JS
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
}
}
/**
* Search for objects to be linked to the current object (i.e "remote" objects)
*
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
*
* @throws \Exception
*/
public function ListResultsSearchForeignKeys(WebPage $oP, $sRemoteClass = '')
{
if ($sRemoteClass != '') {
// assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass));
$oFilter = new DBObjectSearch($sRemoteClass);
} else {
// No remote class specified use the one defined in the linkedset
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
}
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display(
$oP,
"ResultsToAdd_{$this->m_iInputId}",
['menu' => false, 'cssCount' => "#count_{$this->m_iInputId}", 'selection_mode' => true, 'table_id' => "add_{$this->m_iInputId}"]
);
}
}

View File

@@ -122,6 +122,11 @@ class utils
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_VARIABLE_NAME = 'variable_name';
/**
* @var string For module codes (e.g. `itop-portal-base`, `combodo-webhook-integration`, `some-module-code-x.y`, ...)
* @since 3.2.3 3.3.0 N°8554
*/
public const ENUM_SANITIZATION_FILTER_MODULE_CODE = 'module_code';
/**
* @var string
* @since 2.7.10 3.0.0
@@ -181,6 +186,9 @@ class utils
protected static function LoadParamFile($sParamFile)
{
if (utils::RealPath($sParamFile, APPROOT) !== false) {
throw new Exception("File '".utils::HtmlEntities($sParamFile)."' should be outside iTop");
}
if (!file_exists($sParamFile)) {
throw new Exception("Could not find the parameter file: '".utils::HtmlEntities($sParamFile)."'");
}
@@ -390,6 +398,7 @@ class utils
* @since 2.7.10 N°6606 use the utils::ENUM_SANITIZATION_* const
* @since 2.7.10 N°6606 new case for ENUM_SANITIZATION_FILTER_PHP_CLASS
* @since 3.2.1-1 N°8242 Allow value to be an array for every filter
* @since 3.2.3 3.3.0 N°8554 new case for ENUM_SANITIZATION_FILTER_MODULE_CODE
*
* @link https://www.php.net/manual/en/filter.filters.sanitize.php PHP sanitization filters
*/
@@ -477,7 +486,7 @@ class utils
);
break;
// For XML / HTML node id selector
// For XML / HTML node selector
case static::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR:
$retValue = filter_var(
$value,
@@ -490,6 +499,15 @@ class utils
$retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value);
break;
case static::ENUM_SANITIZATION_FILTER_MODULE_CODE:
// Module codes allow all alphabets letters, numbers, dash and dot characters
$retValue = filter_var(
$value,
FILTER_VALIDATE_REGEXP,
['options' => ['regexp' => '/^[\p{L}\d.-]+$/u']]
);
break;
// For URL
case static::ENUM_SANITIZATION_FILTER_URL:
$retValue = filter_var($value, FILTER_SANITIZE_URL);
@@ -958,7 +976,7 @@ class utils
return self::$oConfig;
}
$sProductionEnvConfigPath = self::GetConfigFilePath('production');
$sProductionEnvConfigPath = self::GetConfigFilePath(ITOP_DEFAULT_ENV);
if (file_exists($sProductionEnvConfigPath)) {
$oProductionEnvDiskConfig = new Config($sProductionEnvConfigPath);
self::SetConfig($oProductionEnvDiskConfig);
@@ -1284,7 +1302,7 @@ class utils
* @throws \CoreException
* @throws \Exception
*/
public static function ExecITopScript(string $sScriptName, array $aArguments, string $sAuthUser = null, string $sAuthPwd = null)
public static function ExecITopScript(string $sScriptName, array $aArguments, ?string $sAuthUser = null, ?string $sAuthPwd = null)
{
$aDisabled = explode(', ', ini_get('disable_functions'));
if (in_array('exec', $aDisabled)) {
@@ -1374,7 +1392,7 @@ class utils
* @return string A path to a folder into which any module can store cache data
* The corresponding folder is created or cleaned upon code compilation
*/
public static function GetCachePath(string $sEnvironment = null): string
public static function GetCachePath(?string $sEnvironment = null): string
{
if (is_null($sEnvironment)) {
$sEnvironment = MetaModel::GetEnvironment();
@@ -1437,6 +1455,12 @@ class utils
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
/** @var \DBObjectSet $param */
// Check if the user has the right to read the objects of this list, otherwise do not propose any action (eg. configure this list, export, etc.)
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_READ, $param) !== UR_ALLOWED_YES) {
break;
}
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink(true);
$sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
@@ -1900,6 +1924,12 @@ SQL;
return $response;
}
public static function QuoteForPHP(string $sValue): string
{
$sEscaped = str_replace(['\\', "'"], ['\\\\', "\\'"], $sValue);
return "'$sEscaped'";
}
/**
* Get a standard list of character sets
*
@@ -2075,7 +2105,9 @@ SQL;
}
// Remove any remaining nulls (for positions that weren't referenced)
$aReplacements = array_filter($aReplacements, static function ($val) { return $val !== null; });
$aReplacements = array_filter($aReplacements, static function ($val) {
return $val !== null;
});
} else {
// For non-positional, we need to map each position
$aReplacements = [];

View File

@@ -4,7 +4,7 @@
"type": "project",
"license": "AGPL-3.0-only",
"require": {
"php": ">=8.1.0 <8.4.0",
"php": ">=8.1.0 <8.5.0",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -12,37 +12,43 @@
"ext-json": "*",
"ext-mysqli": "*",
"ext-soap": "*",
"apereo/phpcas": "~1.6.0",
"firebase/php-jwt": "^6.4.0",
"apereo/phpcas": "dev-master",
"guzzlehttp/guzzle": "^7.5.1",
"league/oauth2-google": "^4.0.1",
"nikic/php-parser": "dev-master",
"nikic/php-parser": "^4.14.0",
"pear/archive_tar": "~1.4.14",
"pelago/emogrifier": "^7.2.0",
"psr/log": "^3.0.0",
"scssphp/scssphp": "^1.12.1",
"soundasleep/html2text": "~2.1",
"scssphp/scssphp": "dev-combodo/1.x",
"symfony/console": "~6.4.0",
"symfony/dotenv": "~6.4.0",
"symfony/form": "^6.4",
"symfony/framework-bundle": "~6.4.0",
"symfony/http-foundation": "~6.4.0",
"symfony/http-kernel": "~6.4.0",
"symfony/mailer": "^6.4",
"symfony/security-csrf": "^6.4",
"symfony/runtime": "~6.4.0",
"symfony/twig-bundle": "~6.4.0",
"symfony/var-dumper": "~6.4.0",
"symfony/yaml": "~6.4.0",
"symfony/mailer": "~6.4.0",
"tecnickcom/tcpdf": "^6.6.0",
"thenetworg/oauth2-azure": "^2.0"
"thenetworg/oauth2-azure": "^2.0",
"soundasleep/html2text": "~2.1"
},
"require-dev": {
"symfony/debug-bundle": "~6.4.0",
"symfony/stopwatch": "~6.4.0",
"symfony/web-profiler-bundle": "~6.4.0"
},
"repositories": [{
"type": "vcs",
"url": "https://github.com/Combodo/PHP-Parser"
}],
"repositories": [
{
"type": "vcs",
"url": "https://github.com/EsupPortail/phpCAS"
},
{
"type": "vcs",
"url": "https://github.com/combodo-itop-libs/scssphp"
}
],
"suggest": {
"ext-libsodium": "Required to use the AttributeEncryptedString.",
"ext-openssl": "Can be used as a polyfill if libsodium is not installed",
@@ -62,7 +68,10 @@
},
"sort-packages": true,
"classmap-authoritative": true,
"platform-check": true
"platform-check": true,
"allow-plugins": {
"symfony/runtime": true
}
},
"autoload": {
"classmap": [
@@ -71,6 +80,7 @@
"sources"
],
"exclude-from-classmap": [
"application/twigextension.class.inc.php",
"core/oql/build/PHP/",
"core/apc-emulation.php",
"application/startup.inc.php",
@@ -90,7 +100,7 @@
"extra": {
"symfony": {
"allow-contrib": false,
"require": "6.4.*"
"require": "3.4.*"
},
"runtime": {
"dotenv_path": "resources/symfony/.env"

1207
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -234,10 +234,11 @@ abstract class Action extends cmdbAbstractObject
}
$oActionFilter = DBObjectSearch::FromOQL($sActionQueryOql, $aActionQueryParams);
$oActionFilter->AllowAllData();
$oSet = new DBObjectSet($oActionFilter, ['date' => false]);
$sPanelTitle = Dict::Format('Action:last_executions_tab_panel_title', $sActionQueryLimit);
$oExecutionsListBlock = DataTableUIBlockFactory::MakeForResult($oPage, 'action_executions_list', $oSet, ['panel_title' => $sPanelTitle]);
$oExecutionsListBlock = DataTableUIBlockFactory::MakeForResult($oPage, 'action_executions_list', $oSet, ['panel_title' => $sPanelTitle, 'display_unauthorized_objects' => true]);
$oPage->AddUiBlock($oExecutionsListBlock);
}

View File

@@ -24,7 +24,7 @@ MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
MetaModel::IncludeModule('application/audit.domain.class.inc.php');
MetaModel::IncludeModule('application/query.class.inc.php');
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
MetaModel::IncludeModule('setup/moduleinstallation/moduleinstallation.class.inc.php');
MetaModel::IncludeModule('core/event.class.inc.php');
MetaModel::IncludeModule('core/action.class.inc.php');

View File

@@ -199,7 +199,7 @@ class CellStatus_SearchIssue extends CellStatus_Issue
* @param null $sAllowedValues : used for additional message that provides allowed values $sAllowedValues for current class
* @param string|null $sAllowedValuesSearch : used to search all allowed values
*/
public function __construct($sSerializedSearch, $sReason, $sClass = null, $sAllowedValues = null, string $sAllowedValuesSearch = null)
public function __construct($sSerializedSearch, $sReason, $sClass = null, $sAllowedValues = null, ?string $sAllowedValuesSearch = null)
{
parent::__construct(null, null, $sReason);
$this->sSerializedSearch = $sSerializedSearch;
@@ -876,7 +876,7 @@ class BulkChange
return $aResults;
}
protected function CreateObject(&$aResult, $iRow, $aRowData, CMDBChange $oChange = null)
protected function CreateObject(&$aResult, $iRow, $aRowData, ?CMDBChange $oChange = null)
{
$oTargetObj = MetaModel::NewObject($this->m_sClass);
@@ -965,7 +965,7 @@ class BulkChange
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
protected function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, CMDBChange $oChange = null)
protected function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, ?CMDBChange $oChange = null)
{
$aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors);
@@ -1008,7 +1008,7 @@ class BulkChange
*
* @throws \BulkChangeException
*/
protected function UpdateMissingObject(&$aResult, $iRow, $oTargetObj, CMDBChange $oChange = null)
protected function UpdateMissingObject(&$aResult, $iRow, $oTargetObj, ?CMDBChange $oChange = null)
{
$aResult[$iRow] = $this->PrepareMissingObject($oTargetObj, $aErrors);
@@ -1043,7 +1043,7 @@ class BulkChange
}
}
public function Process(CMDBChange $oChange = null)
public function Process(?CMDBChange $oChange = null)
{
if ($oChange) {
CMDBObject::SetCurrentChange($oChange);

View File

@@ -20,9 +20,6 @@
*
*/
use Combodo\iTop\Config\Validator\iTopConfigAstValidator;
use Combodo\iTop\Config\Validator\iTopConfigSyntaxValidator;
define('ITOP_APPLICATION', 'iTop');
define('ITOP_APPLICATION_SHORT', 'iTop');
@@ -78,6 +75,7 @@ define('DEFAULT_EXT_AUTH_VARIABLE', '$_SERVER[\'REMOTE_USER\']');
define('DEFAULT_ENCRYPTION_KEY', '@iT0pEncr1pti0n!'); // We'll use a random generated key later (if possible)
define('DEFAULT_ENCRYPTION_LIB', 'Mcrypt'); // We'll define the best encryption available later
define('DEFAULT_HASH_ALGO', PASSWORD_DEFAULT);
/**
* Config
* configuration data (this class cannot not be localized, because it is responsible for loading the dictionaries)
@@ -211,6 +209,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
],
'allowed_login_types' => [
'type' => 'string',
'description' => 'List of login types allowed (separated by | ): form, external, basic, token',
'default' => DEFAULT_ALLOWED_LOGIN_TYPES,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
],
'app_icon_url' => [
'type' => 'string',
'description' => 'Hyperlink to redirect the user when clicking on the application icon (in the main window, or login/logoff pages)',
@@ -863,6 +869,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'ext_auth_variable' => [
'type' => 'string',
'description' => 'External authentication expression (allowed: $_SERVER[\'key\'], $_COOKIE[\'key\'], $_REQUEST[\'key\'], getallheaders()[\'Header-Name\'])',
'default' => '$_SERVER[\'REMOTE_USER\']',
'value' => '$_SERVER[\'REMOTE_USER\']',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'login_debug' => [
'type' => 'bool',
'description' => 'Activate the login FSM debug',
@@ -920,7 +934,7 @@ class Config
'type' => 'string',
'description' => 'Actions that are available as direct buttons next to the "Actions" menu',
// examples... not used
'default' => 'UI:Menu:Modify,UI:Menu:New',
'default' => 'UI:Menu:Modify,UI:Menu:New,UI:Menu:impacts_down,UI:Menu:impacts_up',
'value' => 'UI:Menu:Modify',
'source_of_value' => '',
'show_in_conf_sample' => true,
@@ -1165,8 +1179,8 @@ class Config
'tracking_level_linked_set_default' => [
'type' => 'integer',
'description' => 'Default tracking level if not explicitly set at the attribute level, for AttributeLinkedSet (defaults to NONE in case of a fresh install, LIST otherwise - this to preserve backward compatibility while upgrading from a version older than 2.0.3 - see TRAC #936)',
'default' => LINKSET_TRACKING_LIST,
'value' => LINKSET_TRACKING_LIST,
'default' => LINKSET_TRACKING_NONE,
'value' => LINKSET_TRACKING_NONE,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
@@ -1605,7 +1619,7 @@ class Config
'show_in_conf_sample' => false,
],
'search_manual_submit' => [
'type' => 'array',
'type' => 'bool',
'description' => 'Force manual submit of search all requests',
'default' => false,
'value' => true,
@@ -1732,6 +1746,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.disable_joined_classes_filter' => [
'type' => 'bool',
'description' => 'If true, scope filters aren\'t applied to joined classes or union classes not directly listed in the SELECT clause.',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.hide_administrators' => [
'type' => 'bool',
'description' => 'If true, non-administrator users will not be able to see the administrator accounts, the Administrator profile and the links between the administrator accounts and their profiles.',
@@ -1740,6 +1762,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.disable_exec_forced_login_for_all_enpoints' => [
'type' => 'bool',
'description' => 'If true, when no delegated authentication module is defined, no login will be forced on modules exec endpoints',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'behind_reverse_proxy' => [
'type' => 'bool',
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',
@@ -1946,16 +1976,6 @@ 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
*/
protected $m_sExtAuthVariable;
/**
* @var string Encryption key used for all attributes of type "encrypted string". Can be set to a random value
* unless you want to import a database from another iTop instance, in which case you must use
@@ -2024,8 +2044,6 @@ 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;
$this->m_iPasswordHashAlgo = DEFAULT_HASH_ALGO;
@@ -2171,8 +2189,6 @@ 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;
$this->m_aCharsets = isset($MySettings['csv_import_charsets']) ? $MySettings['csv_import_charsets'] : [];
@@ -2331,12 +2347,76 @@ class Config
public function GetAllowedLoginTypes()
{
return explode('|', $this->m_sAllowedLoginTypes);
return explode('|', $this->m_aSettings['allowed_login_types']['value']);
}
/**
* @return bool|mixed
* @since 3.2.3 return the parsed value instead of an unsecured variable name
*/
public function GetExternalAuthenticationVariable()
{
return $this->m_sExtAuthVariable;
$sExpression = $this->Get('ext_auth_variable');
$aParsed = $this->ParseExternalAuthVariableExpression($sExpression);
if ($aParsed === null) {
return false;
}
$sKey = $aParsed['key'];
switch ($aParsed['type']) {
case 'server':
return $_SERVER[$sKey] ?? false;
case 'cookie':
return $_COOKIE[$sKey] ?? false;
case 'request':
return $_REQUEST[$sKey] ?? false;
case 'header':
if (!function_exists('getallheaders')) {
return false;
}
$aHeaders = getallheaders();
if (!is_array($aHeaders)) {
return false;
}
return $aHeaders[$sKey] ?? false;
}
return false;
}
/**
* @param $sExpression
* @return array|null
*/
private function ParseExternalAuthVariableExpression($sExpression)
{
// If it's a configuration parameter it's probably already trimmed, but just in case
$sExpression = trim((string) $sExpression);
if ($sExpression === '') {
return null;
}
// Match $_SERVER/$_COOKIE/$_REQUEST['key'] with optional whitespace and single/double quotes.
if (preg_match('/^\$_(SERVER|COOKIE|REQUEST)\s*\[\s*(["\'])\s*([^"\']+)\2\s*\]\s*$/', $sExpression, $aMatches) === 1) {
$sContext = strtoupper($aMatches[1]);
$sKey = $aMatches[3];
return [
'type' => strtolower($sContext),
'key' => $sKey,
'normalized' => '$_'.$sContext.'[\''.$sKey.'\']',
];
}
// Match getallheaders()['Header-Name'] in a case-insensitive way.
if (preg_match('/^getallheaders\(\)\s*\[\s*(["\'])\s*([^"\']+)\1\s*\]\s*$/i', $sExpression, $aMatches) === 1) {
$sKey = $aMatches[2];
return [
'type' => 'header',
'key' => $sKey,
'normalized' => 'getallheaders()[\''.$sKey.'\']',
];
}
return null;
}
public function GetCSVImportCharsets()
@@ -2409,7 +2489,7 @@ class Config
public function SetAllowedLoginTypes($aAllowedLoginTypes)
{
$this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes);
$this->Set('allowed_login_types', implode('|', $aAllowedLoginTypes));
}
/**
@@ -2432,7 +2512,7 @@ class Config
public function SetExternalAuthenticationVariable($sExtAuthVariable)
{
$this->m_sExtAuthVariable = $sExtAuthVariable;
$this->Set('ext_auth_variable', $sExtAuthVariable);
}
public function SetEncryptionKey($sKey)
@@ -2486,8 +2566,6 @@ 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;
$aSettings['csv_import_charsets'] = $this->m_aCharsets;
@@ -2590,8 +2668,6 @@ 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,
'csv_import_charsets' => $this->m_aCharsets,
@@ -2676,14 +2752,13 @@ class Config
*
* @param array $aParamValues
* @param ?string $sModulesDir
* @param bool $bPreserveModuleSettings
*
* @return void The current object is modified directly
*
* @throws \Exception
* @throws \CoreException
*/
public function UpdateFromParams($aParamValues, $sModulesDir = null, $bPreserveModuleSettings = false)
public function UpdateFromParams($aParamValues, $sModulesDir = null)
{
if (isset($aParamValues['application_path'])) {
$this->Set('app_root_url', $aParamValues['application_path']);
@@ -2731,7 +2806,10 @@ class Config
} else {
$aSelectedModules = null;
}
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
if (! is_null($sModulesDir)) {
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
}
if (isset($aParamValues['source_dir'])) {
$this->Set('source_dir', $aParamValues['source_dir']);
@@ -2749,17 +2827,13 @@ class Config
*
* @throws Exception
*/
public function UpdateIncludes($sModulesDir, $aSelectedModules = null)
public function UpdateIncludes(string $sModulesDir, $aSelectedModules = null)
{
if ($sModulesDir === null) {
return;
}
// Initialize the arrays below with default values for the application...
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
$aAddOns = $oEmptyConfig->GetAddOns();
$aModules = ModuleDiscovery::GetAvailableModules([APPROOT.$sModulesDir]);
$aModules = ModuleDiscovery::GetModulesOrderedByDependencies([APPROOT.$sModulesDir]);
foreach ($aModules as $sModuleId => $aModuleInfo) {
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules)) {

View File

@@ -5,6 +5,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\ExportHelper;
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
@@ -13,7 +14,6 @@ use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectUIBlockFactory
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory;
use Combodo\iTop\Application\Helper\ExportHelper;
use Combodo\iTop\Application\WebPage\Page;
use Combodo\iTop\Application\WebPage\WebPage;
@@ -55,6 +55,8 @@ class CSVBulkExport extends TabularBulkExport
$this->aStatusInfo['charset'] = strtoupper(utils::ReadParam('charset', 'UTF-8', true, 'raw_data'));
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
$this->aStatusInfo['ignore_excel_sanitization'] = (bool)utils::ReadParam('ignore_excel_sanitization', 0, true, utils::ENUM_SANITIZATION_FILTER_INTEGER);
$sDateFormatRadio = utils::ReadParam('csv_date_format_radio', '');
switch ($sDateFormatRadio) {
case 'default':
@@ -223,6 +225,10 @@ class CSVBulkExport extends TabularBulkExport
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
$oFieldSetDate->AddSubBlock($oRadioCustom);
$oFieldSetSecurity = FieldSetUIBlockFactory::MakeStandard(Dict::S('Core:BulkExport:Security'));
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oFieldSetSecurity));
$oFieldSetSecurity->AddSubBlock(ExportHelper::GetInputForSanitizeExcelExport());
$oP->add_ready_script(
<<<EOF
$('#form_part_csv_options').on('preview_updated', function() { FormatDatesInPreview('csv', 'csv'); });
@@ -264,6 +270,13 @@ EOF
default:
$sRet = trim($oObj->GetAsCSV($sAttCode), '"');
}
// If the option to ignore Excel sanitization is not set or explicitly set to false, apply sanitization
if (!(array_key_exists('ignore_excel_sanitization', $this->aStatusInfo)) || $this->aStatusInfo['ignore_excel_sanitization'] === false) {
return ExportHelper::SanitizeField($sRet, $this->aStatusInfo['text_qualifier'] ?? '');
}
// The option to ignore Excel sanitization is explicitly set to true: return the raw value without sanitization
return $sRet;
}
@@ -337,6 +350,12 @@ EOF
$sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput, !$this->aStatusInfo['formatted_text']);
}
}
// If the option to ignore Excel sanitization is not set or absent, sanitize the field
if (!(array_key_exists('ignore_excel_sanitization', $this->aStatusInfo)) || $this->aStatusInfo['ignore_excel_sanitization'] === false) {
$sField = ExportHelper::SanitizeField($sField, $this->aStatusInfo['text_qualifier']);
}
if ($this->aStatusInfo['charset'] != 'UTF-8') {
// Note: due to bugs in the glibc library it's safer to call iconv on the smallest possible string
// and thus to convert field by field and not the whole row or file at once (see ticket N°991)

View File

@@ -425,7 +425,7 @@
</php_parent>
<parent>cmdbAbstractObject</parent>
<properties>
<category>core/cmdb,view_in_gui</category>
<category>core/cmdb,grant_by_profile,silo</category>
<abstract>false</abstract>
<key_type>autoincrement</key_type>
<db_table>priv_event_newsroom</db_table>
@@ -904,7 +904,7 @@
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>Event</parent>
<properties>
<category>core/cmdb,view_in_gui</category>
<category>core/cmdb,grant_by_profile,silo</category>
</properties>
<fields>
<field id="message" xsi:type="AttributeText"/>

View File

@@ -2567,7 +2567,7 @@ abstract class DBObject implements iDisplay
*
* @see \RestUtils::FindObjectFromKey for the same check in the REST endpoint
*/
final public function CheckChangedExtKeysValues(callable $oIsObjectLoadableCallback = null)
final public function CheckChangedExtKeysValues(?callable $oIsObjectLoadableCallback = null)
{
if (is_null($oIsObjectLoadableCallback)) {
$oIsObjectLoadableCallback = function ($sClass, $sId) {
@@ -3727,7 +3727,7 @@ abstract class DBObject implements iDisplay
* @throws \MySQLException
* @throws \OQLException
*/
private function ActivateOnObjectUpdateTriggers(?DBObject $oObject, array $aAttributes = null): void
private function ActivateOnObjectUpdateTriggers(?DBObject $oObject, ?array $aAttributes = null): void
{
if (is_null($oObject)) {
return;
@@ -5108,8 +5108,8 @@ abstract class DBObject implements iDisplay
protected function GetReferencingObjectsForDeletion($bAllowAllData = false)
{
$aDependentObjects = [];
$aRererencingMe = MetaModel::EnumReferencingClasses(get_class($this));
foreach ($aRererencingMe as $sRemoteClass => $aExtKeys) {
$aReferencingMe = MetaModel::EnumReferencingClasses(get_class($this));
foreach ($aReferencingMe as $sRemoteClass => $aExtKeys) {
/** @var \AttributeExternalKey $oExtKeyAttDef */
foreach ($aExtKeys as $sExtKeyAttCode => $oExtKeyAttDef) {
// skip if external key doesn't require the deletion cascading

View File

@@ -1932,4 +1932,37 @@ class DBObjectSearch extends DBSearch
{
return $this->GetCriteria()->ListParameters();
}
/**
* @inheritDoc
* @return DBObjectSearch
*/
protected function ApplyDataFilters(): DBObjectSearch
{
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
return $this;
}
$oSearch = $this;
$aClassesToFilter = $this->GetSelectedClasses();
// Opt-in for joined classes filtering, otherwise only filter the selected class(es)
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === false) {
$aClassesToFilter = $this->GetJoinedClasses();
}
// Apply filter (this is similar to the one in DBSearch but the factorization could make it less readable)
foreach ($aClassesToFilter as $sClassAlias => $sClass) {
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
if ($oVisibleObjects === false) {
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
}
if (is_object($oVisibleObjects)) {
$oVisibleObjects->AllowAllData();
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
$oSearch->SetDataFiltered();
}
}
return $oSearch;
}
}

View File

@@ -650,7 +650,7 @@ abstract class DBSearch
*
* @throws OQLException
*/
public static function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel = null)
public static function FromOQL($sQuery, $aParams = null, ?ModelReflection $oMetaModel = null)
{
if (empty($sQuery)) {
return null;
@@ -1048,21 +1048,7 @@ abstract class DBSearch
*/
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null, $aSelectExpr = null)
{
$oSearch = $this;
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered()) {
foreach ($this->GetSelectedClasses() as $sClassAlias => $sClass) {
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
if ($oVisibleObjects === false) {
// Make sure this is a valid search object, saying NO for all
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
}
if (is_object($oVisibleObjects)) {
$oVisibleObjects->AllowAllData();
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
$oSearch->SetDataFiltered();
}
}
}
$oSearch = $this->ApplyDataFilters();
if (is_array($aGroupByExpr)) {
foreach ($aGroupByExpr as $sAlias => $oGroupByExp) {
@@ -1524,4 +1510,33 @@ abstract class DBSearch
* @return array{\VariableExpression}
*/
abstract public function GetExpectedArguments(): array;
/**
* Apply data filters to the search, if needed
*
* @return DBSearch
* @throws CoreException
*/
protected function ApplyDataFilters(): DBSearch
{
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
return $this;
}
$oSearch = $this;
$aClassesToFilter = $this->GetSelectedClasses();
foreach ($aClassesToFilter as $sClassAlias => $sClass) {
$oVisibleObjects = UserRights::GetSelectFilter($sClass, $this->GetModifierProperties('UserRightsGetSelectFilter'));
if ($oVisibleObjects === false) {
$oVisibleObjects = DBObjectSearch::FromEmptySet($sClass);
}
if (is_object($oVisibleObjects)) {
$oVisibleObjects->AllowAllData();
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
$oSearch->SetDataFiltered();
}
}
return $oSearch;
}
}

View File

@@ -59,7 +59,7 @@ class DBUnionSearch extends DBSearch
public function AllowAllData($bAllowAllData = true)
{
foreach ($this->aSearches as $oSearch) {
$oSearch->AllowAllData();
$oSearch->AllowAllData($bAllowAllData);
}
}
@@ -673,4 +673,30 @@ class DBUnionSearch extends DBSearch
return $aVariableCriteria;
}
/**
* @inheritDoc
* @return DBUnionSearch
*/
protected function ApplyDataFilters(): DBUnionSearch
{
if ($this->IsAllDataAllowed() || $this->IsDataFiltered()) {
return $this;
}
// Opt-in for joined classes filtering, otherwise fallback on DBSearch filtering
if (MetaModel::GetConfig()->Get('security.disable_joined_classes_filter') === true) {
return parent::ApplyDataFilters();
}
// Apply filters per sub-search
$aFilteredSearches = [];
foreach ($this->GetSearches() as $oSubSearch) {
// Recursively call ApplyDataFilters on sub-searches
$aFilteredSearches[] = $oSubSearch->ApplyDataFilters();
}
$oSearch = new DBUnionSearch($aFilteredSearches);
return $oSearch;
}
}

View File

@@ -41,7 +41,7 @@ use utils;
/**
* Class \Combodo\iTop\DesignDocument
*
* A design document is the DOM tree that modelize behaviors. One of its
* A design document is the DOM tree that models behaviors. One of its
* characteristics is that it can be altered by the mean of the same kind of document.
*
*/
@@ -360,10 +360,10 @@ class DesignElement extends \DOMElement
* @param string $sTagName
* @param string|null $sDefault
*
* @return string
* @return null|string
* @throws \DOMFormatException
*/
public function GetChildText($sTagName, $sDefault = null)
public function GetChildText($sTagName, $sDefault = null): ?string
{
$sRet = $sDefault;
if ($oChild = $this->GetOptionalElement($sTagName)) {
@@ -453,7 +453,7 @@ class DesignElement extends \DOMElement
* @throws Exception
* @since 3.1.2 3.2.0 N°6974
*/
public static function _FindNode(DOMNode $oParent, DesignElement $oRefNode, string $sSearchId = null): ?DesignElement
public static function _FindNode(DOMNode $oParent, DesignElement $oRefNode, ?string $sSearchId = null): ?DesignElement
{
$oNodes = self::_FindNodes($oParent, $oRefNode, $sSearchId);
if ($oNodes instanceof DOMNodeList) {
@@ -477,7 +477,7 @@ class DesignElement extends \DOMElement
* @return \DOMNodeList|false|mixed
* @since 3.1.2 3.2.0 N°6974
*/
public static function _FindNodes(DOMNode $oParent, DesignElement $oRefNode, string $sSearchId = null)
public static function _FindNodes(DOMNode $oParent, DesignElement $oRefNode, ?string $sSearchId = null)
{
if ($oParent instanceof DOMDocument) {
$oDoc = $oParent->firstChild->ownerDocument;

View File

@@ -632,7 +632,7 @@ class DisplayableGroupNode extends DisplayableNode
$this->aObjects = [];
}
public function AddObject(DBObject $oObj = null)
public function AddObject(?DBObject $oObj = null)
{
if (is_object($oObj)) {
$sPrevClass = $this->GetObjectClass();

View File

@@ -26,7 +26,7 @@ class Event extends DBObject implements iDisplay
{
$aParams =
[
"category" => "core/cmdb,view_in_gui",
"category" => "core/cmdb,grant_by_profile,silo",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
@@ -120,7 +120,7 @@ class EventNotification extends Event
{
$aParams =
[
"category" => "core/cmdb,view_in_gui",
"category" => "core/cmdb,grant_by_profile,silo",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
@@ -156,7 +156,7 @@ class EventNotificationEmail extends EventNotification
{
$aParams =
[
"category" => "core/cmdb,view_in_gui",
"category" => "core/cmdb,grant_by_profile,silo",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
@@ -192,7 +192,7 @@ class EventIssue extends Event
{
$aParams =
[
"category" => "core/cmdb,view_in_gui",
"category" => "core/cmdb,grant_by_profile,silo",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
@@ -286,7 +286,7 @@ class EventWebService extends Event
{
$aParams =
[
"category" => "core/cmdb,view_in_gui",
"category" => "core/cmdb,grant_by_profile,silo",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
@@ -321,7 +321,7 @@ class EventRestService extends Event
{
$aParams =
[
"category" => "core/cmdb,view_in_gui",
"category" => "core/cmdb,grant_by_profile,silo",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
@@ -356,7 +356,7 @@ class EventLoginUsage extends Event
{
$aParams =
[
"category" => "core/cmdb,view_in_gui",
"category" => "core/cmdb,grant_by_profile,silo",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
@@ -394,7 +394,7 @@ class EventOnObject extends Event
{
$aParams =
[
"category" => "core/cmdb,view_in_gui",
"category" => "core/cmdb,grant_by_profile,silo",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",

View File

@@ -5,13 +5,13 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\ExportHelper;
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory;
use Combodo\iTop\Application\Helper\ExportHelper;
use Combodo\iTop\Application\WebPage\Page;
use Combodo\iTop\Application\WebPage\WebPage;
@@ -63,6 +63,8 @@ class ExcelBulkExport extends TabularBulkExport
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
}
$this->aStatusInfo['ignore_excel_sanitization'] = (bool)utils::ReadParam('ignore_excel_sanitization', 0, true, utils::ENUM_SANITIZATION_FILTER_INTEGER);
}
public function EnumFormParts()
@@ -121,6 +123,10 @@ class ExcelBulkExport extends TabularBulkExport
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
$oFieldSetDate->AddSubBlock($oRadioCustom);
$oFieldSetSecurity = FieldSetUIBlockFactory::MakeStandard(Dict::S('Core:BulkExport:Security'));
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oFieldSetSecurity));
$oFieldSetSecurity->AddSubBlock(ExportHelper::GetInputForSanitizeExcelExport());
$oP->add_ready_script(
<<<EOF
$('#form_part_xlsx_options').on('preview_updated', function() { FormatDatesInPreview('excel', 'xlsx'); });
@@ -216,6 +222,12 @@ EOF
}
}
}
// If the option to ignore Excel sanitization is not set or absent, sanitize the field
if (!(array_key_exists('ignore_excel_sanitization', $this->aStatusInfo)) || $this->aStatusInfo['ignore_excel_sanitization'] === false) {
return ExportHelper::SanitizeField($sRet, '');
}
return $sRet;
}

View File

@@ -259,13 +259,18 @@ class InlineImage extends DBObject
* that refer to an InlineImage (detected via the attribute data-img-id="") so that
* the URL is consistent with the current URL of the application.
*
* @param string $sHtml The HTML fragment to process
N°8681 * @param string|null $sHtml The HTML fragment to process
*
* @return string The modified HTML
* @throws \Exception
*
* @since 3.3.0 N°8681 Add type hint for parameters and return value
*/
public static function FixUrls($sHtml)
public static function FixUrls(string|null $sHtml): string
{
// N°8681 - Ensure to have a string value
$sHtml = $sHtml ?? '';
$aNeedles = [];
$aReplacements = [];
// Find img tags with an attribute data-img-id
@@ -293,6 +298,46 @@ class InlineImage extends DBObject
return $sHtml;
}
/**
* Replace <img> tags with a data-img-id attribute by the actual image in base64 representation
* so that the image can be displayed even if the download URL is not accessible (e.g. in unauthenticated approval templates)
*
* @param string $sHtml The HTML fragment to process
*
* @return String The modified HTML
* @since 3.2.3
*/
public static function ReplaceInlineImagesWithBase64Representation(string $sHtml): String
{
return preg_replace_callback(
'/<img\s+[^>]*'.static::DOM_ATTR_ID.'="(\d+)"[^>]*>/i',
function ($matches) {
// Extract inline image ID from the tag
$id = $matches[1];
try {
// Retrieve inline image
$oInline = MetaModel::GetObject(InlineImage::class, $id, true, true);
$oOrmDocument = $oInline->Get('contents');
// Replace src image by the base64 representation
$sInlineImageAsBase64 = base64_encode($oOrmDocument->GetData());
$sDataUri = 'data:'.$oOrmDocument->GetMimeType().';base64,'.$sInlineImageAsBase64;
$sImage = preg_replace('/src=["\'][^"\']+["\']/', 'src="'.$sDataUri.'"', $matches[0]);
// Remove sensitive information (the image ID and secret) from the tag
$sImage = preg_replace('/'.static::DOM_ATTR_ID.'="\d+"\s+'.static::DOM_ATTR_SECRET.'="\w+"/', '', $sImage);
} catch (Exception $e) {
$sImage = '<img src="" alt="'.Dict::S('UI:MissingInlineImage').'">';
}
return $sImage;
},
$sHtml
);
}
/**
* Add an extra attribute data-img-id for images which are based on an actual InlineImage
* so that we can later reconstruct the full "src" URL when needed
@@ -389,7 +434,7 @@ JS
* Resize an image so that it fits the maximum width/height defined in the config file
* @param ormDocument $oImage The original image stored as an array (content / mimetype / filename)
* @return ormDocument The resampled image (or the original one if it already fit)
* @deprecated Replaced by ormDocument::ResizeImageToFit
* @deprecated since 3.3.0 Replaced by ormDocument::ResizeImageToFit
*/
public static function ResizeImageToFit(ormDocument $oImage, &$aDimensions = null)
{

View File

@@ -1,18 +1,20 @@
<?php
/**
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\EventRegister\ApplicationEvents;
use Combodo\iTop\Core\Kpi\KpiLogData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\Events\iEventServiceSetup;
use Combodo\iTop\Service\Module\ModuleService;
/**
* Measures operations duration, memory usage, etc. (and some other KPIs)
*/
class ExecutionKPI
class ExecutionKPI implements iEventServiceSetup
{
protected static $m_bEnabled_Duration = false;
protected static $m_bEnabled_Memory = false;
@@ -23,15 +25,18 @@ class ExecutionKPI
protected static $m_aStats = []; // Recurrent operations
protected static $m_aExecData = []; // One shot operations
/**
* @var array[ExecutionKPI]
*/
protected static $m_aExecutionStack = []; // embedded execution stats
/** @var true */
private static bool $bMetamodelStarted = false;
private static ?float $fLastReportTime = null;
private static ?float $iLastReportMemory = null;
// For stats
protected $m_fStarted = null;
protected $m_fChildrenDuration = 0; // Count embedded
protected $m_iInitialMemory = null;
private static array $aBootstrapOperations = [];
public static function EnableDuration($iLevel)
{
if ($iLevel > 0) {
@@ -71,6 +76,7 @@ class ExecutionKPI
return true;
}
}
return false;
}
@@ -97,7 +103,7 @@ class ExecutionKPI
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
$sSlowQueries = '';
if (self::$m_fSlowQueries > 0) {
$sSlowQueries = ". Slow Queries: ".self::$m_fSlowQueries."s";
$sSlowQueries = '. Slow Queries: '.self::$m_fSlowQueries.'s';
}
$aExtensions = [];
@@ -127,7 +133,7 @@ class ExecutionKPI
$sRequest .= ' operation: '.$_POST['operation'];
}
$fStop = MyHelpers::getmicrotime();
$fStop = microtime(true);
if (($fStop - $fItopStarted) > self::$m_fSlowQueries) {
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
@@ -151,17 +157,17 @@ class ExecutionKPI
$sTableStyle = 'background-color: #ccc; margin: 10px;';
$sHtml = "<hr/>";
$sHtml = '<hr/>';
$sHtml .= "<div style=\"background-color: grey; padding: 10px;\">";
$sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - $sRequest</h3>";
$oStarted = DateTime::createFromFormat('U.u', $fItopStarted);
$sHtml .= '<p>'.$oStarted->format('Y-m-d H:i:s.u').'</p>';
$sHtml .= "<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>";
$sHtml .= "<div>";
$sHtml .= '<p>log_kpi_user_id: '.UserRights::GetUserId().'</p>';
$sHtml .= '<div>';
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
$sHtml .= "<thead>";
$sHtml .= " <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>";
$sHtml .= "</thead>";
$sHtml .= '<thead>';
$sHtml .= ' <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>';
$sHtml .= '</thead>';
foreach (self::$m_aExecData as $aOpStats) {
$sOperation = $aOpStats['op'];
$sBegin = round($aOpStats['time_begin'], 3);
@@ -180,12 +186,12 @@ class ExecutionKPI
}
}
$sHtml .= "<tr>";
$sHtml .= '<tr>';
$sHtml .= " <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>";
$sHtml .= "</tr>";
$sHtml .= '</tr>';
}
$sHtml .= "</table>";
$sHtml .= "</div>";
$sHtml .= '</table>';
$sHtml .= '</div>';
$aConsolidatedStats = [];
foreach (self::$m_aStats as $sOperation => $aOpStats) {
@@ -208,20 +214,20 @@ class ExecutionKPI
}
}
$aConsolidatedStats[$sOperation] = [
'count' => $iTotalOp,
'count' => $iTotalOp,
'duration' => $fTotalOp,
'min' => $fMinOp,
'max' => $fMaxOp,
'avg' => $fTotalOp / $iTotalOp,
'min' => $fMinOp,
'max' => $fMaxOp,
'avg' => $fTotalOp / $iTotalOp,
'max_args' => $sMaxOpArguments,
];
}
$sHtml .= "<div>";
$sHtml .= '<div>';
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
$sHtml .= "<thead>";
$sHtml .= " <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>";
$sHtml .= "</thead>";
$sHtml .= '<thead>';
$sHtml .= ' <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>';
$sHtml .= '</thead>';
foreach ($aConsolidatedStats as $sOperation => $aOpStats) {
$sOperation = '<a href="#'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
$sCount = $aOpStats['count'];
@@ -230,14 +236,14 @@ class ExecutionKPI
$sMax = '<a href="#'.md5($sExecId.$aOpStats['max_args']).'">'.round($aOpStats['max'], 3).'</a>';
$sAvg = round($aOpStats['avg'], 3);
$sHtml .= "<tr>";
$sHtml .= '<tr>';
$sHtml .= " <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>";
$sHtml .= "</tr>";
$sHtml .= '</tr>';
}
$sHtml .= "</table>";
$sHtml .= "</div>";
$sHtml .= '</table>';
$sHtml .= '</div>';
$sHtml .= "</div>";
$sHtml .= '</div>';
$sHtml .= "<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>";
@@ -287,18 +293,18 @@ class ExecutionKPI
$sOperationHtml = '<a name="'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
$sHtml .= "<h4>$sOperationHtml</h4>";
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
$sHtml .= "<thead>";
$sHtml .= " <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>";
$sHtml .= "</thead>";
$sHtml .= '<thead>';
$sHtml .= ' <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>';
$sHtml .= '</thead>';
$bDisplayHeader = false;
}
$sHtml .= "<tr>";
$sHtml .= '<tr>';
$sHtml .= " <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>";
$sHtml .= "</tr>";
$sHtml .= '</tr>';
}
}
if (!$bDisplayHeader) {
$sHtml .= "</table>";
$sHtml .= '</table>';
$sHtml .= "<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>";
}
self::Report($sHtml);
@@ -333,39 +339,50 @@ class ExecutionKPI
$aNewEntry = null;
$fStarted = $this->m_fStarted;
$fStopped = $this->m_fStarted;
if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime();
$aNewEntry = [
'op' => $sOperationDesc,
'time_begin' => $this->m_fStarted - $fItopStarted,
'time_end' => $fStopped - $fItopStarted,
];
// Reset for the next operation (if the object is recycled)
$this->m_fStarted = $fStopped;
if (is_null(static::$fLastReportTime)) {
static::$fLastReportTime = $fItopStarted;
}
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
$iCurrentMemory = 0;
$iPeakMemory = 0;
if (is_null(static::$iLastReportMemory)) {
global $iItopInitialMemory;
static::$iLastReportMemory = $iItopInitialMemory;
}
$fStarted = static::$fLastReportTime;
$fStopped = microtime(true);
if (self::$m_bEnabled_Duration) {
$aNewEntry = [
'op' => $sOperationDesc,
'time_begin' => $fStarted - $fItopStarted,
'time_end' => $fStopped - $fItopStarted,
];
}
static::$fLastReportTime = $fStopped;
$iInitialMemory = static::$iLastReportMemory;
$iCurrentMemory = $iInitialMemory;
$iPeakMemory = $iInitialMemory;
if (self::$m_bEnabled_Memory) {
$iCurrentMemory = self::memory_get_usage();
if (is_null($aNewEntry)) {
$aNewEntry = ['op' => $sOperationDesc];
}
$aNewEntry['mem_begin'] = $this->m_iInitialMemory;
$aNewEntry['mem_begin'] = $iInitialMemory;
$aNewEntry['mem_end'] = $iCurrentMemory;
$iPeakMemory = self::memory_get_peak_usage();
$aNewEntry['mem_peak'] = $iPeakMemory;
// Reset for the next operation (if the object is recycled)
$this->m_iInitialMemory = $iCurrentMemory;
static::$iLastReportMemory = $iCurrentMemory;
}
if (self::$m_bEnabled_Duration || self::$m_bEnabled_Memory) {
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$aCallstack = ['callstack' => $this->GetCallStack()];
if (static::$bMetamodelStarted) {
foreach (static::$aBootstrapOperations as $oLog) {
$this->LogOperation($oLog);
}
static::$aBootstrapOperations = [];
// Invoke extensions to log the KPI operation
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
$oKPILogData = new KpiLogData(
KpiLogData::TYPE_REPORT,
@@ -376,9 +393,24 @@ class ExecutionKPI
$sExtension,
$iInitialMemory,
$iCurrentMemory,
$iPeakMemory
$iPeakMemory,
$aCallstack
);
$oExtensionInstance->LogOperation($oKPILogData);
$this->LogOperation($oKPILogData);
} else {
$oKPILogData = new KpiLogData(
KpiLogData::TYPE_REPORT,
'Step',
$sOperationDesc,
$fStarted,
$fStopped,
'',
$iInitialMemory,
$iCurrentMemory,
$iPeakMemory,
$aCallstack
);
static::$aBootstrapOperations[] = $oKPILogData;
}
}
@@ -388,13 +420,21 @@ class ExecutionKPI
$this->ResetCounters();
}
private function LogOperation(KpiLogData $oKPILogData): void
{
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$oExtensionInstance->LogOperation($oKPILogData);
}
}
/**
* Compute statistics for a call to an extension
* Note: not working in dev mode (with links to env-production)
*
* @param object|string $object object called
* @param string $sMethod method called on the object
* @param string $sMessage additional message
* @param string $sMethod method called on the object
* @param string $sMessage additional message
*
* @return bool true if an extension was found for this object::method
* @throws \ReflectionException
@@ -423,21 +463,23 @@ class ExecutionKPI
$fDuration = 0;
if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime();
$fStopped = microtime(true);
$fDuration = $fStopped - $this->m_fStarted;
$aCallstack = [];
if (self::$m_bGenerateLegacyReport) {
if (self::$m_bBlameCaller) {
$aCallstack = MyHelpers::get_callstack(1);
self::$m_aStats[$sOperation][$sArguments][] = [
'time' => $fDuration,
'callers' => $aCallstack,
'time' => $fDuration,
'callers' => $aCallstack,
];
} else {
self::$m_aStats[$sOperation][$sArguments][] = [
'time' => $fDuration,
'time' => $fDuration,
];
}
} else {
$aCallstack = ['callstack' => $this->GetCallStack()];
}
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
@@ -448,33 +490,45 @@ class ExecutionKPI
$iPeakMemory = self::memory_get_peak_usage();
}
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
//$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
$sExtension = '';
if (static::$bMetamodelStarted) {
foreach (static::$aBootstrapOperations as $oLog) {
$this->LogOperation($oLog);
}
static::$aBootstrapOperations = [];
$oKPILogData = new KpiLogData(
KpiLogData::TYPE_STATS,
$sOperation,
$sArguments,
$this->m_fStarted,
$fStopped,
$sExtension,
'',
$iInitialMemory,
$iCurrentMemory,
$iPeakMemory,
$aCallstack
);
$oExtensionInstance->LogOperation($oKPILogData);
$this->LogOperation($oKPILogData);
} else {
$oKPILogData = new KpiLogData(
KpiLogData::TYPE_STATS,
$sOperation,
$sArguments,
$this->m_fStarted,
$fStopped,
'',
$iInitialMemory,
$iCurrentMemory,
$iPeakMemory,
$aCallstack
);
static::$aBootstrapOperations[] = $oKPILogData;
}
}
}
protected function ResetCounters()
{
if (self::$m_bEnabled_Duration) {
$this->m_fStarted = microtime(true);
}
$this->m_fStarted = microtime(true);
if (self::$m_bEnabled_Memory) {
$this->m_iInitialMemory = self::memory_get_usage();
@@ -503,7 +557,33 @@ class ExecutionKPI
if (function_exists('memory_get_peak_usage')) {
return memory_get_peak_usage($bRealUsage);
}
// PHP > 5.2.1 - this verb depends on a compilation option
return 0;
}
/*
* ModuleHandlerApiInterface methods
*/
public static function OnMetaModelStarted()
{
static::$bMetamodelStarted = true;
}
public function RegisterEventsAndListeners()
{
EventService::RegisterListener(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED, [$this, 'OnMetaModelStarted']);
}
private function GetCallStack(): string
{
$aCallStack = MyHelpers::get_callstack(2);
$sCallStack = "Call stack:\n";
foreach ($aCallStack as $index => $aLine) {
$sCallStack .= "#$index ".$aLine['File'].'('.$aLine['Line'].'): '.$aLine['Function']."\n";
}
return $sCallStack;
}
}

View File

@@ -691,6 +691,28 @@ abstract class LogAPI
static::$m_oMockMetaModelConfig = $oMetaModelConfig;
}
public static function Exception(string $sMessage, throwable $oException, ?string $sChannel = null, array $aContext = []): void
{
$aErrorLogs = [];
$aErrorLogs[] = static::PrepareErrorLog($sMessage, $oException, $aContext);
$oException = $oException->getPrevious();
while ($oException !== null) {
$aErrorLogs[] = static::PrepareErrorLog($oException->getMessage(), $oException, $aContext, true);
$oException = $oException->getPrevious();
}
$aErrorLogs = array_reverse($aErrorLogs);
foreach ($aErrorLogs as $aErrorLog) {
static::Error($aErrorLog['message'], $sChannel, $aErrorLog['context']);
}
}
private static function PrepareErrorLog(string $sMessage, throwable $oException, array $aContext, bool $isPrevious = false): array
{
$aContext['Error Message'] = $oException->getMessage();
$aContext['Stack Trace'] = $oException->getTraceAsString();
return ['message' => ($isPrevious ? "Previous " : '')."Exception: $sMessage", 'context' => $aContext];
}
public static function Error($sMessage, $sChannel = null, $aContext = [])
{
static::Log(self::LEVEL_ERROR, $sMessage, $sChannel, $aContext);

View File

@@ -22,6 +22,8 @@ use Combodo\iTop\Application\EventRegister\ApplicationEvents;
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Setup\ModuleDependency\Module;
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
require_once APPROOT.'core/modulehandler.class.inc.php';
require_once APPROOT.'core/querymodifier.class.inc.php';
@@ -128,7 +130,7 @@ abstract class MetaModel
/** @var array */
private static $m_aClassToFile = [];
/** @var string */
protected static $m_sEnvironment = 'production';
protected static $m_sEnvironment = ITOP_DEFAULT_ENV;
/**
* Objects currently created/updated.
@@ -462,6 +464,43 @@ abstract class MetaModel
return call_user_func([$sClass, 'GetClassDescription'], $sClass);
}
/**
* @param string $sClass
*
* @return string
* @throws \CoreException
*/
final public static function GetModuleName($sClass)
{
try {
$oReflectionClass = new ReflectionClass($sClass);
$sDir = realpath(dirname($oReflectionClass->getFileName()));
$sApproot = realpath(APPROOT);
while (($sDir !== $sApproot) && (str_contains($sDir, $sApproot))) {
$aFiles = glob("$sDir/module.*.php");
if (count($aFiles) > 1) {
return 'core';
}
if (count($aFiles) == 0) {
$sDir = realpath(dirname($sDir));
continue;
}
$sModuleFilePath = $aFiles[0];
$aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);
$sModuleId = $aModuleInfo[ModuleFileReader::MODULE_INFO_ID];
list($sModuleName, ) = ModuleDiscovery::GetModuleName($sModuleId);
return $sModuleName;
}
} catch (\Exception $e) {
throw new CoreException("Cannot find class module", ['class' => $sClass], '', $e);
}
return 'core';
}
/**
* @param string $sClass
*
@@ -5709,7 +5748,7 @@ abstract class MetaModel
* @throws \DictExceptionUnknownLanguage
* @throws \Exception
*/
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = 'production')
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = ITOP_DEFAULT_ENV)
{
// Startup on a new environment is not supported
static $bStarted = false;

View File

@@ -55,6 +55,11 @@ abstract class ModelReflection
abstract public function GetFiltersList($sClass);
abstract public function IsValidFilterCode($sClass, $sFilterCode);
/**
* @since 3.3.0
*/
abstract public function IsAbstract($sClass): bool;
/**
* @param string $sOQL
*
@@ -148,7 +153,7 @@ class ModelReflectionRuntime extends ModelReflection
$sAttributeClass = get_class($oAttDef);
if ($aScope != null) {
foreach ($aScope as $sScopeClass) {
if (($sAttributeClass == $sScopeClass) || is_subclass_of($sAttributeClass, $sScopeClass)) {
if (is_a($sAttributeClass, $sScopeClass, true)) {
$aAttributes[$sAttCode] = $sAttributeClass;
break;
}
@@ -230,6 +235,11 @@ class ModelReflectionRuntime extends ModelReflection
return MetaModel::IsValidFilterCode($sClass, $sFilterCode);
}
public function IsAbstract($sClass): bool
{
return MetaModel::IsAbstract($sClass);
}
public function GetQuery($sOQL)
{
return new QueryReflectionRuntime($sOQL, $this);

View File

@@ -1656,7 +1656,7 @@ class PHP_ParserGenerator_Data
function emit_code($out, PHP_ParserGenerator_Rule $rp, &$lineno)
{
$linecnt = 0;
/* Generate code to do the reduce action */
if ($rp->code) {
$this->tplt_linedir($out, $rp->line, $this->filename);

View File

@@ -2103,12 +2103,18 @@ class VariableExpression extends UnaryExpression
/**
* Evaluate the value of the expression
*
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
*
* @return mixed
* @throws \MissingQueryArgument
*/
public function Evaluate(array $aArgs)
{
throw new Exception('not implemented yet');
if (!isset($aArgs[$this->m_sName])) {
throw new MissingQueryArgument('Missing variable expression argument', array('expecting'=>$this->m_sName));
}
return $aArgs[$this->m_sName];
}
/**

View File

@@ -1084,7 +1084,7 @@ static public $yy_action = array(
function yy_find_shift_action($iLookAhead)
{
$stateno = $this->yystack[$this->yyidx]->stateno;
/* if ($this->yyidx < 0) return self::YY_NO_ACTION; */
if (!isset(self::$yy_shift_ofst[$stateno])) {
// no shift actions
@@ -1767,7 +1767,7 @@ throw new OQLParserParseFailureException($this->m_sSourceQuery, $this->m_iLine,
function yy_syntax_error($yymajor, $TOKEN)
{
#line 25 "..\oql-parser.y"
throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
#line 1779 "..\oql-parser.php"
}
@@ -1806,7 +1806,7 @@ throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $
// $yyact; /* The parser action. */
// $yyendofinput; /* True if we are at the end of input */
$yyerrorhit = 0; /* True if yymajor has invoked an error */
/* (re)initialize the parser, if necessary */
if ($this->yyidx === null || $this->yyidx < 0) {
/* if ($yymajor == 0) return; // not sure why this was here... */
@@ -1819,7 +1819,7 @@ throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $
array_push($this->yystack, $x);
}
$yyendofinput = ($yymajor==0);
if (self::$yyTraceFILE) {
fprintf(
self::$yyTraceFILE,
@@ -1828,7 +1828,7 @@ throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $
self::$yyTokenName[$yymajor]
);
}
do {
$yyact = $this->yy_find_shift_action($yymajor);
if ($yymajor < self::YYERRORSYMBOL
@@ -2002,7 +2002,7 @@ class OQLParser extends OQLParserRaw
$this->m_sSourceQuery = $sQuery;
// no constructor - parent::__construct();
}
public function doParse($token, $value, $iCurrPosition = 0)
{
$this->m_iColPrev = $this->m_iCol;
@@ -2016,7 +2016,7 @@ class OQLParser extends OQLParserRaw
$this->doParse(0, 0);
return $this->my_result;
}
public function __destruct()
{
// Bug in the original destructor, causing an infinite loop !

View File

@@ -72,9 +72,15 @@ class OQLException extends CoreException
}
else
{
$sExpectations = '{'.implode(', ', $this->m_aExpecting).'}';
$sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput'";
if (count($this->m_aExpecting) < 30) {
$sExpectations = '{'.implode(', ', $this->m_aExpecting).'}';
$sMessage .= ', expecting '.json_encode($sExpectations);
}
$sSuggest = self::FindClosestString($this->m_sUnexpected, $this->m_aExpecting);
$sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput', expecting $sExpectations, I would suggest to use '$sSuggest'";
if (strlen($sSuggest) > 0) {
$sMessage .= ", I would suggest to use ".json_encode($sSuggest);
}
}
// make sure everything is assigned properly
@@ -155,5 +161,3 @@ class OQLException extends CoreException
return $sRet;
}
}
?>

View File

@@ -57,15 +57,15 @@ class OqlName
{
return $this->m_iPos;
}
public function __toString()
{
return $this->m_sValue;
}
}
}
/**
*
*
* Store hexadecimal values as strings so that we can support 64-bit values
*
*/
@@ -77,12 +77,12 @@ class OqlHexValue
{
$this->m_sValue = $sValue;
}
public function __toString()
{
return $this->m_sValue;
}
}
class OqlJoinSpec
@@ -109,6 +109,7 @@ class OqlJoinSpec
{
return $this->m_oClass->GetValue();
}
public function GetClassAlias()
{
return $this->m_oClassAlias->GetValue();
@@ -118,6 +119,7 @@ class OqlJoinSpec
{
return $this->m_oClass;
}
public function GetClassAliasDetails()
{
return $this->m_oClassAlias;
@@ -127,10 +129,12 @@ class OqlJoinSpec
{
return $this->m_oLeftField;
}
public function GetRightField()
{
return $this->m_oRightField;
}
public function GetOperator()
{
return $this->m_sOperator;
@@ -146,8 +150,9 @@ interface CheckableExpression
* @param ModelReflection $oModelReflection MetaModel to consider
* @param array $aAliases Aliases to class names (for the current query)
* @param string $sSourceQuery For the reporting
*
* @throws OqlNormalizeException
*/
*/
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery);
}
@@ -168,13 +173,11 @@ class MatchOqlExpression extends MatchExpression implements CheckableExpression
$this->m_oRightExpr->Check($oModelReflection, $aAliases, $sSourceQuery);
// Only field MATCHES scalar is allowed
if (!$this->m_oLeftExpr instanceof FieldExpression)
{
if (!$this->m_oLeftExpr instanceof FieldExpression) {
throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oLeftExpr->RenderExpression(true), 0));
}
// Only field MATCHES scalar is allowed
if (!$this->m_oRightExpr instanceof ScalarExpression && !$this->m_oRightExpr instanceof VariableOqlExpression)
{
if (!$this->m_oRightExpr instanceof ScalarExpression && !$this->m_oRightExpr instanceof VariableOqlExpression) {
throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oRightExpr->RenderExpression(true), 0));
}
}
@@ -198,7 +201,7 @@ class NestedQueryOqlExpression extends NestedQueryExpression implements Checkabl
*
* @param OQLObjectQuery $oOQLObjectQuery
*/
public function __construct($oOQLObjectQuery )
public function __construct($oOQLObjectQuery)
{
parent::__construct($oOQLObjectQuery->ToDBSearch(""));
$this->m_oOQLObjectQuery = $oOQLObjectQuery;
@@ -232,8 +235,7 @@ class FieldOqlExpression extends FieldExpression implements CheckableExpression
public function __construct($oName, $oParent = null)
{
if (is_null($oParent))
{
if (is_null($oParent)) {
$oParent = new OqlName('', 0);
}
$this->m_oParent = $oParent;
@@ -256,37 +258,28 @@ class FieldOqlExpression extends FieldExpression implements CheckableExpression
{
$sClassAlias = $this->GetParent();
$sFltCode = $this->GetName();
if (empty($sClassAlias))
{
if (empty($sClassAlias)) {
// Try to find an alias
// Build an array of field => array of aliases
$aFieldClasses = array();
foreach($aAliases as $sAlias => $sReal)
{
foreach($oModelReflection->GetFiltersList($sReal) as $sAnFltCode)
{
foreach ($aAliases as $sAlias => $sReal) {
foreach ($oModelReflection->GetFiltersList($sReal) as $sAnFltCode) {
$aFieldClasses[$sAnFltCode][] = $sAlias;
}
}
if (!array_key_exists($sFltCode, $aFieldClasses))
{
if (!array_key_exists($sFltCode, $aFieldClasses)) {
throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
}
if (count($aFieldClasses[$sFltCode]) > 1)
{
if (count($aFieldClasses[$sFltCode]) > 1) {
throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails());
}
$sClassAlias = $aFieldClasses[$sFltCode][0];
}
else
{
if (!array_key_exists($sClassAlias, $aAliases))
{
} else {
if (!array_key_exists($sClassAlias, $aAliases)) {
throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $this->GetParentDetails(), array_keys($aAliases));
}
$sClass = $aAliases[$sClassAlias];
if (!$oModelReflection->IsValidFilterCode($sClass, $sFltCode))
{
if (!$oModelReflection->IsValidFilterCode($sClass, $sFltCode)) {
throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), $oModelReflection->GetFiltersList($sClass));
}
}
@@ -305,8 +298,7 @@ class ListOqlExpression extends ListExpression implements CheckableExpression
{
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
{
foreach ($this->GetItems() as $oItemExpression)
{
foreach ($this->GetItems() as $oItemExpression) {
$oItemExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
}
}
@@ -316,8 +308,7 @@ class FunctionOqlExpression extends FunctionExpression implements CheckableExpre
{
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
{
foreach ($this->GetArgs() as $oArgExpression)
{
foreach ($this->GetArgs() as $oArgExpression) {
$oArgExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
}
}
@@ -350,6 +341,7 @@ abstract class OqlQuery
* Determine the class
*
* @param ModelReflection $oModelReflection MetaModel to consider
*
* @return string
* @throws Exception
*/
@@ -392,6 +384,7 @@ class OqlObjectQuery extends OqlQuery
* Determine the class
*
* @param ModelReflection $oModelReflection MetaModel to consider
*
* @return string
* @throws Exception
*/
@@ -415,6 +408,7 @@ class OqlObjectQuery extends OqlQuery
{
return $this->m_oClass;
}
public function GetClassAliasDetails()
{
return $this->m_oClassAlias;
@@ -424,6 +418,7 @@ class OqlObjectQuery extends OqlQuery
{
return $this->m_aJoins;
}
public function GetCondition()
{
return $this->m_oCondition;
@@ -432,44 +427,37 @@ class OqlObjectQuery extends OqlQuery
/**
* Recursively check the validity of the expression with regard to the data model
* and the query in which it is used
*
* @param ModelReflection $oModelReflection MetaModel to consider
*
* @param ModelReflection $oModelReflection MetaModel to consider
*
* @throws OqlNormalizeException
*/
*/
public function Check(ModelReflection $oModelReflection, $sSourceQuery, $aParentAliases = array())
{
$sClass = $this->GetClass($oModelReflection);
$sClassAlias = $this->GetClassAlias();
if (!$oModelReflection->IsValidClass($sClass))
{
if (!$oModelReflection->IsValidClass($sClass)) {
throw new UnknownClassOqlException($sSourceQuery, $this->GetClassDetails(), $oModelReflection->GetClasses());
}
$aAliases = array_merge(array($sClassAlias => $sClass),$aParentAliases);
$aAliases = array_merge(array($sClassAlias => $sClass), $aParentAliases);
$aJoinSpecs = $this->GetJoins();
if (is_array($aJoinSpecs))
{
foreach ($aJoinSpecs as $oJoinSpec)
{
if (is_array($aJoinSpecs)) {
foreach ($aJoinSpecs as $oJoinSpec) {
$sJoinClass = $oJoinSpec->GetClass();
$sJoinClassAlias = $oJoinSpec->GetClassAlias();
if (!$oModelReflection->IsValidClass($sJoinClass))
{
if (!$oModelReflection->IsValidClass($sJoinClass)) {
throw new UnknownClassOqlException($sSourceQuery, $oJoinSpec->GetClassDetails(), $oModelReflection->GetClasses());
}
if (array_key_exists($sJoinClassAlias, $aAliases))
{
if ($sJoinClassAlias != $sJoinClass)
{
if (array_key_exists($sJoinClassAlias, $aAliases)) {
if ($sJoinClassAlias != $sJoinClass) {
throw new OqlNormalizeException('Duplicate class alias', $sSourceQuery, $oJoinSpec->GetClassAliasDetails());
}
else
{
} else {
throw new OqlNormalizeException('Duplicate class name', $sSourceQuery, $oJoinSpec->GetClassDetails());
}
}
}
// Assumption: ext key on the left only !!!
// normalization should take care of this
@@ -480,85 +468,74 @@ class OqlObjectQuery extends OqlQuery
$oRightField = $oJoinSpec->GetRightField();
$sToClass = $oRightField->GetParent();
$sPKeyDescriptor = $oRightField->GetName();
if ($sPKeyDescriptor != 'id')
{
if ($sPKeyDescriptor != 'id') {
throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sSourceQuery, $oRightField->GetNameDetails(), array('id'));
}
$aAliases[$sJoinClassAlias] = $sJoinClass;
if (!array_key_exists($sFromClass, $aAliases))
{
if (!array_key_exists($sFromClass, $aAliases)) {
throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sSourceQuery, $oLeftField->GetParentDetails(), array_keys($aAliases));
}
if (!array_key_exists($sToClass, $aAliases))
{
if (!array_key_exists($sToClass, $aAliases)) {
throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sSourceQuery, $oRightField->GetParentDetails(), array_keys($aAliases));
}
$aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], \Combodo\iTop\Core\AttributeDefinition\AttributeExternalKey::class);
$aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], \Combodo\iTop\Core\AttributeDefinition\AttributeObjectKey::class);
$aAllKeys = array_merge($aExtKeys, $aObjKeys);
if (!array_key_exists($sExtKeyAttCode, $aAllKeys))
{
if (!array_key_exists($sExtKeyAttCode, $aAllKeys)) {
throw new OqlNormalizeException('Unknown key in join condition (left expression)', $sSourceQuery, $oLeftField->GetNameDetails(), array_keys($aAllKeys));
}
if ($sFromClass == $sJoinClassAlias)
{
if ($sFromClass == $sJoinClassAlias) {
if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
{
$sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
{
if (!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass)) {
throw new OqlNormalizeException("The joined class ($aAliases[$sFromClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
}
}
}
else
{
} else {
$sOperator = $oJoinSpec->GetOperator();
switch($sOperator)
{
switch ($sOperator) {
case '=':
$iOperatorCode = TREE_OPERATOR_EQUALS;
break;
$iOperatorCode = TREE_OPERATOR_EQUALS;
break;
case 'BELOW':
$iOperatorCode = TREE_OPERATOR_BELOW;
break;
$iOperatorCode = TREE_OPERATOR_BELOW;
break;
case 'BELOW_STRICT':
$iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
break;
$iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
break;
case 'NOT_BELOW':
$iOperatorCode = TREE_OPERATOR_NOT_BELOW;
break;
$iOperatorCode = TREE_OPERATOR_NOT_BELOW;
break;
case 'NOT_BELOW_STRICT':
$iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
break;
$iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
break;
case 'ABOVE':
$iOperatorCode = TREE_OPERATOR_ABOVE;
break;
$iOperatorCode = TREE_OPERATOR_ABOVE;
break;
case 'ABOVE_STRICT':
$iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
break;
$iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
break;
case 'NOT_ABOVE':
$iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
break;
$iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
break;
case 'NOT_ABOVE_STRICT':
$iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
break;
$iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
break;
}
if (array_key_exists($sExtKeyAttCode, $aExtKeys)) // Skip that check for object keys
{
$sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
if(!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass))
{
if (!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass)) {
throw new OqlNormalizeException("The joined class ($aAliases[$sToClass]) is not compatible with the external key, which is pointing to $sTargetClass", $sSourceQuery, $oLeftField->GetNameDetails());
}
}
$aAttList = $oModelReflection->ListAttributes($aAliases[$sFromClass]);
$sAttType = $aAttList[$sExtKeyAttCode];
if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_subclass_of($sAttType, \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class) && ($sAttType != \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class))
{
if (($iOperatorCode != TREE_OPERATOR_EQUALS) && !is_a($sAttType, \Combodo\iTop\Core\AttributeDefinition\AttributeHierarchicalKey::class, true)) {
throw new OqlNormalizeException("The specified tree operator $sOperator is not applicable to the key", $sSourceQuery, $oLeftField->GetNameDetails());
}
}
@@ -567,26 +544,23 @@ class OqlObjectQuery extends OqlQuery
// Check the select information
//
foreach ($this->GetSelectedClasses() as $oClassDetails)
{
foreach ($this->GetSelectedClasses() as $oClassDetails) {
$sClassToSelect = $oClassDetails->GetValue();
if (!array_key_exists($sClassToSelect, $aAliases))
{
if (!array_key_exists($sClassToSelect, $aAliases)) {
throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $oClassDetails, array_keys($aAliases));
}
}
// Check the condition tree
//
if ($this->m_oCondition instanceof Expression)
{
if ($this->m_oCondition instanceof Expression) {
$this->m_oCondition->Check($oModelReflection, $aAliases, $sSourceQuery);
}
}
/**
* Make the relevant DBSearch instance (FromOQL)
*/
*/
public function ToDBSearch($sQuery)
{
$sClass = $this->GetClass(new ModelReflectionRuntime());
@@ -594,6 +568,7 @@ class OqlObjectQuery extends OqlQuery
$oSearch = new DBObjectSearch($sClass, $sClassAlias);
$oSearch->InitFromOqlQuery($this, $sQuery);
return $oSearch;
}
}
@@ -606,19 +581,15 @@ class OqlUnionQuery extends OqlQuery
{
parent::__construct();
$this->aQueries[] = $oLeftQuery;
if ($oRightQueryOrUnion instanceof OqlUnionQuery)
{
foreach ($oRightQueryOrUnion->GetQueries() as $oSingleQuery)
{
if ($oRightQueryOrUnion instanceof OqlUnionQuery) {
foreach ($oRightQueryOrUnion->GetQueries() as $oSingleQuery) {
$this->aQueries[] = $oSingleQuery;
}
}
else
{
} else {
$this->aQueries[] = $oRightQueryOrUnion;
}
}
public function GetQueries()
{
return $this->aQueries;
@@ -627,66 +598,54 @@ class OqlUnionQuery extends OqlQuery
/**
* Check the validity of the expression with regard to the data model
* and the query in which it is used
*
* @param ModelReflection $oModelReflection MetaModel to consider
*
* @param ModelReflection $oModelReflection MetaModel to consider
*
* @throws OqlNormalizeException
*/
*/
public function Check(ModelReflection $oModelReflection, $sSourceQuery)
{
$aColumnToClasses = array();
foreach ($this->aQueries as $iQuery => $oQuery)
{
foreach ($this->aQueries as $iQuery => $oQuery) {
$oQuery->Check($oModelReflection, $sSourceQuery);
$aAliasToClass = array($oQuery->GetClassAlias() => $oQuery->GetClass($oModelReflection));
$aJoinSpecs = $oQuery->GetJoins();
if (is_array($aJoinSpecs))
{
foreach ($aJoinSpecs as $oJoinSpec)
{
if (is_array($aJoinSpecs)) {
foreach ($aJoinSpecs as $oJoinSpec) {
$aAliasToClass[$oJoinSpec->GetClassAlias()] = $oJoinSpec->GetClass();
}
}
$aSelectedClasses = $oQuery->GetSelectedClasses();
if ($iQuery != 0)
{
if (count($aSelectedClasses) < count($aColumnToClasses))
{
if ($iQuery != 0) {
if (count($aSelectedClasses) < count($aColumnToClasses)) {
$oLastClass = end($aSelectedClasses);
throw new OqlNormalizeException('Too few selected classes in the subquery', $sSourceQuery, $oLastClass);
}
if (count($aSelectedClasses) > count($aColumnToClasses))
{
if (count($aSelectedClasses) > count($aColumnToClasses)) {
$oLastClass = end($aSelectedClasses);
throw new OqlNormalizeException('Too many selected classes in the subquery', $sSourceQuery, $oLastClass);
}
}
foreach ($aSelectedClasses as $iColumn => $oClassDetails)
{
foreach ($aSelectedClasses as $iColumn => $oClassDetails) {
$sAlias = $oClassDetails->GetValue();
$sClass = $aAliasToClass[$sAlias];
$aColumnToClasses[$iColumn][] = array(
'alias' => $sAlias,
'class' => $sClass,
'alias' => $sAlias,
'class' => $sClass,
'class_name' => $oClassDetails,
);
}
}
foreach ($aColumnToClasses as $iColumn => $aClasses)
{
foreach ($aColumnToClasses as $iColumn => $aClasses) {
$sRootClass = null;
foreach ($aClasses as $iQuery => $aData)
{
if ($iQuery == 0)
{
foreach ($aClasses as $iQuery => $aData) {
if ($iQuery == 0) {
// Establish the reference
$sRootClass = $oModelReflection->GetRootClass($aData['class']);
}
else
{
if ($oModelReflection->GetRootClass($aData['class']) != $sRootClass)
{
} else {
if ($oModelReflection->GetRootClass($aData['class']) != $sRootClass) {
$aSubclasses = $oModelReflection->EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL);
throw new OqlNormalizeException('Incompatible classes: could not find a common ancestor', $sSourceQuery, $aData['class_name'], $aSubclasses);
}
@@ -699,21 +658,21 @@ class OqlUnionQuery extends OqlQuery
* Determine the class
*
* @param ModelReflection $oModelReflection MetaModel to consider
*
* @return string
* @throws Exception
*/
public function GetClass(ModelReflection $oModelReflection)
{
$aFirstColClasses = array();
foreach ($this->aQueries as $iQuery => $oQuery)
{
foreach ($this->aQueries as $iQuery => $oQuery) {
$aFirstColClasses[] = $oQuery->GetClass($oModelReflection);
}
$sClass = self::GetLowestCommonAncestor($oModelReflection, $aFirstColClasses);
if (is_null($sClass))
{
if (is_null($sClass)) {
throw new Exception('Could not determine the class of the union query. This issue should have been detected earlier by calling OqlQuery::Check()');
}
return $sClass;
}
@@ -726,6 +685,7 @@ class OqlUnionQuery extends OqlQuery
public function GetClassAlias()
{
$sAlias = $this->aQueries[0]->GetClassAlias();
return $sAlias;
}
@@ -735,29 +695,25 @@ class OqlUnionQuery extends OqlQuery
*
* @param ModelReflection $oModelReflection MetaModel to consider
* @param array $aClasses Flat list of classes
*
* @return string the lowest common ancestor amongst classes, null if none has been found
* @throws Exception
*/
public static function GetLowestCommonAncestor(ModelReflection $oModelReflection, $aClasses)
{
$sAncestor = null;
foreach($aClasses as $sClass)
{
if (is_null($sAncestor))
{
foreach ($aClasses as $sClass) {
if (is_null($sAncestor)) {
// first loop
$sAncestor = $sClass;
}
elseif ($oModelReflection->GetRootClass($sClass) != $oModelReflection->GetRootClass($sAncestor))
{
} elseif ($oModelReflection->GetRootClass($sClass) != $oModelReflection->GetRootClass($sAncestor)) {
$sAncestor = null;
break;
}
else
{
} else {
$sAncestor = self::LowestCommonAncestor($oModelReflection, $sAncestor, $sClass);
}
}
return $sAncestor;
}
@@ -766,37 +722,32 @@ class OqlUnionQuery extends OqlQuery
*/
protected static function LowestCommonAncestor(ModelReflection $oModelReflection, $sClassA, $sClassB)
{
if ($sClassA == $sClassB)
{
if ($sClassA == $sClassB) {
$sRet = $sClassA;
}
elseif (in_array($sClassA, $oModelReflection->EnumChildClasses($sClassB)))
{
} elseif (in_array($sClassA, $oModelReflection->EnumChildClasses($sClassB))) {
$sRet = $sClassB;
}
elseif (in_array($sClassB, $oModelReflection->EnumChildClasses($sClassA)))
{
} elseif (in_array($sClassB, $oModelReflection->EnumChildClasses($sClassA))) {
$sRet = $sClassA;
}
else
{
} else {
// Recurse
$sRet = self::LowestCommonAncestor($oModelReflection, $sClassA, $oModelReflection->GetParentClass($sClassB));
}
return $sRet;
}
/**
* Make the relevant DBSearch instance (FromOQL)
*/
*/
public function ToDBSearch($sQuery)
{
$aSearches = array();
foreach ($this->aQueries as $oQuery)
{
foreach ($this->aQueries as $oQuery) {
$aSearches[] = $oQuery->ToDBSearch($sQuery);
}
$oSearch = new DBUnionSearch($aSearches);
return $oSearch;
}
}

View File

@@ -370,7 +370,7 @@ class ormCaseLog
/**
* Produces an HTML representation, aimed at being used within the iTop framework
*/
public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
public function GetAsHTML(?WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
{
$bPrintableVersion = (utils::ReadParam('printable', '0') == '1');

View File

@@ -350,20 +350,22 @@ class ormDocument
if (!is_object($oObj)) {
// If access to the document is not granted, check if the access to the host object is allowed
$oObj = MetaModel::GetObject($sClass, $id, false, true);
$bHasHostRights = false;
if ($oObj instanceof Attachment) {
$sItemClass = $oObj->Get('item_class');
$sItemId = $oObj->Get('item_id');
$oHost = MetaModel::GetObject($sItemClass, $sItemId, false, false);
if (!is_object($oHost)) {
$oObj = null;
if (is_object($oHost)) {
$bHasHostRights = true;
}
}
if (!is_object($oObj)) {
// We could neither read the object nor get a host object matching our rights
if ($bHasHostRights !== true) {
throw new Exception("Invalid id ($id) for class '$sClass' - the object does not exist or you are not allowed to view it");
}
}
if (($sSecretField != null) && ($oObj->Get($sSecretField) != $sSecretValue)) {
usleep(200);
if (($sSecretField != null) && !hash_equals($oObj->Get($sSecretField), $sSecretValue)) {
throw new Exception("Invalid secret for class '$sClass' - the object does not exist or you are not allowed to view it");
}
/** @var \ormDocument $oDocument */

View File

@@ -93,7 +93,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
* @param DBObjectSet|null $oOriginalSet
* @throws Exception
*/
public function __construct($sHostClass, $sAttCode, DBObjectSet $oOriginalSet = null)
public function __construct($sHostClass, $sAttCode, ?DBObjectSet $oOriginalSet = null)
{
$this->sHostClass = $sHostClass;
$this->sAttCode = $sAttCode;
@@ -470,17 +470,6 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|| ($this->oOriginalSet->GetFilter()->ToOQL() == $oFellow->oOriginalSet->GetFilter()->ToOQL())) {
$bUpdateFromDelta = true;
}
} else {
//@since 3.2.2 N°2364 - API : remove old linkedset persistance
/* Goo pattern to use:
* $oCISet = $oTicket->Get(functioncis_list);
* $oCISet->AddItem(MetaModel::NewObject(lnkFunctionCIToTicket, array(ci_id=> 12345));
* $oCISet->RemoveItem(123456);
* $oTicket->Set(functionalcis_list, $oCISet);
*/
if (!ContextTag::Check(ContextTag::TAG_SETUP)) {
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('old pattern - please get previous value of the linked set, modify it and set it back to the host object');
}
}
if ($bUpdateFromDelta) {

View File

@@ -98,9 +98,9 @@ class ormPassword
$bResult = false;
$aInfo = password_get_info($this->m_sHashed);
if (is_null($aInfo["algo"]) || $aInfo["algo"] === 0) {
//unknown, assume it's a legacy password
// - Unknown algorithm, assume it's a legacy password
$sHashedPwd = $this->ComputeHash($sClearTextPassword);
$bResult = ($this->m_sHashed == $sHashedPwd);
$bResult = hash_equals($this->m_sHashed, $sHashedPwd);
} else {
$bResult = password_verify($sClearTextPassword, $this->m_sHashed);
}

View File

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

View File

@@ -4,3 +4,4 @@
*/
@import "bulk-modify";
@import "bulk-export";

View File

@@ -0,0 +1,10 @@
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
#form_part_csv_options:has(#ibo-sanitize-excel-export--input:checked), #form_part_xlsx_options:has(#ibo-sanitize-excel-export--input:checked){
#ibo-sanitize-excel-export--alert {
display: none;
}
}

View File

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

View File

@@ -0,0 +1,10 @@
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-badge--spacing-left--with-same-block: $ibo-spacing-200 !default;
.ibo-badge + .ibo-badge {
margin-left: $ibo-badge--spacing-left--with-same-block;
}

View File

@@ -0,0 +1,11 @@
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-extension-details--margin-top: $ibo-spacing-300 !default;
.ibo-extension-details + .ibo-extension-details,
.ibo-extension-details--information--description .ibo-extension-details {
margin-top: $ibo-extension-details--margin-top;
}

View File

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

View File

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

View File

@@ -0,0 +1,41 @@
$ibo-badge--padding-x : $ibo-spacing-200 !default;
$ibo-badge--padding-y : $ibo-spacing-100 !default;
$ibo-badge--border-radius : $ibo-border-radius-400 !default;
$ibo-badge-colors: (
'primary': ($ibo-color-primary-100, $ibo-color-primary-900),
'secondary': ($ibo-color-secondary-100, $ibo-color-secondary-900),
'neutral': ($ibo-color-secondary-100, $ibo-color-secondary-900),
'information': ($ibo-color-information-100, $ibo-color-information-900),
'success': ($ibo-color-success-100, $ibo-color-success-900),
'failure': ($ibo-color-danger-100, $ibo-color-danger-900),
'warning': ($ibo-color-warning-100,$ibo-color-warning-900),
'danger': ($ibo-color-danger-100,$ibo-color-danger-900),
'grey' : ($ibo-color-grey-100, $ibo-color-grey-900),
'blue-grey': ($ibo-color-blue-grey-100, $ibo-color-blue-grey-900),
'blue': ($ibo-color-blue-100, $ibo-color-blue-900),
'cyan': ($ibo-color-cyan-100, $ibo-color-cyan-900),
'green': ($ibo-color-green-100, $ibo-color-green-900),
'orange' : ($ibo-color-orange-100, $ibo-color-orange-900),
'red': ($ibo-color-red-100, $ibo-color-red-900),
'pink': ($ibo-color-pink-100, $ibo-color-pink-900),
) !default;
.ibo-badge {
display: inline-block;
white-space: nowrap;
padding : $ibo-badge--padding-y $ibo-badge--padding-x;
border-radius : $ibo-badge--border-radius;
@extend %ibo-font-ral-med-50;
@each $sColor, $aColorValues in $ibo-badge-colors {
$bg-color: nth($aColorValues, 1);
$text-color: nth($aColorValues, 2);
&.ibo-is-#{$sColor} {
background-color: $bg-color;
color: $text-color;
}
}
}

View File

@@ -6,4 +6,72 @@
.ibo-prop-header {
@extend %ibo-font-size-150;
padding-bottom: 14px;
}
}
.help-text{
padding: 1px 5px;
background-color: #d7e3f8;
border: 1px solid #c6e7f5;
border-radius: 5px;
margin: 5px 0;
font-size: 0.9em;
}
.form-error ul{
padding: 1px 5px;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 5px;
margin: 5px 0;
font-size: 0.9em;
}
.subform{
background-color: #efefef;
border-radius: 5px;
padding: 10px;
}
.form-buttons{
margin: 20px 0;
}
.form select{
padding: 0;
overflow-y: auto;
}
.form select option{
height: 30px;
display: flex;
align-items: center;
}
.turbo-refreshing{
opacity: .5;
}
.ibo-field legend{
margin-top: 24px;
}
collection-entry-element {
margin-top: 8px;
display: block;
padding: 10px 10px;
background-color: #f5f5f5;
border-radius: 5px;
}
.ts-control{
height: auto;
min-height: 30px;
}
.ibo-form-actions > .ibo-button > span{
margin-right: 5px;
}
.ibo-form textarea{
resize: vertical;
}

View File

@@ -72,9 +72,12 @@ $ibo-panel--icon--spacing--as-medallion--is-sticking: $ibo-panel--icon--spacing-
$ibo-panel--icon--bottom--as-medallion--is-sticking: -12px !default;
$ibo-panel--icon--border--as-medallion--is-sticking: 1px $ibo-panel--base-border-style $ibo-panel--base-border-color !default;
$ibo-panel--icon-background--size--must-contain: contain !default;
$ibo-panel--icon-background--size--must-cover: cover !default;
$ibo-panel--icon-background--size--must-zoomout: 66.67% !default;
$ibo-panel--icon-background--size--must-contain: contain !default; // deprecated, to be removed in favor of $ibo-panel--icon-img--size--must-contain
$ibo-panel--icon-background--size--must-cover: cover !default; // deprecated, to be removed in favor of $ibo-panel--icon-img--size--must-cover
$ibo-panel--icon-background--size--must-zoomout: 66.67% !default; // deprecated, to be removed in favor of $ibo-panel--icon-img--size--must-zoomout
$ibo-panel--icon-img--size--must-contain: $ibo-panel--icon-background--size--must-contain !default; // TODO remove when dealing with N°9317
$ibo-panel--icon-img--size--must-cover: $ibo-panel--icon-background--size--must-cover !default; // TODO remove when dealing with N°9317
$ibo-panel--icon-img--size--must-zoomout: $ibo-panel--icon-background--size--must-zoomout !default; // TODO remove when dealing with N°9317
$ibo-panel--title--font-size--is-sticking: $ibo-font-size-150 !default;
$ibo-panel--title--color: $ibo-color-grey-900 !default;
@@ -179,24 +182,25 @@ $ibo-panel--is-selectable--body--after--font-size: $ibo-font-size-700 !default;
min-height: $ibo-panel--icon--size;
}
.ibo-panel--icon-background {
.ibo-panel--icon-img, .ibo-panel--icon-background { // second class is deprecated, remove it when dealing with N°9317
width: 100%;
height: 100%;
background-position: center;
background-repeat: no-repeat;
background-size: $ibo-panel--icon-background--size--must-contain;
background-size: $ibo-panel--icon-img--size--must-contain;
}
.ibo-panel--icon-background--must-contain {
background-size: $ibo-panel--icon-background--size--must-contain;
.ibo-panel--icon-img--must-contain, .ibo-panel--icon-background--must-contain { // second class is deprecated, remove it when dealing with N°9317
background-size: $ibo-panel--icon-img--size--must-contain;
}
.ibo-panel--icon-background--must-cover {
background-size: $ibo-panel--icon-background--size--must-cover;
.ibo-panel--icon-img--must-cover, .ibo-panel--icon-background--must-cover { // second class is deprecated, remove it when dealing with N°9317
background-size: $ibo-panel--icon-img--size--must-cover;
}
.ibo-panel--icon-background--must-zoomout {
background-size: $ibo-panel--icon-background--size--must-zoomout;
.ibo-panel--icon-img--must-zoomout, .ibo-panel--icon-background--must-zoomout { // second class is deprecated, remove it when dealing with N°9317
width: $ibo-panel--icon-img--size--must-zoomout;
height: $ibo-panel--icon-img--size--must-zoomout;
}
.ibo-panel--title {

View File

@@ -21,6 +21,8 @@ $ibo-search-form-panel--more-criteria--color: $ibo-color-blue-grey-800 !default;
$ibo-search-form-panel--more-criteria--background-color: $ibo-color-white-100 !default;
$ibo-search-form-panel--more-criteria--icon--color: $ibo-color-primary-600 !default;
$ibo-search-form-panel--more-criteria--border-color: $ibo-search-form-panel--criteria--border-color !default;
// calc is redundant but avoid SCSS min() from being used instead of CSS min()
$ibo-search-form-panel--criteria--max-height: calc(min(#{$ibo-size-750}, 50vh)) !default;
$ibo-search-form-panel--items--hover--color: $ibo-color-grey-200 !default;
@@ -278,9 +280,10 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
}
.sfc_form_group {
display: block;
margin-top: -1px;
z-index: -1;
display: flex;
flex-direction: column;
margin-top: -1px;
z-index: -1;
}
}
@@ -346,11 +349,15 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
display: none;
max-width: 450px;
width: max-content;
max-height: 520px;
max-height: $ibo-search-form-panel--criteria--max-height;
overflow-x: auto;
overflow-y: hidden;
.sfc_fg_operators {
display: flex;
flex-direction: column;
overflow: auto;
min-height: 0;
font-size: 12px;
.sfc_fg_operator {
@@ -387,6 +394,9 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
}
.sfc_opc_multichoices {
display: flex;
flex-direction: column;
height: 100%;
label > input {
vertical-align: text-top;
margin-left: $ibo-spacing-0;
@@ -398,7 +408,6 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
}
.sfc_opc_mc_items_wrapper {
max-height: 415px; /* Must be less than .sfc_form_group:max-height - .sfc_opc_mc_toggler:height - .sfc_opc_mc_filter:height */
overflow-y: auto;
margin: $ibo-spacing-0 -8px; /* Compensate .sfc_opc_multichoices side padding so the hover style can take the full with */
@@ -560,8 +569,14 @@ $ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search
&.search_form_criteria_enum {
.sfc_form_group {
.sfc_fg_operator_in {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
> label {
display: inline-block;
display: flex;
height: 100%;
min-height: 0;
width: 100%;
line-height: initial;
white-space: nowrap;

View File

@@ -11,9 +11,12 @@ $ibo-title--icon--size: 90px !default;
$ibo-title--icon--size-2: 80px !default;
$ibo-title--icon--size-3: 70px !default;
$ibo-title--icon-background--size--must-contain: contain !default;
$ibo-title--icon-background--size--must-cover: cover !default;
$ibo-title--icon-background--size--must-zoomout: 66.67% !default;
$ibo-title--icon-background--size--must-contain: contain !default; // deprecated, to be removed in favor of $ibo-title--icon-img--size--must-contain
$ibo-title--icon-background--size--must-cover: cover !default; // deprecated, to be removed in favor of $ibo-title--icon-img--size--must-cover
$ibo-title--icon-background--size--must-zoomout: 66.67% !default; // deprecated, to be removed in favor of $ibo-title--icon-img--size--must-zoomout
$ibo-title--icon-img--size--must-contain: $ibo-title--icon-background--size--must-contain !default; // TODO remove when dealing with N°9317
$ibo-title--icon-img--size--must-cover: $ibo-title--icon-background--size--must-cover !default; // TODO remove when dealing with N°9317
$ibo-title--icon-img--size--must-zoomout: $ibo-title--icon-background--size--must-zoomout !default; // TODO remove when dealing with N°9317
.ibo-title {
@@ -44,24 +47,23 @@ $ibo-title--icon-background--size--must-zoomout: 66.67% !default;
min-height: $ibo-title--icon--size-3;
}
.ibo-title--icon-background {
.ibo-title--icon-img, .ibo-title--icon-background { // second class is deprecated, remove it when dealing with N°9317
width: 100%;
height: 100%;
background-position: center;
background-repeat: no-repeat;
background-size: $ibo-title--icon-background--size--must-contain;
object-position: center;
background-size: $ibo-title--icon-img--size--must-contain;
}
.ibo-title--icon-background--must-contain {
background-size: $ibo-title--icon-background--size--must-contain;
.ibo-title--icon-img--must-contain, .ibo-title--icon-background--must-contain { // second class is deprecated, remove it when dealing with N°9317
background-size: $ibo-title--icon-img--size--must-contain;
}
.ibo-title--icon-background--must-cover {
background-size: $ibo-title--icon-background--size--must-cover;
.ibo-title--icon-img--must-cover, .ibo-title--icon-background--must-cover { // second class is deprecated, remove it when dealing with N°9317
background-size: $ibo-title--icon-img--size--must-cover;
}
.ibo-title--icon-background--must-zoomout {
background-size: $ibo-title--icon-background--size--must-zoomout;
.ibo-title--icon-img--must-zoomout, .ibo-title--icon-background--must-zoomout { // second class is deprecated, remove it when dealing with N°9317
background-size: $ibo-title--icon-img--size--must-zoomout;
}
.ibo-title--for-object-details {

View File

@@ -8,6 +8,7 @@ $ibo-toggler--wrapper--height: 20px !default;
$ibo-toggler--slider--border-radius: $ibo-border-radius-900 !default;
$ibo-toggler--slider--background-color: $ibo-color-secondary-600 !default;
$ibo-toggler--slider--disabled--background-color: $ibo-color-secondary-200 !default;
$ibo-toggler--slider--before--left: 3px !default;
$ibo-toggler--slider--before--bottom: 3px !default;
@@ -17,6 +18,7 @@ $ibo-toggler--slider--before--border-radius: $ibo-border-radius-full !default;
$ibo-toggler--slider--before--background-color: $ibo-color-grey-100 !default;
$ibo-toggler--slider--checked--background-color: $ibo-color-primary-600 !default;
$ibo-toggler--slider--checked-disabled--background-color: $ibo-color-primary-200 !default;
$ibo-toggler--slider--focus--box-shadow: 0 0 1px $ibo-color-primary-600 !default;
$ibo-toggler--label--margin-left: 4px !default;
@@ -61,6 +63,13 @@ $ibo-toggler--label--margin-left: 4px !default;
background-color: $ibo-toggler--slider--checked--background-color;
}
.ibo-toggler--wrapper input:disabled + .ibo-toggler--slider {
background-color: $ibo-toggler--slider--disabled--background-color;
}
.ibo-toggler--wrapper input:checked:disabled + .ibo-toggler--slider {
background-color: $ibo-toggler--slider--checked-disabled--background-color;
}
input:focus + .ibo-toggler--slider {
box-shadow: $ibo-toggler--slider--focus--box-shadow;
}

View File

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

View File

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

View File

@@ -0,0 +1,65 @@
$ibo-extension-details--information--metadata--padding: $ibo-spacing-200 !default;
$ibo-extension-details--information--metadata--delimiter: "-" !default;
$ibo-extension-details--information--metadata--color: $ibo-color-grey-700 !default;
$ibo-extension-details--actions--button--padding-y: 3px !default;
$ibo-extension-details--actions--button--padding-x: $ibo-button--padding-x !default;
.ibo-extension-details {
display: inline-flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
}
.ibo-extension-details--information {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.ibo-extension-details--actions {
display: flex;
}
.ibo-extension-details--information--label {
@extend %ibo-font-ral-med-150;
}
.ibo-extension-details--information--metadata {
@extend %ibo-font-ral-med-100;
color: $ibo-extension-details--information--metadata--color;
}
.ibo-extension-details--information--description {
@extend %ibo-font-ral-med-100;
}
.ibo-extension-details--information--metadata span + span:before {
content: $ibo-extension-details--information--metadata--delimiter;
padding-left: $ibo-extension-details--information--metadata--padding;
padding-right: $ibo-extension-details--information--metadata--padding;
}
//ibo-extension-details can have other ibo-extension-details inside its ibo-extension-details--information--description in the setup. We need to only affect direct children
.ibo-extension-details:has(>.ibo-extension-details--actions input:is([type="checkbox"], [type="radio"]):checked){
&>.ibo-extension-details--information>.ibo-extension-details--information--label .ibo-badge.unchecked {
display: none;
}
}
//Merging the two lines below with :is([type="checkbox"], [type="radio"]) will generate a warning in scss compiler
.ibo-extension-details:has(>.ibo-extension-details--actions input[type="checkbox"]:not(:checked)),
.ibo-extension-details:has(>.ibo-extension-details--actions input[type="radio"]:not(:checked)) {
&>.ibo-extension-details--information>.ibo-extension-details--information--label .ibo-badge.checked {
display: none;
}
}
.ibo-extension-details--actions > button {
padding: $ibo-extension-details--actions--button--padding-y $ibo-extension-details--actions--button--padding-x;
}
.ibo-extension-details--actions:has(.toggler-install:not(:disabled)) .ibo-popover-menu--section a[data-resource-id="force_uninstall"] {
display: none;
}

View File

@@ -5,9 +5,11 @@
$ibo-multi-column--margin-x: -$ibo-spacing-500 !default; /* This is to compensate columns padding and make the whole multicolumn align with the parent borders (cf. Bootstrap rows / cols) */
$ibo-multi-column--margin-y: $ibo-spacing-0 !default;
$ibo-multi-column--row-gap: $ibo-spacing-800 !default;
.ibo-multi-column {
display: flex;
flex-wrap: wrap;
margin: $ibo-multi-column--margin-y $ibo-multi-column--margin-x;
row-gap: $ibo-multi-column--row-gap;
}

View File

@@ -29,7 +29,7 @@ $ibo-vendors-selectize--element--active--background: $ibo-color-blue-100 !defaul
$ibo-vendors-selectize--element--active--color: $ibo-color-grey-900 !default;
$ibo-vendors-selectize--dropdown--background-color: $ibo-vendors-selectize-input--background-color !default;
$ibo-vendors-selectize--dropdown--color: $ibo-vendors-selectize-input--color!default;
$ibo-vendors-selectize--dropdown--color: $ibo-vendors-selectize-input--color !default;
$ibo-vendors-selectize--header--padding-x: 8px !default;
$ibo-vendors-selectize--header--padding-y: 5px !default;

View File

@@ -0,0 +1,3 @@
@import "../../../node_modules/tom-select/dist/scss/tom-select.scss";
$select-color-item-active-border: $ibo-input--focus--border-color;

File diff suppressed because one or more lines are too long

View File

@@ -311,28 +311,39 @@ fieldset {
}
.module-selection-body {
overflow: auto;
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, .06) !important;
background-color: #F7FAFC;
padding: 10px;
.wiz-choice:checked ~ .description {
#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked),
#itop-ticket-mgmt-itil-enhanced-portal:not(:checked) {
~ .description::after {
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
display: block;
margin-top: 0.5em;
font-weight: bold;
color: $legacy-portal-removal-text-color;
}
}
}
overflow: auto;
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, .06) !important;
background-color: #F7FAFC;
padding: 10px;
.wiz-choice{
&:not(:checked) ~ label .checked{
display:none;
}
&:checked ~ label .unchecked{
display:none;
}
}
}
.ibo-extension-details:has(>.ibo-extension-details--actions>input:checked) {
.ibo-extension-details:has(#itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked), #itop-ticket-mgmt-itil-enhanced-portal:not(:checked)) {
.ibo-extension-details--information--description::after {
content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore.";
display: block;
margin-top: 0.5em;
font-weight: bold;
color: $legacy-portal-removal-text-color;
}
}
}
.ibo-extension-details--information--metadata{
color: $ibo-color-grey-800;
}
.choice-disabled {
color: $ibo-color-grey-700;
}
body {
font-size: 1.17rem;
@@ -516,10 +527,12 @@ body {
}
}
.ibo-setup-summary-title {
font-size: $ibo-font-size-150;
.ibo-setup-summary-title, .ibo-setup-summary-title:visited, .ibo-setup-summary-title:hover {
font-size: $ibo-font-size-150;
color: inherit;
}
#ibo-setup-licenses--components-list {
background-color: $ibo-color-white-200;
padding: 12px;
@@ -595,6 +608,51 @@ body {
color: $ibo-color-blue-700;
font-size: $ibo-font-size-200;
}
.setup-extension--missing .setup-extension--icon{
color:#a00000;
}
.setup-extension-tag {
display: inline-flex;
background-color: grey;
border-radius: 8px;
padding-left: 3px;
padding-right: 3px;
margin-right: 3px;
&.installed{
background-color:#9eff9e
}
&.notinstalled{
background-color:#ed9eff
}
&.tobeinstalled{
background-color:#9ef0ff
}
&.tobeuninstalled{
background-color:#ff9e9e
}
&.notuninstallable{
background-color:#ffc98c
}
&.removed{
background-color: #969594
}
}
.ibo-extension-details {
align-items: flex-start;
}
.ibo-extension-details--actions input{
margin:0.2em 0.5em;
width: 12px;
}
:not(.ibo-badge) ~ .ibo-badge{
margin-left:0.5em;
}
.ibo-extension-details--information--label i{
font-size : 0.9em;
margin-left:0.3em;
}
.setup--wizard-choice--label + .setup--wizard-choice--more-info {
margin-left: 0.5rem;
}
@@ -646,9 +704,6 @@ body {
overflow: auto;
text-align: center;
}
#installation_progress {
display: none;
}
#fresh_content{
border: 0;
min-height: 300px;

View File

View File

@@ -2,7 +2,7 @@
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.3">
<module_parameters>
<parameters id="authent-local" _delta="define">
<password_validation.pattern>^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,}$</password_validation.pattern>
<password_validation.pattern>^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{12,}$</password_validation.pattern>
<password_validation.message type="hash"/>
</parameters>
</module_parameters>

View File

@@ -29,7 +29,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Heslo nemůže uživatel změnit.',
'Class:UserLocal/Attribute:password_renewed_date' => 'Heslo bylo obnoveno',
'Class:UserLocal/Attribute:password_renewed_date+' => 'Termín, kdy bylo heslo změneno',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Heslo musí obsahovat minimálně 8 znaků a musí obsahovat minimálně jedno velké písmeno, jedno malé písmeno, jedno číslo a speciální znak.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Heslo musí obsahovat minimálně 12 znaků a musí obsahovat minimálně jedno velké písmeno, jedno malé písmeno, jedno číslo a speciální znak.',
'UserLocal:password:expiration' => 'Níže uvedená pole vyžadují rozšíření',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Nastavení exspirace "Jednorázového hesla" nelze u vlastního účtu uživatele.',
]);

View File

@@ -28,7 +28,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
'UserLocal:password:expiration' => 'The fields below require an extension~~',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
]);

View File

@@ -28,7 +28,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
'Class:UserLocal/Attribute:password_renewed_date' => 'Letzte Passworterneuerung',
'Class:UserLocal/Attribute:password_renewed_date+' => 'Letztes Änderungsdatum',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Das Passwort entspricht nicht dem in den Konfigurationsregeln hinterlegten RegEx-Ausdruck',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Das Passwort muss mindestens 12 Zeichen lang sein und Großbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten.',
'UserLocal:password:expiration' => 'Die folgenden Felder benötigen eine '.ITOP_APPLICATION_SHORT.' Erweiterung',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Das setzen des Passwortablaufs auf "Einmalpasswort" ist für den eigenen Benutzer nicht erlaubt.',
]);

View File

@@ -55,7 +55,7 @@ Dict::Add('EN US', 'English', 'English', [
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on',
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.',
'UserLocal:password:expiration' => 'The fields below require an extension',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User',
]);

View File

@@ -55,7 +55,7 @@ Dict::Add('EN GB', 'British English', 'British English', [
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on',
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.',
'UserLocal:password:expiration' => 'The fields below require an extension',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User',
]);

View File

@@ -25,7 +25,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'El usuario no puede cambiar la contraseña.',
'Class:UserLocal/Attribute:password_renewed_date' => 'Renovación de contraseña',
'Class:UserLocal/Attribute:password_renewed_date+' => 'Cuando fue el último cambio de contraseña',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La contraseña debe ser de al menos 8 caracteres e incluír mayúsculas, minúsculas, números y caracteres especiales.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La contraseña debe ser de al menos 12 caracteres e incluir mayúsculas, minúsculas, números y caracteres especiales.',
'UserLocal:password:expiration' => 'El siguiente campo requiere una extensión',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Configurar expiración de contraseña para "ontraseña de un solo uso" no está permitido para su propio Usuario',
]);

View File

@@ -27,7 +27,7 @@ Dict::Add('FR FR', 'French', 'Français', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
'Class:UserLocal/Attribute:password_renewed_date' => 'Mot de passe changé le',
'Class:UserLocal/Attribute:password_renewed_date+' => 'Dernière date à laquelle le mot de passe a été changé',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Le mot de passe doit contenir au moins 8 caractères, avec minuscule, majuscule, nombre et caractère spécial.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Le mot de passe doit contenir au moins 12 caractères, avec minuscule, majuscule, nombre et caractère spécial.',
'UserLocal:password:expiration' => 'Les champs ci-dessous nécessitent une extension',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Impossible de mettre "Usage unique" comme validité du mot de passe pour son propre utilisateur.',
]);

View File

@@ -27,7 +27,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'A felhasználó nem változtathat jelszót.',
'Class:UserLocal/Attribute:password_renewed_date' => 'Jelszó megújítás ideje',
'Class:UserLocal/Attribute:password_renewed_date+' => 'A jelszó legutóbbi módosításának időpontja',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'A jelszónak legalább 8 karakterből kell állnia, és tartalmaznia kell nagybetűket, kisbetűket, numerikus és speciális karaktereket.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'A jelszónak legalább 12 karakterből kell állnia, és tartalmaznia kell nagybetűket, kisbetűket, numerikus és speciális karaktereket.',
'UserLocal:password:expiration' => 'Az alábbi mezőkhöz egy bővítmény szükséges',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'A jelszó lejárati idejének beállítása "Egyszeri jelszóra" nem engedélyezett a saját Felhasználó számára.',
]);

View File

@@ -27,7 +27,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'La password non può essere cambiata dall\'utente.',
'Class:UserLocal/Attribute:password_renewed_date' => 'Rinnovo della password',
'Class:UserLocal/Attribute:password_renewed_date+' => 'Quando è stata cambiata l\'ultima volta la password',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La password deve essere di almeno 8 caratteri e includere lettere maiuscole, minuscole, numeri e caratteri speciali.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La password deve essere di almeno 12 caratteri e includere lettere maiuscole, minuscole, numeri e caratteri speciali.',
'UserLocal:password:expiration' => 'I campi sottostanti richiedono un\'estensione',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Impostare la scadenza della password su "Password monouso" non è consentito per il proprio utente',
]);

View File

@@ -28,7 +28,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
'UserLocal:password:expiration' => 'The fields below require an extension~~',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
]);

View File

@@ -28,7 +28,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'De gebruiker kan dit wachtwoord niet veranderen.',
'Class:UserLocal/Attribute:password_renewed_date' => 'Wachtwoord laatst aangepast',
'Class:UserLocal/Attribute:password_renewed_date+' => 'Tijdstip waarop het wachtwoord het laatst aangepast werd.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Het wachtwoord bestaat uit minstens 8 tekens en bestaat uit een mix van minstens 1 hoofdletter, kleine letter, cijfer en speciaal teken.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Het wachtwoord bestaat uit minstens 12 tekens en bestaat uit een mix van minstens 1 hoofdletter, kleine letter, cijfer en speciaal teken.',
'UserLocal:password:expiration' => 'De velden hieronder vereisen een extensie.',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Je kan geen eenmalig wachtwoord instellen voor je eigen gebruiker.',
]);

View File

@@ -27,7 +27,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Hasło nie może być zmienione przez użytkownika.',
'Class:UserLocal/Attribute:password_renewed_date' => 'Odnowienie hasła',
'Class:UserLocal/Attribute:password_renewed_date+' => 'Kiedy ostatnio zmieniano hasło',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Hasło musi mieć co najmniej 8 znaków i zawierać duże, małe litery, cyfry i znaki specjalne.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Hasło musi mieć co najmniej 12 znaków i zawierać duże, małe litery, cyfry i znaki specjalne.',
'UserLocal:password:expiration' => 'Poniższe pola wymagają rozszerzenia',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Ustawienie wygaśnięcia hasła "Hasło jednorazowe" nie jest dozwolone dla własnego użytkownika',
]);

View File

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

View File

@@ -27,7 +27,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
'UserLocal:password:expiration' => 'The fields below require an extension~~',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
]);

View File

@@ -28,7 +28,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewed on~~',
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 12 characters and include uppercase, lowercase, numeric and special characters.~~',
'UserLocal:password:expiration' => 'The fields below require an extension~~',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
]);

View File

@@ -51,7 +51,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '用户不允许修改密码.',
'Class:UserLocal/Attribute:password_renewed_date' => '密码更新',
'Class:UserLocal/Attribute:password_renewed_date+' => '上次修改密码的时间',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => '密码必须至少8个字符, 包含大小写, 数字和特殊字符.',
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => '密码必须至少12个字符, 包含大小写, 数字和特殊字符.',
'UserLocal:password:expiration' => '下面的区域需要插件扩展',
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => '不允许用户为自己设置 "一次性密码" 的失效期限',
]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -68,47 +68,47 @@ $ibo-color-information-900: #0f172a !default;
$ibo-color-information-950: #020617 !default;
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600;
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600;
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-yellow-700;
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700;
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-failure-state-primary-color: $ibo-color-orange-800;
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200;
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700;
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600 !default;
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600 !default;
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-yellow-700 !default;
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700 !default;
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-failure-state-primary-color: $ibo-color-orange-800 !default;
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200 !default;
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700 !default;
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700;
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-yellow-700;
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700 !default;
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-yellow-700 !default;
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100 !default;
$ibo-caselog-highlight-color-1: $ibo-color-blue-700;
$ibo-caselog-highlight-color-2: $ibo-color-yellow-700;
$ibo-caselog-highlight-color-3: $ibo-color-information-600;
$ibo-caselog-highlight-color-4: $ibo-color-yellow-500;
$ibo-caselog-highlight-color-5: $ibo-color-blue-500;
$ibo-caselog-highlight-color-6: $ibo-color-yellow-300;
$ibo-caselog-highlight-color-7: $ibo-color-blue-300;
$ibo-caselog-highlight-color-1: $ibo-color-blue-700 !default;
$ibo-caselog-highlight-color-2: $ibo-color-yellow-700 !default;
$ibo-caselog-highlight-color-3: $ibo-color-information-600 !default;
$ibo-caselog-highlight-color-4: $ibo-color-yellow-500 !default;
$ibo-caselog-highlight-color-5: $ibo-color-blue-500 !default;
$ibo-caselog-highlight-color-6: $ibo-color-yellow-300 !default;
$ibo-caselog-highlight-color-7: $ibo-color-blue-300 !default;
$ibo-input-wrapper--is-error--border-color: $ibo-color-warning-700;
$ibo-field-validation: $ibo-color-warning-800;
$ibo-input-wrapper--is-error--border-color: $ibo-color-warning-700 !default;
$ibo-field-validation: $ibo-color-warning-800 !default;
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-blue-400;
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-blue-400 !default;
$ibo-wizard-container--background-color: $ibo-color-information-200;
$ibo-wizard-container--border-color: $ibo-color-information-600;
$ibo-wizard-container--background-color: $ibo-color-information-200 !default;
$ibo-wizard-container--border-color: $ibo-color-information-600 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100;
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-danger-100;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-danger-500;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-500;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500;
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-danger-100 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-danger-500 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-500 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500 !default;
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-blue-600;
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-blue-600 !default;

View File

@@ -32,47 +32,47 @@ $ibo-color-information-900: #0f172a !default;
$ibo-color-information-950: #020617 !default;
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600;
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600;
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-red-200;
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-red-800;
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700;
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-failure-state-primary-color: $ibo-color-red-800;
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200;
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700;
$ibo-lifecycle-new-state-primary-color: $ibo-color-information-600 !default;
$ibo-lifecycle-new-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-neutral-state-primary-color: $ibo-color-information-600 !default;
$ibo-lifecycle-neutral-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-waiting-state-primary-color: $ibo-color-red-200 !default;
$ibo-lifecycle-waiting-state-secondary-color: $ibo-color-red-800 !default;
$ibo-lifecycle-success-state-primary-color: $ibo-color-blue-700 !default;
$ibo-lifecycle-success-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-failure-state-primary-color: $ibo-color-red-800 !default;
$ibo-lifecycle-failure-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-frozen-state-primary-color: $ibo-color-information-200 !default;
$ibo-lifecycle-frozen-state-secondary-color: $ibo-color-information-700 !default;
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700;
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-red-700;
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100;
$ibo-lifecycle-active-state-primary-color: $ibo-color-blue-700 !default;
$ibo-lifecycle-active-state-secondary-color: $ibo-color-white-100 !default;
$ibo-lifecycle-inactive-state-primary-color: $ibo-color-red-700 !default;
$ibo-lifecycle-inactive-state-secondary-color: $ibo-color-white-100 !default;
$ibo-caselog-highlight-color-1: $ibo-color-blue-700;
$ibo-caselog-highlight-color-2: $ibo-color-red-700;
$ibo-caselog-highlight-color-3: $ibo-color-information-600;
$ibo-caselog-highlight-color-4: $ibo-color-red-500;
$ibo-caselog-highlight-color-5: $ibo-color-blue-500;
$ibo-caselog-highlight-color-6: $ibo-color-red-300;
$ibo-caselog-highlight-color-7: $ibo-color-blue-300;
$ibo-caselog-highlight-color-1: $ibo-color-blue-700 !default;
$ibo-caselog-highlight-color-2: $ibo-color-red-700 !default;
$ibo-caselog-highlight-color-3: $ibo-color-information-600 !default;
$ibo-caselog-highlight-color-4: $ibo-color-red-500 !default;
$ibo-caselog-highlight-color-5: $ibo-color-blue-500 !default;
$ibo-caselog-highlight-color-6: $ibo-color-red-300 !default;
$ibo-caselog-highlight-color-7: $ibo-color-blue-300 !default;
$ibo-input-wrapper--is-error--border-color: $ibo-color-pink-700;
$ibo-field-validation: $ibo-color-pink-800;
$ibo-input-wrapper--is-error--border-color: $ibo-color-pink-700 !default;
$ibo-field-validation: $ibo-color-pink-800 !default;
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-pink-600;
$ibo-navigation-menu--visual-hint--background-color: $ibo-color-pink-600 !default;
$ibo-wizard-container--background-color: $ibo-color-information-200;
$ibo-wizard-container--border-color: $ibo-color-information-600;
$ibo-wizard-container--background-color: $ibo-color-information-200 !default;
$ibo-wizard-container--border-color: $ibo-color-information-600 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100;
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-pink-100;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-pink-600;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-400;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500;
$ibo-navigation-menu--notifications--item--new-message-indicator--background-color: $ibo-color-white-100 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--border: solid 2px $ibo-color-grey-500 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--background-color: $ibo-color-pink-100 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-1--border: solid 2px $ibo-color-pink-600 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--background-color: $ibo-color-warning-100 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-2--border: solid 2px $ibo-color-warning-400 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--background-color: $ibo-color-success-100 !default;
$ibo-navigation-menu--notifications--item--new-message-indicator--is-priority-3--border: solid 2px $ibo-color-success-500 !default;
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-pink-500;
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-pink-500 !default;

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
{
"config": {
"classmap-authoritative": true
},
"autoload": {
"psr-4": {
"Combodo\\iTop\\DataFeatureRemoval\\": "src",
"": "src/NoNamespace"
}
},
"name": "combodo/combodo-data-feature-removal",
"type": "itop-extension",
"description": "iTop Data Feature Removal",
"require": {
"composer-runtime-api": "^2.0"
}
}

View File

@@ -0,0 +1,20 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b862a55cbf5448fb99f0905a4db6529b",
"packages": [],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"composer-runtime-api": "^2.0"
},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

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