Compare commits

...

127 Commits

Author SHA1 Message Date
denis.flaven@combodo.com
46aaeb4301 Merge branch 'support/2.7' into support/3.1 2025-02-07 10:24:18 +01:00
denis.flaven@combodo.com
affed69999 Version number bump. 2025-02-07 10:09:48 +01:00
denis.flaven@combodo.com
64a216e0f6 Merge branch 'support/2.7' into support/3.1 2025-01-31 17:13:09 +01:00
denis.flaven@combodo.com
d5754fc568 N°8135 - Bump datamodel version. 2025-01-31 17:04:56 +01:00
jf-cbd
ccb1ca9d79 Merge remote-tracking branch 'origin/support/2.7' into support/3.1 2025-01-28 10:32:18 +01:00
jf-cbd
44290db312 N°8134 - Portal user profile is broken, regression from 7776 2025-01-28 10:23:44 +01:00
Eric Espie
025af923ea N°8131 - Issue on DBlinkchange event when object is delete 2025-01-24 14:23:50 +01:00
Eric Espie
858b12abaa N°8131 - Issue on DBlinkchange event when object is delete 2025-01-24 11:59:51 +01:00
Eric Espie
a7bc4bd411 Add new measure points for KPI logger 2025-01-22 16:34:31 +01:00
jf-cbd
a07f66c061 Merge remote-tracking branch 'origin/support/2.7' into support/3.1 2025-01-21 16:48:33 +01:00
jf-cbd
c49ceae75e Fix HandleForm call 2025-01-21 16:46:15 +01:00
jf-cbd
4da975cb64 Merge remote-tracking branch 'origin/support/2.7' into support/3.1 2025-01-21 12:15:06 +01:00
jf-cbd
8980f627e9 Fix format 2025-01-21 12:09:06 +01:00
jf-cbd
ec61b52238 N°7776 remove twig from ajax calls, 3.1 edition 2025-01-20 16:02:26 +01:00
jf-cbd
072596a53b Merge remote-tracking branch 'origin/support/2.7' into support/3.1
# Conflicts:
#	datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php
2025-01-20 15:53:34 +01:00
jf-cbd
160bfd714b N°7776 remove twig from ajax calls 2025-01-20 15:41:22 +01:00
jf-cbd
1c5cb1547f Merge remote-tracking branch 'origin/support/2.7' into support/3.1 2025-01-16 17:15:12 +01:00
jf-cbd
8d58372074 Update unattended installation script documentation 2025-01-16 17:13:26 +01:00
odain
5780f26817 N°7810: fix merge 2024-12-27 11:13:31 +01:00
odain
343f3286b8 Merge branch 'support/2.7' into support/3.1 2024-12-27 09:08:47 +01:00
Eric Espie
37fc1a5723 N°7810 - security hardening 2024-12-27 09:04:28 +01:00
jf-cbd
1fa50f695d Security hardening 2024-12-16 10:47:06 +01:00
jf-cbd
692cf4f635 Merge branch 'support/2.7' into support/3.1 2024-12-16 10:27:00 +01:00
jf-cbd
95aa444ee6 Security hardening 2024-12-13 16:48:13 +01:00
jf-cbd
f5de808c7c Security hardening (#685)
* security hardening
2024-12-13 15:09:18 +01:00
jf-cbd
cbb4281a37 N°7980 - security hardening 2024-11-29 16:40:34 +01:00
Benjamin Dalsass
06dcae1dd1 Merge remote-tracking branch 'origin/support/2.7' into support/3.1 2024-11-27 09:50:57 +01:00
Benjamin Dalsass
e03033ce52 N°7219 - Fatal error following dashboard modification when dashboard title contains an é 2024-11-27 09:40:22 +01:00
Karel Vlk
c70d62a51e 🐛 N°7916 SF#2274 EmailLaminas.php: Keep charset with part header in multipart email (#672)
* 🐛 N°2274 EmailLaminas.php: Keep charset with part header in multipart email

* Add a unit test

---------

Co-authored-by: Stephen Abello <stephen.abello@combodo.com>
2024-11-08 09:38:57 +01:00
jf-cbd
0d5ff261fe Merge remote-tracking branch 'origin/support/2.7' into support/3.1 2024-11-07 14:51:32 +01:00
jf-cbd
374b35f78a 🚀 Fix GitHub action 2024-11-07 14:50:46 +01:00
Stephen Abello
9371bc6d7b N°7925 Fix incorrectly formatted In-Reply-To email header 2024-11-04 14:38:31 +01:00
Karel Vlk
58e964fb8c 🐛 N°7917 SF#2272 EmailLaminas.php: Fix Message-ID format (#671)
* 🐛 N°2272 EmailLaminas.php: Fix Message-ID format

* EmailLaminas.php: Add MessageId import

as suggested by @steffunky in PR 671
2024-11-04 14:37:15 +01:00
jf-cbd
e8ecc85828 Merge remote-tracking branch 'origin/support/2.7' into support/3.1 2024-10-22 16:09:02 +02:00
jf-cbd
04bd8cc5ce 🚀 Update GitHub actions to improve PR classification 2024-10-22 16:07:47 +02:00
Eric Espie
88756a443a Fix event listeners display when the listener is a static method 2024-10-14 09:25:05 +02:00
denis.flaven@combodo.com
052e2a1a42 🔖 Prepare 3.1.2 version 2024-10-03 12:14:32 +02:00
Eric Espie
eb1ecff7d8 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-09-26 17:37:43 +02:00
Eric Espie
8141723869 Merge remote-tracking branch 'origin/support/2.7' into support/3.0
# Conflicts:
#	approot.inc.php
#	css/css-variables.scss
#	datamodels/2.x/authent-cas/module.authent-cas.php
#	datamodels/2.x/authent-external/module.authent-external.php
#	datamodels/2.x/authent-ldap/module.authent-ldap.php
#	datamodels/2.x/authent-local/module.authent-local.php
#	datamodels/2.x/combodo-db-tools/module.combodo-db-tools.php
#	datamodels/2.x/itop-attachments/module.itop-attachments.php
#	datamodels/2.x/itop-backup/module.itop-backup.php
#	datamodels/2.x/itop-bridge-virtualization-storage/module.itop-bridge-virtualization-storage.php
#	datamodels/2.x/itop-change-mgmt-itil/module.itop-change-mgmt-itil.php
#	datamodels/2.x/itop-change-mgmt/module.itop-change-mgmt.php
#	datamodels/2.x/itop-config-mgmt/module.itop-config-mgmt.php
#	datamodels/2.x/itop-config/module.itop-config.php
#	datamodels/2.x/itop-core-update/module.itop-core-update.php
#	datamodels/2.x/itop-datacenter-mgmt/module.itop-datacenter-mgmt.php
#	datamodels/2.x/itop-endusers-devices/module.itop-endusers-devices.php
#	datamodels/2.x/itop-files-information/module.itop-files-information.php
#	datamodels/2.x/itop-full-itil/module.itop-full-itil.php
#	datamodels/2.x/itop-hub-connector/module.itop-hub-connector.php
#	datamodels/2.x/itop-incident-mgmt-itil/module.itop-incident-mgmt-itil.php
#	datamodels/2.x/itop-knownerror-mgmt/module.itop-knownerror-mgmt.php
#	datamodels/2.x/itop-oauth-client/module.itop-oauth-client.php
#	datamodels/2.x/itop-portal-base/module.itop-portal-base.php
#	datamodels/2.x/itop-portal/module.itop-portal.php
#	datamodels/2.x/itop-problem-mgmt/module.itop-problem-mgmt.php
#	datamodels/2.x/itop-profiles-itil/module.itop-profiles-itil.php
#	datamodels/2.x/itop-request-mgmt-itil/module.itop-request-mgmt-itil.php
#	datamodels/2.x/itop-request-mgmt/module.itop-request-mgmt.php
#	datamodels/2.x/itop-service-mgmt-provider/module.itop-service-mgmt-provider.php
#	datamodels/2.x/itop-service-mgmt/module.itop-service-mgmt.php
#	datamodels/2.x/itop-sla-computation/module.itop-sla-computation.php
#	datamodels/2.x/itop-storage-mgmt/module.itop-storage-mgmt.php
#	datamodels/2.x/itop-tickets/module.itop-tickets.php
#	datamodels/2.x/itop-virtualization-mgmt/module.itop-virtualization-mgmt.php
#	datamodels/2.x/itop-welcome-itil/module.itop-welcome-itil.php
#	datamodels/2.x/version.xml
2024-09-26 17:37:07 +02:00
denis.flaven@combodo.com
8cb701bda3 🔖 Prepare 2.7.11 version 2024-09-26 16:53:24 +02:00
jf-cbd
1b29746806 Rename github token 2024-09-23 17:14:41 +02:00
jf-cbd
fb9c317256 Add an action in the workflow to automatically add pull requests to the Combodo PRs dashboard 2024-09-23 14:43:33 +02:00
Molkobain
8c704951e1 N°7730 - Rename constant as it is actually for ID and class selectors, not only IDs 2024-08-19 18:41:32 +02:00
Eric Espie
24c23628d6 N°7730 - code hardening 2024-08-19 15:15:22 +02:00
jf-cbd
141c22ff67 Fix selectize bug when multiple selectize fields exist on the same page 2024-07-17 14:17:09 +02:00
Thomas Casteleyn
1e3c425e81 N°7645 - PHP 8.1: Fix usage of strpos() & str_replace() with null value when compiling empty dictionary (#600)
* fix(compiler): provide empty string instead of null value

* Apply review suggestions
2024-07-10 10:07:12 +02:00
Benjamin Dalsass
b2e3fb6354 N°7024 - Fix opening an object with abstract class indirect linked set in Portal 2024-07-05 09:10:19 +02:00
jf-cbd
45ce68e16e Merge remote-tracking branch 'refs/remotes/origin/support/3.0' into support/3.1 2024-07-04 13:55:11 +02:00
jf-cbd
1aef576403 N°7604 - Security hardening 2024-07-04 13:52:19 +02:00
jf-cbd
bfd9be8280 Merge remote-tracking branch 'refs/remotes/origin/support/3.0' into support/3.1 2024-07-04 10:59:57 +02:00
jf-cbd
96e1388dde N°7603 - Security hardening + UI blocks examples updated 2024-07-04 10:56:08 +02:00
Timothee
86b48b8980 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-07-03 17:00:22 +02:00
Timothee
69c8791fc5 Fix merge conflit resolution d3b9965283 2024-07-03 16:48:08 +02:00
Eric Espie
5fd8678a3a N°7619 - Object deletion not cascaded to legacy extensions 2024-07-02 10:38:25 +02:00
Eric Espie
86df9ac035 N°7619 - Object deletion not cascaded to legacy extensions 2024-07-02 10:37:36 +02:00
Molkobain
3f997b416f N°7313 - Revert "fix" as it breaks grouping on ext. keys, OQL functions and more
We keep the enhancements to the test framework though.
2024-06-28 14:54:51 +02:00
Eric Espie
cceb6809e7 Fix CI 2024-06-24 14:20:33 +02:00
Eric Espie
48b559472e Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
2024-06-24 14:02:04 +02:00
Eric Espie
cddc452693 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2024-06-24 13:55:29 +02:00
Eric Espie
0904a21e3f Cleanup ItopTestCase 2024-06-24 11:50:37 +02:00
Timothee
66230199f8 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-06-21 12:41:17 +02:00
Timothee
1f1a2b660f N°7581 Improve error message readability during object creation/modification in the portal (regression introduced with N°7545) 2024-06-21 12:36:52 +02:00
Molkobain
4f36369ea1 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-06-21 11:32:21 +02:00
Molkobain
33a906f11a Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2024-06-21 11:29:18 +02:00
Molkobain
82d11eeb47 N°7127 - Upgrade handlebars.js to v4.7.8 2024-06-21 11:19:39 +02:00
Eric Espie
b8bf66031b Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-06-20 11:07:57 +02:00
Eric Espie
2596a150bf Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2024-06-20 11:07:36 +02:00
Eric Espie
142d6c8993 N°7533 - Detect and warns on Galera clusters 2024-06-20 11:06:57 +02:00
Timothee
d70e5dff45 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-06-17 16:55:26 +02:00
Timothee
c4fc0ed982 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2024-06-17 16:51:30 +02:00
Timothee
320922a13d N°7545 Correctly display error message 2024-06-17 16:49:33 +02:00
Eric Espie
f96d28a0a8 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-06-12 16:48:39 +02:00
Eric Espie
d3b9965283 Merge remote-tracking branch 'origin/support/2.7' into support/3.0
# Conflicts:
#	core/cmdbsource.class.inc.php
2024-06-12 16:48:06 +02:00
Eric Espie
f03d731b1d N°7533 - Prevent installation of iTop on Galera clusters 2024-06-12 16:14:23 +02:00
Eric Espie
3e3ac0d83f N°7542 - code hardening 2024-05-30 12:04:36 +02:00
Eric Espie
32835f70b9 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-05-29 18:19:48 +02:00
Eric Espie
63cf78f64d Merge remote-tracking branch 'origin/support/2.7' into support/3.0
# Conflicts:
#	pages/preferences.php
2024-05-29 18:18:55 +02:00
Eric Espie
8be7628668 N°7548 - Code hardening 2024-05-29 18:11:36 +02:00
Eric Espie
5e64be8580 N°7491 - Fix unit tests 2024-05-24 09:48:07 +02:00
Eric Espie
a0b76a25be N°7491 - Fix Events during DBObject CRUD 2024-05-24 09:35:00 +02:00
Eric Espie
e2f2afad54 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-05-21 15:07:28 +02:00
Eric Espie
f632cf3155 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2024-05-21 15:07:08 +02:00
Eric Espie
13dfdc3d9d Merge remote-tracking branch 'origin/support/2.7' into support/3.1 2024-05-21 14:29:10 +02:00
Eric Espie
62caf16153 N°7364 - Code hardening 2024-05-21 14:20:30 +02:00
jf-cbd
020a37ead9 N°7526 - Profile input unfocus: Previously entered value still filters 2024-05-17 10:58:29 +02:00
odain
163a3afc0f N°7426 - no session created - replace php_sapi_name() by PHP_SAPI in unattended 2024-05-16 15:31:08 +02:00
odain
35265718c1 Merge branch 'support/3.0' into support/3.1 2024-05-16 14:37:41 +02:00
odain
d98e35d918 Merge branch 'support/2.7' into support/3.0 2024-05-16 14:13:24 +02:00
odain
f8b54be896 N°7426 - no session created - replace php_sapi_name() by PHP_SAPI 2024-05-16 14:10:54 +02:00
Romain Quetiez
a88ce075cc Merge branch 'support/3.0' into support/3.1
# Conflicts:
#	tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
2024-05-16 14:07:36 +02:00
Romain Quetiez
c6f3e36451 Merge branch 'support/2.7' into support/3.0
# Conflicts:
#	tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
#	tests/php-unit-tests/unitary-tests/core/iTopConfigParserTest.php
2024-05-16 10:09:11 +02:00
Romain Quetiez
53dc452d61 Avoid unnecessary custom test environment compilations (base compilation of file modification time) 2024-05-16 09:53:04 +02:00
Romain Quetiez
ccaf2dc5b7 Make the tests compatible with windows (and linux) 2024-05-16 09:53:04 +02:00
Molkobain
d9581a083d Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-05-07 10:41:04 +02:00
Molkobain
46738d4ba4 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2024-05-07 10:38:50 +02:00
Molkobain
5d5df5ad1a N°7255 - Fix misc. stylesheets not working in portal since N°7047 2024-05-07 10:37:39 +02:00
jf-cbd
8c46c99ecc N°7313 - Bad display of single quotes in charts 2024-04-30 15:04:28 +02:00
jf-cbd
02be397e8f Merge remote-tracking branch 'refs/remotes/origin/support/3.0' into support/3.1 2024-04-30 10:57:06 +02:00
jf-cbd
61469a28b9 N°7445 - Invalid Unicode escape sequence on dashlet Header with statistics 2024-04-30 10:56:09 +02:00
jf-cbd
9ff54cead8 Merge remote-tracking branch 'refs/remotes/origin/support/3.0' into support/3.1 2024-04-30 08:14:11 +02:00
jf-cbd
dbcbb187b2 N°7445 - Invalid Unicode escape sequence on dashlet Header with statistics 2024-04-30 08:13:37 +02:00
jf-cbd
71b4d672d4 Merge remote-tracking branch 'refs/remotes/origin/support/3.0' into support/3.1
# Conflicts:
#	pages/ajax.render.php
2024-04-30 08:04:45 +02:00
jf-cbd
93bba66323 N°7445 - Invalid Unicode escape sequence on dashlet Header with statistics 2024-04-30 08:03:14 +02:00
Molkobain
0a95aa385a Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	tests/php-unit-tests/unitary-tests/application/applicationextension/Delta/application-extension-usages-in-snippets.xml
2024-04-29 14:03:16 +02:00
Molkobain
cab6394cba Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2024-04-29 13:58:13 +02:00
Molkobain
32140b360f Cherry pick fixes from 59a955f4 2024-04-29 11:45:09 +02:00
Benjamin Dalsass
06a8481511 N°7279 - AttributeClass defined in XML datamodel compilation issue 2024-04-25 11:39:54 +02:00
jf-cbd
59a955f491 🐛 N°7313 - Bad display of single quotes in charts (#627)
* 🐛 N°7313 - Bad display of single quotes in charts
* Fix and improve ItopCustomDatamodelTestCase
2024-04-24 17:03:47 +02:00
jf-cbd
2ffcfd2f57 Merge remote-tracking branch 'refs/remotes/origin/support/3.0' into support/3.1 2024-04-24 12:00:47 +02:00
jf-cbd
e657052d17 Merge remote-tracking branch 'refs/remotes/origin/support/2.7' into support/3.0 2024-04-24 11:58:13 +02:00
jf-cbd
d85767a838 Update test to run only on commmunity builds 2024-04-24 11:14:40 +02:00
jf-cbd
e56d5ed3aa Merge remote-tracking branch 'refs/remotes/origin/support/3.0' into support/3.1
# Conflicts:
#	datamodels/2.x/itop-hub-connector/launch.php
#	setup/wizardsteps.class.inc.php
2024-04-23 14:13:40 +02:00
jf-cbd
e5a8bd61b0 Merge remote-tracking branch 'refs/remotes/origin/support/2.7' into support/3.0
# Conflicts:
#	datamodels/2.x/itop-hub-connector/launch.php
2024-04-23 14:03:15 +02:00
jf-cbd
eeec57536b Security hardening 2024-04-23 11:55:39 +02:00
jf-cbd
7fba61ff35 N°7445 - Invalid Unicode escape sequence on dashlet Header with statistics 2024-04-19 15:21:48 +02:00
jf-cbd
1bf156bdda Revert "Merge branch 'refs/heads/support/3.0' into support/3.1"
This reverts commit 1164e757de, reversing
changes made to 1235452a1b.
2024-04-19 15:16:57 +02:00
jf-cbd
1164e757de Merge branch 'refs/heads/support/3.0' into support/3.1
# Conflicts:
#	pages/ajax.render.php
2024-04-19 11:24:56 +02:00
jf-cbd
514e0b80a5 N°7445 - Invalid Unicode escape sequence on dashlet Header with statistics 2024-04-19 11:17:09 +02:00
Molkobain
1235452a1b Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-04-19 09:24:21 +02:00
Molkobain
35f4ab4941 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2024-04-19 09:22:32 +02:00
Molkobain
16ff6341d0 N°7455 - Fix regression from 4c784886, wrong class tested 2024-04-19 09:14:53 +02:00
Molkobain
12ad3c2732 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-04-18 18:49:08 +02:00
Molkobain
ac826cb9f1 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2024-04-18 18:48:24 +02:00
Molkobain
9dab8679d6 N°7448 - Update dictionary entry 2024-04-18 18:44:20 +02:00
Molkobain
4f3b25aa46 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2024-04-18 18:17:41 +02:00
Molkobain
f737bcb9a0 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2024-04-18 18:16:24 +02:00
Molkobain
4c78488644 N°7455 - Ensure form renderer class extends FormRenderer 2024-04-18 18:15:02 +02:00
111 changed files with 2070 additions and 1574 deletions

43
.github/workflows/action.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Add PRs to Combodo PRs Dashboard
on:
pull_request_target:
types:
- opened
jobs:
add-to-project:
name: Add PR to Combodo Project
runs-on: ubuntu-latest
steps:
- name: Check if author is a member of the organization
id: check-membership
run: |
ORG="Combodo"
AUTHOR=$(jq -r .pull_request.user.login "$GITHUB_EVENT_PATH")
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
"https://api.github.com/orgs/$ORG/members/$AUTHOR")
if [ "$RESPONSE" == "404" ]; then
echo "project_url=https://github.com/orgs/Combodo/projects/5" >> $GITHUB_ENV
echo "is_member=false" >> $GITHUB_ENV
else
echo "project_url=https://github.com/orgs/Combodo/projects/4" >> $GITHUB_ENV
echo "is_member=true" >> $GITHUB_ENV
fi
- name: Add internal tag if member
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"]}'
env:
is_member: ${{ env.is_member }}
- name: Add PR to the appropriate project
uses: actions/add-to-project@v1.0.2
with:
project-url: ${{ env.project_url }}
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}

View File

@@ -27,7 +27,7 @@ $iTopFolder = __DIR__."/../../../";
require_once("$iTopFolder/approot.inc.php");
require_once(APPROOT."/application/utils.inc.php");
if (php_sapi_name() !== 'cli')
if (PHP_SAPI !== 'cli')
{
throw new \Exception('This script can only run from CLI');
}
@@ -48,4 +48,4 @@ if (!file_exists($sCssFile))
{
fwrite(STDERR, "Failed to compile $sCssFile, exiting.");
exit(1);
}
}

View File

@@ -26,7 +26,7 @@ $iTopFolder = __DIR__ . "/../../" ;
require_once ("$iTopFolder/approot.inc.php");
require_once (APPROOT."/setup/setuputils.class.inc.php");
if (php_sapi_name() !== 'cli')
if (PHP_SAPI !== 'cli')
{
throw new \Exception('This script can only run from CLI');
}
@@ -70,4 +70,4 @@ if (false === empty($aMissing)) {
echo "Some new tests dirs exists !\n"
.' They must be declared either in the allowed or denied list in '.iTopComposer::class." (see N°2651).\n"
.' List of dirs:'."\n".var_export($aMissing, true);
}
}

View File

@@ -1115,7 +1115,9 @@ HTML
// Note: DisplayBareHeader is called before adding $oObjectDetails to the page, so it can inject HTML before it through $oPage.
/** @var \iTopWebPage $oPage */
$oKPI = new ExecutionKPI();
$aHeadersBlocks = $this->DisplayBareHeader($oPage, $bEditMode);
$oKPI->ComputeStatsForExtension($this, 'DisplayBareHeader');
if (false === empty($aHeadersBlocks['subtitle'])) {
$oObjectDetails->AddSubTitleBlocks($aHeadersBlocks['subtitle']);
}
@@ -1128,8 +1130,12 @@ HTML
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, '', $oObjectDetails);
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
$oPage->SetCurrentTab('UI:PropertiesTab');
$oKPI = new ExecutionKPI();
$this->DisplayBareProperties($oPage, $bEditMode);
$oKPI->ComputeStatsForExtension($this, 'DisplayBareProperties');
$oKPI = new ExecutionKPI();
$this->DisplayBareRelations($oPage, $bEditMode);
$oKPI->ComputeStatsForExtension($this, 'DisplayBareRelations');
// Note: Adding the JS snippet which enables the image upload should have been done directly by the ActivityPanel which would have kept the independance principle
@@ -4583,6 +4589,8 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
@@ -4664,7 +4672,22 @@ HTML;
return $oDeletionPlan;
}
protected function PostDeleteActions(): void
final protected function PreDeleteActions(): void
{
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBDelete()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
}
parent::PreDeleteActions();
}
final protected function PostDeleteActions(): void
{
parent::PostDeleteActions();
}
@@ -4678,6 +4701,8 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBDelete()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
@@ -4699,6 +4724,7 @@ HTML;
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified()");
$oKPI = new ExecutionKPI();
$bIsModified = $oExtensionInstance->OnIsModified($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
@@ -4758,6 +4784,8 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToWrite()");
$oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
@@ -4808,6 +4836,8 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToDelete()");
$oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
@@ -5934,7 +5964,7 @@ JS
final protected function FireEventAfterWrite(array $aChanges, bool $bIsNew): void
{
$this->NotifyAttachedObjectsOnLinkClassModification();
$this->FireEventDbLinksChangedForCurrentObject();
$this->RemoveObjectAwaitingEventDbLinksChanged(get_class($this), $this->GetKey());
$this->FireEvent(EVENT_DB_AFTER_WRITE, ['is_new' => $bIsNew, 'changes' => $aChanges]);
}
@@ -6047,31 +6077,6 @@ JS
}
}
/**
* Fire the EVENT_DB_LINKS_CHANGED event if current object is registered
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreException
*
* @since 3.1.0 N°5906
*/
final protected function FireEventDbLinksChangedForCurrentObject(): void
{
if (true === static::IsEventDBLinksChangedBlocked()) {
return;
}
$sClass = get_class($this);
$sId = $this->GetKey();
$bIsObjectAwaitingEventDbLinksChanged = self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
if (false === $bIsObjectAwaitingEventDbLinksChanged) {
return;
}
self::FireEventDbLinksChangedForObject($this);
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
}
/**
* Fire the EVENT_DB_LINKS_CHANGED event if given object is registered, and unregister it
*
@@ -6101,7 +6106,9 @@ JS
// We want to avoid launching the listener twice, first here, and secondly after saving the Ticket in the listener
// By disabling the event to be fired, we can remove the current object from the attribute !
$oObject = MetaModel::GetObject($sClass, $sId, false);
self::FireEventDbLinksChangedForObject($oObject);
if (!is_null($oObject)) {
self::FireEventDbLinksChangedForObject($oObject);
}
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
}
@@ -6109,13 +6116,11 @@ JS
{
self::SetEventDBLinksChangedBlocked(true);
// N°6408 The object can have been deleted
if (!is_null($oObject)) {
MetaModel::StartReentranceProtection($oObject);
$oObject->FireEvent(EVENT_DB_LINKS_CHANGED);
MetaModel::StopReentranceProtection($oObject);
if (count($oObject->ListChanges()) !== 0) {
$oObject->DBUpdate();
}
$oObject->FireEvent(EVENT_DB_LINKS_CHANGED);
// Update the object if needed
if (count($oObject->ListChanges()) !== 0) {
$oObject->DBUpdate();
}
cmdbAbstractObject::SetEventDBLinksChangedBlocked(false);
}

View File

@@ -1264,12 +1264,12 @@ EOF
$sOkButtonLabel = Dict::S('UI:Button:Save');
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
$sId = utils::HtmlEntities($this->sId);
$sLayoutClass = utils::HtmlEntities($this->sLayoutClass);
$sId = json_encode($this->sId);
$sLayoutClass = json_encode($this->sLayoutClass);
$sAutoReload = $this->bAutoReload ? 'true' : 'false';
$sAutoReloadSec = (string) $this->iAutoReloadSec;
$sTitle = utils::HtmlEntities($this->sTitle);
$sFile = utils::HtmlEntities($this->GetDefinitionFile());
$sTitle = json_encode($this->sTitle);
$sFile = json_encode($this->GetDefinitionFile());
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
$sReloadURL = $this->GetReloadURL();
@@ -1325,15 +1325,15 @@ $('#dashboard_editor').dialog({
});
$('#dashboard_editor .ui-layout-center').runtimedashboard({
dashboard_id: '$sId',
layout_class: '$sLayoutClass',
title: '$sTitle',
dashboard_id: $sId,
layout_class: $sLayoutClass,
title: $sTitle,
auto_reload: $sAutoReload,
auto_reload_sec: $sAutoReloadSec,
submit_to: '$sUrl',
submit_parameters: {operation: 'save_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
submit_parameters: {operation: 'save_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
render_to: '$sUrl',
render_parameters: {operation: 'render_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
render_parameters: {operation: 'render_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
new_dashlet_parameters: {operation: 'new_dashlet'}
});

View File

@@ -704,7 +704,7 @@ class DisplayBlock
if ($bDoSearch)
{
// Keep the table_id identifying this table if we're performing a search
$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
$sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
if ($sTableId != null)
{
$aExtraParams['table_id'] = $sTableId;

View File

@@ -60,6 +60,24 @@ class CoreCannotSaveObjectException extends CoreException
return $sContent;
}
public function getTextMessage()
{
$sTitle = Dict::S('UI:Error:SaveFailed');
$sContent = utils::HtmlEntities($sTitle);
if (count($this->aIssues) == 1) {
$sIssue = reset($this->aIssues);
$sContent .= utils::HtmlEntities($sIssue);
} else {
foreach ($this->aIssues as $sError) {
$sContent .= " ".utils::HtmlEntities($sError).", ";
}
}
return $sContent;
}
public function getIssues()
{
return $this->aIssues;

View File

@@ -109,6 +109,11 @@ class utils
* @since 2.7.10 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER = 'element_identifier';
/**
* @var string For XML / HTML node id/class selector
* @since 3.1.2 3.2.1
*/
public const ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR = 'element_selector';
/**
* @var string For variables names
* @since 3.0.0
@@ -238,13 +243,8 @@ class utils
public static function IsModeCLI()
{
$sSAPIName = php_sapi_name();
$sCleanName = strtolower(trim($sSAPIName));
if ($sCleanName == 'cli') {
return true;
} else {
return false;
}
$sCleanName = strtolower(trim(PHP_SAPI));
return ($sCleanName === 'cli');
}
/**
@@ -367,13 +367,13 @@ class utils
}
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
}
public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter')
{
$retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
}
public static function Sanitize($value, $defaultValue, $sSanitizationFilter)
{
if ($value === $defaultValue)
@@ -389,7 +389,7 @@ class utils
$retValue = $defaultValue;
}
}
return $retValue;
return $retValue;
}
/**
@@ -494,8 +494,17 @@ class utils
}
break;
// For XML / HTML node identifiers
case static::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER:
$retValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $value);
$retValue = filter_var($retValue, FILTER_VALIDATE_REGEXP,
['options' => ['regexp' => '/^[A-Za-z0-9][A-Za-z0-9_-]*$/']]);
break;
// For XML / HTML node id selector
case static::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
['options' => ['regexp' => '/^[#\.][A-Za-z0-9][A-Za-z0-9_-]*$/']]);
break;
case static::ENUM_SANITIZATION_FILTER_VARIABLE_NAME:
@@ -545,11 +554,11 @@ class utils
$sMimeType = self::GetFileMimeType($sTmpName);
$oDocument = new ormDocument($doc_content, $sMimeType, $sName);
break;
case UPLOAD_ERR_NO_FILE:
// no file to load, it's a normal case, just return an empty document
break;
case UPLOAD_ERR_FORM_SIZE:
case UPLOAD_ERR_INI_SIZE:
throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize')));
@@ -558,7 +567,7 @@ class utils
case UPLOAD_ERR_PARTIAL:
throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.'));
break;
case UPLOAD_ERR_NO_TMP_DIR:
throw new FileUploadException(Dict::S('UI:Error:NoTmpDir'));
break;
@@ -571,7 +580,7 @@ class utils
$sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName));
break;
default:
throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError));
break;
@@ -677,17 +686,17 @@ class utils
return $aSelectedObj;
}
public static function GetNewTransactionId()
{
return privUITransaction::GetNewTransactionId();
}
public static function IsTransactionValid($sId, $bRemoveTransaction = true)
{
return privUITransaction::IsTransactionValid($sId, $bRemoveTransaction);
}
public static function RemoveTransaction($sId)
{
return privUITransaction::RemoveTransaction($sId);
@@ -871,9 +880,9 @@ class utils
$aDateTokens = array_keys($aSpec);
$aDateRegexps = array_values($aSpec);
}
$sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat);
if (preg_match('!^(?<head>)'.$sDateRegexp.'(?<tail>)$!', $sDate, $aMatches))
{
$sYear = isset($aMatches['year']) ? $aMatches['year'] : 0;
@@ -890,7 +899,7 @@ class utils
}
// http://www.spaweditor.com/scripts/regex/index.php
}
/**
* Convert an old date/time format specification (using % placeholders)
* to a format compatible with DateTime::createFromFormat
@@ -1488,7 +1497,7 @@ class utils
$aResult = [];
// 1st - add standard built-in menu items
//
//
switch($iMenuId)
{
case iPopupMenuExtension::MENU_OBJLIST_ACTIONS:
@@ -1872,7 +1881,7 @@ SQL;
return $sProposed;
}
}
/**
* Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore)
* @param string $sId The ID to sanitize
@@ -1882,7 +1891,7 @@ SQL;
{
return str_replace(array(':', '[', ']', '+', '-', ' '), '_', $sId);
}
/**
* Helper to execute an HTTP POST request, uses CURL PHP extension
*
@@ -1967,7 +1976,7 @@ SQL;
/**
* Get a standard list of character sets
*
*
* @param array $aAdditionalEncodings Additional values
* @return array of iconv code => english label, sorted by label
*/
@@ -2074,7 +2083,7 @@ SQL;
return $e->getMessage();
}
}
/**
* Convert (?) plain text to some HTML markup by replacing newlines by <br/> tags
* and escaping HTML entities
@@ -2091,7 +2100,7 @@ SQL;
return str_replace("\n", '<br/>', utils::EscapeHtml($sText));
}
/**
* Eventually compiles the SASS (.scss) file into the CSS (.css) file
*
@@ -2205,7 +2214,7 @@ SQL;
case 'image/png':
$img = @imagecreatefromstring($oImage->GetData());
break;
default:
// Unsupported image type, return the image as-is
//throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
@@ -2219,14 +2228,14 @@ SQL;
else
{
// Let's scale the image, preserving the transparency for GIFs and PNGs
$fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
// Preserve transparency
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
{
@@ -2234,38 +2243,38 @@ SQL;
imagealphablending($new, false);
imagesavealpha($new, true);
}
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
ob_start();
switch ($oImage->GetMimeType())
{
case 'image/gif':
imagegif($new); // send image to output buffer
break;
case 'image/jpeg':
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
break;
case 'image/png':
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
break;
}
$oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
@ob_end_clean();
imagedestroy($img);
imagedestroy($new);
return $oResampledImage;
}
}
/**
* Create a 128 bit UUID in the format: {########-####-####-####-############}
*
*
* Note: this method can be run from the command line as well as from the web server.
* Note2: this method is not cryptographically secure! If you need a cryptographically secure value
* consider using open_ssl or PHP 7 methods.
@@ -2303,7 +2312,7 @@ SQL;
{
return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1);
}
/**
* **Warning** : returned result can be invalid as we're using backtrace to find the module dir name
*
@@ -2340,7 +2349,7 @@ SQL;
{
return ModuleService::GetInstance()->GetCurrentModuleUrl(1);
}
/**
* @param string $sProperty The name of the property to retrieve
* @param mixed $defaultvalue
@@ -2350,7 +2359,7 @@ SQL;
{
return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue);
}
/**
* @param string $sModuleName
* @return string|NULL compiled version of a given module, as it was seen by the compiler
@@ -2359,7 +2368,7 @@ SQL;
{
return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName);
}
/**
* Check if the given path/url is an http(s) URL
* @param string $sPath
@@ -2374,7 +2383,7 @@ SQL;
}
return $bRet;
}
/**
* Check if the given URL is a link to download a document/image on the CURRENT iTop
* In such a case we can read the content of the file directly in the database (if the users rights allow) and return the ormDocument
@@ -2423,7 +2432,7 @@ SQL;
}
return $result;
}
/**
* Read the content of a file (and retrieve its MIME type) from either:
* - an URL pointing to a blob (image/document) on the current iTop server
@@ -2467,7 +2476,7 @@ SQL;
'html' => 'text/html',
'exe' => 'application/octet-stream',
);
$sData = null;
$sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters...
$sFileName = 'uploaded-file'; // Default name for downloaded-files
@@ -2525,7 +2534,7 @@ SQL;
}
$sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION));
$sFileName = basename($sPath);
if (array_key_exists($sExtension, $aKnownExtensions))
{
$sMimeType = $aKnownExtensions[$sExtension];
@@ -2539,7 +2548,7 @@ SQL;
}
return $oUploadedDoc;
}
protected static function ParseHeaders($aHeaders)
{
$aCleanHeaders = array();
@@ -2564,7 +2573,7 @@ SQL;
}
return $aCleanHeaders;
}
/**
* @return string a string based on compilation time or (if not available because the datamodel has not been loaded)
* the version of iTop. This string is useful to prevent browser side caching of content that may vary at each

View File

@@ -23,7 +23,7 @@ define('ITOP_DESIGN_LATEST_VERSION', '3.1');
* @used-by utils::GetItopVersionWikiSyntax()
* @used-by iTopModulesPhpVersionIntegrationTest
*/
define('ITOP_CORE_VERSION', '3.1.1');
define('ITOP_CORE_VERSION', '3.1.3');
/**
* @var string

View File

@@ -68,7 +68,7 @@ if (file_exists(MAINTENANCE_MODE_FILE) && !$bBypassMaintenance)
http_response_code(503);
// Display message depending on the request
include(APPROOT.'application/maintenancemsg.php');
$sSAPIName = strtoupper(trim(php_sapi_name()));
$sSAPIName = strtoupper(trim(PHP_SAPI));
switch (true)
{

View File

@@ -11508,6 +11508,11 @@ class AttributeClassAttCodeSet extends AttributeSet
}
return '<span class="'.implode(' ', $this->aCSSClasses).'">'.$value.'</span>';
}
public function IsNull($proposedValue)
{
return (empty($proposedValue));
}
}
class AttributeQueryAttCodeSet extends AttributeSet

View File

@@ -1169,8 +1169,8 @@ class CMDBSource
*/
public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType)
{
list($sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
list($sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sDbFieldType);
[$sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
[$sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sDbFieldType);
if (strcasecmp($sItopFieldDataType, $sDbFieldDataType) !== 0)
{
@@ -1603,7 +1603,19 @@ class CMDBSource
return false;
}
/**
public static function GetClusterNb()
{
$result = 0;
$sSql = "SHOW STATUS LIKE 'wsrep_cluster_size';";
$aRows = self::QueryToArray($sSql);
if (count($aRows) > 0)
{
$result = $aRows[0]['Value'];
}
return intval($result);
}
/**
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-database.html
* @return string query to upgrade database charset and collation if needed, null if not
* @throws \MySQLException

View File

@@ -766,6 +766,42 @@ abstract class DBObject implements iDisplay
$this->Set($sAttCode, $sValue);
}
/**
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \OQLException
* @throws \ReflectionException
*/
protected function PreDeleteActions(): void
{
$this->SetReadOnly('No modification allowed before delete');
$this->FireEventAboutToDelete();
$oKPI = new ExecutionKPI();
$this->OnDelete();
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
// Activate any existing trigger
$sClass = get_class($this);
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnObjectDelete AS t WHERE t.target_class IN (:class_list)'), array(),
$aParams);
while ($oTrigger = $oSet->Fetch()) {
/** @var \TriggerOnObjectDelete $oTrigger */
try {
$oKPI = new ExecutionKPI();
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch (Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
finally {
$oKPI->ComputeStatsForExtension($this, 'TriggerOnObjectDelete');
}
}
}
/**
* @return void
* @throws \ReflectionException
@@ -3332,6 +3368,9 @@ abstract class DBObject implements iDisplay
*/
public function DBInsertNoReload()
{
// Prevent DBUpdate at this point (reentrancy protection with temp id)
MetaModel::StartReentranceProtection($this);
$sClass = get_class($this);
$this->AddCurrentObjectInCrudStack('INSERT');
@@ -3378,6 +3417,8 @@ abstract class DBObject implements iDisplay
}
$this->ComputeStopWatchesDeadline(true);
// With temp id
MetaModel::StopReentranceProtection($this);
$iTransactionRetry = 1;
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
@@ -3570,6 +3611,12 @@ abstract class DBObject implements iDisplay
*/
public function DBUpdate()
{
if (!MetaModel::StartReentranceProtection($this)) {
$this->LogCRUDExit(__METHOD__, 'Rejected (reentrance)');
return false;
}
$this->LogCRUDEnter(__METHOD__);
if (!$this->m_bIsInDB)
{
@@ -3579,12 +3626,6 @@ abstract class DBObject implements iDisplay
$this->AddCurrentObjectInCrudStack('UPDATE');
if (!MetaModel::StartReentranceProtection($this)) {
$this->RemoveCurrentObjectInCrudStack();
$this->LogCRUDExit(__METHOD__, 'Rejected (reentrance)');
return false;
}
try {
// Protect against infinite loop
$this->iUpdateLoopCount++;
@@ -4085,16 +4126,17 @@ abstract class DBObject implements iDisplay
CMDBSource::DeleteFrom($sDeleteSQL);
}
/**
* @internal
*
* @throws ArchivedObjectException
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws MySQLException
* @throws MySQLHasGoneAwayException
* @throws OQLException
*/
/**
* @internal
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \Random\RandomException
* @throws \ReflectionException
*/
protected function DBDeleteSingleObject()
{
$this->LogCRUDEnter(__METHOD__);
@@ -4105,29 +4147,7 @@ abstract class DBObject implements iDisplay
return;
}
$this->SetReadOnly("No modification allowed before delete");
$this->FireEventAboutToDelete();
$oKPI = new ExecutionKPI();
$this->OnDelete();
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
// Activate any existing trigger
$sClass = get_class($this);
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectDelete AS t WHERE t.target_class IN (:class_list)"), array(),
$aParams);
while ($oTrigger = $oSet->Fetch())
{
/** @var \TriggerOnObjectDelete $oTrigger */
try
{
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch(Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
$this->PreDeleteActions();
$this->RecordObjDeletion($this->m_iKey); // May cause a reload for storing history information

View File

@@ -145,7 +145,7 @@ class EMail implements iEMail
*/
public function SetInReplyTo(string $sMessageId)
{
$this->AddToHeader('In-Reply-To', $sMessageId);
$this->oMailer->SetInReplyTo($sMessageId);
}
public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null)

View File

@@ -6852,6 +6852,9 @@ abstract class MetaModel
/**
* Instantiate an object already persisted to the Database.
*
* Note that LinkedSet attributes are not loaded.
* DBObject::Reload() will be called when getting a LinkedSet attribute
*
* @api
* @see MetaModel::GetObjectWithArchive to get object even if it's archived
* @see utils::PushArchiveMode() to enable search on archived objects

View File

@@ -537,7 +537,7 @@ EOF
}
else
{
throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
throw new Exception('graphviz not found');
}
return $sHtml;
}
@@ -592,7 +592,7 @@ EOF
}
else
{
throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
throw new Exception('graphviz not found');
}
return $sHtml;
}

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'authent-cas/3.1.1',
'authent-cas/3.1.3',
array(
// Identification
//

View File

@@ -27,7 +27,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'authent-external/3.1.1',
'authent-external/3.1.3',
array(
// Identification
//

View File

@@ -9,7 +9,7 @@ if (function_exists('ldap_connect'))
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'authent-ldap/3.1.1',
'authent-ldap/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'authent-local/3.1.1',
'authent-local/3.1.3',
array(
// Identification
//

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'combodo-backoffice-darkmoon-theme/3.1.1',
'combodo-backoffice-darkmoon-theme/3.1.3',
array(
// Identification
//

View File

@@ -24,7 +24,7 @@
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'combodo-db-tools/3.1.1',
'combodo-db-tools/3.1.3',
array(
// Identification
//

View File

@@ -19,7 +19,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-attachments/3.1.1',
'itop-attachments/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-backup/3.1.1',
'itop-backup/3.1.3',
array(
// Identification
//

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-bridge-cmdb-services/3.1.1',
'itop-bridge-cmdb-services/3.1.3',
array(
// Identification
//

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-bridge-cmdb-ticket/3.1.1',
'itop-bridge-cmdb-ticket/3.1.3',
array(
// Identification
//

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-bridge-datacenter-mgmt-services/3.1.1',
'itop-bridge-datacenter-mgmt-services/3.1.3',
array(
// Identification
//

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-bridge-endusers-devices-services/3.1.1',
'itop-bridge-endusers-devices-services/3.1.3',
array(
// Identification
//

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-bridge-storage-mgmt-services/3.1.1',
'itop-bridge-storage-mgmt-services/3.1.3',
array(
// Identification
//

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-bridge-virtualization-mgmt-services/3.1.1',
'itop-bridge-virtualization-mgmt-services/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-bridge-virtualization-storage/3.1.1',
'itop-bridge-virtualization-storage/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-change-mgmt-itil/3.1.1',
'itop-change-mgmt-itil/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-change-mgmt/3.1.1',
'itop-change-mgmt/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-config-mgmt/3.1.1',
'itop-config-mgmt/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-config/3.1.1',
'itop-config/3.1.3',
array(
// Identification
//

View File

@@ -24,7 +24,7 @@
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-core-update/3.1.1',
'itop-core-update/3.1.3',
[
// Identification
//

View File

@@ -18,7 +18,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-datacenter-mgmt/3.1.1',
'itop-datacenter-mgmt/3.1.3',
array(
// Identification
//

View File

@@ -25,7 +25,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-endusers-devices/3.1.1',
'itop-endusers-devices/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-faq-light/3.1.1',
'itop-faq-light/3.1.3',
array(
// Identification
//

View File

@@ -24,7 +24,7 @@
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-files-information/3.1.1',
'itop-files-information/3.1.3',
array(
// Identification
//

View File

@@ -6,7 +6,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-full-itil/3.1.1',
'itop-full-itil/3.1.3',
array(
// Identification
//

View File

@@ -0,0 +1,19 @@
<?php
class TokenValidation
{
// construct function
public function __construct()
{
}
public function isSetupTokenValid($sParamToken) : bool
{
if (!file_exists(APPROOT.'data/.setup')) {
return false;
}
$sSetupToken = trim(file_get_contents(APPROOT.'data/.setup'));
unlink(APPROOT.'data/.setup');
return $sParamToken === $sSetupToken;
}
}

View File

@@ -263,6 +263,7 @@ try {
require_once('hubconnectorpage.class.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
require_once('TokenValidation.php');
$sTargetRoute = utils::ReadParam('target', ''); // ||browse_extensions|deploy_extensions|
@@ -278,13 +279,23 @@ try {
switch ($sTargetRoute) {
case 'inform_after_setup':
// Hidden IFRAME at the end of the setup
$oPage = new NiceWebPage('');
$aDataToPost = MakeDataToPost($sTargetRoute);
$oPage->add('<form id="hub_launch_form" action="'.$sHubUrlStateless.'" method="post">');
$oPage->add('<input type="hidden" name="json" value="'.utils::EscapeHtml(json_encode($aDataToPost)).'">');
$oPage->add_ready_script('$("#hub_launch_form").submit();');
break;
// Hidden IFRAME at the end of the setup
require_once (APPROOT.'/application/ajaxwebpage.class.inc.php');
$sParamToken = utils::ReadParam('setup_token');
$oTokenValidation = new TokenValidation();
$bIsTokenValid = $oTokenValidation->isSetupTokenValid($sParamToken);
if (UserRights::IsAdministrator() || $bIsTokenValid) {
$oPage = new NiceWebPage('');
$aDataToPost = MakeDataToPost($sTargetRoute);
$oPage->add('<form id="hub_launch_form" action="'.$sHubUrlStateless.'" method="post">');
$oPage->add('<input type="hidden" name="json" value="'.utils::EscapeHtml(json_encode($aDataToPost)).'">');
$oPage->add_ready_script('$("#hub_launch_form").submit();');
} else {
IssueLog::Error('TokenValidation failed on inform_after_setup page');
throw new Exception("Not allowed");
}
break;
default:
// All other cases, special "Hub like" web page

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-hub-connector/3.1.1',
'itop-hub-connector/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-incident-mgmt-itil/3.1.1',
'itop-incident-mgmt-itil/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-knownerror-mgmt/3.1.1',
'itop-knownerror-mgmt/3.1.3',
array(
// Identification
//

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-oauth-client/3.1.1',
'itop-oauth-client/3.1.3',
array(
// Identification
//

View File

@@ -20,7 +20,7 @@
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-portal-base/3.1.1', array(
'itop-portal-base/3.1.3', array(
// Identification
'label' => 'Portal Development Library',
'category' => 'Portal',

View File

@@ -15,6 +15,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with iTop. If not, see <http://www.gnu.org/licenses/>
p_user_profile_brick_edit_person:
path: '/user/edit_person'
defaults:
_controller: 'Combodo\iTop\Portal\Controller\UserProfileBrickController::EditPerson'
p_user_profile_brick:
path: '/user/{sBrickId}'
defaults:

File diff suppressed because one or more lines are too long

View File

@@ -1334,6 +1334,11 @@ class ObjectController extends BrickController
$bIgnoreSilos = $oScopeValidator->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
$aParams = array('objects_id' => $aObjectIds);
$oSearch = DBObjectSearch::FromOQL("SELECT $sObjectClass WHERE id IN (:objects_id)");
if (!$oScopeValidator->AddScopeToQuery($oSearch, $sObjectClass)
) {
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . ' object.');
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
}
if ($bIgnoreSilos === true) {
$oSearch->AllowAllData();
}
@@ -1389,6 +1394,10 @@ class ObjectController extends BrickController
$aObjectAttCodes = $oRequestManipulator->ReadParam('aObjectAttCodes', array(), FILTER_UNSAFE_RAW);
$aLinkAttCodes = $oRequestManipulator->ReadParam('aLinkAttCodes', array(), FILTER_UNSAFE_RAW);
$sDateTimePickerWidgetParent = $oRequestManipulator->ReadParam('sDateTimePickerWidgetParent', array(), FILTER_UNSAFE_RAW);
if (!MetaModel::IsLinkClass($sLinkClass)) {
IssueLog::Warning(__METHOD__.' at line '.__LINE__.' : User #'.UserRights::GetUserId().' asked for wrong lnk class '.$sLinkClass);
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
}
if (empty($sObjectClass) || empty($aObjectIds) || empty($aObjectAttCodes)) {
IssueLog::Info(__METHOD__.' at line '.__LINE__.' : sObjectClass, aObjectIds and aObjectAttCodes expected, "'.$sObjectClass.'", "'.implode('/',
@@ -1400,7 +1409,12 @@ class ObjectController extends BrickController
$bIgnoreSilos = $oScopeValidator->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
$aParams = array('objects_id' => $aObjectIds);
$oSearch = DBObjectSearch::FromOQL("SELECT $sObjectClass WHERE id IN (:objects_id)");
if ($bIgnoreSilos === true)
if (!$oScopeValidator->AddScopeToQuery($oSearch, $sObjectClass)
) {
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . ' object.');
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
}
if ($bIgnoreSilos === true)
{
$oSearch->AllowAllData();
}
@@ -1418,10 +1432,35 @@ class ObjectController extends BrickController
// Prepare link data
$aObjectData = $this->PrepareObjectInformation($oObject, $aObjectAttCodes);
// New link object (needed for renderers)
$oNewLink = new $sLinkClass();
$aAttCodes = MetaModel::GetAttributesList($sLinkClass, ['AttributeExternalKey']);
$sAttCodeToObject = '';
foreach ($aAttCodes as $sAttCode) {
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
/** @var \AttributeExternalKey $oAttDef */
if ($oAttDef->GetTargetClass() === $sObjectClass) {
$sAttCodeToObject = $sAttCode;
}
}
if ($sAttCodeToObject === '') {
IssueLog::Warning(__METHOD__.' at line '.__LINE__.' : User #'.UserRights::GetUserId().' asked for incoherent lnk class '.$sLinkClass.' with object class '.$sObjectClass);
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
}
$oNewLink = MetaModel::NewObject($sLinkClass, [
$sAttCodeToObject => $oObject->GetKey(), // so later placeholders in filters will be applied on external keys on the same link
]);
foreach ($aLinkAttCodes as $sAttCode) {
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
/** @var \Combodo\iTop\Form\Field\SelectObjectField $oField */
$oField = $oAttDef->MakeFormField($oNewLink);
if ($oAttDef::GetFormFieldClass() === '\\Combodo\\iTop\\Form\\Field\\SelectObjectField') {
$oFieldSearch = $oField->GetSearch();
$sFieldClass = $oFieldSearch->GetClass();
if ($oScopeValidator->AddScopeToQuery($oFieldSearch, $sFieldClass)){
$oField->SetSearch($oFieldSearch);
} else {
$oField->SetSearch(DBObjectSearch::FromOQL("SELECT $sFieldClass WHERE 1=0"));
}
}
// Prevent datetimepicker popup to be truncated
if ($oField instanceof DateTimeField) {
$oField->SetDateTimePickerWidgetParent($sDateTimePickerWidgetParent);

View File

@@ -35,7 +35,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use UserRights;
use utils;
use Dict;
/**
* Class UserProfileBrickController
*
@@ -66,34 +66,9 @@ class UserProfileBrickController extends BrickController
$oRequestManipulator = $this->get('request_manipulator');
/** @var \Combodo\iTop\Portal\Helper\ObjectFormHandlerHelper $ObjectFormHandler */
$ObjectFormHandler = $this->get('object_form_handler');
/** @var \Combodo\iTop\Portal\Brick\BrickCollection $oBrickCollection */
$oBrickCollection = $this->get('brick_collection');
$oBrick = $this->GetBrick($sBrickId);
// If the brick id was not specified, we get the first one registered that is an instance of UserProfileBrick as default
if ($sBrickId === null)
{
/** @var \Combodo\iTop\Portal\Brick\PortalBrick $oTmpBrick */
foreach ($oBrickCollection->GetBricks() as $oTmpBrick)
{
if ($oTmpBrick instanceof UserProfileBrick)
{
$oBrick = $oTmpBrick;
}
}
// We make sure a UserProfileBrick was found
if (!isset($oBrick) || $oBrick === null)
{
$oBrick = new UserProfileBrick();
//throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'UserProfileBrick : Brick could not be loaded as there was no UserProfileBrick loaded in the application.');
}
}
else
{
$oBrick = $oBrickCollection->GetBrickById($sBrickId);
}
$aData = array();
$aData = array();
// Setting form mode regarding the demo mode parameter
$bDemoMode = MetaModel::GetConfig()->Get('demo_mode');
@@ -130,11 +105,12 @@ class UserProfileBrickController extends BrickController
$oCurContact = UserRights::GetContactObject();
$sCurContactClass = get_class($oCurContact);
$sCurContactId = $oCurContact->GetKey();
$aForm = $oBrick->GetForm();
$aForm['submit_endpoint'] = $this->generateUrl('p_user_profile_brick_edit_person', ['sBrickId' => $sBrickId]);
// Preparing forms
$aData['forms']['contact'] = $ObjectFormHandler->HandleForm($oRequest, $sFormMode, $sCurContactClass, $sCurContactId,
$oBrick->GetForm());
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
$aData['forms']['contact'] = $ObjectFormHandler->HandleForm($oRequest, $sFormMode, $sCurContactClass, $sCurContactId,
$aForm);
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
// - If user can change password, we display the form
$aData['forms']['password'] = (UserRights::CanChangePassword()) ? $this->HandlePasswordForm($oRequest, $sFormMode) : null;
@@ -150,6 +126,35 @@ class UserProfileBrickController extends BrickController
return $oResponse;
}
public function EditPerson(Request $oRequest)
{
/** @var \Combodo\iTop\Portal\Helper\ObjectFormHandlerHelper $oObjectFormHandler */
$oObjectFormHandler = $this->get('object_form_handler');
/** @var \Combodo\iTop\Portal\Helper\SecurityHelper $oSecurityHelper */
$oSecurityHelper = $this->get('security_helper');
$oCurContact = UserRights::GetContactObject();
$sObjectClass = get_class($oCurContact);
$sObjectId = $oCurContact->GetKey();
// Checking security layers
// Warning : This is a dirty quick fix to allow editing its own contact information
$bAllowWrite = ($sObjectClass === 'Person' && $sObjectId == UserRights::GetContactId());
if (!$oSecurityHelper->IsActionAllowed(UR_ACTION_MODIFY, $sObjectClass, $sObjectId) && !$bAllowWrite) {
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to modify ' . $sObjectClass . '::' . $sObjectId . ' object.');
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
}
$aForm = $this->GetBrick()->GetForm();
$aForm['submit_endpoint'] = $this->generateUrl('p_user_profile_brick_edit_person');
$aData = ['sMode' => 'edit'];
$aData['form'] = $oObjectFormHandler->HandleForm($oRequest, $aData['sMode'], $sObjectClass, $sObjectId, $aForm);
return new JsonResponse($aData);
}
/**
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param string $sFormMode
@@ -381,7 +386,7 @@ class UserProfileBrickController extends BrickController
'sObjectField' => $sPictureAttCode,
'cache' => 86400,
's' => $oOrmDoc->GetSignature(),
]);
]);
$aFormData['validation'] = array(
'valid' => true,
'messages' => array(),
@@ -394,4 +399,34 @@ class UserProfileBrickController extends BrickController
return $aFormData;
}
/**
* @param $sBrickId
* @return \Combodo\iTop\Portal\Brick\PortalBrick|UserProfileBrick
* @throws \Combodo\iTop\Portal\Brick\BrickNotFoundException
*/
public function GetBrick($sBrickId = null)
{
/** @var \Combodo\iTop\Portal\Brick\BrickCollection $oBrickCollection */
$oBrickCollection = $this->get('brick_collection');
// If the brick id was not specified, we get the first one registered that is an instance of UserProfileBrick as default
if ($sBrickId === null) {
/** @var \Combodo\iTop\Portal\Brick\PortalBrick $oTmpBrick */
foreach ($oBrickCollection->GetBricks() as $oTmpBrick) {
if ($oTmpBrick instanceof UserProfileBrick) {
$oBrick = $oTmpBrick;
}
}
// We make sure a UserProfileBrick was found
if (!isset($oBrick) || $oBrick === null) {
$oBrick = new UserProfileBrick();
//throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'UserProfileBrick : Brick could not be loaded as there was no UserProfileBrick loaded in the application.');
}
} else {
$oBrick = $oBrickCollection->GetBrickById($sBrickId);
}
return $oBrick;
}
}

View File

@@ -242,13 +242,17 @@ class ObjectFormHandlerHelper
case static::ENUM_MODE_CREATE:
case static::ENUM_MODE_EDIT:
case static::ENUM_MODE_VIEW:
$sFormEndpoint = $this->oUrlGenerator->generate(
'p_object_'.$sMode,
array(
'sObjectClass' => $sObjectClass,
'sObjectId' => $sObjectId,
)
);
if(array_key_exists('submit_endpoint', $aFormProperties)) {
$sFormEndpoint = $aFormProperties['submit_endpoint'];
} else {
$sFormEndpoint = $this->oUrlGenerator->generate(
'p_object_' . $sMode,
array(
'sObjectClass' => $sObjectClass,
'sObjectId' => $sObjectId,
)
);
}
break;
case static::ENUM_MODE_APPLY_STIMULUS:
@@ -281,7 +285,8 @@ class ObjectFormHandlerHelper
->SetActionRulesToken($sActionRulesToken)
->SetRenderer($oFormRenderer)
->SetFormProperties($aFormProperties);
$oFormManager->PrepareFormAndHTMLDocument();
$oFormManager->PrepareFields();
$oFormManager->Build();
$aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId();
// Check the number of editable fields
@@ -399,7 +404,7 @@ class ObjectFormHandlerHelper
ApplicationContext::MakeObjectUrl($sObjectClass, $sObjectId)
);
}
return $aFormData;
}

View File

@@ -476,8 +476,8 @@
sBody = '{{ 'Error:XHR:Fail'|dict_format(constant('ITOP_APPLICATION_SHORT'))|escape('js') }}';
}
var oModalElem = $('#modal-for-alert');
oModalElem.find('.modal-content .modal-header .modal-title').html(sTitle);
oModalElem.find('.modal-content .modal-body .alert').addClass('alert-danger').html(sBody);
oModalElem.find('.modal-content .modal-header .modal-title').text(sTitle);
oModalElem.find('.modal-content .modal-body .alert').addClass('alert-danger').text(sBody);
oModalElem.modal('show');
};
{% endblock %}

View File

@@ -20,7 +20,7 @@
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-portal/3.1.1', array(
'itop-portal/3.1.3', array(
// Identification
'label' => 'Enhanced Customer Portal',
'category' => 'Portal',

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-problem-mgmt/3.1.1',
'itop-problem-mgmt/3.1.3',
array(
// Identification
//

View File

@@ -19,7 +19,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-profiles-itil/3.1.1',
'itop-profiles-itil/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-request-mgmt-itil/3.1.1',
'itop-request-mgmt-itil/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-request-mgmt/3.1.1',
'itop-request-mgmt/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-service-mgmt-provider/3.1.1',
'itop-service-mgmt-provider/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-service-mgmt/3.1.1',
'itop-service-mgmt/3.1.3',
array(
// Identification
//

View File

@@ -18,7 +18,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-sla-computation/3.1.1',
'itop-sla-computation/3.1.3',
array(
// Identification
//

View File

@@ -25,7 +25,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-storage-mgmt/3.1.1',
'itop-storage-mgmt/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-structure/3.1.1',
'itop-structure/3.1.3',
array(
// Identification
//

View File

@@ -5,7 +5,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-themes-compat/3.1.1',
'itop-themes-compat/3.1.3',
array(
// Identification
//

View File

@@ -227,6 +227,11 @@
<callback>OnLinksChangedTicket</callback>
<rank>0</rank>
</event_listener>
<event_listener id="UpdateImpactAnalysisLocal">
<event>EVENT_DB_BEFORE_WRITE</event>
<callback>OnBeforeWriteTicket</callback>
<rank>0</rank>
</event_listener>
</event_listeners>
<methods>
<method id="OnLinksChangedTicket">
@@ -237,6 +242,20 @@
public function OnLinksChangedTicket(Combodo\iTop\Service\Events\EventData $oEventData)
{
$this->UpdateImpactedItems();
}
]]></code>
</method>
<method id="OnBeforeWriteTicket">
<static>false</static>
<access>public</access>
<type>EventListener</type>
<code><![CDATA[
public function OnBeforeWriteTicket(Combodo\iTop\Service\Events\EventData $oEventData)
{
$aChanges = $this->ListChanges();
if ($this->IsNew() || array_key_exists('functionalcis_list', $aChanges) || array_key_exists('contacts_list', $aChanges)) {
$this->UpdateImpactedItems();
}
}
]]></code>
</method>

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__,
'itop-tickets/3.1.1',
'itop-tickets/3.1.3',
array(
// Identification
//

View File

@@ -16,7 +16,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-virtualization-mgmt/3.1.1',
'itop-virtualization-mgmt/3.1.3',
array(
// Identification
//

View File

@@ -3,7 +3,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-welcome-itil/3.1.1',
'itop-welcome-itil/3.1.3',
array(
// Identification
//

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<information>
<version>3.1.1</version>
<version>3.1.3</version>
</information>

View File

@@ -74,7 +74,7 @@ try
// First check if we can redirect the route to a dedicated controller
$sRoute = utils::ReadParam('route', '', false, utils::ENUM_SANITIZATION_FILTER_ROUTE);
$oRouter = Router::GetInstance();
if ($oRouter->CanDispatchRoute($sRoute)) {
if ($operation === '' && $oRouter->CanDispatchRoute($sRoute)) {
$mResponse = $oRouter->DispatchRoute($sRoute);
// If response isn't a \WebPage, it is most likely that the output already occured, stop the script.
@@ -767,12 +767,12 @@ try
$sClass = utils::ReadParam('className', '', false, 'class');
$sRootClass = utils::ReadParam('baseClass', '', false, 'class');
$currentId = utils::ReadParam('currentId', '');
$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
$sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
$sAction = utils::ReadParam('action', '');
$sSelectionMode = utils::ReadParam('selection_mode', null, false, 'raw_data');
$sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null, false, 'raw_data');
$scssCount = utils::ReadParam('css_count', null, false, 'raw_data');
$sTableInnerId = utils::ReadParam('table_inner_id', $sTableId, false, 'raw_data');
$sSelectionMode = utils::ReadParam('selection_mode');
$sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null,false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); // actually an Id not a selector
$scssCount = utils::ReadParam('css_count', null,false,utils::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR);
$sTableInnerId = utils::ReadParam('table_inner_id', null,false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
$oFilter = new DBObjectSearch($sClass);
$oSet = new CMDBObjectSet($oFilter);
@@ -1097,31 +1097,28 @@ EOF
$aUpdatedDecoded[] = $sDecodedProp;
}
$oDashlet->FromParams($aCurrentValues);
$sPrevClass = get_class($oDashlet);
$oDashlet = $oDashlet->Update($aValues, $aUpdatedDecoded);
$sNewClass = get_class($oDashlet);
if ($sNewClass != $sPrevClass) {
$oPage->add_ready_script("$('#dashlet_$sDashletId').dashlet('option', {dashlet_class: '$sNewClass'});");
}
if ($oDashlet->IsRedrawNeeded()) {
$oBlock = $oDashlet->DoRender($oPage, true, false, $aExtraParams);
$sHtml = ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock);
$sHtml = str_replace("\n", '', $sHtml);
$sHtml = str_replace("\r", '', $sHtml);
$sHtml = str_replace("'", "\'", $sHtml);
$oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');");
}
if ($oDashlet->IsFormRedrawNeeded()) {
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true, '.itop-dashboard'));
$sHtml = str_replace("\n", '', $sHtml);
$sHtml = str_replace("\r", '', $sHtml);
$oPage->add_script("$('#dashlet_properties_$sDashletId').html('$sHtml')");
}
$oDashlet->FromParams($aCurrentValues);
$sPrevClass = get_class($oDashlet);
$oDashlet = $oDashlet->Update($aValues, $aUpdatedDecoded);
$sNewClass = get_class($oDashlet);
if ($sNewClass != $sPrevClass) {
$oPage->add_ready_script("$('#dashlet_$sDashletId').dashlet('option', {dashlet_class: '$sNewClass'});");
}
break;
if ($oDashlet->IsRedrawNeeded()) {
$oBlock = $oDashlet->DoRender($oPage, true, false, $aExtraParams);
$sHtml = ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock);
$sHtml= json_encode($sHtml);
$oPage->add_script("$('#dashlet_$sDashletId').html({$sHtml});");
}
if ($oDashlet->IsFormRedrawNeeded()) {
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
$sHtml = $oForm->RenderAsPropertySheet($oPage, true, '.itop-dashboard');
$sHtml= json_encode($sHtml);
$oPage->add_script("$('#dashlet_properties_$sDashletId').html({$sHtml});");
}
}
break;
case 'dashlet_creation_dlg':
$sOQL = utils::ReadParam('oql', '', false, 'raw_data');

View File

@@ -245,11 +245,11 @@ JS
$aMoreInfoBlocks = [];
$oDevelopedQuerySet = new FieldSet(Dict::S('UI:RunQuery:DevelopedQuery'));
$oDevelopedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode(utils::EscapeHtml($oFilter->ToOQL())));
$oDevelopedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode($oFilter->ToOQL()));
$aMoreInfoBlocks[] = $oDevelopedQuerySet;
$oSerializedQuerySet = new FieldSet(Dict::S('UI:RunQuery:SerializedFilter'));
$oSerializedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode(utils::EscapeHtml($oFilter->serialize())));
$oSerializedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode($oFilter->serialize()));
$aMoreInfoBlocks[] = $oSerializedQuerySet;

View File

@@ -343,6 +343,8 @@ function DisplayEvents(WebPage $oPage, $sClass)
}
}
$sListener = $sListenerClass.'->'.$aListener['callback'][1].'(\Combodo\iTop\Service\Events\EventData $oEventData)';
} else if (is_array($aListener['callback'])) {
$sListener = $aListener['callback'][0].'::'.$aListener['callback'][1];
} else {
$sListener = $aListener['callback'].'(\Combodo\iTop\Service\Events\EventData $oEventData)';
}

View File

@@ -1165,6 +1165,7 @@ EOF
*/
protected function QuoteForPHP($sStr, $bSimpleQuotes = false)
{
$sStr = $sStr ?? '';
if ($bSimpleQuotes)
{
$sEscaped = str_replace(array('\\', "'"), array('\\\\', "\\'"), $sStr);
@@ -3229,10 +3230,11 @@ EOF;
$aEntriesPHP = array();
$oEntries = $oDictionaryNode->GetUniqueElement('entries');
/** @var MFElement $oEntry */
foreach ($oEntries->getElementsByTagName('entry') as $oEntry)
{
$sStringCode = $oEntry->getAttribute('id');
$sValue = $oEntry->GetText();
$sValue = $oEntry->GetText('');
$aEntriesPHP[] = "\t'$sStringCode' => ".self::QuoteForPHP(self::FilterDictString($sValue), true).",";
}
$sEntriesPHP = implode("\n", $aEntriesPHP);
@@ -3267,7 +3269,7 @@ EOF;
file_put_contents($sLanguagesFile, $sLanguagesFileContent);
}
protected static function FilterDictString($s)
protected static function FilterDictString(string $s): string
{
if (strpos($s, '~') !== false)
{

View File

@@ -1295,6 +1295,12 @@ EOF
$aResult['checks'][] = new CheckResult(CheckResult::INFO, "MySQL server's max_connections is set to $iMaxConnections.");
}
$iClusters = $oDBSource->GetClusterNb();
if ($iClusters > 0) {
SetupLog::Warning('Warning - Using Galera will cause malfunctions and data corruptions. Combodo does not support this type of infrastructure.');
$aResult['checks'][] = new CheckResult(CheckResult::WARNING, 'Using Galera will cause malfunctions and data corruptions. Combodo does not support this type of infrastructure.');
}
try {
$aResult['databases'] = $oDBSource->ListDB();
}

View File

@@ -2,24 +2,72 @@
This script allows to install and update iTop via CLI.
For more information, see the official Wiki : [Automated installation [iTop Documentation]](https://www.itophub.io/wiki/page?id=latest:advancedtopics:automatic_install)
For more information, see the official Wiki : [Automated installation [iTop Documentation]](https://www.itophub.io/wiki/page?id=latest:advancedtopics:automatic_install)
# unattended-install.php
## Usage
Execution of the unattended installation
> Note:
> Because the installation runs from the command line, make sure that the current user has enough rights to access the different locations and that the web server will be able to access the files and directories created during the scripted installation. In order to exactly emulate the behavior of
the interactive installation it may be a good practice to run this installation from the user account used for running the web server process.
Launch the script with the following command: ```bash php unattended_install.php --param-file=fresh-install.xml ```
Where: `fresh-install.xml` is the response file containing your desired settings for the installation (there are 4 models available in the folder `xml_setup`: fresh-install.xml, itil-fresh-install.xml, itil-upgrade.xml, upgrade.xml)
Fresh installation parameters
> Important:
> In the case of a fresh installation (<mode>install</mode>), do not forget to complete below mandatory parameters before:
```xml
<database>
<server></server>
<user></user>
<pwd></pwd>
<name></name>
<db_tls_enabled></db_tls_enabled>
<db_tls_ca></db_tls_ca>
<prefix></prefix>
</database>
<url>
</url>
<graphviz_path>/usr/bin/dot</graphviz_path>
<admin_account>
<user></user>
<pwd></pwd>
<language></language>
</admin_account>
<language></language>
```
## Options
To get all available options of the script, you can perform the following command :
```php unattended-install.php --help```
# install-itop.sh
## Usage
#install-itop.sh
You can install your iTop by only using config-itop.php settings and run either
- a non-ITIL iTop fresh installation (use itil-fresh-install.xml to have ITIL modules instead)
```
./install-itop.sh ./xml_setup/fresh-install.xml
```
- a non-ITIL iTop upgrade (use itil-upgrade.xml to have ITIL modules instead)
```
./install-itop.sh ./xml_setup/upgrade.xml
```
- a specific iTop installation by providing both xml setup file
in below example file provided is the one generated by iTop during last setup.
in below example file provided is the one generated by iTop during last setup.
```
./install-itop.sh ../../log/install-2024-04-03.xml
```

View File

@@ -25,7 +25,9 @@ EOF;
exit(-1);
}
/////////////////////////////////////////////////
if (! utils::IsModeCLI())
$sCleanName = strtolower(trim(PHP_SAPI));
if ($sCleanName !== 'cli')
{
echo "Mode CLI only";
exit(-1);

View File

@@ -2589,7 +2589,13 @@ class WizStepDone extends WizardStep
$oProductionEnv->InitDataModel($oConfig, true);
$sIframeUrl = $oConfig->GetModuleSetting('itop-hub-connector', 'setup_url', '');
if ($sIframeUrl != '') {
$sSetupTokenFile = APPROOT.'data/.setup';
$sSetupToken = bin2hex(random_bytes(12));
file_put_contents($sSetupTokenFile, $sSetupToken);
$sIframeUrl.= "&setup_token=$sSetupToken";
if ($sIframeUrl != '')
{
$oPage->add('<iframe id="fresh_content" frameborder="0" scrolling="auto" src="'.$sIframeUrl.'"></iframe>');
$oPage->add_script("

View File

@@ -27,6 +27,7 @@ use DBObject;
use DBObjectSearch;
use DBObjectSet;
use Exception;
use ExecutionKPI;
use IssueLog;
use MetaModel;
@@ -58,6 +59,7 @@ class ActivityPanelFactory
*/
public static function MakeForObjectDetails(DBObject $oObject, string $sMode = cmdbAbstractObject::DEFAULT_DISPLAY_MODE)
{
$oKPI = new ExecutionKPI();
$sObjClass = get_class($oObject);
$sObjId = $oObject->GetKey();
@@ -171,6 +173,8 @@ class ActivityPanelFactory
}
}
$oKPI->ComputeStatsForExtension(new ActivityPanelFactory(), 'MakeForObjectDetails');
return $oActivityPanel;
}
}

View File

@@ -45,33 +45,45 @@ class UIContentBlockUIBlockFactory extends AbstractUIBlockFactory
* The \n are replaced by <br>
*
* @api
* @param string $sCode
* @param string $sCode plain text code
* @param string|null $sId
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
*/
public static function MakeForCode(string $sCode, string $sId = null)
{
$oCode = new UIContentBlock($sId, ['ibo-is-code']);
$sCode = str_replace("\n", '<br>', $sCode);
$oCode->AddSubBlock(new Html($sCode));
$sCode = str_replace("\n", '<br>', \utils::HtmlEntities($sCode));
return $oCode;
return self::MakeFromHTMLCode($sId, $sCode);
}
/**
* Used to display a block of preformatted text in a <pre> tag.
*
* @api
* @param string $sCode
* @param string $sCode plain text code
* @param string|null $sId
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
*/
public static function MakeForPreformatted(string $sCode, string $sId = null)
{
$sCode = '<pre>'.$sCode.'</pre>';
$sCode = '<pre>'.\utils::HtmlEntities($sCode).'</pre>';
return static::MakeForCode($sCode, $sId);
return self::MakeFromHTMLCode($sId, $sCode);
}
/**
* @param string|null $sId
* @param string $sCode
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
*/
private static function MakeFromHTMLCode(?string $sId, string $sCode): UIContentBlock
{
$oCode = new UIContentBlock($sId, ['ibo-is-code']);
$oCode->AddSubBlock(new Html($sCode));
return $oCode;
}
}

View File

@@ -53,7 +53,7 @@ class BlockList extends UIContentBlock
{
return '$("#'.$this->sId.'").block();
$.post("ajax.render.php?operation=refreshDashletList",
{ style: "list", filter: "'.$this->sFilter.'", extra_params: '.json_encode($this->aExtraParams).' },
{ style: "list", filter: '.json_encode($this->sFilter).', extra_params: '.json_encode($this->aExtraParams).' },
function(data){
$("#'.$this->sId.'")
.empty()

View File

@@ -7,7 +7,8 @@
*/
use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory;
use Laminas\Mail\Header\ContentType;
use Laminas\Mail\Header\InReplyTo;
use Laminas\Mail\Header\MessageId;
use Laminas\Mail\Message;
use Laminas\Mail\Protocol\Smtp\Auth\Oauth;
use Laminas\Mail\Transport\File;
@@ -331,11 +332,11 @@ class EMailLaminas extends Email
{
$this->m_aData['message_id'] = $sId;
// Note: Swift will add the angle brackets for you
// Note: The email library will add the angle brackets for you
// so let's remove the angle brackets if present, for historical reasons
$sId = str_replace(array('<', '>'), '', $sId);
$this->m_oMessage->getHeaders()->addHeaderLine('Message-ID', $sId);
$this->m_oMessage->getHeaders()->addHeader((new MessageId())->setId($sId));
}
public function SetReferences($sReferences)
@@ -354,7 +355,11 @@ class EMailLaminas extends Email
*/
public function SetInReplyTo(string $sMessageId)
{
$this->AddToHeader('In-Reply-To', $sMessageId);
// Note: Laminas will add the angle brackets for you
// so let's remove the angle brackets if present, for historical reasons
$sId = str_replace(array('<', '>'), '', $sMessageId);
$this->m_oMessage->getHeaders()->addHeader((new InReplyTo())->setIds([$sId]));
}
/**
@@ -398,19 +403,6 @@ class EMailLaminas extends Email
$oBody->addPart($oAdditionalPart);
}
if ($oBody->isMultiPart()) {
$oContentTypeHeader = $this->m_oMessage->getHeaders();
foreach ($oContentTypeHeader as $oHeader) {
if (!$oHeader instanceof ContentType) {
continue;
}
$oHeader->setType(Mime::MULTIPART_MIXED);
$oHeader->addParameter('boundary', $oBody->getMime()->boundary());
break;
}
}
$this->m_oMessage->setBody($oBody);
}
@@ -431,22 +423,13 @@ class EMailLaminas extends Email
$oNewPart = new Part($sText);
$oNewPart->encoding = Mime::ENCODING_8BIT;
$oNewPart->type = $sMimeType;
$this->m_oMessage->getBody()->addPart($oNewPart);
// setBody called only to refresh Content-Type to multipart/mixed
$this->m_oMessage->setBody($this->m_oMessage->getBody()->addPart($oNewPart));
}
public function AddAttachment($data, $sFileName, $sMimeType)
{
$oBody = $this->m_oMessage->getBody();
if (!$oBody->isMultiPart()) {
$multipart_content = new Part($oBody->generateMessage());
$multipart_content->setType($oBody->getParts()[0]->getType());
$multipart_content->setBoundary($oBody->getMime()->boundary());
$oBody = new Laminas\Mime\Message();
$oBody->addPart($multipart_content);
}
if (!array_key_exists('attachments', $this->m_aData)) {
$this->m_aData['attachments'] = array();
}
@@ -457,23 +440,8 @@ class EMailLaminas extends Email
$oNewAttachment->disposition = Mime::DISPOSITION_ATTACHMENT;
$oNewAttachment->encoding = Mime::ENCODING_BASE64;
$oBody->addPart($oNewAttachment);
if ($oBody->isMultiPart()) {
$oContentTypeHeader = $this->m_oMessage->getHeaders();
foreach ($oContentTypeHeader as $oHeader) {
if (!$oHeader instanceof ContentType) {
continue;
}
$oHeader->setType(Mime::MULTIPART_MIXED);
$oHeader->addParameter('boundary', $oBody->getMime()->boundary());
break;
}
}
$this->m_oMessage->setBody($oBody);
// setBody called only to refresh Content-Type to multipart/mixed
$this->m_oMessage->setBody($this->m_oMessage->getBody()->addPart($oNewAttachment));
}
public function SetSubject($sSubject)

View File

@@ -21,6 +21,7 @@
namespace Combodo\iTop\Form;
use Combodo\iTop\Renderer\FormRenderer;
use CoreException;
/**
* Description of formmanager
@@ -59,6 +60,12 @@ abstract class FormManager
$oFormManager = new static();
$sFormRendererClass = $aJson['formrenderer_class'];
// N°7455 - Ensure form renderer class extends FormRenderer
if (false === is_a($sFormRendererClass, FormRenderer::class, true))
{
throw new CoreException('Form renderer class must extend '.FormRenderer::class);
}
/** @var \Combodo\iTop\Renderer\FormRenderer $oFormRenderer */
$oFormRenderer = new $sFormRendererClass();
$oFormRenderer->SetEndpoint($aJson['formrenderer_endpoint']);

View File

@@ -768,6 +768,15 @@ JS
*/
protected function InjectRendererFileAssets(string $sClass, array $aAttributesCodesToDisplay, $oOutput)
{
// handle abstract class
while(MetaModel::IsAbstract($sClass)){
$aChildClasses = MetaModel::EnumChildClasses($sClass);
if(count($aChildClasses) > 0){
$sClass = $aChildClasses[0];
}
}
// create a fake object to pass to renderers for retrieving global assets
$oItem = MetaModel::NewObject($sClass);
// Iterate throw attributes...
@@ -776,10 +785,13 @@ JS
// Retrieve attribute definition
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
// make form field from attribute
$oField = $oAttDef->MakeFormField($oItem);
// retrieve the form field renderer
$sFieldRendererClass = static::GetFieldRendererClass($oField);
// retrieve renderer global assets
if ($sFieldRendererClass !== null) {
/** @var FieldRenderer $oFieldRenderer */
$oFieldRenderer = new $sFieldRendererClass($oField);

View File

@@ -592,6 +592,7 @@ EOF
*/
private static function GetDivAlert(string $message): string
{
$message = utils::EscapeHtml($message);
return "<div class=\"ibo-csv-import--cell-error ibo-csv-import--cell-message\">$message</div>\n";
}

View File

@@ -46,6 +46,8 @@ class ModuleService
$sExtension = $this->GetModuleNameFromObject($oReflectionClass->getName());
if (strlen($sExtension) !== 0) {
$sSignature .= '['.$sExtension.'] ';
} else {
$sSignature .= '[core] ';
}
$sSignature .= $oReflectionClass->getShortName().'::'.$sMethod.'()';

View File

@@ -108,6 +108,7 @@ let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
// Filter old options data to keep selected values
// (options with force flag will be kept event if they doesn't be part of the current value)
let options = Object.values(me.options);
me.optionsBeforeFilter = options;
options = options.filter(item => (typeof(item.force) !== "undefined" && item.force === true) || aSelectedItems.includes(item['{{ oDataProvider.GetDataValueField() }}']));
// Merge kept and new values
options = $.merge(options, res.data.search_data);
@@ -199,6 +200,11 @@ let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
CombodoTooltip.InitTooltipFromMarkup($item);
},
onBlur: function(){
this.clearOptionGroups()
this.addOption(this.optionsBeforeFilter)
},
{# plugin combodo_add_button #}
{% if oUIBlock.HasAddOptionButton() and oUIBlock.HasAddOptionButtonJsOnClick() %}
onAdd: function(){

View File

@@ -7,7 +7,7 @@
<div id="login-content">
<h1>{{ 'UI:ResetPwd-Title'|dict_s }}</h1>
{% if bNoUser %}
<p>{{ 'UI:ResetPwd-Error-WrongLogin'|dict_format(sAuthUser) }}</p>
<p>{{ 'UI:ResetPwd-EmailSent'|dict_s }}</p>
{% elseif bBadToken %}
<p>{{ 'UI:ResetPwd-Error-InvalidToken'|dict_s }}</p>
{% else %}

View File

@@ -8,7 +8,7 @@
<div id="login-title">
<h1>{{ 'UI:ResetPwd-Title'|dict_s }}</h1>
{% if bNoUser and sErrorMessage is null %}
<p>{{ 'UI:ResetPwd-Error-WrongLogin'|dict_format(sAuthUser) }}</p>
<p>{{ 'UI:ResetPwd-EmailSent'|dict_s }}</p>
{% elseif bBadToken and sErrorMessage is null %}
<p>{{ 'UI:ResetPwd-Error-InvalidToken'|dict_s }}</p>
{% else %}

View File

@@ -41,6 +41,7 @@ use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\Object\ObjectFactory;
use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockWithJSRefreshCallback;
use iTopWebPage;
use LoginWebPage;
@@ -355,6 +356,22 @@ $oDashletFieldset2->AddSubBlock($oDashletField4);
$oDashletFieldset2->AddSubBlock($oDashletField5);
$oDashletFieldset2->AddSubBlock($oDashletField6);
/////////
// Code
/////////
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Code examples (MakeForCode)', 2 ));
$oCode1 = UIContentBlockUIBlockFactory::MakeForCode('function mean(int $a, int $b) {
return ($a + $b)/2
}');
$oPage->AddUiBlock($oCode1);
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Code examples (MakeForPreformatted)', 2 ));
$oCode2 = UIContentBlockUIBlockFactory::MakeForPreformatted('function mean(int $a, int $b) {
return ($a + $b)/2
}');
$oPage->AddUiBlock($oCode2);
/////////
// Pill
/////////

View File

@@ -4,6 +4,9 @@
"sempro/phpunit-pretty-print": "^1.4"
},
"autoload": {
"classmap": [
"unitary-tests/"
],
"psr-4": {
"Combodo\\iTop\\Test\\UnitTest\\": "src/BaseTestCase/",
"Combodo\\iTop\\Test\\UnitTest\\Hook\\": "src/Hook/",

View File

@@ -19,10 +19,6 @@
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
>
<extensions>
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
</extensions>
<php>
<ini name="error_reporting" value="E_ALL"/>
<ini name="display_errors" value="On"/>

View File

@@ -19,10 +19,6 @@
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
>
<extensions>
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
</extensions>
<php>
<ini name="memory_limit" value="512M"/>
<ini name="error_reporting" value="E_ALL"/>

View File

@@ -19,10 +19,6 @@
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
>
<extensions>
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
</extensions>
<php>
<ini name="error_reporting" value="E_ALL"/>
<ini name="display_errors" value="On"/>

View File

@@ -7,7 +7,6 @@
namespace Combodo\iTop\Test\UnitTest;
use CMDBSource;
use Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook;
use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment;
use Config;
use Exception;
@@ -30,9 +29,9 @@ use utils;
abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
{
/**
* @var bool[]
*/
protected static $aReadyCustomEnvironments = [];
* @var UnitTestRunTimeEnvironment
*/
protected $oEnvironment = null;
/**
* @inheritDoc
@@ -50,11 +49,19 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
$this->setRunClassInSeparateProcess(true);
}
/**
/**
* @return string Abs path to the XML delta to use for the tests of that class
*/
abstract public function GetDatamodelDeltaAbsPath(): string;
protected function setUp(): void
{
static::LoadRequiredItopFiles();
$this->oEnvironment = new UnitTestRunTimeEnvironment('production', $this->GetTestEnvironment());
parent::setUp();
}
/**
* @inheritDoc
*/
@@ -92,40 +99,16 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
}
/**
* Mark {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} as ready (compiled)
*
* @return void
*/
private function MarkEnvironmentReady(): void
{
if (false === $this->IsEnvironmentReady()) {
touch(static::GetTestEnvironmentFolderAbsPath());
}
}
/**
* @return bool True if the {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} is ready (compiled, but not started)
*
* @details Having the environment ready means that it has been compiled for this global tests run, not that it is a relic from a previous global tests run
* @return bool True if the {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} is ready (compiled, up-to-date, but not necessarily started)
*/
final protected function IsEnvironmentReady(): bool
{
// As these test cases run in separate processes, the best way we found to let know a process if its environment was already prepared for **this run** was to compare the modification times of:
// - its own env-<ENV> folder
// - a file generated at the beginning of the global test run {@see \Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook}
$sRunStartedFilePath = TestsRunStartHook::GetRunStartedFileAbsPath();
$sEnvFolderPath = static::GetTestEnvironmentFolderAbsPath();
clearstatcache();
if (false === file_exists($sRunStartedFilePath) || false === file_exists($sEnvFolderPath)) {
if (false === file_exists($this->GetTestEnvironmentFolderAbsPath())) {
return false;
}
$iRunStartedFileModificationTime = filemtime($sRunStartedFilePath);
$iEnvFolderModificationTime = filemtime($sEnvFolderPath);
return $iEnvFolderModificationTime >= $iRunStartedFileModificationTime;
}
return $this->oEnvironment->IsUpToDate();
}
/**
* @inheritDoc
@@ -140,6 +123,12 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
// Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once.
// This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457
if (false === $this->IsEnvironmentReady()) {
$this->debug("Preparing custom environment '$sTestEnv' with the following datamodel files:");
foreach ($this->oEnvironment->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
$this->debug(" - $sCustomDatamodelFile");
}
//----------------------------------------------------
// Clear any previous "$sTestEnv" environment
//----------------------------------------------------
@@ -152,14 +141,6 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
SetupUtils::tidydir($sConfFolder);
}
// - Datamodel delta files
// - Cache folder
// - Compiled folder
// We don't need to clean them as they are already by the compilation
// - Drop database
// We don't do that now, it will be done before re-creating the DB, once the metamodel is started
//----------------------------------------------------
// Prepare "$sTestEnv" environment
//----------------------------------------------------
@@ -178,7 +159,7 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
$oTestConfig->Set('db_name', $oTestConfig->Get('db_name').'_'.$sTestEnvSanitizedForDBName);
// - Compile env. based on the existing 'production' env.
$oEnvironment = new UnitTestRunTimeEnvironment($sTestEnv);
$oEnvironment = new UnitTestRunTimeEnvironment($sSourceEnv, $sTestEnv);
$oEnvironment->WriteConfigFileSafe($oTestConfig);
$oEnvironment->CompileFrom($sSourceEnv);
@@ -192,8 +173,7 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
// N°7446 For some reason we need to create the DB schema before starting the MM, then only we can create the tables.
MetaModel::DBCreate();
$this->MarkEnvironmentReady();
$this->debug('Preparation of custom environment "'.$sTestEnv.'" done.');
$this->debug("Custom environment '$sTestEnv' is ready!");
}
parent::PrepareEnvironment();

View File

@@ -50,7 +50,7 @@ abstract class ItopTestCase extends TestCase
static::$DEBUG_UNIT_TEST = getenv('DEBUG_UNIT_TEST');
require_once static::GetAppRoot() . 'approot.inc.php';
require_once __DIR__.'/../../../../approot.inc.php';
if ((static::DISABLE_DEPRECATEDCALLSLOG_ERRORHANDLER)
&& (false === defined(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME))) {
@@ -78,6 +78,7 @@ abstract class ItopTestCase extends TestCase
}
}
/**
* @param array $args
* @param string $sExportFileName relative to log folder
@@ -93,43 +94,41 @@ abstract class ItopTestCase extends TestCase
* @return string
* @throws \ReflectionException
*/
public static function ExportFunctionParameterValues(array $args, string $sExportFileName, array $aExcludedParams = []): string
{
public static function ExportFunctionParameterValues(array $args, string $sExportFileName, array $aExcludedParams = []): string
{
// get sclass et function dans la callstrack
// in the callstack get the call function name
$aCallStack = debug_backtrace();
$sCallFunction = $aCallStack[1]['function'];
// in the casll stack get the call class name
$sCallClass = $aCallStack[1]['class'];
$reflectionFunc = new ReflectionMethod($sCallClass, $sCallFunction);
$parameters = $reflectionFunc->getParameters();
// in the callstack get the call function name
$aCallStack = debug_backtrace();
$sCallFunction = $aCallStack[1]['function'];
// in the casll stack get the call class name
$sCallClass = $aCallStack[1]['class'];
$reflectionFunc = new ReflectionMethod($sCallClass, $sCallFunction);
$parameters = $reflectionFunc->getParameters();
$aParamValues = [];
foreach ($parameters as $index => $param) {
$aParamValues[$param->getName()] = $args[$index] ?? null;
}
$aParamValues = [];
foreach ($parameters as $index => $param) {
$aParamValues[$param->getName()] = $args[$index] ?? null;
}
$paramValues = $aParamValues;
foreach ($aExcludedParams as $sExcludedParam) {
unset($paramValues[$sExcludedParam]);
}
$paramValues = $aParamValues;
foreach ($aExcludedParams as $sExcludedParam) {
unset($paramValues[$sExcludedParam]);
}
// extract oPage from the array in parameters and make a foreach on exlucded parameters
foreach ($aExcludedParams as $sExcludedParam) {
unset($paramValues[$sExcludedParam]);
}
// extract oPage from the array in parameters and make a foreach on exlucded parameters
foreach ($aExcludedParams as $sExcludedParam) {
unset($paramValues[$sExcludedParam]);
}
$var_export = var_export($paramValues, true);
file_put_contents(APPROOT.'/log/' .$sExportFileName, $var_export);
$var_export = var_export($paramValues, true);
file_put_contents(APPROOT.'/log/' .$sExportFileName, $var_export);
return $var_export;
}
}
protected function setUp(): void {
protected function setUp(): void {
parent::setUp();
$this->debug("\n----------\n---------- ".$this->getName()."\n----------\n");
$this->LoadRequiredItopFiles();
$this->LoadRequiredTestFiles();
}
@@ -170,6 +169,27 @@ abstract class ItopTestCase extends TestCase
return $sAppRootPath . '/';
}
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
{
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
return $sSearchPath . '/';
}
$iOffsetSep = strrpos($sSearchPath, '/');
if ($iOffsetSep === false) {
$iOffsetSep = strrpos($sSearchPath, '\\');
if ($iOffsetSep === false) {
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
return 'Could not find the approot file in ' . $sSearchPath;
}
}
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
}
return null;
}
/**
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceItopFile()}
*
@@ -178,8 +198,9 @@ abstract class ItopTestCase extends TestCase
*/
protected function LoadRequiredItopFiles(): void
{
// Empty until we actually need to require some files in the class
}
// At least make sure that the autoloader will be loaded, and that the APPROOT constant is defined
require_once __DIR__.'/../../../../approot.inc.php';
}
/**
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceUnitTestFile()}
@@ -206,23 +227,6 @@ abstract class ItopTestCase extends TestCase
require_once $this->GetAppRoot() . $sFileRelPath;
}
/**
* Helper to load a module file. The caller test must be in that module !
* Will browse dir up to find a module.*.php
*
* @param string $sFileRelPath for example 'portal/src/Helper/ApplicationHelper.php'
* @since 2.7.10 3.1.1 3.2.0 N°6709 method creation
*/
protected function RequireOnceCurrentModuleFile(string $sFileRelPath): void
{
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
$sCallerFileFullPath = $aStack[0]['file'];
$sCallerDir = dirname($sCallerFileFullPath);
$sModuleRootPath = static::GetFirstDirUpContainingFile($sCallerDir, 'module.*.php');
require_once $sModuleRootPath . $sFileRelPath;
}
/**
* Require once a unit test file (eg. a mock class) from its relative path from the *current* dir.
* This ensure that required files don't crash when unit tests dir is moved in the iTop structure (see N°5608)
@@ -240,26 +244,6 @@ abstract class ItopTestCase extends TestCase
require_once $sCallerDirAbsPath . DIRECTORY_SEPARATOR . $sFileRelPath;
}
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
{
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
return $sSearchPath . '/';
}
$iOffsetSep = strrpos($sSearchPath, '/');
if ($iOffsetSep === false) {
$iOffsetSep = strrpos($sSearchPath, '\\');
if ($iOffsetSep === false) {
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
return 'Could not find the approot file in ' . $sSearchPath;
}
}
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
}
return null;
}
protected function debug($sMsg)
{
if (static::$DEBUG_UNIT_TEST) {
@@ -402,11 +386,11 @@ abstract class ItopTestCase extends TestCase
*/
private function GetProperty(string $sClass, string $sProperty): \ReflectionProperty
{
$class = new \ReflectionClass($sClass);
$property = $class->getProperty($sProperty);
$property->setAccessible(true);
$oClass = new \ReflectionClass($sClass);
$oProperty = $oClass->getProperty($sProperty);
$oProperty->setAccessible(true);
return $property;
return $oProperty;
}
@@ -417,7 +401,7 @@ abstract class ItopTestCase extends TestCase
*
* @since 2.7.8 3.0.3 3.1.0
*/
public function SetNonPublicProperty(object $oObject, string $sProperty, $value)
public function SetNonPublicProperty($oObject, string $sProperty, $value)
{
$oProperty = $this->GetProperty(get_class($oObject), $sProperty);
$oProperty->setValue($oObject, $value);

View File

@@ -1,63 +0,0 @@
<?php
declare(strict_types=1);
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Hook;
require_once __DIR__ . '/../../../../approot.inc.php';
use PHPUnit\Runner\AfterLastTestHook;
use PHPUnit\Runner\BeforeFirstTestHook;
use utils;
/**
* Class TestsRunStartHook
*
* IMPORTANT: This will no longer work in PHPUnit 10.0 and there is no alternative for now, so we will have to migrate it when the time comes
* @link https://localheinz.com/articles/2023/02/14/extending-phpunit-with-its-new-event-system/#content-hooks-event-system
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @package Combodo\iTop\Test\UnitTest\Hook
* @since N°6097 2.7.10 3.0.4 3.1.1
*/
class TestsRunStartHook implements BeforeFirstTestHook, AfterLastTestHook
{
/**
* Use the modification time on this file to check whereas it is newer than the requirements in a test case
*
* @return string Abs. path to a file generated when the global tests run starts.
*/
public static function GetRunStartedFileAbsPath(): string
{
// Note: This can't be put in the cache-<ENV> folder as we have multiple <ENV> running across the test cases
// We also don't want to put it in the unit tests folder as it is not supposed to be writable
return APPROOT.'data/.php-unit-tests-run-started';
}
/**
* @inheritDoc
*/
public function executeBeforeFirstTest(): void
{
// Create / change modification timestamp of file marking the beginning of the tests run
touch(static::GetRunStartedFileAbsPath());
}
/**
* @inheritDoc
*/
public function executeAfterLastTest(): void
{
// Cleanup of file marking the beginning of the tests run
if (file_exists(static::GetRunStartedFileAbsPath())) {
unlink(static::GetRunStartedFileAbsPath());
}
}
}

View File

@@ -9,9 +9,13 @@ namespace Combodo\iTop\Test\UnitTest\Service;
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
use IssueLog;
use LogChannels;
use MFCoreModule;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
use RunTimeEnvironment;
use utils;
/**
@@ -25,62 +29,140 @@ use RunTimeEnvironment;
class UnitTestRunTimeEnvironment extends RunTimeEnvironment
{
/**
* @var string[]
*/
protected $aCustomDatamodelFiles = null;
/**
* @var string
*/
protected $sSourceEnv;
public function __construct($sSourceEnv, $sTargetEnv)
{
parent::__construct($sTargetEnv);
$this->sSourceEnv = $sSourceEnv;
}
public function GetEnvironment(): string
{
return $this->sFinalEnv;
}
public function IsUpToDate()
{
clearstatcache();
$fLastCompilationTime = filemtime(APPROOT.'env-'.$this->sFinalEnv);
$aModifiedFiles = [];
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'datamodels/2.x', $aModifiedFiles);
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'extensions', $aModifiedFiles);
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'data/production-modules', $aModifiedFiles);
foreach ($this->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
if (filemtime($sCustomDatamodelFile) > $fLastCompilationTime) {
$aModifiedFiles[] = $sCustomDatamodelFile;
}
}
if (count($aModifiedFiles) > 0) {
echo "The following files have been modified after the last compilation:\n";
foreach ($aModifiedFiles as $sFile) {
echo " - $sFile\n";
}
}
return (count($aModifiedFiles) === 0);
}
/**
* @inheritDoc
*/
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
{
$aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir);
/** @var string[] $aDeltaFiles Referential of loaded deltas. Mostly to avoid duplicates. */
$aDeltaFiles = [];
foreach (get_declared_classes() as $sClass) {
// Filter on classes derived from this \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCaseItopCustomDatamodelTestCase
if (false === is_a($sClass, ItopCustomDatamodelTestCase::class, true)) {
continue;
}
$oReflectionClass = new ReflectionClass($sClass);
$oReflectionMethod = $oReflectionClass->getMethod('GetDatamodelDeltaAbsPath');
// Filter on classes with an actual XML delta (eg. not \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase and maybe some other deriving from a class with a delta)
if ($oReflectionMethod->isAbstract()) {
continue;
}
/** @var \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase $oTestClassInstance */
$oTestClassInstance = new $sClass();
// Check test class is for desired environment
if ($oTestClassInstance->GetTestEnvironment() !== $this->sFinalEnv) {
continue;
}
// Check XML delta actually exists
$sDeltaFile = $oTestClassInstance->GetDatamodelDeltaAbsPath();
if (false === is_file($sDeltaFile)) {
$this->fail("Could not prepare '$this->sFinalEnv' as the XML delta file '$sDeltaFile' (used in $sClass) does not seem to exist");
}
// Avoid duplicates
if (in_array($sDeltaFile, $aDeltaFiles)) {
continue;
}
// Prepare fake module name for delta
$sDeltaName = preg_replace('/[^\d\w]/', '', $sDeltaFile);
// Note: We can't use \MFDeltaModule as we can't specify the ID which leads to only 1 delta being applied... In the future we might introduce a new MFXXXModule, but in the meantime it feels alright (GLA / RQU)
$oDelta = new MFCoreModule($sDeltaName, $sDeltaName, $sDeltaFile);
IssueLog::Debug('XML delta found for unit tests', static::class, [
'Unit test class' => $sClass,
'Delta file path' => $sDeltaFile,
]);
$aDeltaFiles[] = $sDeltaFile;
$aRet[$sDeltaName] = $oDelta;
foreach ($this->GetCustomDatamodelFiles() as $sDeltaFile) {
$sDeltaId = preg_replace('/[^\d\w]/', '', $sDeltaFile);
$sDeltaName = basename($sDeltaFile);
$sDeltaDir = dirname($sDeltaFile);
$oDelta = new MFCoreModule($sDeltaName, "$sDeltaDir/$sDeltaName", $sDeltaFile);
$aRet[$sDeltaId] = $oDelta;
}
return $aRet;
}
public function GetCustomDatamodelFiles()
{
if (!is_null($this->aCustomDatamodelFiles)) {
return $this->aCustomDatamodelFiles;
}
$this->aCustomDatamodelFiles = [];
// Search for the PHP files implementing the method GetDatamodelDeltaAbsPath
// and extract the delta file path from the method
foreach(['unitary-tests', 'integration-tests'] as $sTestDir) {
// Iterate on all PHP files in subdirectories
// Note: grep is not available on Windows, so we will use the PHP Reflection API
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__."/../../$sTestDir")) as $oFile) {
if ($oFile->isDir()){
continue;
}
if (pathinfo($oFile->getFilename(), PATHINFO_EXTENSION) !== 'php') {
continue;
}
$sFile = $oFile->getPathname();
$sContent = file_get_contents($sFile);
if (strpos($sContent, 'GetDatamodelDeltaAbsPath') === false) {
continue;
}
$sClass = '';
$aMatches = [];
if (preg_match('/namespace\s+([^;]+);/', $sContent, $aMatches)) {
$sNamespace = $aMatches[1];
$sClass = $sNamespace.'\\'.basename($sFile, '.php');
}
if (preg_match('/\s+class\s+([^ ]+)\s+/', $sContent, $aMatches)) {
$sClass = $sNamespace.'\\'.$aMatches[1];
}
if ($sClass === '') {
continue;
}
require_once $sFile;
$oReflectionClass = new ReflectionClass($sClass);
if ($oReflectionClass->isAbstract()) {
continue;
}
// Check if the class extends ItopCustomDatamodelTestCase
if (!$oReflectionClass->isSubclassOf(ItopCustomDatamodelTestCase::class)) {
continue;
}
/** @var \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase $oTestClassInstance */
$oTestClassInstance = new $sClass();
if ($oTestClassInstance->GetTestEnvironment() !== $this->sFinalEnv) {
continue;
}
$sDeltaFile = $oTestClassInstance->GetDatamodelDeltaAbsPath();
if (!is_file($sDeltaFile)) {
throw new \Exception("Unknown delta file: $sDeltaFile, from test class '$sClass'");
}
if (!in_array($sDeltaFile, $this->aCustomDatamodelFiles)) {
$this->aCustomDatamodelFiles[] = $sDeltaFile;
}
}
}
return $this->aCustomDatamodelFiles;
}
private function FindFilesModifiedAfter(float $fReferenceTimestamp, string $sPathToScan, array &$aModifiedFiles)
{
if (!is_dir($sPathToScan)) {
return;
}
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($sPathToScan)) as $oFile) {
if ($oFile->isDir()) {
continue;
}
if (filemtime($oFile->getPathname()) > $fReferenceTimestamp) {
$aModifiedFiles[] = $oFile->getPathname();
}
}
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
<classes>
<class id="UserRequest">
<fields>
<field id="status" xsi:type="AttributeEnum">
<always_load_in_tables>true</always_load_in_tables>
<sort_type>rank</sort_type>
<values>
<value id="New_org_name_with_quote" _delta="define">
<code>New'status</code>
<rank>32</rank>
</value>
</values>
</field>
</fields>
</class>
</classes>
</itop_design>

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