Compare commits

...

146 Commits

Author SHA1 Message Date
Eric Espie
8bd72409f1 N°6716 - High memory Consomption and performance issue 2023-09-14 15:16:11 +02:00
Eric Espie
3eb06f8ada N°6716 - High memory Consomption and performance issue 2023-09-14 14:38:17 +02:00
Eric Espie
44c189223e Merge branch 'support/3.1.0' into support/3.1
# Conflicts:
#	tests/php-unit-tests/unitary-tests/core/DBObjectTest.php
2023-09-14 14:31:29 +02:00
Eric Espie
7fdbb59c30 N°6716 - High memory Consomption and performance issue 2023-09-14 14:09:05 +02:00
Eric Espie
5acf38ac36 Fix unit tests for MariaDB
(cherry picked from commit 61a9a4ac65)
2023-09-14 14:09:05 +02:00
odain
3e258f32cc N°5491-fix redundant GetNonPublicStaticProperty 2023-09-13 10:30:56 +02:00
odain
3c51d6fb98 N°5491- fix dictionary test 2023-09-13 10:27:19 +02:00
odain
7cfe1389aa Merge branch 'support/3.0' into support/3.1 2023-09-13 10:20:38 +02:00
Stephen Abello
7292a8540b N°6547 - Disallow linkset edition when lnk attribute is readonly 2023-09-13 10:53:32 +02:00
odain
f65c690462 N°5491-fix test 2023-09-13 10:03:05 +02:00
odain
ecf8bc42fa Merge branch 'support/2.7' into support/3.0 2023-09-13 10:01:15 +02:00
odain-cbd
e76728b2bf N°5491 - Inconsistent dictionnary entries regarding arguments to pass to Dict::Format-test first (#545) 2023-09-13 12:02:49 +02:00
vdumas
faba812fc1 N°6646 - Wrong dictionary entry for FR - Lnk Contact / Contrat 2023-09-12 12:48:33 +02:00
vdumas
add433d702 N°6706 - Missing dictionary entry for DE - Lnk Provider Contract / Service 2023-09-12 12:24:56 +02:00
vdumas
9c99cb35e5 N°6706 - Wrong dictionary entry for FR - Lnk Provider Contract / Service 2023-09-12 12:21:25 +02:00
Pierre Goiffon
9d392ad167 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-09-07 14:53:02 +02:00
Pierre Goiffon
ea8509db1f N°6709 Use ItopTestCase::RequireOnceCurrentModuleFile in GetAppRoot 2023-09-07 14:47:36 +02:00
Pierre Goiffon
df25ce76b6 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2023-09-07 14:43:29 +02:00
Pierre Goiffon
e946fc65fc N°6709 New ItopTestCase::RequireOnceCurrentModuleFile 2023-09-07 14:38:19 +02:00
Pierre Goiffon
d203e075a8 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-09-06 09:09:37 +02:00
Pierre Goiffon
dbe2f66539 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2023-09-06 09:07:45 +02:00
Pierre Goiffon
0d8ff7bbac N°6629 Set commit tests back to Mysql
For now we have perf issues on Jenkins with MariaDB (see N°6694)
2023-09-04 10:25:41 +02:00
Eric Espie
48eb022824 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-09-01 14:15:06 +02:00
Stephen Abello
03c9ffc033 N°6560 - Fix unescaped backtick in friendlyname breaking details page scripts 2023-09-01 09:41:23 +02:00
Eric Espie
61a9a4ac65 Fix unit tests for MariaDB 2023-09-01 09:29:21 +02:00
acognet
38962e68ee Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	pages/ajax.render.php
2023-08-31 16:22:01 +02:00
Pierre Goiffon
483dbb4a5d N°6658 Remove useless annotations
See comment for ItopTestCase::$preserveGlobalState
2023-08-31 16:06:34 +02:00
acognet
1f4dcc4f9e N°5136 - Relations: Fix "Select All objects" adding obsolete objects even if "show obsolete data" param. not activated - Merge from support/2.7 2023-08-31 16:04:03 +02:00
acognet
e86309669e Merge remote-tracking branch 'origin/support/2.7' into support/3.0
# Conflicts:
#	pages/ajax.render.php
2023-08-31 15:56:16 +02:00
Pierre Goiffon
6d6f55acf7 Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
2023-08-31 15:40:56 +02:00
Pierre Goiffon
6ebcd44bb1 💡 N°6658 Add more comments and since tags 2023-08-31 15:34:44 +02:00
Anne-Catherine
f8fb51fea0 N°5145 - Fix attachments missing in new ticket when clone from an old ticket with object copier (#530) 2023-08-31 15:27:23 +02:00
Anne-Catherine
bf768311c2 N°5136 - "Select All objects" add obsolete objects even if the parameter show obsolete data is not activated (#467) 2023-08-31 15:13:20 +02:00
Anne-Catherine
d797436786 N°6555 - Add class description in tooltip of Dashlet badge (#504)
cheery pick from branch develop due to target branch error
2023-08-31 14:55:06 +02:00
Anne-Catherine
b508c0d983 N°6152 - Search: Criteria & object list loaded twice (#495) 2023-08-31 12:03:31 +02:00
Anne-Catherine
351893bbdd N°4494 - Fix auto-locking on log save and transition (#358) 2023-08-31 11:23:58 +02:00
vdumas
59e4bb028f N°6682 - Pbs with Audit classes and AccessRight 2023-08-29 16:41:27 +02:00
vdumas
6d895371ec N°6682 - AuditDomain XML meta declaration missing 2023-08-29 16:41:27 +02:00
Stephen Abello
ab91631e68 N°6677 - Ensure emails in test are never sent to cc'd and bcc'd addresses 2023-08-29 15:56:52 +02:00
Eric Espie
3366bae0ab N°6061 - Add tests on Expression evaluation 2023-08-18 15:34:06 +02:00
Romain Quetiez
03b484c349 Tests: fix test not working on MariaDB (unexpected coma tolerated by MySQL) 2023-08-18 12:13:11 +02:00
Molkobain
70081ecf33 N°6436 - Add unit test for API introduced in 3.1 (\iFieldRendererMappingsExtension) 2023-08-18 10:31:05 +02:00
Molkobain
575ba1cd7b Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	core/metamodel.class.php
2023-08-18 10:24:50 +02:00
Molkobain
d130959692 Merge remote-tracking branch 'origin/support/2.7' into support/3.0
# Conflicts:
#	core/metamodel.class.php
2023-08-18 10:14:51 +02:00
Molkobain
a8c689c6c0 N°6436 - Add unit test to ensure that we don't lose an API during merge between branches 2023-08-18 09:55:45 +02:00
Molkobain
1990ccb5d8 N°6436 - Move interfaces enumeration from 1 line to 1 line / interface (and re-ordered them) for easier merges in newer branches 2023-08-18 09:52:55 +02:00
Molkobain
e107be56e4 N°6097 - Tests: Fix missing hook entry in PHPUnit XML file that led to compiled environment being re-build for each test case 2023-08-18 09:51:15 +02:00
Romain Quetiez
0f8e87e001 Tests: allow execution of RouterTest alone, fix tool to execute each test class separately 2023-08-18 08:44:54 +02:00
Molkobain
d92d2b5e9e Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	core/metamodel.class.php
2023-08-17 21:36:19 +02:00
Romain Quetiez
ebd0136773 Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
2023-08-17 18:36:34 +02:00
Molkobain
f6653e1594 N°6436 - Restore 3.0 APIs lost during 6433678d merge 2023-08-17 17:47:46 +02:00
Romain Quetiez
65bb76b9e3 N°6658 - Boost PHPUnit tests execution 2023-08-17 17:27:55 +02:00
Molkobain
f238593966 Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
#	tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
2023-08-11 09:19:49 +02:00
Molkobain
d951d3b872 💚 Fix typo in extended class name 2023-08-11 09:05:30 +02:00
Molkobain
ccceb870e3 Merge remote-tracking branch 'origin/support/2.7' into support/3.0
# Conflicts:
#	tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
#	tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
2023-08-10 15:53:05 +02:00
Molkobain
ed6df77cbb N°6097 - Tests: Optimize performances by creating custom env. only once and re-using it across test classes 2023-08-10 15:45:39 +02:00
Molkobain
1ad28312ec N°6097 - Tests: Introduce autoloader for "utility" classes and move them to a sub-folder for better organization as folder was still messy
Note that unittestautoload.php is now useless. We just keep for now until everything is migrated (projects / branches / modules)
2023-08-10 15:45:39 +02:00
Molkobain
f002aa04cd N°6097 - Tests: Enable PHP unit tests on a custom DataModel 2023-08-10 15:45:39 +02:00
Molkobain
b86d70623e N°6097 - Tests: Temporarily add test case for the new ItopCustomDatamodelTestCase class 2023-08-10 15:45:39 +02:00
Molkobain
fe3467309d N°6097 - Tests: Refactor base test classes for better extensibility 2023-08-10 15:45:39 +02:00
Molkobain
851ab9c356 N°6097 - Add \utils::GetDataPath() method to avoid duplicating manual path build 2023-08-10 15:45:39 +02:00
Molkobain
aef3c2e609 N°6097 - Fix \CMDBSource::DropDB() not resetting cache like \CMDBSource::DropTable() which can lead to errors when trying to re-create it afterwards 2023-08-10 15:45:39 +02:00
Pierre Goiffon
5212e15cc4 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-08-10 14:41:05 +02:00
Pierre Goiffon
f04fc546b5 N°6643 Fix TypeError in \CMDBSource::LogDeadLock 2023-08-10 14:34:09 +02:00
Lars Kaltefleiter
caf3076b12 N°3441 - Portal: Fix failure to open an object containing a link to an archived object (#523)
* N°3441 - Portal : cannot open an object containing a link to an archived object

* N°3441 - Display fa-archive icon in portal

* Update sources/Renderer/Bootstrap/FieldRenderer/BsSelectObjectFieldRenderer.php

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

* Update sources/Renderer/Bootstrap/FieldRenderer/BsSelectObjectFieldRenderer.php

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

* Update sources/Renderer/Bootstrap/FieldRenderer/BsSelectObjectFieldRenderer.php

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

---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2023-08-10 09:52:22 +02:00
Pierre Goiffon
c4c400d852 N°6638 💡 More explanations on CompiledDictionariesConsistencyTest::testImportCsvMessageStillOk 2023-08-09 14:54:19 +02:00
Pierre Goiffon
6cc4cc4fb6 📝 Version history : add 3.1.0-2 2023-08-09 10:20:27 +02:00
Pierre Goiffon
d7495af207 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-08-08 15:42:39 +02:00
Pierre Goiffon
13ad98b9b3 Add other integration tests in the beforeSetup group
All of those tests can be ran without a running iTop instance, and are blocking
2023-08-08 15:34:27 +02:00
Pierre Goiffon
4be54fdd65 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2023-08-08 15:33:36 +02:00
Pierre Goiffon
6d13397ba1 Add other integration tests in the beforeSetup group
All of those tests can be ran without a running iTop instance, and are blocking
2023-08-08 15:33:09 +02:00
Pierre Goiffon
48e7e0309a N°6638 Fix DictionariesConsistencyTest::testImportCsvMessageStillOk not run on Jenkins
Was contained in a class with a beforeSetup group annotation, whereas it tries to read files in env-production (!)
Plus the dataprovider was using APPROOT const + utils class, which aren't available by default :(
=> Fixed by moving in a dedicated class (CompiledDictionariesConsistencyTest) and removing the dataprovider
2023-08-08 15:30:01 +02:00
Pierre Goiffon
2ce9b2afaf Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-08-04 14:58:38 +02:00
Pierre Goiffon
d64a91d4ce Merge remote-tracking branch 'origin/support/2.7' into support/3.0
# Conflicts:
#	core/metamodel.class.php
2023-08-04 14:58:22 +02:00
Pierre Goiffon
c0c8a13864 💡 \MetaModel::GetObject : remove documented throw Exception 2023-08-04 14:55:38 +02:00
Pierre Goiffon
5ffa41bc16 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-08-03 11:09:14 +02:00
Pierre Goiffon
d2eef06276 AttributeURLTest : remove useless separateProcess annotations 2023-08-03 11:08:47 +02:00
Pierre Goiffon
77b14c516e Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-08-03 09:41:22 +02:00
Pierre Goiffon
880a824f2f N°6562 Replace new DOMText() by \DOMDocument::createTextNode
Because init using constructor outputs a read only node, see https://www.php.net/manual/en/domelement.construct.php
Thanks @Hipska
See conversation in 734a788
2023-08-03 09:40:39 +02:00
Molkobain
f7f1b5f399 Merge remote-tracking branch 'origin/support/3.1.0' into support/3.1 2023-08-02 15:27:17 +02:00
Pierre Goiffon
18efbfa803 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-08-02 10:39:51 +02:00
Pierre Goiffon
7aa478d6ff N°6562 💡 Fix comment
Thanks @Molkobain !
2023-08-02 10:35:30 +02:00
Pierre Goiffon
97700dbf15 N°6562 Re-enable failing tests
Conditional disabling was made in ea8e7c5
2023-08-01 14:27:57 +02:00
Pierre Goiffon
c25c69d746 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-08-01 14:27:41 +02:00
Pierre Goiffon
734a788340 N°6562 Fix DOMNode->textContent write
This attribute is read only
Causes layout issues on PHP 8.1.21 and 8.2.8
2023-08-01 14:22:56 +02:00
Eric Espie
eb1eb15791 N°6061 - Allow services to implement interfaces
N°6061 - allow local path from an arbitrary path

(cherry picked from commit 19e7fc9cb9)
(cherry picked from commit fb23bddeb2)
(cherry picked from commit 750ecd4804)
2023-07-28 10:46:06 +02:00
Pierre Goiffon
a84077782d N°4354 N°6587 Remove duplicated ItopDataTestCase::CreateContactlessUser method
Regression introduced by 26048150
2023-07-27 16:48:49 +02:00
Pierre Goiffon
26048150d3 Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	tests/php-unit-tests/ItopDataTestCase.php
2023-07-27 16:44:02 +02:00
Pierre Goiffon
e5b6e2eb8c N°4354 N°6587 Add test to cover $oUser->Get('profile_list') VS security.hide_administrators config param 2023-07-27 16:42:56 +02:00
Pierre Goiffon
87b6ea4def 📝 Version history : add missing 3.1.0-beta 2023-07-27 14:55:55 +02:00
purplegrape
72873a3343 🌐 Improve zh-cn dict
Manual merge for #516
Translation by @purplegrape, many thanks !
2023-07-27 11:17:09 +02:00
Pierre Goiffon
5ef25ccb77 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-07-26 12:07:50 +02:00
Pierre Goiffon
1682a85cc0 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2023-07-26 12:07:35 +02:00
Pierre Goiffon
cd9beec313 Merge remote-tracking branch 'origin/support/2.6' into support/2.7 2023-07-26 12:07:09 +02:00
Pierre Goiffon
8295eaed90 Merge remote-tracking branch 'origin/support/2.5' into support/2.6 2023-07-26 12:06:32 +02:00
Eric Espie
86a7cefa68 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-07-25 17:56:12 +02:00
Eric Espie
829b648dd2 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2023-07-25 17:55:45 +02:00
Eric Espie
5475b9fbbe N°3454 - MoveToProd in 2 steps - fix utils::GetCurrentModuleName() 2023-07-25 17:44:43 +02:00
Eric Espie
6f8e7c7002 N°3454 - MoveToProd in 2 steps - fix utils::GetCurrentModuleUrl() 2023-07-25 17:20:37 +02:00
Pierre Goiffon
67ca554261 📝 Version history : add 3.1.0-1 2023-07-25 17:07:30 +02:00
Pierre Goiffon
f89953f39e Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-07-24 15:39:09 +02:00
Pierre Goiffon
772368ef8a 💡 PHPDoc for object list panels 2023-07-24 15:38:57 +02:00
Pierre Goiffon
2e049aa244 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-07-24 11:59:57 +02:00
Pierre Goiffon
a57b6471c9 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2023-07-24 11:59:40 +02:00
Pierre Goiffon
bc7c1b4744 N°6590 Fix DictionariesConsistencyTest for PL dict files 2023-07-24 11:14:37 +02:00
Eric Espie
12c78697f4 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-07-19 15:20:15 +02:00
Eric Espie
046e857768 Merge remote-tracking branch 'origin/support/2.7' into support/3.0
# Conflicts:
#	core/config.class.inc.php
2023-07-19 15:19:06 +02:00
Eric Espie
4d8246c4d8 N°6436 - Integrate Performance Audit pre requisite in iTop Pro 2.7.9 (changed config variable name) 2023-07-19 15:13:43 +02:00
Eric Espie
5c61d725e1 N°6436 - Integrate Performance Audit pre requisite in iTop Pro 2.7.9 (changed config variable name) 2023-07-19 15:06:00 +02:00
Eric Espie
0c7195f1a3 Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	core/kpi.class.inc.php
2023-07-19 10:53:09 +02:00
Eric Espie
00b070b3cf Merge remote-tracking branch 'origin/support/2.7' into support/3.0
# Conflicts:
#	core/kpi.class.inc.php
2023-07-19 10:44:22 +02:00
Eric Espie
2c4cad4dac N°6436 - Integrate Performance Audit pre requisite in iTop Pro 2.7.9 (avoid unnecessary calls) 2023-07-19 10:37:41 +02:00
Eric Espie
9c37d5c23e Merge remote-tracking branch 'origin/support/3.0' into support/3.1
# Conflicts:
#	application/applicationextension.inc.php
#	application/cmdbabstract.class.inc.php
#	core/dbobject.class.php
#	core/kpi.class.inc.php
#	core/metamodel.class.php
#	lib/composer/autoload_classmap.php
#	lib/composer/autoload_static.php
#	tests/php-unit-tests/unitary-tests/setup/DBBackupTest.php
2023-07-19 09:26:46 +02:00
Stephen Abello
89145593ef N°6552 - Security hardening 2023-07-19 09:25:48 +02:00
Eric Espie
b2e80d37dd N°6436 - typo 2023-07-18 14:48:32 +02:00
Eric Espie
6432678de9 Merge remote-tracking branch 'origin/support/2.7' into support/3.0
# Conflicts:
#	application/cmdbabstract.class.inc.php
#	application/utils.inc.php
#	bootstrap.inc.php
#	composer.json
#	core/MyHelpers.class.inc.php
#	core/cmdbsource.class.inc.php
#	core/config.class.inc.php
#	core/dbobject.class.php
#	core/kpi.class.inc.php
#	core/metamodel.class.php
#	lib/composer/autoload_classmap.php
#	lib/composer/autoload_real.php
#	lib/composer/autoload_static.php
2023-07-18 14:36:58 +02:00
Pierre Goiffon
952194b385 N°6570 Fix BulkChangeExtKeyTest errors
- transaction started but never stopped
- invalid value label typo
- urlencode on search url
2023-07-18 14:12:29 +02:00
Pierre Goiffon
bfb452dd69 N°6570 Rename BulkChangeExtKeyTest file so it is run in Jenkins
Was *Test.inc.php instead of default *Test.php
2023-07-18 14:12:29 +02:00
Pierre Goiffon
64baeba1c7 Merge remote-tracking branch 'origin/support/3.0' into support/3.1 2023-07-18 09:49:03 +02:00
Molkobain
71ed784c60 N°6532 - Fix missing "/" in path
(cherry picked from commit 32fd75bc4b)
2023-07-18 09:40:34 +02:00
Eric Espie
da45651121 Merge branch 'feature/6548_Hide_DBHost_and_DBUser_in_log' into support/2.7 2023-07-18 09:34:48 +02:00
Eric Espie
d388ce9a06 Merge branch 'feature/6548_Hide_DBHost_and_DBUser_in_log' into support/2.7 2023-07-18 09:17:40 +02:00
Eric Espie
47e71d8838 Merge branch 'feature/6436-Integrate_Performance_Audit_extensibility' into support/2.7 2023-07-18 09:17:05 +02:00
Stephen Abello
2b5973ec67 N°6436 - Integrate Performance Audit pre requisite in iTop Pro 2.7.9 2023-07-18 09:15:37 +02:00
Benjamin Dalsass
e58918f53e N°6546 - AttributeLinkedSetIndirect filter dosen't work 2023-07-18 08:53:26 +02:00
Molkobain
125715af3f N°6562 - Temporarily disable XML conversion unit tests failing in PHP 8.2.8 2023-07-14 21:09:37 +02:00
Molkobain
ea8e7c5131 N°6562 - Temporarily disable XML conversion unit tests failing in PHP 8.1.21 2023-07-13 10:11:56 +02:00
Eric Espie
06e5e0b102 Merge branch 'support/3.1.0' into support/3.1 2023-07-12 13:31:34 +02:00
Pierre Goiffon
5247f5b3ea 🔖 Prepare version 3.1.1 2023-07-11 09:56:59 +02:00
Eric Espie
78396d8e4a 6548 - [ER] Hide DBHost and DBUser in log 2023-07-10 17:37:27 +02:00
Pierre Goiffon
f1ee22cbed Merge branch 'release/3.1.0' into develop 2023-07-10 16:57:23 +02:00
Molkobain
39305468f8 N°6043 - Booking: Move \TemporaryObjectDescriptor to /core to keep compatibility with legacy/custom packages 2023-07-10 12:25:32 +02:00
Molkobain
32fd75bc4b N°6532 - Fix missing "/" in path 2023-07-10 09:43:28 +02:00
Pierre Goiffon
efadf2cc79 Merge remote-tracking branch 'origin/support/3.0' into develop 2023-07-07 10:24:57 +02:00
Pierre Goiffon
40d63a2fa4 N°3663 💡 Fix depreciation comment in core/coreexception.class.inc.php 2023-07-07 10:24:15 +02:00
Pierre Goiffon
baa6dedbcf Merge remote-tracking branch 'origin/support/3.0' into develop 2023-07-07 09:32:14 +02:00
Pierre Goiffon
556b9ad89a N°6532 Fix "failed to open stream" error on require_once approot in coreexception.class.inc.php
Was occurring in TemplateFieldValueTest templates-base phpunit test
2023-07-07 09:31:34 +02:00
Stephen Abello
9afc22bd8f N°6123 - Add tests and comments 2023-07-07 09:29:15 +02:00
Pierre Goiffon
ef0b0f88c9 Merge remote-tracking branch 'origin/support/3.0' into develop
# Conflicts:
#	sources/Core/Email/EmailSwiftMailer.php
#	tests/php-unit-tests/integration-tests/DictionariesConsistencyTest.php
#	tests/php-unit-tests/postbuild_integration.xml.dist
2023-07-06 17:11:10 +02:00
Pierre Goiffon
a010239efb Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2023-07-06 15:48:42 +02:00
Pierre Goiffon
264a8cd70a N°6494 - Some tests are run twice, some never
(cherry picked from commit a2a0b2cd0b)

(cherry picked from commit 4c9ea0c9d4)

# Conflicts:
#	tests/php-unit-tests/integration-tests/DictionariesConsistencyTest.php
2023-07-06 15:45:09 +02:00
Stephen Abello
aa1834170b N°6427 - Fix SwiftMailer not retrieving sendmail path 2023-07-06 14:31:54 +02:00
Stephen Abello
f94d67ab35 N°6340 - Fix permission refused when sending an email and renewing OAuth token in synchronous mode 2023-07-06 10:28:10 +02:00
Stephen Abello
3048c8c41f N°5560 - Display an error when trying to regenerate an expired OAuth token 2023-07-06 09:52:00 +02:00
Stephen Abello
246e4a9f50 N°6123 - Fix warnings when launching a backup on MariaDB > v10.6.1 with localhost dbhost 2023-07-06 09:28:01 +02:00
odain
0001e8ffc4 💚 use new ci validation 2020-10-09 10:13:51 +02:00
188 changed files with 4886 additions and 2044 deletions

View File

@@ -62,6 +62,12 @@ gitGraph
commit id: "2022-12-28" tag: "2.7.8"
checkout support/3.0
commit id: "2023-04-12" tag: "3.0.3"
checkout develop
commit id: "2023-06-19" tag: "3.1.0-beta" type: REVERSE
commit id: "2023-07-26" tag: "3.1.0-1" type: HIGHLIGHT
branch support/3.1 order: 840
checkout support/3.1
commit id: "2023-08-09" tag: "3.1.0-2"
```
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).

View File

@@ -2246,3 +2246,27 @@ interface iModuleExtension
*/
public function __construct();
}
/**
* KPI logging extensibility point
*
* KPI Logger extension
*/
interface iKPILoggerExtension
{
/**
* Init the statistics collected
*
* @return void
*/
public function InitStats();
/**
* Add a new KPI to the stats
*
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKpiLogData
*
* @return mixed
*/
public function LogOperation($oKpiLogData);
}

View File

@@ -34,15 +34,15 @@ class AuditCategory extends cmdbAbstractObject
{
$aParams = array
(
"category" => "application, grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array('name'),
"db_table" => "priv_auditcategory",
"db_key_field" => "id",
"category" => "application,grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array('name'),
"db_table" => "priv_auditcategory",
"db_key_field" => "id",
"db_finalclass_field" => "",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'),
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'),
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));

View File

@@ -35,7 +35,7 @@ class AuditDomain extends cmdbAbstractObject
{
$aParams = array
(
"category" => "application, grant_by_profile",
"category" => "application,grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "name",
"complementary_name_attcode" => array('description'),

View File

@@ -35,7 +35,7 @@ class AuditRule extends cmdbAbstractObject
{
$aParams = array
(
"category" => "application, grant_by_profile",
"category" => "application,grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",

View File

@@ -1166,7 +1166,7 @@ HTML
/**
* @param \WebPage $oPage
* @param \CMDBObjectSet $oSet
* @param array $aExtraParams
* @param array $aExtraParams See possible values in {@see DataTableUIBlockFactory::RenderDataTable()}
*
* @throws \ApplicationException
* @throws \CoreException
@@ -4547,7 +4547,9 @@ HTML;
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
}
}
@@ -4562,13 +4564,16 @@ HTML;
protected function DBCloneTracked_Internal($newKey = null)
{
$oNewObj = parent::DBCloneTracked_Internal($newKey);
/** @var cmdbAbstractObject $oNewObj */
$oNewObj = MetaModel::GetObject(get_class($this), parent::DBCloneTracked_Internal($newKey));
// Invoke extensions after insertion (the object must exist, have an id, etc.)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
}
return $oNewObj;
@@ -4605,7 +4610,9 @@ HTML;
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBUpdate()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBUpdate');
}
}
@@ -4649,7 +4656,9 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
}
return parent::DBDeleteTracked_Internal($oDeletionPlan);
@@ -4668,7 +4677,10 @@ HTML;
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
if ($oExtensionInstance->OnIsModified($this)) {
$oKPI = new ExecutionKPI();
$bIsModified = $oExtensionInstance->OnIsModified($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
if ($bIsModified) {
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> true");
return true;
} else {
@@ -4724,7 +4736,9 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
{
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
@@ -4772,7 +4786,9 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
if (is_array($aNewIssues) && count($aNewIssues) > 0)
{
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);

View File

@@ -918,6 +918,11 @@ class RuntimeDashboard extends Dashboard
{
$bCustomized = false;
$sDashboardFileSanitized = utils::RealPath($sDashboardFile, APPROOT);
if (false === $sDashboardFileSanitized) {
throw new SecurityException('Invalid dashboard file !');
}
// Search for an eventual user defined dashboard
$oUDSearch = new DBObjectSearch('UserDashboard');
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
@@ -929,7 +934,7 @@ class RuntimeDashboard extends Dashboard
$sDashboardDefinition = $oUserDashboard->Get('contents');
$bCustomized = true;
} else {
$sDashboardDefinition = @file_get_contents($sDashboardFile);
$sDashboardDefinition = @file_get_contents($sDashboardFileSanitized);
}
@@ -937,7 +942,7 @@ class RuntimeDashboard extends Dashboard
$oDashboard = new RuntimeDashboard($sDashBoardId);
$oDashboard->FromXml($sDashboardDefinition);
$oDashboard->SetCustomFlag($bCustomized);
$oDashboard->SetDefinitionFile($sDashboardFile);
$oDashboard->SetDefinitionFile($sDashboardFileSanitized);
} else {
$oDashboard = null;
}

View File

@@ -1246,6 +1246,10 @@ JS
} else {
$oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams);
}
$sClassDescription = MetaModel::GetClassDescription($sClass);
if (utils::IsNotNullOrEmptyString($sClassDescription)) {
$oBlock->SetClassDescription($sClassDescription);
}
return $oBlock;
}

View File

@@ -296,7 +296,7 @@ class QueryOQL extends Query
}
catch
(OQLException $e) {
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::Format('UI:RunQuery:Error'), $e->getHtmlDesc())
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::S('UI:RunQuery:Error'), $e->getHtmlDesc())
->SetIsClosable(false)
->SetIsCollapsible(false);
$oAlert->AddCSSClass('mb-5');

View File

@@ -99,4 +99,10 @@ else
Session::Set('itop_env', ITOP_DEFAULT_ENV);
}
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
try {
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
}
catch (MySQLException $e) {
IssueLog::Debug($e->getMessage());
throw new MySQLException('Could not connect to the DB server', []);
}

View File

@@ -178,17 +178,18 @@ class UILinksWidget
$oDisplayBlock = new DisplayBlock($oFilter, 'search', false);
$oBlock->AddSubBlock($oDisplayBlock->GetDisplay($oPage, "SearchFormToAdd_{$sLinkedSetId}",
array(
'menu' => false,
[
'menu' => false,
'result_list_outer_selector' => "SearchResultsToAdd_{$sLinkedSetId}",
'table_id' => "add_{$sLinkedSetId}",
'table_inner_id' => "ResultsToAdd_{$sLinkedSetId}",
'selection_mode' => true,
'json' => $sJson,
'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix,
'query_params' => $oFilter->GetInternalParams(),
'hidden_criteria' => $sAlreadyLinkedExpression,
)));
'table_id' => "add_{$sLinkedSetId}",
'table_inner_id' => "ResultsToAdd_{$sLinkedSetId}",
'selection_mode' => true,
'json' => $sJson,
'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix,
'query_params' => $oFilter->GetInternalParams(),
'hidden_criteria' => $sAlreadyLinkedExpression,
'submit_on_load' => false,
]));
$oBlock->AddForm();
}

View File

@@ -20,6 +20,7 @@
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Service\Module\ModuleService;
use ScssPhp\ScssPhp\Compiler;
use ScssPhp\ScssPhp\OutputStyle;
use ScssPhp\ScssPhp\ValueConverter;
@@ -1396,13 +1397,23 @@ class utils
return APPROOT . 'env-' . MetaModel::GetEnvironment() . '/';
}
/**
* @return string A path to the folder into which data can be written
* @internal
* @since N°6097 2.7.10 3.0.4 3.1.1
*/
public static function GetDataPath(): string
{
return APPROOT.'data/';
}
/**
* @return string A path to a folder into which any module can store cache data
* The corresponding folder is created or cleaned upon code compilation
*/
public static function GetCachePath()
{
return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
return static::GetDataPath().'cache-'.MetaModel::GetEnvironment().'/';
}
/**
@@ -2265,24 +2276,7 @@ SQL;
*/
public static function GetCurrentModuleName($iCallDepth = 0)
{
$sCurrentModuleName = '';
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
foreach(GetModulesInfo() as $sModuleName => $aInfo)
{
if ($aInfo['root_dir'] !== '')
{
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
{
$sCurrentModuleName = $sModuleName;
break;
}
}
}
return $sCurrentModuleName;
return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1);
}
/**
@@ -2304,24 +2298,7 @@ SQL;
*/
public static function GetCurrentModuleDir($iCallDepth)
{
$sCurrentModuleDir = '';
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
foreach(GetModulesInfo() as $sModuleName => $aInfo)
{
if ($aInfo['root_dir'] !== '')
{
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
{
$sCurrentModuleDir = basename($sRootDir);
break;
}
}
}
return $sCurrentModuleDir;
return ModuleService::GetInstance()->GetCurrentModuleDir($iCallDepth);
}
/**
@@ -2336,12 +2313,7 @@ SQL;
*/
public static function GetCurrentModuleUrl()
{
$sDir = static::GetCurrentModuleDir(1);
if ( $sDir !== '')
{
return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
}
return '';
return ModuleService::GetInstance()->GetCurrentModuleUrl(1);
}
/**
@@ -2351,8 +2323,7 @@ SQL;
*/
public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
{
$sModuleName = static::GetCurrentModuleName(1);
return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue);
return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue);
}
/**
@@ -2361,12 +2332,7 @@ SQL;
*/
public static function GetCompiledModuleVersion($sModuleName)
{
$aModulesInfo = GetModulesInfo();
if (array_key_exists($sModuleName, $aModulesInfo))
{
return $aModulesInfo[$sModuleName]['version'];
}
return null;
return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName);
}
/**
@@ -2691,24 +2657,26 @@ SQL;
}
/**
* Returns the local path relative to the iTop installation of an existing file
* Returns the local path relative to the iTop installation (APPROOT or the given base path)
* Dir separator is changed to '/' for consistency among the different OS
*
* @param string $sAbsolutePath absolute path
* @param string $sBasePath Base path for the resulting local path (default APPROOT)
*
* @return false|string
* @return false|string The generated local path or false if absolute path is not under the base path
* @since 3.1.1 Added base path defaulted to previous version APPROOT
*/
final public static function LocalPath($sAbsolutePath)
final public static function LocalPath($sAbsolutePath, string $sBasePath = APPROOT)
{
$sRootPath = realpath(APPROOT);
$sRootPath = realpath($sBasePath);
$sFullPath = realpath($sAbsolutePath);
if (($sFullPath === false) || !self::StartsWith($sFullPath, $sRootPath))
{
return false;
}
$sLocalPath = substr($sFullPath, strlen($sRootPath.DIRECTORY_SEPARATOR));
$sLocalPath = str_replace(DIRECTORY_SEPARATOR, '/', $sLocalPath);
return $sLocalPath;
return str_replace(DIRECTORY_SEPARATOR, '/', $sLocalPath);
}
/**
@@ -2900,7 +2868,7 @@ HTML;
// Add already loaded classes
$aCurrentClasses = array_fill_keys(get_declared_classes(), '');
$aClassMap = array_merge($aClassMap, $aCurrentClasses);
$aClassMap = array_merge($aCurrentClasses, $aClassMap);
foreach ($aClassMap as $sPHPClass => $sPHPFile) {
$bSkipped = false;
@@ -2925,11 +2893,12 @@ HTML;
$bSkipped = true; // file not found
}
}
if(!$bSkipped){
try {
$oRefClass = new ReflectionClass($sPHPClass);
if ($oRefClass->implementsInterface($sInterface) && $oRefClass->isInstantiable()) {
if ($oRefClass->implementsInterface($sInterface) &&
!$oRefClass->isInterface() && !$oRefClass->isAbstract() && !$oRefClass->isTrait()) {
$aMatchingClasses[] = $sPHPClass;
}
} catch (Exception $e) {

View File

@@ -45,6 +45,7 @@ define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
$fItopStarted = microtime(true);
$iItopInitialMemory = memory_get_usage(true);
if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) {
require_once APPROOT.'/lib/autoload.php';

View File

@@ -59,9 +59,16 @@ class DbConnectionWrapper
* Use this to register a mock that will handle {@see mysqli::query()}
*
* @param \mysqli|null $oMysqli
* @since 3.0.4 3.1.1 3.2.0 Param $oMysqli becomes nullable
*/
public static function SetDbConnectionMockForQuery(?mysqli $oMysqli): void
public static function SetDbConnectionMockForQuery(?mysqli $oMysqli = null): void
{
static::$oDbCnxMockableForQuery = $oMysqli;
if (is_null($oMysqli)) {
// Reset to standard connection
static::$oDbCnxMockableForQuery = static::$oDbCnxStandard;
}
else {
static::$oDbCnxMockableForQuery = $oMysqli;
}
}
}

View File

@@ -419,6 +419,7 @@ class MyHelpers
//}
return $sOutput;
}
}
/**
@@ -523,5 +524,3 @@ class Str
return (strtolower($sString) == $sString);
}
}
?>

View File

@@ -650,6 +650,9 @@ class ActionEmail extends ActionNotification
$aMessageContent['subject'] = 'TEST['.$aMessageContent['subject'].']';
$aMessageContent['body'] = $sTestBody;
$aMessageContent['to'] = $this->Get('test_recipient');
// N°6677 Ensure emails in test are never sent to cc'd and bcc'd addresses
$aMessageContent['cc'] = '';
$aMessageContent['bcc'] = '';
}
// Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification
$aMessageContent['in_reply_to'] = $aMessageContent['references'];

View File

@@ -431,6 +431,7 @@ class CMDBSource
{
self::$m_sDBName = '';
}
self::_TablesInfoCacheReset(); // reset the table info cache!
}
public static function CreateTable($sQuery)
@@ -607,8 +608,9 @@ class CMDBSource
{
self::LogDeadLock($e, true);
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
}
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
} finally {
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
}
if ($oResult === false) {
$aContext = array('query' => $sSql);
@@ -626,18 +628,24 @@ class CMDBSource
}
/**
* @param \Exception $e
* @param Exception $e
* @param bool $bForQuery to get the proper DB connection
* @param bool $bCheckMysqliErrno if false won't try to check for mysqli::errno value
*
* @since 2.7.1
* @since 3.0.0 N°4325 add new optional parameter to use the correct DB connection
* @since 3.0.4 3.1.1 3.2.0 N°6643 new bCheckMysqliErrno parameter as a workaround for mysqli::errno cannot be mocked
*/
private static function LogDeadLock(Exception $e, $bForQuery = false)
private static function LogDeadLock(Exception $e, $bForQuery = false, $bCheckMysqliErrno = true)
{
// checks MySQL error code
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno;
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) {
return;
if ($bCheckMysqliErrno) {
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno;
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) {
return;
}
} else {
$iMySqlErrorNo = "N/A";
}
// Get error info
@@ -664,7 +672,10 @@ class CMDBSource
);
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage());
IssueLog::Error($sMessage, LogChannels::DEADLOCK, [
'exception.class' => get_class($e),
'exception.message' => $e->getMessage(),
]);
}
/**

View File

@@ -29,7 +29,7 @@ define('ITOP_APPLICATION_SHORT', 'iTop');
*
* @see ITOP_CORE_VERSION to get iTop core version
*/
define('ITOP_VERSION', '3.1.0-dev');
define('ITOP_VERSION', '3.1.1-dev');
define('ITOP_VERSION_NAME', 'Fullmoon');
define('ITOP_REVISION', 'svn');
@@ -656,22 +656,22 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'email_transport_smtp.allow_self_signed' => array(
'email_transport_smtp.allow_self_signed' => [
'type' => 'bool',
'description' => 'Allow self signed peer certificates',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_transport_smtp.verify_peer' => array(
],
'email_transport_smtp.verify_peer' => [
'type' => 'bool',
'description' => 'Verify peer certificate',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
],
'email_css' => [
'type' => 'string',
'description' => 'CSS that will override the standard stylesheet used for the notifications',
@@ -1069,6 +1069,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'log_kpi_generate_legacy_report' => [
'type' => 'bool',
'description' => 'Generate the legacy KPI report (kpi.html)',
'default' => true,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'max_linkset_output' => [
'type' => 'integer',
'description' => 'Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.',

View File

@@ -1,8 +1,11 @@
<?php
/**
* This file is only here for compatibility issues. Will be removed in iTop 3.1.0 (N°3664)
* This file is only here for compatibility reasons.
* It will be removed in future iTop versions (N°6533)
*
* @deprecated 3.0.0 N°3663 Exception classes were moved to `/application/exceptions`, use autoloader instead of require !
*/
require_once '../approot.inc.php';
DeprecatedCallsLog::NotifyDeprecatedFile('Classes were moved to /application/exceptions');
require_once __DIR__ . '/../approot.inc.php';
DeprecatedCallsLog::NotifyDeprecatedFile('Classes were moved to /application/exceptions and can be used directly with the autoloader');

View File

@@ -188,8 +188,8 @@ final class ItopCounter
if (!$hDBLink)
{
throw new Exception("Could not connect to the DB server (host=$sDBHost, user=$sDBUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
}
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
}
return $hDBLink;
}

View File

@@ -219,6 +219,19 @@
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields>
</class>
<class id="AuditDomain" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<category>application, grant_by_profile</category>
</properties>
<fields>
<field id="name" xsi:type="AttributeString"/>
<field id="description" xsi:type="AttributeString"/>
<field id="icon" xsi:type="AttributeImage"/>
<field id="categories_list" xsi:type="AttributeLinkedSet"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields>
</class>
<class id="Query" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>

View File

@@ -6,7 +6,9 @@
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventException;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\Events\EventServiceLog;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
/**
@@ -203,6 +205,8 @@ abstract class DBObject implements iDisplay
const MAX_UPDATE_LOOP_COUNT = 10;
private $aEventListeners = [];
/**
* DBObject constructor.
*
@@ -255,6 +259,10 @@ abstract class DBObject implements iDisplay
$this->RegisterEventListeners();
}
/**
* @see RegisterCRUDListener
* @see EventService::RegisterListener()
*/
protected function RegisterEventListeners()
{
}
@@ -1144,7 +1152,9 @@ abstract class DBObject implements iDisplay
return; //skip!
}
$this->FireEventComputeValues();
$oKPI = new ExecutionKPI();
$this->ComputeValues();
$oKPI->ComputeStatsForExtension($this, 'ComputeValues');
}
/**
@@ -2480,7 +2490,6 @@ abstract class DBObject implements iDisplay
{
$this->m_aCheckIssues = array();
$oKPI = new ExecutionKPI();
if ($bDoComputeValues) {
$this->DoComputeValues();
}
@@ -2490,8 +2499,9 @@ abstract class DBObject implements iDisplay
$this->FireEventCheckToWrite();
$this->SetReadWrite();
$oKPI = new ExecutionKPI();
$this->DoCheckToWrite();
$oKPI->ComputeStats('CheckToWrite', get_class($this));
$oKPI->ComputeStatsForExtension($this, 'DoCheckToWrite');
if (count($this->m_aCheckIssues) == 0)
{
$this->m_bCheckStatus = true;
@@ -3114,7 +3124,9 @@ abstract class DBObject implements iDisplay
// Ensure the update of the values (we are accessing the data directly)
$this->DoComputeValues();
$oKPI = new ExecutionKPI();
$this->OnInsert();
$oKPI->ComputeStatsForExtension($this, 'OnInsert');
$this->FireEventBeforeWrite();
@@ -3170,7 +3182,9 @@ abstract class DBObject implements iDisplay
$this->DBInsertSingleTable($sParentClass);
}
$oKPI = new ExecutionKPI();
$this->OnObjectKeyReady();
$oKPI->ComputeStatsForExtension($this, 'OnObjectKeyReady');
$this->UpdateCurrentObjectInCrudStack();
$this->DBWriteLinks();
@@ -3247,7 +3261,9 @@ abstract class DBObject implements iDisplay
public function PostInsertActions(): void
{
$this->FireEventAfterWrite([], true);
$oKPI = new ExecutionKPI();
$this->AfterInsert();
$oKPI->ComputeStatsForExtension($this, 'AfterInsert');
// Activate any existing trigger
$sClass = get_class($this);
@@ -3345,7 +3361,9 @@ abstract class DBObject implements iDisplay
try {
$this->DoComputeValues();
$this->ComputeStopWatchesDeadline(false);
$oKPI = new ExecutionKPI();
$this->OnUpdate();
$oKPI->ComputeStatsForExtension($this, 'OnUpdate');
$this->FireEventBeforeWrite();
@@ -3559,7 +3577,9 @@ abstract class DBObject implements iDisplay
public function PostUpdateActions(array $aChanges): void
{
$this->FireEventAfterWrite($aChanges, false);
$oKPI = new ExecutionKPI();
$this->AfterUpdate();
$oKPI->ComputeStatsForExtension($this, 'AfterUpdate');
// - TriggerOnObjectUpdate
$aParams = array('class_list' => MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL));
@@ -3786,7 +3806,9 @@ abstract class DBObject implements iDisplay
return;
}
$oKPI = new ExecutionKPI();
$this->OnDelete();
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
// Activate any existing trigger
$sClass = get_class($this);
@@ -3894,7 +3916,9 @@ abstract class DBObject implements iDisplay
}
$this->FireEventAfterDelete();
$oKPI = new ExecutionKPI();
$this->AfterDelete();
$oKPI->ComputeStatsForExtension($this, 'AfterDelete');
$this->m_bIsInDB = false;
@@ -6165,6 +6189,51 @@ abstract class DBObject implements iDisplay
return OPT_ATT_NORMAL;
}
public final function GetListeners(): array
{
$aListeners = [];
foreach ($this->aEventListeners as $aEventListener) {
$aListeners = array_merge($aListeners, $aEventListener);
}
return $aListeners;
}
/**
* Register a callback for a specific event. The method to call will be saved in the object instance itself whereas calling {@see EventService::RegisterListener()} would
* save a callable (thus the method name AND the whole DBObject instance)
*
* @param string $sEvent corresponding event
* @param string $callback The callback method to call
* @param float $fPriority optional priority for callback order
* @param string $sModuleId
*
* @see EventService::RegisterListener()
*
* @since 3.1.0-3 3.1.1 3.2.0 N°6716
*/
final protected function RegisterCRUDListener(string $sEvent, string $callback, float $fPriority = 0.0, string $sModuleId = '')
{
$aEventCallbacks = $this->aEventListeners[$sEvent] ?? [];
$aEventCallbacks[] = array(
'event' => $sEvent,
'callback' => $callback,
'priority' => $fPriority,
'module' => $sModuleId,
);
usort($aEventCallbacks, function ($a, $b) {
$fPriorityA = $a['priority'];
$fPriorityB = $b['priority'];
if ($fPriorityA == $fPriorityB) {
return 0;
}
return ($fPriorityA < $fPriorityB) ? -1 : 1;
});
$this->aEventListeners[$sEvent] = $aEventCallbacks;
}
/**
* @param string $sEvent
* @param array $aEventData
@@ -6176,15 +6245,53 @@ abstract class DBObject implements iDisplay
*/
public function FireEvent(string $sEvent, array $aEventData = array()): void
{
if (EventService::IsEventRegistered($sEvent)) {
$aEventData['debug_info'] = 'from: '.get_class($this).':'.$this->GetKey();
$aEventData['object'] = $this;
$aEventSources = [$this->m_sObjectUniqId];
foreach (MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL, false) as $sClass) {
$aEventSources[] = $sClass;
$aEventData['debug_info'] = 'from: '.get_class($this).':'.$this->GetKey();
$aEventData['object'] = $this;
// Call local listeners first
$aEventCallbacks = $this->aEventListeners[$sEvent] ?? [];
$oFirstException = null;
$sFirstExceptionMessage = '';
foreach ($aEventCallbacks as $aEventCallback) {
$oKPI = new ExecutionKPI();
$sCallback = $aEventCallback['callback'];
if (!method_exists($this, $sCallback)) {
EventServiceLog::Error("Callback '".get_class($this).":$sCallback' does not exist");
continue;
}
EventServiceLog::Debug("Fire event '$sEvent' calling '".get_class($this).":$sCallback'");
try {
call_user_func([$this, $sCallback], new EventData($sEvent, null, $aEventData));
}
catch (EventException $e) {
EventServiceLog::Error("Event '$sEvent' for '$sCallback'} failed with blocking error: ".$e->getMessage());
throw $e;
}
catch (Exception $e) {
$sMessage = "Event '$sEvent' for '$sCallback'} failed with non-blocking error: ".$e->getMessage();
EventServiceLog::Error($sMessage);
if (is_null($oFirstException)) {
$sFirstExceptionMessage = $sMessage;
$oFirstException = $e;
}
}
finally {
$oKPI->ComputeStats('FireEvent', $sEvent);
}
EventService::FireEvent(new EventData($sEvent, $aEventSources, $aEventData));
}
if (!is_null($oFirstException)) {
throw new Exception($sFirstExceptionMessage, $oFirstException->getCode(), $oFirstException);
}
// Call global event listeners
if (!EventService::IsEventRegistered($sEvent)) {
return;
}
$aEventSources = [];
foreach (MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL, false) as $sClass) {
$aEventSources[] = $sClass;
}
EventService::FireEvent(new EventData($sEvent, $aEventSources, $aEventData));
}
//////////////////

View File

@@ -767,7 +767,10 @@ class DBObjectSet implements iDBObjectSetIterator
try
{
$oKPI = new ExecutionKPI();
$this->m_oSQLResult = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, $this->GetRealSortOrder(), $this->m_iLimitCount, $this->m_iLimitStart, false);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
} catch (MySQLException $e)
{
// 1116 = ER_TOO_MANY_TABLES
@@ -847,8 +850,11 @@ class DBObjectSet implements iDBObjectSetIterator
{
if (is_null($this->m_iNumTotalDBRows))
{
$oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true);
$resQuery = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), 0, 0, true);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
if (!$resQuery) return 0;
$aRow = CMDBSource::FetchArray($resQuery);
@@ -859,6 +865,42 @@ class DBObjectSet implements iDBObjectSetIterator
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
}
/**
* @param \DBSearch $oFilter
* @param array $aOrder
* @param int $iLimitCount
* @param int $iLimitStart
* @param bool $bCount
*
* @return string
*/
private function GetPseudoOQL($oFilter, $aOrder, $iLimitCount, $iLimitStart, $bCount)
{
$sOQL = '';
if ($bCount) {
$sOQL .= 'COUNT ';
}
$sOQL .= $oFilter->ToOQL();
if ($iLimitCount > 0) {
$sOQL .= ' LIMIT ';
if ($iLimitStart > 0) {
$sOQL .= "$iLimitStart, ";
}
$sOQL .= "$iLimitCount";
}
if (count($aOrder) > 0) {
$sOQL .= ' ORDER BY ';
$aOrderBy = [];
foreach ($aOrder as $sAttCode => $bAsc) {
$aOrderBy[] = $sAttCode.' '.($bAsc ? 'ASC' : 'DESC');
}
$sOQL .= implode(', ', $aOrderBy);
}
return $sOQL;
}
/**
* Check if the count exceeds a given limit
*
@@ -875,8 +917,11 @@ class DBObjectSet implements iDBObjectSetIterator
{
if (is_null($this->m_iNumTotalDBRows))
{
$oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
$resQuery = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
if ($resQuery)
{
$aRow = CMDBSource::FetchArray($resQuery);
@@ -887,7 +932,7 @@ class DBObjectSet implements iDBObjectSetIterator
{
$iCount = 0;
}
}
}
else
{
$iCount = $this->m_iNumTotalDBRows;
@@ -912,8 +957,11 @@ class DBObjectSet implements iDBObjectSetIterator
{
if (is_null($this->m_iNumTotalDBRows))
{
$oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
$resQuery = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
if ($resQuery)
{
$aRow = CMDBSource::FetchArray($resQuery);
@@ -924,7 +972,7 @@ class DBObjectSet implements iDBObjectSetIterator
{
$iCount = 0;
}
}
}
else
{
$iCount = $this->m_iNumTotalDBRows;

View File

@@ -3,7 +3,7 @@
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -56,10 +56,11 @@ class Dict
* @param $sLanguageCode
*
* @throws \DictExceptionUnknownLanguage
* @since 3.0.4 3.1.1 3.2.0 Param $sLanguageCode becomes nullable
*/
public static function SetUserLanguage($sLanguageCode)
public static function SetUserLanguage($sLanguageCode = null)
{
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
if (!is_null($sLanguageCode) && !array_key_exists($sLanguageCode, self::$m_aLanguages))
{
throw new DictExceptionUnknownLanguage($sLanguageCode);
}
@@ -115,33 +116,50 @@ class Dict
* @return string
*/
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
$aInfo = self::GetLabelAndLangCode($sStringCode, $sDefault, $bUserLanguageOnly);
return $aInfo['label'];
}
/**
* Returns a localised string from the dictonary with its associated lang code
*
* @param string $sStringCode The code identifying the dictionary entry
* @param string $sDefault Default value if there is no match in the dictionary
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
*
* @return array{
* lang: string, label: string
* } with localized label string and used lang code
*/
private static function GetLabelAndLangCode($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
// Attempt to find the string in the user language
//
$sLangCode = self::GetUserLanguage();
self::InitLangIfNeeded($sLangCode);
if (!array_key_exists($sLangCode, self::$m_aData))
if (! array_key_exists($sLangCode, self::$m_aData))
{
IssueLog::Warning("Cannot find $sLangCode in dictionnaries. default labels displayed");
IssueLog::Warning("Cannot find $sLangCode in all registered dictionaries.");
// It may happen, when something happens before the dictionaries get loaded
return $sStringCode;
return [ 'label' => $sStringCode, 'lang' => $sLangCode ];
}
$aCurrentDictionary = self::$m_aData[$sLangCode];
if (is_array($aCurrentDictionary) && array_key_exists($sStringCode, $aCurrentDictionary))
{
return $aCurrentDictionary[$sStringCode];
return [ 'label' => $aCurrentDictionary[$sStringCode], 'lang' => $sLangCode ];
}
if (!$bUserLanguageOnly)
{
// Attempt to find the string in the default language
//
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
{
return $aDefaultDictionary[$sStringCode];
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => self::$m_sDefaultLanguage ];
}
// Attempt to find the string in english
//
@@ -150,17 +168,17 @@ class Dict
$aDefaultDictionary = self::$m_aData['EN US'];
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
{
return $aDefaultDictionary[$sStringCode];
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => 'EN US' ];
}
}
// Could not find the string...
//
if (is_null($sDefault))
{
return $sStringCode;
return [ 'label' => $sStringCode, 'lang' => null ];
}
return $sDefault;
return [ 'label' => $sDefault, 'lang' => null ];
}
@@ -176,19 +194,25 @@ class Dict
*/
public static function Format($sFormatCode /*, ... arguments ... */)
{
$sLocalizedFormat = self::S($sFormatCode);
['label' => $sLocalizedFormat, 'lang' => $sLangCode] = self::GetLabelAndLangCode($sFormatCode);
$aArguments = func_get_args();
array_shift($aArguments);
if ($sLocalizedFormat == $sFormatCode)
{
// Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded)
return $sFormatCode.' - '.implode(', ', $aArguments);
}
return vsprintf($sLocalizedFormat, $aArguments);
try{
return vsprintf($sLocalizedFormat, $aArguments);
} catch(\Throwable $e){
\IssueLog::Error("Cannot format dict key", null, ["sFormatCode" => $sFormatCode, "sLangCode" => $sLangCode, 'exception_msg' => $e->getMessage() ]);
return $sFormatCode.' - '.implode(', ', $aArguments);
}
}
/**
* Initialize a the entries for a given language (replaces the former Add() method)
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
@@ -198,7 +222,7 @@ class Dict
{
self::$m_aData[$sLanguageCode] = $aEntries;
}
/**
* Set the list of available languages
* @param hash $aLanguagesList
@@ -259,7 +283,7 @@ class Dict
{
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
require_once($sDictFile);
if (self::GetApcService()->function_exists('apc_store')
&& (self::$m_sApplicationPrefix !== null))
{
@@ -269,7 +293,7 @@ class Dict
}
return $bResult;
}
/**
* Enable caching (cached using APC)
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
@@ -312,14 +336,14 @@ class Dict
}
}
}
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
{
$aMissing = array(); // Strings missing for the target language
$aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary
$aNotTranslated = array(); // Strings having the same value in both dictionaries
$aOK = array(); // Strings having different values in both dictionaries
foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue)
{
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode]))
@@ -327,7 +351,7 @@ class Dict
$aMissing[$sStringCode] = $sValue;
}
}
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
{
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
@@ -350,7 +374,7 @@ class Dict
}
return array($aMissing, $aUnexpected, $aNotTranslated, $aOK);
}
public static function Dump()
{
MyHelpers::var_dump_html(self::$m_aData);
@@ -373,7 +397,7 @@ class Dict
// No need to actually load the strings since it's only used to know the list of languages
// at setup time !!
}
/**
* Export all the dictionary entries - of the given language - whose code matches the given prefix
* missing entries in the current language will be replaced by entries in the default language
@@ -386,7 +410,7 @@ class Dict
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
$aEntries = array();
$iLength = strlen($sStartingWith);
// First prefill the array with entries from the default language
foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
{
@@ -395,7 +419,7 @@ class Dict
$aEntries[$sCode] = $sEntry;
}
}
// Now put (overwrite) the entries for the user language
foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
{

View File

@@ -1,27 +1,14 @@
<?php
// Copyright (C) 2010-2023 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Core\Kpi\KpiLogData;
use Combodo\iTop\Service\Module\ModuleService;
/**
* Measures operations duration, memory usage, etc. (and some other KPIs)
*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ExecutionKPI
@@ -30,6 +17,8 @@ class ExecutionKPI
static protected $m_bEnabled_Memory = false;
static protected $m_bBlameCaller = false;
static protected $m_sAllowedUser = '*';
static protected $m_bGenerateLegacyReport = true;
static protected $m_fSlowQueries = 0;
static protected $m_aStats = []; // Recurrent operations
static protected $m_aExecData = []; // One shot operations
@@ -86,14 +75,39 @@ class ExecutionKPI
return false;
}
static public function SetGenerateLegacyReport($bReportExtensionsOnly)
{
self::$m_bGenerateLegacyReport = $bReportExtensionsOnly;
}
static public function SetSlowQueries($fSlowQueries)
{
self::$m_fSlowQueries = $fSlowQueries;
}
static public function GetDescription()
{
$aFeatures = array();
if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration';
if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage';
$sFeatures = implode(', ', $aFeatures);
$sFeatures = 'Measures: '.implode(', ', $aFeatures);
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
return "KPI logging is active for $sFor. Measures: $sFeatures";
$sSlowQueries = '';
if (self::$m_fSlowQueries > 0) {
$sSlowQueries = ". Slow Queries: ".self::$m_fSlowQueries."s";
}
$aExtensions = [];
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$aExtensions[] = ModuleService::GetInstance()->GetModuleNameFromObject($oExtensionInstance);
}
$sExtensions = '';
if (count($aExtensions) > 0) {
$sExtensions = '. KPI Extensions: ['.implode(', ', $aExtensions).']';
}
return "KPI logging is active for $sFor. $sFeatures$sSlowQueries$sExtensions";
}
static public function ReportStats()
@@ -101,7 +115,28 @@ class ExecutionKPI
if (!self::IsEnabled()) return;
global $fItopStarted;
global $iItopInitialMemory;
$sExecId = microtime(); // id to differentiate the hrefs!
$sRequest = $_SERVER['REQUEST_URI'].' ('.$_SERVER['REQUEST_METHOD'].')';
if (isset($_POST['operation'])) {
$sRequest .= ' operation: '.$_POST['operation'];
}
$fStop = MyHelpers::getmicrotime();
if (($fStop - $fItopStarted) > self::$m_fSlowQueries) {
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
$iCurrentMemory = self::memory_get_usage();
$iPeakMemory = self::memory_get_peak_usage();
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$oKPILogData = new KpiLogData(KpiLogData::TYPE_REQUEST, 'Page', $sRequest, $fItopStarted, $fStop, '', $iItopInitialMemory, $iCurrentMemory, $iPeakMemory);
$oExtensionInstance->LogOperation($oKPILogData);
}
}
if (!self::$m_bGenerateLegacyReport) {
return;
}
$aBeginTimes = array();
foreach (self::$m_aExecData as $aOpStats)
@@ -114,9 +149,9 @@ class ExecutionKPI
$sHtml = "<hr/>";
$sHtml .= "<div style=\"background-color: grey; padding: 10px;\">";
$sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>";
$sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - $sRequest</h3>";
$oStarted = DateTime::createFromFormat('U.u', $fItopStarted);
$sHtml .= "<p>".$oStarted->format('Y-m-d H:i:s.u')."</p>";
$sHtml .= '<p>'.$oStarted->format('Y-m-d H:i:s.u').'</p>';
$sHtml .= "<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>";
$sHtml .= "<div>";
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
@@ -257,7 +292,7 @@ class ExecutionKPI
$sTotalInter = round($fTotalInter, 3);
$sMinInter = round($fMinInter, 3);
$sMaxInter = round($fMaxInter, 3);
if (($fTotalInter >= $fSlowQueries))
if (($fTotalInter >= self::$m_fSlowQueries))
{
if ($bDisplayHeader)
{
@@ -285,37 +320,19 @@ class ExecutionKPI
self::Report($sHtml);
}
public static function InitStats()
{
// Invoke extensions to initialize the KPI statistics
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$oExtensionInstance->InitStats();
}
}
public function __construct()
{
$this->ResetCounters();
self::Push($this);
}
/**
* Stack executions to remove children duration from stats
*
* @param \ExecutionKPI $oExecutionKPI
*/
private static function Push(ExecutionKPI $oExecutionKPI)
{
self::$m_aExecutionStack[] = $oExecutionKPI;
}
/**
* Pop current child and count its duration in its parent
*
* @param float|int $fChildDuration
*/
private static function Pop(float $fChildDuration = 0)
{
array_pop(self::$m_aExecutionStack);
// Update the parent's children duration
$oPrevExecutionKPI = end(self::$m_aExecutionStack);
if ($oPrevExecutionKPI) {
$oPrevExecutionKPI->m_fChildrenDuration += $fChildDuration;
}
}
}
// Get the duration since startup, and reset the counter for the next measure
//
@@ -323,9 +340,15 @@ class ExecutionKPI
{
global $fItopStarted;
if (!self::IsEnabled()) {
return;
}
$aNewEntry = null;
if (self::$m_bEnabled_Duration) {
$fStarted = $this->m_fStarted;
$fStopped = $this->m_fStarted;
if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime();
$aNewEntry = array(
'op' => $sOperationDesc,
@@ -336,6 +359,9 @@ class ExecutionKPI
$this->m_fStarted = $fStopped;
}
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
$iCurrentMemory = 0;
$iPeakMemory = 0;
if (self::$m_bEnabled_Memory)
{
$iCurrentMemory = self::memory_get_usage();
@@ -345,40 +371,103 @@ class ExecutionKPI
}
$aNewEntry['mem_begin'] = $this->m_iInitialMemory;
$aNewEntry['mem_end'] = $iCurrentMemory;
if (function_exists('memory_get_peak_usage'))
{
$aNewEntry['mem_peak'] = memory_get_peak_usage();
}
$iPeakMemory = self::memory_get_peak_usage();
$aNewEntry['mem_peak'] = $iPeakMemory;
// Reset for the next operation (if the object is recycled)
$this->m_iInitialMemory = $iCurrentMemory;
}
if (!is_null($aNewEntry))
if (self::$m_bEnabled_Duration || self::$m_bEnabled_Memory) {
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance)
{
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
$oKPILogData = new KpiLogData(
KpiLogData::TYPE_REPORT,
'Step',
$sOperationDesc,
$fStarted,
$fStopped,
$sExtension,
$iInitialMemory,
$iCurrentMemory,
$iPeakMemory);
$oExtensionInstance->LogOperation($oKPILogData);
}
}
if (!is_null($aNewEntry) && self::$m_bGenerateLegacyReport)
{
self::$m_aExecData[] = $aNewEntry;
}
$this->ResetCounters();
}
public function ComputeStatsForExtension($object, $sMethod)
{
if (!self::IsEnabled()) {
return;
}
$sSignature = ModuleService::GetInstance()->GetModuleMethodSignature($object, $sMethod);
if (utils::StartsWith($sSignature, '[')) {
$this->ComputeStats('Extension', $sSignature);
}
}
public function ComputeStats($sOperation, $sArguments)
{
if (!self::IsEnabled()) {
return;
}
$fDuration = 0;
if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime();
$fDuration = $fStopped - $this->m_fStarted;
$fSelfDuration = $fDuration - $this->m_fChildrenDuration;
if (self::$m_bBlameCaller) {
self::$m_aStats[$sOperation][$sArguments][] = array(
'time' => $fSelfDuration,
'callers' => MyHelpers::get_callstack(1),
);
} else {
self::$m_aStats[$sOperation][$sArguments][] = array(
'time' => $fSelfDuration,
);
}
}
self::Pop($fDuration);
$aCallstack = [];
if (self::$m_bGenerateLegacyReport) {
if (self::$m_bBlameCaller) {
$aCallstack = MyHelpers::get_callstack(1);
self::$m_aStats[$sOperation][$sArguments][] = [
'time' => $fDuration,
'callers' => $aCallstack,
];
} else {
self::$m_aStats[$sOperation][$sArguments][] = [
'time' => $fDuration
];
}
}
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
$iCurrentMemory = 0;
$iPeakMemory = 0;
if (self::$m_bEnabled_Memory)
{
$iCurrentMemory = self::memory_get_usage();
$iPeakMemory = self::memory_get_peak_usage();
}
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
$oKPILogData = new KpiLogData(
KpiLogData::TYPE_STATS,
$sOperation,
$sArguments,
$this->m_fStarted,
$fStopped,
$sExtension,
$iInitialMemory,
$iCurrentMemory,
$iPeakMemory,
$aCallstack);
$oExtensionInstance->LogOperation($oKPILogData);
}
}
}
protected function ResetCounters()
@@ -408,35 +497,7 @@ class ExecutionKPI
static protected function memory_get_usage()
{
if (function_exists('memory_get_usage'))
{
return memory_get_usage(true);
}
// Copied from the PHP manual
//
//If its Windows
//Tested on Win XP Pro SP2. Should work on Win 2003 Server too
//Doesn't work for 2000
//If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642
if (substr(PHP_OS,0,3) == 'WIN')
{
$output = array();
exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output);
return preg_replace( '/[\D]/', '', $output[5] ) * 1024;
}
else
{
//We now assume the OS is UNIX
//Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
//This should work on most UNIX systems
$pid = getmypid();
exec("ps -eo%mem,rss,pid | grep $pid", $output);
$output = explode(" ", $output[0]);
//rss is given in 1024 byte units
return $output[1] * 1024;
}
return memory_get_usage(true);
}
static public function memory_get_peak_usage($bRealUsage = false)

View File

@@ -1138,7 +1138,7 @@ class DeprecatedCallsLog extends LogAPI
parent::Enable($sTargetFile);
if (
(false === defined(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME))
(false === defined('ITOP_PHPUNIT_RUNNING_CONSTANT_NAME'))
&& static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)
) {
set_error_handler([static::class, 'DeprecatedNoticesErrorHandler'], E_DEPRECATED | E_USER_DEPRECATED);

View File

@@ -6298,6 +6298,13 @@ abstract class MetaModel
*/
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = 'production')
{
// Startup on a new environment is not supported
static $bStarted = false;
if ($bStarted) {
return;
}
$bStarted = true;
self::$m_sEnvironment = $sEnvironment;
try {
@@ -6376,7 +6383,9 @@ abstract class MetaModel
ExecutionKPI::EnableDuration(self::$m_oConfig->Get('log_kpi_duration'));
ExecutionKPI::EnableMemory(self::$m_oConfig->Get('log_kpi_memory'));
ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
ExecutionKPI::SetGenerateLegacyReport(self::$m_oConfig->Get('log_kpi_generate_legacy_report'));
ExecutionKPI::SetSlowQueries(self::$m_oConfig->Get('log_kpi_slow_queries'));
self::$m_bSkipCheckToWrite = self::$m_oConfig->Get('skip_check_to_write');
self::$m_bSkipCheckExtKeys = self::$m_oConfig->Get('skip_check_ext_keys');
@@ -6495,6 +6504,7 @@ abstract class MetaModel
CMDBSource::InitFromConfig(self::$m_oConfig);
// Later when timezone implementation is correctly done: CMDBSource::SetTimezone($sDBTimezone);
ExecutionKPI::InitStats();
}
/**
@@ -6526,6 +6536,19 @@ abstract class MetaModel
return $value;
}
/**
* @internal Used for resetting the configuration during automated tests
* @param \Config $oConfiguration
*
* @return void
* @since 3.0.4 3.1.1 3.2.0
*/
public static function SetConfig(Config $oConfiguration)
{
self::$m_oConfig = $oConfiguration;
}
/**
* @return Config
*/
@@ -6807,25 +6830,21 @@ abstract class MetaModel
* $bMustBeFound=false)
* @throws CoreException if no result found and $bMustBeFound=true
* @throws ArchivedObjectException if archive mode disabled and result is archived and $bMustBeFound=true
* @throws \Exception
*
*/
public static function GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false, $aModifierProperties = null)
{
$oObject = self::GetObjectWithArchive($sClass, $iKey, $bMustBeFound, $bAllowAllData, $aModifierProperties);
if (empty($oObject))
{
if (empty($oObject)) {
return null;
}
if (!utils::IsArchiveMode() && $oObject->IsArchived())
{
if (!utils::IsArchiveMode() && $oObject->IsArchived()) {
if ($bMustBeFound) {
throw new ArchivedObjectException("The object $sClass::$iKey is archived");
} else {
return null;
}
return null;
}
return $oObject;
@@ -7608,14 +7627,12 @@ abstract class MetaModel
// Build the list of available extensions
//
$aInterfaces = [
'iApplicationUIExtension',
'iPreferencesExtension',
'iApplicationObjectExtension',
'iLoginFSMExtension',
'iLoginUIExtension',
'iLogoutExtension',
'iQueryModifier',
'iOnClassInitialization',
'iLoginUIExtension',
'iPreferencesExtension',
'iApplicationUIExtension',
'iApplicationObjectExtension',
'iPopupMenuExtension',
'iPageUIExtension',
'iPageUIBlockExtension',
@@ -7629,9 +7646,12 @@ abstract class MetaModel
'iBackofficeDictEntriesExtension',
'iBackofficeDictEntriesPrefixesExtension',
'iPortalUIExtension',
'iQueryModifier',
'iOnClassInitialization',
'iModuleExtension',
'iKPILoggerExtension',
'ModuleHandlerApiInterface',
'iNewsroomProvider',
'iModuleExtension',
];
foreach ($aInterfaces as $sInterface) {
self::$m_aExtensionClassNames[$sInterface] = array();

View File

@@ -257,7 +257,7 @@ class iTopMutex
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
if (!$this->hDBLink) {
throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
}
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,

View File

@@ -121,7 +121,9 @@ abstract class Trigger extends cmdbAbstractObject
$oAction = MetaModel::GetObject('Action', $iActionId);
if ($oAction->IsActive())
{
$oKPI = new ExecutionKPI();
$oAction->DoExecute($this, $aContextArgs);
$oKPI->ComputeStatsForExtension($oAction, 'DoExecute');
}
}
}

View File

@@ -761,14 +761,25 @@ class UserRights
protected static $m_aCacheContactPictureAbsUrl = [];
/** @var UserRightsAddOnAPI $m_oAddOn */
protected static $m_oAddOn;
protected static $m_oUser;
protected static $m_oRealUser;
protected static $m_oUser = null;
protected static $m_oRealUser = null;
protected static $m_sSelfRegisterAddOn = null;
protected static $m_aAdmins = array();
protected static $m_aPortalUsers = array();
/** @var array array('sName' => $sName, 'bSuccess' => $bSuccess); */
private static $m_sLastLoginStatus = null;
/**
* @return void
* @since 3.0.4 3.1.1 3.2.0
*/
protected static function ResetCurrentUserData()
{
self::$m_oUser = null;
self::$m_oRealUser = null;
self::$m_sLastLoginStatus = null;
}
/**
* @param string $sModuleName
*
@@ -787,8 +798,7 @@ class UserRights
}
self::$m_oAddOn = new $sModuleName;
self::$m_oAddOn->Init();
self::$m_oUser = null;
self::$m_oRealUser = null;
self::ResetCurrentUserData();
}
/**
@@ -855,6 +865,8 @@ class UserRights
*/
public static function Login($sLogin, $sAuthentication = 'any')
{
static::Logoff();
$oUser = self::FindUser($sLogin, $sAuthentication);
if (is_null($oUser))
{
@@ -872,6 +884,17 @@ class UserRights
return true;
}
/**
* @return void
* @since 3.0.4 3.1.1 3.2.0
*/
public static function Logoff()
{
self::ResetCurrentUserData();
Dict::SetUserLanguage(null);
self::_ResetSessionCache();
}
/**
* @param string $sLogin Login of the user to check the credentials for
* @param string $sPassword

View File

@@ -20,6 +20,8 @@ $ibo-dashlet-badge--icon--size: 48px !default;
$ibo-dashlet-badge--action-icon--margin-right: $ibo-spacing-300 !default;
$ibo-dashlet-badge--body--tooltip-title--margin-bottom: $ibo-spacing-500 !default;
/* CSS variables (can be changed directly from the browser) */
:root {
--ibo-dashlet-badge--min-width: #{$ibo-dashlet-badge--min-width};
@@ -74,18 +76,27 @@ $ibo-dashlet-badge--action-icon--margin-right: $ibo-spacing-300 !default;
@extend %ibo-hyperlink-inherited-colors;
}
}
.ibo-dashlet-badge--action-list-count{
margin-right: $ibo-dashlet-badge--action-list-count--margin-right;
@extend %ibo-font-ral-bol-450;
.ibo-dashlet-badge--action-list-count {
margin-right: $ibo-dashlet-badge--action-list-count--margin-right;
@extend %ibo-font-ral-bol-450;
}
.ibo-dashlet-badge--action-list-label{
display: inline-block;
@extend %ibo-text-truncated-with-ellipsis;
.ibo-dashlet-badge--action-list-label {
display: inline-block;
@extend %ibo-text-truncated-with-ellipsis;
}
.ibo-dashlet-badge--action-create{
@extend %ibo-baseline-centered-content;
@extend %ibo-font-size-150;
.ibo-dashlet-badge--action-create {
@extend %ibo-baseline-centered-content;
@extend %ibo-font-size-150;
}
.ibo-dashlet-badge--action-create-icon{
margin-right: $ibo-dashlet-badge--action-icon--margin-right;
.ibo-dashlet-badge--action-create-icon {
margin-right: $ibo-dashlet-badge--action-icon--margin-right;
}
.ibo-dashlet-badge--body--tooltip-title {
@extend %ibo-font-weight-600;
margin-bottom: $ibo-dashlet-badge--body--tooltip-title--margin-bottom;
}

View File

@@ -344,6 +344,7 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
while ($oAttachment = $oSet->Fetch())
{
$oTempAttachment = clone $oAttachment;
$oTempAttachment->Set('expire', time() + utils::GetConfig()->Get('draft_attachments_lifetime'));
$oTempAttachment->Set('item_id', null);
$oTempAttachment->Set('temp_id', $sTempId);
$oTempAttachment->DBInsert();

View File

@@ -235,13 +235,16 @@ class DBRestore extends DBBackup
if (in_array($oFileInfo->getFilename(), $aStandardFiles)) {
continue;
}
if (strncmp($oFileInfo->getPathname(), $sDataDir.'/production-modules', strlen($sDataDir.'/production-modules')) == 0) {
// Normalize filenames to cope with Windows backslashes
$sPath = str_replace('\\', '/', $oFileInfo->getPathname());
$sRefPath = str_replace('\\', '/', $sDataDir.'/production-modules');
if (strncmp($sPath, $sRefPath, strlen($sRefPath)) == 0) {
continue;
}
$aExtraFiles[$oFileInfo->getPathname()] = APPROOT.substr($oFileInfo->getPathname(), strlen($sDataDir));
}
return $aExtraFiles;
}
}

View File

@@ -58,7 +58,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:FAQ/Attribute:category_id+' => '',
'Class:FAQ/Attribute:category_name' => '类别名称',
'Class:FAQ/Attribute:category_name+' => '',
'Class:FAQ/Attribute:error_code' => '错误码',
'Class:FAQ/Attribute:error_code' => '错误码',
'Class:FAQ/Attribute:error_code+' => '',
'Class:FAQ/Attribute:key_words' => '关键字',
'Class:FAQ/Attribute:key_words+' => '',

View File

@@ -66,11 +66,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:Incident/Attribute:status+' => '',
'Class:Incident/Attribute:status/Value:new' => '新建',
'Class:Incident/Attribute:status/Value:new+' => '',
'Class:Incident/Attribute:status/Value:escalated_tto' => '已升级响应时间',
'Class:Incident/Attribute:status/Value:escalated_tto' => '已升级TTO',
'Class:Incident/Attribute:status/Value:escalated_tto+' => '',
'Class:Incident/Attribute:status/Value:assigned' => '已分配',
'Class:Incident/Attribute:status/Value:assigned+' => '',
'Class:Incident/Attribute:status/Value:escalated_ttr' => '已升级解决时间',
'Class:Incident/Attribute:status/Value:escalated_ttr' => '已升级TTR',
'Class:Incident/Attribute:status/Value:escalated_ttr+' => '',
'Class:Incident/Attribute:status/Value:waiting_for_approval' => '等待批准',
'Class:Incident/Attribute:status/Value:waiting_for_approval+' => '',
@@ -90,8 +90,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:Incident/Attribute:impact/Value:3+' => '',
'Class:Incident/Attribute:priority' => '优先级',
'Class:Incident/Attribute:priority+' => '',
'Class:Incident/Attribute:priority/Value:1' => '非常高',
'Class:Incident/Attribute:priority/Value:1+' => '非常高',
'Class:Incident/Attribute:priority/Value:1' => '紧急',
'Class:Incident/Attribute:priority/Value:1+' => '紧急',
'Class:Incident/Attribute:priority/Value:2' => '高',
'Class:Incident/Attribute:priority/Value:2+' => '高',
'Class:Incident/Attribute:priority/Value:3' => '中',
@@ -100,8 +100,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:Incident/Attribute:priority/Value:4+' => '低',
'Class:Incident/Attribute:urgency' => '紧急度',
'Class:Incident/Attribute:urgency+' => '',
'Class:Incident/Attribute:urgency/Value:1' => '非常高',
'Class:Incident/Attribute:urgency/Value:1+' => '非常高',
'Class:Incident/Attribute:urgency/Value:1' => '紧急',
'Class:Incident/Attribute:urgency/Value:1+' => '紧急',
'Class:Incident/Attribute:urgency/Value:2' => '高',
'Class:Incident/Attribute:urgency/Value:2+' => '高',
'Class:Incident/Attribute:urgency/Value:3' => '中',
@@ -136,7 +136,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:Incident/Attribute:escalation_flag/Value:no+' => '否',
'Class:Incident/Attribute:escalation_flag/Value:yes' => '是',
'Class:Incident/Attribute:escalation_flag/Value:yes+' => '是',
'Class:Incident/Attribute:escalation_reason' => '热门',
'Class:Incident/Attribute:escalation_reason' => '升级原因',
'Class:Incident/Attribute:escalation_reason+' => '',
'Class:Incident/Attribute:assignment_date' => '分配日期',
'Class:Incident/Attribute:assignment_date+' => '',
@@ -146,21 +146,21 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:Incident/Attribute:last_pending_date+' => '',
'Class:Incident/Attribute:cumulatedpending' => '累计待定',
'Class:Incident/Attribute:cumulatedpending+' => '',
'Class:Incident/Attribute:tto' => '响应时间',
'Class:Incident/Attribute:tto+' => '',
'Class:Incident/Attribute:ttr' => '解决时间',
'Class:Incident/Attribute:ttr+' => '',
'Class:Incident/Attribute:tto_escalation_deadline' => '响应时间截止',
'Class:Incident/Attribute:tto' => 'TTO',
'Class:Incident/Attribute:tto+' => '响应时间',
'Class:Incident/Attribute:ttr' => 'TTR',
'Class:Incident/Attribute:ttr+' => '解决时限',
'Class:Incident/Attribute:tto_escalation_deadline' => 'TTO截止日期',
'Class:Incident/Attribute:tto_escalation_deadline+' => '',
'Class:Incident/Attribute:sla_tto_passed' => '超过SLA响应时间',
'Class:Incident/Attribute:sla_tto_passed' => 'SLA TTO 合格',
'Class:Incident/Attribute:sla_tto_passed+' => '',
'Class:Incident/Attribute:sla_tto_over' => 'SLA响应时间结束',
'Class:Incident/Attribute:sla_tto_over' => 'SLA TTO 超时',
'Class:Incident/Attribute:sla_tto_over+' => '',
'Class:Incident/Attribute:ttr_escalation_deadline' => '解决时间截止',
'Class:Incident/Attribute:ttr_escalation_deadline' => 'TTR截止日期',
'Class:Incident/Attribute:ttr_escalation_deadline+' => '',
'Class:Incident/Attribute:sla_ttr_passed' => '超过SLA解决时间',
'Class:Incident/Attribute:sla_ttr_passed' => 'SLA TTR 合格',
'Class:Incident/Attribute:sla_ttr_passed+' => '',
'Class:Incident/Attribute:sla_ttr_over' => 'SLA解决时间结束',
'Class:Incident/Attribute:sla_ttr_over' => 'SLA TTR 超时',
'Class:Incident/Attribute:sla_ttr_over+' => '',
'Class:Incident/Attribute:time_spent' => '耗时',
'Class:Incident/Attribute:time_spent+' => '',
@@ -182,7 +182,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:Incident/Attribute:resolution_code/Value:training+' => '培训',
'Class:Incident/Attribute:solution' => '解决方案',
'Class:Incident/Attribute:solution+' => '',
'Class:Incident/Attribute:pending_reason' => '待定原因',
'Class:Incident/Attribute:pending_reason' => '待定原因',
'Class:Incident/Attribute:pending_reason+' => '',
'Class:Incident/Attribute:parent_incident_id' => '父级事件',
'Class:Incident/Attribute:parent_incident_id+' => '',

View File

@@ -66,7 +66,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:KnownError/Attribute:workaround+' => '',
'Class:KnownError/Attribute:solution' => '解决方案',
'Class:KnownError/Attribute:solution+' => '',
'Class:KnownError/Attribute:error_code' => '错误码',
'Class:KnownError/Attribute:error_code' => '错误码',
'Class:KnownError/Attribute:error_code+' => '',
'Class:KnownError/Attribute:domain' => '类型',
'Class:KnownError/Attribute:domain+' => '',

View File

@@ -230,6 +230,7 @@ HTML
$this->Set('refresh_token', $oAccessToken->getRefreshToken());
}
$this->Set('status', 'active');
$this->AllowWrite();
$this->DBUpdate();
}
]]></code>

View File

@@ -11,6 +11,7 @@ use Combodo\iTop\Application\TwigBase\Controller\Controller;
use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory;
use Dict;
use IssueLog;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use MetaModel;
use utils;
use WebPage;
@@ -65,13 +66,15 @@ class AjaxOauthClientController extends Controller
}
if (isset($aQuery['code'])) {
$sCode = $aQuery['code'];
$oAccessToken = OAuthClientProviderFactory::GetAccessTokenFromCode($oOAuthClient, $sCode);
$oOAuthClient->SetAccessToken($oAccessToken);
$aResult['status'] = 'success';
try {
$oAccessToken = OAuthClientProviderFactory::GetAccessTokenFromCode($oOAuthClient, $sCode);
$oOAuthClient->SetAccessToken($oAccessToken);
$aResult['status'] = 'success';
}
catch (IdentityProviderException $e) {
$aResult['status'] = 'error';
$aResult['error_description'] = $e->getMessage();
}
}
} else {
$aResult['status'] = 'error';

View File

@@ -103,8 +103,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:Problem/Attribute:impact/Value:3+' => '',
'Class:Problem/Attribute:urgency' => '紧急度',
'Class:Problem/Attribute:urgency+' => '',
'Class:Problem/Attribute:urgency/Value:1' => '非常高',
'Class:Problem/Attribute:urgency/Value:1+' => '非常高',
'Class:Problem/Attribute:urgency/Value:1' => '紧急',
'Class:Problem/Attribute:urgency/Value:1+' => '紧急',
'Class:Problem/Attribute:urgency/Value:2' => '高',
'Class:Problem/Attribute:urgency/Value:2+' => '高',
'Class:Problem/Attribute:urgency/Value:3' => '中',
@@ -113,8 +113,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:Problem/Attribute:urgency/Value:4+' => '低',
'Class:Problem/Attribute:priority' => '优先级',
'Class:Problem/Attribute:priority+' => '',
'Class:Problem/Attribute:priority/Value:1' => '非常高',
'Class:Problem/Attribute:priority/Value:1+' => '非常高',
'Class:Problem/Attribute:priority/Value:1' => '紧急',
'Class:Problem/Attribute:priority/Value:1+' => '紧急',
'Class:Problem/Attribute:priority/Value:2' => '高',
'Class:Problem/Attribute:priority/Value:2+' => '高',
'Class:Problem/Attribute:priority/Value:3' => '中',

View File

@@ -125,6 +125,7 @@
<group id="Audit" _delta="define">
<classes>
<!-- This class list is also present in AdminTools group -->
<class id="AuditDomain"/>
<class id="AuditCategory"/>
<class id="AuditRule"/>
<class id="ResourceRunQueriesMenu"/>
@@ -166,6 +167,7 @@
<class id="URP_UserProfile"/>
<class id="URP_Profiles"/>
<!-- Audit group -->
<class id="AuditDomain"/>
<class id="AuditCategory"/>
<class id="AuditRule"/>
<!-- Query group -->

View File

@@ -58,11 +58,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:UserRequest/Attribute:status+' => '',
'Class:UserRequest/Attribute:status/Value:new' => '新建',
'Class:UserRequest/Attribute:status/Value:new+' => '',
'Class:UserRequest/Attribute:status/Value:escalated_tto' => '已升级响应时间',
'Class:UserRequest/Attribute:status/Value:escalated_tto' => '已升级TTO',
'Class:UserRequest/Attribute:status/Value:escalated_tto+' => '',
'Class:UserRequest/Attribute:status/Value:assigned' => '已分配',
'Class:UserRequest/Attribute:status/Value:assigned+' => '',
'Class:UserRequest/Attribute:status/Value:escalated_ttr' => '已升级解决时间',
'Class:UserRequest/Attribute:status/Value:escalated_ttr' => '已升级TTR',
'Class:UserRequest/Attribute:status/Value:escalated_ttr+' => '',
'Class:UserRequest/Attribute:status/Value:waiting_for_approval' => '等待批准',
'Class:UserRequest/Attribute:status/Value:waiting_for_approval+' => '',
@@ -90,8 +90,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:UserRequest/Attribute:impact/Value:3+' => '',
'Class:UserRequest/Attribute:priority' => '优先级',
'Class:UserRequest/Attribute:priority+' => '',
'Class:UserRequest/Attribute:priority/Value:1' => '非常高',
'Class:UserRequest/Attribute:priority/Value:1+' => '非常高',
'Class:UserRequest/Attribute:priority/Value:1' => '紧急',
'Class:UserRequest/Attribute:priority/Value:1+' => '紧急',
'Class:UserRequest/Attribute:priority/Value:2' => '高',
'Class:UserRequest/Attribute:priority/Value:2+' => '高',
'Class:UserRequest/Attribute:priority/Value:3' => '中',
@@ -100,8 +100,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:UserRequest/Attribute:priority/Value:4+' => '低',
'Class:UserRequest/Attribute:urgency' => '紧急度',
'Class:UserRequest/Attribute:urgency+' => '',
'Class:UserRequest/Attribute:urgency/Value:1' => '非常高',
'Class:UserRequest/Attribute:urgency/Value:1+' => '非常高',
'Class:UserRequest/Attribute:urgency/Value:1' => '紧急',
'Class:UserRequest/Attribute:urgency/Value:1+' => '紧急',
'Class:UserRequest/Attribute:urgency/Value:2' => '高',
'Class:UserRequest/Attribute:urgency/Value:2+' => '高',
'Class:UserRequest/Attribute:urgency/Value:3' => '中',
@@ -134,7 +134,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:UserRequest/Attribute:servicesubcategory_id+' => '',
'Class:UserRequest/Attribute:servicesubcategory_name' => '子服务名称',
'Class:UserRequest/Attribute:servicesubcategory_name+' => '',
'Class:UserRequest/Attribute:escalation_flag' => '是否升级',
'Class:UserRequest/Attribute:escalation_flag' => '升级标签',
'Class:UserRequest/Attribute:escalation_flag+' => '',
'Class:UserRequest/Attribute:escalation_flag/Value:no' => '否',
'Class:UserRequest/Attribute:escalation_flag/Value:no+' => '否',
@@ -150,30 +150,30 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:UserRequest/Attribute:last_pending_date+' => '',
'Class:UserRequest/Attribute:cumulatedpending' => '累计待定',
'Class:UserRequest/Attribute:cumulatedpending+' => '',
'Class:UserRequest/Attribute:tto' => '响应时间',
'Class:UserRequest/Attribute:tto' => 'TTO',
'Class:UserRequest/Attribute:tto+' => '',
'Class:UserRequest/Attribute:ttr' => '解决时间',
'Class:UserRequest/Attribute:ttr' => 'TTR',
'Class:UserRequest/Attribute:ttr+' => '',
'Class:UserRequest/Attribute:tto_escalation_deadline' => '响应时间截止',
'Class:UserRequest/Attribute:tto_escalation_deadline' => 'TTO截止日期',
'Class:UserRequest/Attribute:tto_escalation_deadline+' => '',
'Class:UserRequest/Attribute:sla_tto_passed' => '超过SLA响应时间',
'Class:UserRequest/Attribute:sla_tto_passed' => 'SLA TTO 合格',
'Class:UserRequest/Attribute:sla_tto_passed+' => '',
'Class:UserRequest/Attribute:sla_tto_over' => 'SLA响应时间超过',
'Class:UserRequest/Attribute:sla_tto_over' => 'SLA TTO 超时',
'Class:UserRequest/Attribute:sla_tto_over+' => '',
'Class:UserRequest/Attribute:ttr_escalation_deadline' => '解决时间截止',
'Class:UserRequest/Attribute:ttr_escalation_deadline' => 'TTR截止日期',
'Class:UserRequest/Attribute:ttr_escalation_deadline+' => '',
'Class:UserRequest/Attribute:sla_ttr_passed' => '超过SLA解决时间',
'Class:UserRequest/Attribute:sla_ttr_passed' => 'SLA TTR 合格',
'Class:UserRequest/Attribute:sla_ttr_passed+' => '',
'Class:UserRequest/Attribute:sla_ttr_over' => 'SLA解决时间超过',
'Class:UserRequest/Attribute:sla_ttr_over' => 'SLA TTR 超时',
'Class:UserRequest/Attribute:sla_ttr_over+' => '',
'Class:UserRequest/Attribute:time_spent' => '耗时',
'Class:UserRequest/Attribute:time_spent+' => '',
'Class:UserRequest/Attribute:resolution_code' => '解决码',
'Class:UserRequest/Attribute:resolution_code' => '解决码',
'Class:UserRequest/Attribute:resolution_code+' => '',
'Class:UserRequest/Attribute:resolution_code/Value:assistance' => '帮助',
'Class:UserRequest/Attribute:resolution_code/Value:assistance+' => '帮助',
'Class:UserRequest/Attribute:resolution_code/Value:bug fixed' => '缺陷修复',
'Class:UserRequest/Attribute:resolution_code/Value:bug fixed+' => '缺陷修复',
'Class:UserRequest/Attribute:resolution_code/Value:bug fixed' => 'bug修复',
'Class:UserRequest/Attribute:resolution_code/Value:bug fixed+' => 'bug修复',
'Class:UserRequest/Attribute:resolution_code/Value:hardware repair' => '硬件维修',
'Class:UserRequest/Attribute:resolution_code/Value:hardware repair+' => '硬件维修',
'Class:UserRequest/Attribute:resolution_code/Value:other' => '其它',

View File

@@ -62,11 +62,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:UserRequest/Attribute:status+' => '',
'Class:UserRequest/Attribute:status/Value:new' => '新建',
'Class:UserRequest/Attribute:status/Value:new+' => '',
'Class:UserRequest/Attribute:status/Value:escalated_tto' => '已升级响应时间',
'Class:UserRequest/Attribute:status/Value:escalated_tto' => '已升级TTO',
'Class:UserRequest/Attribute:status/Value:escalated_tto+' => '',
'Class:UserRequest/Attribute:status/Value:assigned' => '已分配',
'Class:UserRequest/Attribute:status/Value:assigned+' => '',
'Class:UserRequest/Attribute:status/Value:escalated_ttr' => '已升级解决时间',
'Class:UserRequest/Attribute:status/Value:escalated_ttr' => '已升级TTR',
'Class:UserRequest/Attribute:status/Value:escalated_ttr+' => '',
'Class:UserRequest/Attribute:status/Value:waiting_for_approval' => '等待批准',
'Class:UserRequest/Attribute:status/Value:waiting_for_approval+' => '',
@@ -156,21 +156,21 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:UserRequest/Attribute:last_pending_date+' => '',
'Class:UserRequest/Attribute:cumulatedpending' => '累计待定',
'Class:UserRequest/Attribute:cumulatedpending+' => '',
'Class:UserRequest/Attribute:tto' => '响应时间',
'Class:UserRequest/Attribute:tto+' => '',
'Class:UserRequest/Attribute:ttr' => '解决时间',
'Class:UserRequest/Attribute:ttr+' => '',
'Class:UserRequest/Attribute:tto_escalation_deadline' => '响应时间期限',
'Class:UserRequest/Attribute:tto' => 'TTO',
'Class:UserRequest/Attribute:tto+' => '响应时间',
'Class:UserRequest/Attribute:ttr' => 'TTR',
'Class:UserRequest/Attribute:ttr+' => '解决时限',
'Class:UserRequest/Attribute:tto_escalation_deadline' => 'TTO截止日期',
'Class:UserRequest/Attribute:tto_escalation_deadline+' => '',
'Class:UserRequest/Attribute:sla_tto_passed' => '超过SLA响应时间',
'Class:UserRequest/Attribute:sla_tto_passed' => 'SLA TTO 合格',
'Class:UserRequest/Attribute:sla_tto_passed+' => '',
'Class:UserRequest/Attribute:sla_tto_over' => 'SLA响应时间结束',
'Class:UserRequest/Attribute:sla_tto_over' => 'SLA TTO 超时',
'Class:UserRequest/Attribute:sla_tto_over+' => '',
'Class:UserRequest/Attribute:ttr_escalation_deadline' => '解决时间期限',
'Class:UserRequest/Attribute:ttr_escalation_deadline' => 'TTR截止日期',
'Class:UserRequest/Attribute:ttr_escalation_deadline+' => '',
'Class:UserRequest/Attribute:sla_ttr_passed' => '超过SLA解决时间',
'Class:UserRequest/Attribute:sla_ttr_passed' => 'SLA TTR 合格',
'Class:UserRequest/Attribute:sla_ttr_passed+' => '',
'Class:UserRequest/Attribute:sla_ttr_over' => 'SLA解决时间结束',
'Class:UserRequest/Attribute:sla_ttr_over' => 'SLA TTR 超时',
'Class:UserRequest/Attribute:sla_ttr_over+' => '',
'Class:UserRequest/Attribute:time_spent' => '耗时',
'Class:UserRequest/Attribute:time_spent+' => '',
@@ -192,7 +192,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:UserRequest/Attribute:resolution_code/Value:training+' => '培训',
'Class:UserRequest/Attribute:solution' => '解决方案',
'Class:UserRequest/Attribute:solution+' => '',
'Class:UserRequest/Attribute:pending_reason' => '待定原因',
'Class:UserRequest/Attribute:pending_reason' => '待定原因',
'Class:UserRequest/Attribute:pending_reason+' => '',
'Class:UserRequest/Attribute:parent_request_id' => '父级需求',
'Class:UserRequest/Attribute:parent_request_id+' => '',

View File

@@ -99,9 +99,9 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:Contract/Attribute:organization_name' => 'Nom client',
'Class:Contract/Attribute:organization_name+' => 'Nom commun',
'Class:Contract/Attribute:contacts_list' => 'Contacts',
'Class:Contract/Attribute:contacts_list+' => 'Tous les contacts for ce contrat client',
'Class:Contract/Attribute:contacts_list+' => 'Tous les contacts pour ce contrat client',
'Class:Contract/Attribute:documents_list' => 'Documents',
'Class:Contract/Attribute:documents_list+' => 'Tous les documents for ce contrat client',
'Class:Contract/Attribute:documents_list+' => 'Tous les documents pour ce contrat client',
'Class:Contract/Attribute:description' => 'Description',
'Class:Contract/Attribute:description+' => '',
'Class:Contract/Attribute:start_date' => 'Date de début',

View File

@@ -389,8 +389,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:SLT/Attribute:name+' => '',
'Class:SLT/Attribute:priority' => '优先级',
'Class:SLT/Attribute:priority+' => '',
'Class:SLT/Attribute:priority/Value:1' => '非常高',
'Class:SLT/Attribute:priority/Value:1+' => '非常高',
'Class:SLT/Attribute:priority/Value:1' => '紧急',
'Class:SLT/Attribute:priority/Value:1+' => '紧急',
'Class:SLT/Attribute:priority/Value:2' => '高',
'Class:SLT/Attribute:priority/Value:2+' => '高',
'Class:SLT/Attribute:priority/Value:3' => '中',
@@ -403,15 +403,15 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:SLT/Attribute:request_type/Value:incident+' => '事件',
'Class:SLT/Attribute:request_type/Value:service_request' => '服务需求',
'Class:SLT/Attribute:request_type/Value:service_request+' => '服务需求',
'Class:SLT/Attribute:metric' => '指标',
'Class:SLT/Attribute:metric' => '衡量指标',
'Class:SLT/Attribute:metric+' => '',
'Class:SLT/Attribute:metric/Value:tto' => '响应时间',
'Class:SLT/Attribute:metric/Value:tto' => 'TTO',
'Class:SLT/Attribute:metric/Value:tto+' => '响应时间',
'Class:SLT/Attribute:metric/Value:ttr' => '解决时间',
'Class:SLT/Attribute:metric/Value:ttr+' => '解决时',
'Class:SLT/Attribute:metric/Value:ttr' => 'TTR',
'Class:SLT/Attribute:metric/Value:ttr+' => '解决时',
'Class:SLT/Attribute:value' => '值',
'Class:SLT/Attribute:value+' => '',
'Class:SLT/Attribute:unit' => '单位',
'Class:SLT/Attribute:unit' => '度量单位',
'Class:SLT/Attribute:unit+' => '',
'Class:SLT/Attribute:unit/Value:hours' => '小时',
'Class:SLT/Attribute:unit/Value:hours+' => '小时',

View File

@@ -157,6 +157,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:ProviderContract/Attribute:contracttype_id+' => '',
'Class:ProviderContract/Attribute:contracttype_name' => 'Vertragstyp-Name',
'Class:ProviderContract/Attribute:contracttype_name+' => '',
'Class:ProviderContract/Attribute:services_list' => 'Services',
'Class:ProviderContract/Attribute:services_list+' => 'Alle für diesen Vertrag erworbenen Services',
));
//

View File

@@ -169,6 +169,8 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:ProviderContract/Attribute:contracttype_id+' => '',
'Class:ProviderContract/Attribute:contracttype_name' => 'Contract type name',
'Class:ProviderContract/Attribute:contracttype_name+' => '',
'Class:ProviderContract/Attribute:services_list' => 'Services',
'Class:ProviderContract/Attribute:services_list+' => 'All the services purchased with this contract',
));
//

View File

@@ -157,6 +157,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:ProviderContract/Attribute:contracttype_id+' => '',
'Class:ProviderContract/Attribute:contracttype_name' => 'Nom Type de contrat',
'Class:ProviderContract/Attribute:contracttype_name+' => '',
'Class:ProviderContract/Attribute:services_list' => 'Services',
'Class:ProviderContract/Attribute:services_list+' => 'Tous les services achetés par ce contrat',
));
//

View File

@@ -362,8 +362,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:SLT/Attribute:name+' => '',
'Class:SLT/Attribute:priority' => '优先级',
'Class:SLT/Attribute:priority+' => '',
'Class:SLT/Attribute:priority/Value:1' => '非常高',
'Class:SLT/Attribute:priority/Value:1+' => '非常高',
'Class:SLT/Attribute:priority/Value:1' => '紧急',
'Class:SLT/Attribute:priority/Value:1+' => '紧急',
'Class:SLT/Attribute:priority/Value:2' => '高',
'Class:SLT/Attribute:priority/Value:2+' => '高',
'Class:SLT/Attribute:priority/Value:3' => '中',
@@ -376,15 +376,15 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:SLT/Attribute:request_type/Value:incident+' => '事件',
'Class:SLT/Attribute:request_type/Value:service_request' => '服务需求',
'Class:SLT/Attribute:request_type/Value:service_request+' => '服务需求',
'Class:SLT/Attribute:metric' => '指标',
'Class:SLT/Attribute:metric' => '衡量指标',
'Class:SLT/Attribute:metric+' => '',
'Class:SLT/Attribute:metric/Value:tto' => '响应时间',
'Class:SLT/Attribute:metric/Value:tto' => 'TTO',
'Class:SLT/Attribute:metric/Value:tto+' => '响应时间',
'Class:SLT/Attribute:metric/Value:ttr' => '解决时间',
'Class:SLT/Attribute:metric/Value:ttr+' => '解决时',
'Class:SLT/Attribute:metric/Value:ttr' => 'TTR',
'Class:SLT/Attribute:metric/Value:ttr+' => '解决时',
'Class:SLT/Attribute:value' => '值',
'Class:SLT/Attribute:value+' => '',
'Class:SLT/Attribute:unit' => '单位',
'Class:SLT/Attribute:unit' => '度量单位',
'Class:SLT/Attribute:unit+' => '',
'Class:SLT/Attribute:unit/Value:hours' => '小时',
'Class:SLT/Attribute:unit/Value:hours+' => '小时',

View File

@@ -251,7 +251,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:DocumentNote' => '文档笔记',
'Class:DocumentNote+' => '',
'Class:DocumentNote/Attribute:text' => '文',
'Class:DocumentNote/Attribute:text' => '文',
'Class:DocumentNote/Attribute:text+' => '',
));

View File

@@ -240,9 +240,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:cmdbAbstractObject/Method:ApplyStimulus+' => 'Apply the specified stimulus to the current object',
'Class:cmdbAbstractObject/Method:ApplyStimulus/Param:1' => 'Stimulus code',
'Class:cmdbAbstractObject/Method:ApplyStimulus/Param:1+' => 'A valid stimulus code for the current class',
'Class:ResponseTicketTTO/Interface:iMetricComputer' => '响应时间',
'Class:ResponseTicketTTO/Interface:iMetricComputer' => 'TTO',
'Class:ResponseTicketTTO/Interface:iMetricComputer+' => 'SLT 的响应时间',
'Class:ResponseTicketTTR/Interface:iMetricComputer' => '解决时间',
'Class:ResponseTicketTTR/Interface:iMetricComputer+' => 'SLT 的解决时',
'Class:ResponseTicketTTR/Interface:iMetricComputer' => 'TTR',
'Class:ResponseTicketTTR/Interface:iMetricComputer+' => 'SLT 的解决时',
));

View File

@@ -837,7 +837,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'UI:RunQuery:DevelopedOQLCount' => 'Developed OQL for count',
'UI:RunQuery:ResultSQLCount' => 'Resulting SQL for count',
'UI:RunQuery:ResultSQL' => 'Resulting SQL',
'UI:RunQuery:Error' => 'An error occured while running the query',
'UI:RunQuery:Error' => 'An error occured while running the query: %1$s',
'UI:Query:UrlForExcel' => 'URL to use for MS-Excel web queries',
'UI:Query:UrlV1' => 'The list of fields has been left unspecified. The page <em>export-V2.php</em> cannot be invoked without this information. Therefore, the URL suggested here below points to the legacy page: <em>export.php</em>. This legacy version of the export has the following limitation: the list of exported fields may vary depending on the output format and the data model of '.ITOP_APPLICATION_SHORT.'. <br/>Should you want to guarantee that the list of exported columns will remain stable on the long run, then you must specify a value for the attribute "Fields" and use the page <em>export-V2.php</em>.',
'UI:Schema:Title' => ITOP_APPLICATION_SHORT.' objects schema',

View File

@@ -21,7 +21,7 @@
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'Core:DeletedObjectLabel' => '%1s (törölve)',
'Core:DeletedObjectLabel' => '%1$s (törölve)',
'Core:DeletedObjectTip' => 'A %1$s objektum törölve (%2$s)',
'Core:UnknownObjectLabel' => 'Objektum nem található (osztály: %1$s, id: %2$d)',
'Core:UnknownObjectTip' => 'Az objektumot nem sikerült megtalálni. Lehet, hogy már törölték egy ideje, és a naplót azóta törölték.',

View File

@@ -509,7 +509,7 @@ Reméljük, hogy ezt a verziót ugyanúgy kedvelni fogja, mint ahogy mi élvezt
'UI:Error:2ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s és %2$s.',
'UI:Error:3ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s, %2$s és %3$s.',
'UI:Error:4ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s, %2$s, %3$s és %4$s.',
'UI:Error:IncorrectOQLQuery_Message' => 'Hiba: nem megfelelő OQL lekérdezés: %1$',
'UI:Error:IncorrectOQLQuery_Message' => 'Hiba: nem megfelelő OQL lekérdezés: %1$s',
'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'Hiba történt a lekérdezés futtatása közben: %1$s',
'UI:Error:ObjectAlreadyUpdated' => 'Hiba: az objketum már korábban módosításra került.',
'UI:Error:ObjectCannotBeUpdated' => 'Hiba: az objektum nem frissíthető.',
@@ -715,7 +715,7 @@ Reméljük, hogy ezt a verziót ugyanúgy kedvelni fogja, mint ahogy mi élvezt
'UI:CSVReport-Value-Issue-Null' => 'A nulla nem engedélyezett',
'UI:CSVReport-Value-Issue-NotFound' => 'Az objektum nincs meg',
'UI:CSVReport-Value-Issue-FoundMany' => '%1$d egyezés található',
'UI:CSVReport-Value-Issue-Readonly' => 'A \'%1$\'s attribútum csak olvasható (jelenlegi érték: %2$s, várható érték: %3$s)',
'UI:CSVReport-Value-Issue-Readonly' => 'A \'%1$s attribútum csak olvasható (jelenlegi érték: %2$s, várható érték: %3$s)',
'UI:CSVReport-Value-Issue-Format' => 'A bevitel feldolgozása sikertelen: %1$s',
'UI:CSVReport-Value-Issue-NoMatch' => 'A \'%1$s\' attribútum nem várt értéket kapott: nincs egyezés, ellenőrizze a beírást',
'UI:CSVReport-Value-Issue-AllowedValues' => 'Allowed \'%1$s\' value(s): %2$s~~',

View File

@@ -20,7 +20,7 @@
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Core:DeletedObjectLabel' => '%1s (削除されました)',
'Core:DeletedObjectLabel' => '%1$s (削除されました)',
'Core:DeletedObjectTip' => 'オブジェクトは削除されました %1$s (%2$s)',
'Core:UnknownObjectLabel' => 'オブジェクトは見つかりません (クラス: %1$s, id: %2$d)',
'Core:UnknownObjectTip' => 'オブジェクトは見つかりません。しばらく前に削除され、その後ログが削除されたかもしれません。',

View File

@@ -915,7 +915,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => '%2$sクラスの%1$dオブジェクトの削除',
'UI:Delete:CannotDeleteBecause' => '削除できません: %1$s',
'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => '自動的に削除されるべきですが、出来ません。: %1$s',
'UI:Delete:MustBeDeletedManuallyButNotPossible' => '手動で削除されるべきですが、出来ません。: %1$',
'UI:Delete:MustBeDeletedManuallyButNotPossible' => '手動で削除されるべきですが、出来ません。: %1$s',
'UI:Delete:WillBeDeletedAutomatically' => '自動的に削除されます。',
'UI:Delete:MustBeDeletedManually' => '手動で削除されるべきです。',
'UI:Delete:CannotUpdateBecause_Issue' => '自動的に更新されるべきですが、しかし: %1$s',
@@ -1159,8 +1159,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'Enum:Undefined' => '未定義',
'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s 日 %2$s 時 %3$s 分 %4$s 秒',
'UI:ModifyAllPageTitle' => '全てを修正',
'UI:Modify_ObjectsOf_Class' => 'Modifying objects of class %1$s~~',
'UI:Modify_N_ObjectsOf_Class' => 'クラス%2$Sの%1$dオブジェクトを修正',
'UI:Modify_N_ObjectsOf_Class' => 'クラス%2$sの%1$dオブジェクトを修正',
'UI:Modify_M_ObjectsOf_Class_OutOf_N' => 'クラス%2$sの%3$d中%1$dを修正',
'UI:Menu:ModifyAll' => '修正...',
'UI:Menu:ModifyAll_Class' => 'Modify %1$s objects...~~',
@@ -1180,7 +1179,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'UI:BulkModify_Count_DistinctValues' => '%1$d 個の個別の値:',
'UI:BulkModify:Value_Exists_N_Times' => '%1$s, %2$d 回存在',
'UI:BulkModify:N_MoreValues' => '%1$d 個以上の値...',
'UI:AttemptingToSetAReadOnlyAttribute_Name' => '読み込み専用フィールド %1$にセットしょうとしています。',
'UI:AttemptingToSetAReadOnlyAttribute_Name' => '読み込み専用フィールド %1$sにセットしょうとしています。',
'UI:FailedToApplyStimuli' => 'アクションは失敗しました。',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: クラス%3$sの%2$dオブジェクトを修正',
'UI:CaseLogTypeYourTextHere' => 'テキストを入力ください:',

View File

@@ -9,7 +9,7 @@
*
*/
Dict::Add('RU RU', 'Russian', 'Русский', array(
'Core:DeletedObjectLabel' => '%1ы (удален)',
'Core:DeletedObjectLabel' => '%1$sы (удален)',
'Core:DeletedObjectTip' => 'Объект был удален %1$s (%2$s)',
'Core:UnknownObjectLabel' => 'Объект не найден (class: %1$s, id: %2$d)',
'Core:UnknownObjectTip' => 'Объект не удается найти. Возможно, он был удален некоторое время назад, и журнал с тех пор был очищен.',

View File

@@ -497,7 +497,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'UI:Error:MandatoryTemplateParameter_group_by' => 'Parameter group_by je povinný. Skontrolujte definíciu šablóny zobrazenia.',
'UI:Error:InvalidGroupByFields' => 'Neplatný zoznam polí pre skupinu podľa: "%1$s".',
'UI:Error:UnsupportedStyleOfBlock' => 'Chyba: nepodporovaný štýl bloku: "%1$s".',
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Nesprávna definícia spojenia : trieda objektov na manažovanie : %l$s nebol nájdený ako externý kľúč v triede %2$s',
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Nesprávna definícia spojenia : trieda objektov na manažovanie : %1$s nebol nájdený ako externý kľúč v triede %2$s',
'UI:Error:Object_Class_Id_NotFound' => 'Objekt: %1$s:%2$d nebol nájdený.',
'UI:Error:WizardCircularReferenceInDependencies' => 'Chyba: Cyklický odkaz v závislostiach medzi poliami, skontrolujte dátový model.',
'UI:Error:UploadedFileTooBig' => 'Nahraný súbor je príliš veľký. (Max povolená veľkosť je %1$s). Ak chcete zmeniť tento limit, obráťte sa na správcu ITOP . (Skontrolujte, PHP konfiguráciu pre upload_max_filesize a post_max_size na serveri).',
@@ -1301,7 +1301,7 @@ Keď sú priradené spúštačom, každej akcii je dané číslo "príkazu", šp
'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Deň v mesiaci pre %1$s',
'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (hodina)',
'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (mesiac)',
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$ (deň v týžni)',
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (deň v týžni)',
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (deň v mesiaci)',
'UI:DashletGroupBy:MissingGroupBy' => 'Prosím zvoľte pole na ktorom objekty budú zoskupené spolu',
'UI:DashletGroupByPie:Label' => 'Koláčový graf',

View File

@@ -278,7 +278,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s\'nin değeri %2$s olarak atandı (önceki değer: %3$s)',
'Change:AttName_SetTo' => '%1$s\'nin değeri %2$s olarak atandı',
'Change:Text_AppendedTo_AttName' => '%2$s\'ye %1$s eklendi',
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$\'nin değeri deiştirildi, önceki değer: %2$s',
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s nin değeri deiştirildi, önceki değer: %2$s',
'Change:AttName_Changed' => '%1$s değiştirildi',
'Change:AttName_EntryAdded' => '%1$s değiştirilmiş, yeni giriş eklendi.',
'Change:State_Changed_NewValue_OldValue' => 'Changed from %2$s to %1$s~~',

View File

@@ -19,9 +19,9 @@
// Display DataTable
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'UI:Datatables:Language:Processing' => '请稍候...',
'UI:Datatables:Language:LengthMenu' => '_MENU_ 每页',
'UI:Datatables:Language:LengthMenu' => '每页 _MENU_ ',
'UI:Datatables:Language:ZeroRecords' => '未找到相关结果',
'UI:Datatables:Language:Info' => '_TOTAL_ 项',
'UI:Datatables:Language:Info' => '_TOTAL_ 项',
'UI:Datatables:Language:InfoEmpty' => '未找到相关信息',
'UI:Datatables:Language:EmptyTable' => '表格中暂无数据',
'UI:Datatables:Language:Error' => '运行查询时出错',

View File

@@ -27,8 +27,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'As correspondências em todos os grupos de menus serão exibidas',
'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'Nenhum resultado para este filtro de menu',
'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Olá %1$s!',
'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => 'Imagem do contato %1$',
'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => 'Imagem do contato %1$s',
'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Abrir menu do usuário',
'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filtrar entradas de menu',
));
));

View File

@@ -16,6 +16,7 @@
*
* You should have received a copy of the GNU Affero General Public License
*/
// Navigation menu
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'UI:Layout:NavigationMenu:CompanyLogo:AltText' => '公司logo',

View File

@@ -20,44 +20,60 @@
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Core:DeletedObjectLabel' => '%1s (已删除)',
'Core:DeletedObjectTip' => '对象已被删除于 %1$s (%2$s)',
'Core:UnknownObjectLabel' => '对象找不到 (class: %1$s, id: %2$d)',
'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.~~',
'Core:UniquenessDefaultError' => 'Uniqueness rule \'%1$s\' in error~~',
'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~',
'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~',
'Core:AttributeLinkedSet' => '对象数组',
'Core:AttributeLinkedSet+' => 'Any kind of objects of the same class or subclass~~',
'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~',
'Core:AttributeDashboard' => '仪表盘',
'Core:AttributeDashboard+' => '',
'Core:AttributePhoneNumber' => '电话号码',
'Core:AttributePhoneNumber+' => '',
'Core:AttributeObsolescenceDate' => '报废日期',
'Core:AttributeObsolescenceDate+' => '',
'Core:AttributeTagSet' => '清单',
'Core:AttributeTagSet+' => '',
'Core:AttributeSet:placeholder' => '请点击这里添加',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)~~',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s from %3$s)~~',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s from child classes)~~',
'Core:AttributeCaseLog' => '日志',
'Core:AttributeCaseLog+' => '',
'Core:AttributeMetaEnum' => 'Computed enum~~',
'Core:AttributeMetaEnum+' => '~~',
'Core:AttributeLinkedSetIndirect' => '对象数组(N-N)',
'Core:AttributeLinkedSetIndirect+' => 'Any kind of objects [subclass] of the same class~~',
'Core:AttributeInteger' => '整数',
'Core:AttributeInteger+' => '整数值(可以为负)',
'Core:AttributeDecimal' => '小数',
'Core:AttributeDecimal+' => '小数(可以为负)',
'Core:AttributeBoolean' => '布尔',
'Core:AttributeBoolean+' => '布尔',
'Core:AttributeBoolean+' => '',
'Core:AttributeBoolean/Value:null' => '',
'Core:AttributeBoolean/Value:yes' => '是',
'Core:AttributeBoolean/Value:no' => '否',
'Core:AttributeArchiveFlag' => '是否归档',
'Core:AttributeArchiveFlag/Value:yes' => '是',
'Core:AttributeArchiveFlag/Value:yes+' => '此对象仅在归档模式可见',
@@ -66,6 +82,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Core:AttributeArchiveFlag/Label+' => '',
'Core:AttributeArchiveDate/Label' => '归档日期',
'Core:AttributeArchiveDate/Label+' => '',
'Core:AttributeObsolescenceFlag' => '是否废弃',
'Core:AttributeObsolescenceFlag/Value:yes' => '是',
'Core:AttributeObsolescenceFlag/Value:yes+' => 'This object is excluded from the impact analysis, and hidden from search results~~',
@@ -74,38 +91,54 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Core:AttributeObsolescenceFlag/Label+' => 'Computed dynamically on other attributes~~',
'Core:AttributeObsolescenceDate/Label' => '废弃时间',
'Core:AttributeObsolescenceDate/Label+' => 'Approximative date at which the object has been considered obsolete~~',
'Core:AttributeString' => '字符串',
'Core:AttributeString+' => '字符串',
'Core:AttributeClass' => '类',
'Core:AttributeClass+' => '类别',
'Core:AttributeClass+' => '',
'Core:AttributeApplicationLanguage' => '用户语言',
'Core:AttributeApplicationLanguage+' => '语言和国家地区(EN US)',
'Core:AttributeFinalClass' => '类 (auto)',
'Core:AttributeFinalClass+' => 'Real class of the object (automatically created by the core)',
'Core:AttributePassword' => '密码',
'Core:AttributePassword+' => '外部设备的密码',
'Core:AttributeEncryptedString' => '加密字符串',
'Core:AttributeEncryptedString+' => 'String encrypted with a local key~~',
'Core:AttributeEncryptUnknownLibrary' => '未知的加密库 (%1$s)',
'Core:AttributeEncryptFailedToDecrypt' => '** 解密错误 **',
'Core:AttributeText' => '文本',
'Core:AttributeText+' => '多行字符串',
'Core:AttributeHTML' => 'HTML',
'Core:AttributeHTML+' => 'HTML字符串',
'Core:AttributeEmailAddress' => '邮箱地址',
'Core:AttributeEmailAddress+' => '邮箱地址',
'Core:AttributeIPAddress' => 'IP地址',
'Core:AttributeIPAddress+' => 'IP地址',
'Core:AttributeOQL' => 'OQL',
'Core:AttributeOQL+' => 'Object Query Langage expression~~',
'Core:AttributeEnum' => 'Enum~~',
'Core:AttributeEnum+' => 'List of predefined alphanumeric strings~~',
'Core:AttributeTemplateString' => '字符模板',
'Core:AttributeTemplateString+' => '包含占位符的字符串',
'Core:AttributeTemplateText' => '文字模板',
'Core:AttributeTemplateText+' => '包含占位符的文本',
'Core:AttributeTemplateHTML' => 'HTML模板',
'Core:AttributeTemplateHTML+' => 'HTML containing placeholders~~',
'Core:AttributeDateTime' => '日期/时间',
'Core:AttributeDateTime+' => 'Date and time (年-月-日 时:分:秒)',
'Core:AttributeDateTime?SmartSearch' => '
@@ -123,6 +156,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
<p>
如果不写具体时间,则默认00:00:00
</p>',
'Core:AttributeDate' => '日期',
'Core:AttributeDate+' => '日期 (年-月-日)',
'Core:AttributeDate?SmartSearch' => '
@@ -137,30 +171,43 @@ Operators:<br/>
<b>&lt;</b><em>日期</em><br/>
<b>[</b><em>日期</em>,<em>日期</em><b>]</b>
</p>',
'Core:AttributeDeadline' => '截止日期',
'Core:AttributeDeadline+' => '日期, 显示与当前的相对时间',
'Core:AttributeExternalKey' => '外键',
'Core:AttributeExternalKey+' => 'External (or foreign) key~~',
'Core:AttributeHierarchicalKey' => 'Hierarchical Key~~',
'Core:AttributeHierarchicalKey+' => 'External (or foreign) key to the parent~~',
'Core:AttributeExternalField' => '外部字段',
'Core:AttributeExternalField+' => 'Field mapped to an external key~~',
'Core:AttributeURL' => 'URL',
'Core:AttributeURL+' => 'Absolute or relative URL as a text string~~',
'Core:AttributeBlob' => 'Blob',
'Core:AttributeBlob+' => '任何二进制内容(文档)',
'Core:AttributeOneWayPassword' => '单向密码',
'Core:AttributeOneWayPassword+' => '单向加密(或哈希) 的密码',
'Core:AttributeTable' => '表',
'Core:AttributeTable+' => '带索引的二维数组',
'Core:AttributePropertySet' => '属性',
'Core:AttributePropertySet+' => 'List of untyped properties (name and value)~~',
'Core:AttributeFriendlyName' => '通用名称',
'Core:AttributeFriendlyName+' => 'Attribute created automatically ; the friendly name is computed after several attributes~~',
'Core:FriendlyName-Label' => '全称',
'Core:FriendlyName-Description' => '全称',
'Core:AttributeTag' => '标签',
'Core:AttributeTag+' => '标签',
'Core:Context=REST/JSON' => 'REST',
'Core:Context=Synchro' => '同步',
'Core:Context=Setup' => '安装向导',
@@ -201,10 +248,10 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
//
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:CMDBChangeOp' => '变更操作',
'Class:CMDBChangeOp+' => '变更操作跟踪',
'Class:CMDBChangeOp' => '变更操作跟踪',
'Class:CMDBChangeOp+' => '某人在某时某刻对某个对象的变更操作',
'Class:CMDBChangeOp/Attribute:change' => '变更',
'Class:CMDBChangeOp/Attribute:change+' => '变更',
'Class:CMDBChangeOp/Attribute:change+' => '',
'Class:CMDBChangeOp/Attribute:date' => '日期',
'Class:CMDBChangeOp/Attribute:date+' => '变更的日期和时间',
'Class:CMDBChangeOp/Attribute:userinfo' => '用户',
@@ -325,9 +372,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:EventNotification' => '通知事件',
'Class:EventNotification+' => 'Trace of a notification that has been sent~~',
'Class:EventNotification/Attribute:trigger_id' => '触发器',
'Class:EventNotification/Attribute:trigger_id+' => '用户账',
'Class:EventNotification/Attribute:trigger_id+' => '用户账',
'Class:EventNotification/Attribute:action_id' => '用户',
'Class:EventNotification/Attribute:action_id+' => '用户账',
'Class:EventNotification/Attribute:action_id+' => '用户账',
'Class:EventNotification/Attribute:object_id' => '对象id',
'Class:EventNotification/Attribute:object_id+' => 'object id (class defined by the trigger ?)~~',
));
@@ -408,8 +455,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:EventRestService/Attribute:version+' => '参数 \'版本\'',
'Class:EventRestService/Attribute:json_input' => '输入',
'Class:EventRestService/Attribute:json_input+' => 'Argument \'json_data\'~~',
'Class:EventRestService/Attribute:code' => '码',
'Class:EventRestService/Attribute:code+' => '返回码',
'Class:EventRestService/Attribute:code' => '码',
'Class:EventRestService/Attribute:code+' => '返回码',
'Class:EventRestService/Attribute:json_output' => '响应',
'Class:EventRestService/Attribute:json_output+' => 'HTTP 响应 (json)',
'Class:EventRestService/Attribute:provider' => '提供者',
@@ -660,7 +707,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:TriggerOnAttributeBlobDownload' => 'Trigger (on object\'s document download)~~',
'Class:TriggerOnAttributeBlobDownload+' => 'Trigger on object\'s document field download of [a child class of] the given class~~',
'Class:TriggerOnAttributeBlobDownload/Attribute:target_attcodes' => 'Target fields~~',
'Class:TriggerOnAttributeBlobDownload/Attribute:target_attcodes' => '目标字段',
'Class:TriggerOnAttributeBlobDownload/Attribute:target_attcodes+' => '',
));
@@ -671,7 +718,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:TriggerOnThresholdReached' => '触发器 (基于阈值)',
'Class:TriggerOnThresholdReached+' => '当达到某个阈值时触发',
'Class:TriggerOnThresholdReached/Attribute:stop_watch_code' => '秒表',
'Class:TriggerOnThresholdReached/Attribute:stop_watch_code' => '计时',
'Class:TriggerOnThresholdReached/Attribute:stop_watch_code+' => '',
'Class:TriggerOnThresholdReached/Attribute:threshold_index' => '阈值',
'Class:TriggerOnThresholdReached/Attribute:threshold_index+' => '',
@@ -768,6 +815,7 @@ The hyperlink is displayed in the tooltip appearing on the “Lock” symbol of
'Class:SynchroDataSource/Attribute:user_delete_policy/Value:administrators' => 'Administrators only',
'Class:SynchroDataSource/Attribute:user_delete_policy/Value:everybody' => 'Everybody allowed to delete such objects',
'Class:SynchroDataSource/Attribute:user_delete_policy/Value:nobody' => 'Nobody',
'SynchroDataSource:Description' => '描述',
'SynchroDataSource:Reconciliation' => 'Search &amp; reconciliation~~',
'SynchroDataSource:Deletion' => 'Deletion rules~~',
@@ -1005,8 +1053,9 @@ The hyperlink is displayed in the tooltip appearing on the “Lock” symbol of
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:TagSetFieldData' => '%2$s for class %1$s~~',
'Class:TagSetFieldData+' => '~~',
'Class:TagSetFieldData/Attribute:code' => '代码',
'Class:TagSetFieldData/Attribute:code+' => '内部代码. 必须至少包含3个数字或字母',
'Class:TagSetFieldData/Attribute:code' => '编码',
'Class:TagSetFieldData/Attribute:code+' => '内部编码. 必须至少包含3个数字或字母',
'Class:TagSetFieldData/Attribute:label' => '标签',
'Class:TagSetFieldData/Attribute:label+' => '显示的标签',
'Class:TagSetFieldData/Attribute:description' => '描述',
@@ -1014,6 +1063,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:TagSetFieldData/Attribute:finalclass' => 'Tag class~~~~',
'Class:TagSetFieldData/Attribute:obj_class' => 'Object class~~~~',
'Class:TagSetFieldData/Attribute:obj_attcode' => 'Field code~~~~',
'Core:TagSetFieldData:ErrorDeleteUsedTag' => '已使用的标签无法删除',
'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'Tags codes or labels must be unique~~',
'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'Tags code must contain between 3 and %1$d alphanumeric characters, starting with a letter.~~',
@@ -1139,6 +1189,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:ResourceSystemMenu' => 'Resource System Menu~~',
'Class:ResourceSystemMenu+' => '',
));
// Additional language entries not present in English dict
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'INTERNAL:JQuery-DatePicker:LangCode' => 'zh-CN'

File diff suppressed because it is too large Load Diff

View File

@@ -117,6 +117,7 @@ $(function()
locked_by_someone_else: 'locked_by_someone_else',
},
},
release_lock_promise_resolve: null, // N°4494 - Resolve callback of the Promise used for the action following the log entry send, which must be done only once the lock is released
// the constructor
_create: function () {
@@ -500,7 +501,7 @@ $(function()
{
// Hide all filters' options only if click wasn't on one of them
if(($(oEvent.target).closest(this.js_selectors.activity_filter_options_toggler).length === 0)
&& $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) {
&& $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) {
this._HideAllFiltersOptions();
}
},
@@ -947,9 +948,9 @@ $(function()
// Send request to server
$.post(
GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
oParams,
'json'
GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
oParams,
'json'
)
.fail(function (oXHR, sStatus, sErrorThrown) {
CombodoModal.OpenErrorModal(sErrorThrown);
@@ -972,12 +973,24 @@ $(function()
// For now, we don't hide the forms as the user may want to add something else
me.element.find(me.js_selectors.caselog_entry_form).trigger('clear_entry.caselog_entry_form.itop');
// Redirect to stimulus
// - Convert undefined, null and empty string to null
sStimulusCode = ((sStimulusCode ?? '') === '') ? null : sStimulusCode;
if (null !== sStimulusCode) {
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
if (me.options.lock_enabled) {
// Use a Promise to ensure that we redirect to the stimulus page ONLY when the lock is released, otherwise we might lock ourselves
const oPromise = new Promise(function(resolve) {
// Store the resolve callback so we can call it later from outside
me.release_lock_promise_resolve = resolve;
});
oPromise.then(function () {
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
// Resolve callback is reinitialized in case the redirection fails for any reason and we might need to retry
me.release_lock_promise_resolve = null;
});
} else {
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
}
}
})
.always(function () {
@@ -995,7 +1008,7 @@ $(function()
_IncreaseTabTogglerMessagesCounter: function(sCaseLogAttCode){
let oTabTogglerCounter = this._GetTabTogglerFromCaseLogAttCode(sCaseLogAttCode).find('[data-role="ibo-activity-panel--tab-title-messages-count"]');
let iNewCounterValue = parseInt(oTabTogglerCounter.attr('data-messages-count')) + 1;
oTabTogglerCounter.attr('data-messages-count', iNewCounterValue).text(iNewCounterValue);
},
/**
@@ -1143,11 +1156,10 @@ $(function()
else {
oParams.operation = 'check_lock_state';
}
$.post(
this.options.lock_endpoint,
oParams,
'json'
this.options.lock_endpoint,
oParams,
'json'
)
.fail(function (oXHR, sStatus, sErrorThrown) {
// In case of HTTP request failure (not lock request), put the details in the JS console
@@ -1196,6 +1208,9 @@ $(function()
// Tried to release our lock
else if ('release_lock' === oParams.operation) {
sNewLockStatus = me.enums.lock_status.unknown;
if (me.release_lock_promise_resolve !== null) {
me.release_lock_promise_resolve();
}
}
// Just checked if object was locked
@@ -1430,9 +1445,9 @@ $(function()
limit_results_length: bLimitResultsLength,
};
$.post(
this.options.load_more_entries_endpoint,
oParams,
'json'
this.options.load_more_entries_endpoint,
oParams,
'json'
)
.fail(function (oXHR, sStatus, sErroThrown) {
CombodoModal.OpenErrorModal(sErrorThrown);

View File

@@ -153,16 +153,15 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH
"dataType": "html"
})
.done(function (data) {
/* N°6152 - Hide during data loading and before open */
$('#dlg_'+me.id).hide();
$('#dlg_'+me.id).html(data);
window[sPromiseId].then(function () {
$('#dlg_'+me.id).dialog('open');
me.UpdateSizes(null, null);
if (me.bDoSearch)
{
if (me.bDoSearch) {
me.SearchObjectsToAdd();
}
else
{
} else {
$('#count_'+me.id).change(function () {
let c = this.value;
me.UpdateButtons(c);

View File

@@ -383,6 +383,7 @@ return array(
'Combodo\\iTop\\Core\\Email\\EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php',
'Combodo\\iTop\\Core\\Email\\iEMail' => $baseDir . '/sources/Core/Email/iEMail.php',
'Combodo\\iTop\\Core\\EventListener\\AttributeBlobEventListener' => $baseDir . '/sources/Core/EventListener/AttributeBlobEventListener.php',
'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => $baseDir . '/sources/Core/Kpi/KpiLogData.php',
'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => $baseDir . '/sources/Core/MetaModel/FriendlyNameType.php',
'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => $baseDir . '/sources/Core/MetaModel/HierarchicalKey.php',
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
@@ -461,6 +462,7 @@ return array(
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => $baseDir . '/sources/Service/Links/LinkSetModel.php',
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => $baseDir . '/sources/Service/Links/LinkSetRepository.php',
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => $baseDir . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
'Combodo\\iTop\\Service\\Module\\ModuleService' => $baseDir . '/sources/Service/Module/ModuleService.php',
'Combodo\\iTop\\Service\\Router\\Exception\\RouteNotFoundException' => $baseDir . '/sources/Service/Router/Exception/RouteNotFoundException.php',
'Combodo\\iTop\\Service\\Router\\Exception\\RouterException' => $baseDir . '/sources/Service/Router/Exception/RouterException.php',
'Combodo\\iTop\\Service\\Router\\Router' => $baseDir . '/sources/Service/Router/Router.php',
@@ -2952,6 +2954,7 @@ return array(
'iDBObjectURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php',
'iDisplay' => $baseDir . '/core/dbobject.class.php',
'iFieldRendererMappingsExtension' => $baseDir . '/application/applicationextension.inc.php',
'iKPILoggerExtension' => $baseDir . '/application/applicationextension.inc.php',
'iKeyboardShortcut' => $baseDir . '/sources/Application/UI/Hook/iKeyboardShortcut.php',
'iLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php',
'iLoginExtension' => $baseDir . '/application/applicationextension.inc.php',

View File

@@ -747,6 +747,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Core\\Email\\EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php',
'Combodo\\iTop\\Core\\Email\\iEMail' => __DIR__ . '/../..' . '/sources/Core/Email/iEMail.php',
'Combodo\\iTop\\Core\\EventListener\\AttributeBlobEventListener' => __DIR__ . '/../..' . '/sources/Core/EventListener/AttributeBlobEventListener.php',
'Combodo\\iTop\\Core\\Kpi\\KpiLogData' => __DIR__ . '/../..' . '/sources/Core/Kpi/KpiLogData.php',
'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => __DIR__ . '/../..' . '/sources/Core/MetaModel/FriendlyNameType.php',
'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => __DIR__ . '/../..' . '/sources/Core/MetaModel/HierarchicalKey.php',
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
@@ -825,6 +826,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetModel.php',
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetRepository.php',
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => __DIR__ . '/../..' . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
'Combodo\\iTop\\Service\\Module\\ModuleService' => __DIR__ . '/../..' . '/sources/Service/Module/ModuleService.php',
'Combodo\\iTop\\Service\\Router\\Exception\\RouteNotFoundException' => __DIR__ . '/../..' . '/sources/Service/Router/Exception/RouteNotFoundException.php',
'Combodo\\iTop\\Service\\Router\\Exception\\RouterException' => __DIR__ . '/../..' . '/sources/Service/Router/Exception/RouterException.php',
'Combodo\\iTop\\Service\\Router\\Router' => __DIR__ . '/../..' . '/sources/Service/Router/Router.php',
@@ -3316,6 +3318,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'iDBObjectURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php',
'iDisplay' => __DIR__ . '/../..' . '/core/dbobject.class.php',
'iFieldRendererMappingsExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iKPILoggerExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iKeyboardShortcut' => __DIR__ . '/../..' . '/sources/Application/UI/Hook/iKeyboardShortcut.php',
'iLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php',
'iLoginExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',

View File

@@ -1,13 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.webServer>
<security>
<requestFiltering>
<fileExtensions applyToWebDAV="false" allowUnlisted="false"></fileExtensions>
</requestFiltering>
<authorization>
<deny users="*" /> <!-- Denies all users -->
</authorization>
</security>
</system.webServer>
<system.web>
<authorization>
<deny users="*" /> <!-- Denies all users -->
</authorization>
</system.web>
</configuration>

View File

@@ -105,7 +105,7 @@ if ($oFilter != null)
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/UniversalSearch.php';
$aExtraParams['table_id'] = '1';
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
//$aExtraParams['class'] = $sClassName;
$aExtraParams['submit_on_load'] = false;
$oBlock->Display($oP, 0, $aExtraParams);
// Search results

View File

@@ -290,7 +290,6 @@ function DisplayEvents(WebPage $oPage, $sClass)
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) {
if (!MetaModel::IsAbstract($sChildClass)) {
$oObject = MetaModel::NewObject($sChildClass);
$aSources[] = $oObject->GetObjectUniqId();
break;
}
}
@@ -299,7 +298,6 @@ function DisplayEvents(WebPage $oPage, $sClass)
}
} else {
$oObject = MetaModel::NewObject($sClass);
$aSources[] = $oObject->GetObjectUniqId();
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sParentClass) {
$aSources[] = $sParentClass;
}
@@ -320,12 +318,19 @@ function DisplayEvents(WebPage $oPage, $sClass)
});
$aColumns = [
'event' => ['label' => Dict::S('UI:Schema:Events:Event')],
'listener' => ['label' => Dict::S('UI:Schema:Events:Listener')],
'callback' => ['label' => Dict::S('UI:Schema:Events:Listener')],
'priority' => ['label' => Dict::S('UI:Schema:Events:Rank')],
'module' => ['label' => Dict::S('UI:Schema:Events:Module')],
];
// Get the object listeners first
$aRows = [];
$oReflectionClass = new ReflectionClass($sClass);
if ($oReflectionClass->isInstantiable()) {
/** @var DBObject $oClass */
$oClass = new $sClass();
$aRows = $oClass->GetListeners();
}
foreach ($aListeners as $aListener) {
if (is_object($aListener['callback'][0])) {
$sListenerClass = $sClass;
@@ -343,7 +348,7 @@ function DisplayEvents(WebPage $oPage, $sClass)
}
$aRows[] = [
'event' => $aListener['event'],
'listener' => $sListener,
'callback' => $sListener,
'priority' => $aListener['priority'],
'module' => $aListener['module'],
];

View File

@@ -109,6 +109,7 @@ try
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/tagadmin.php';
$aExtraParams['table_id'] = '1';
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
$aExtraParams['submit_on_load'] = false;
$oBlock->Display($oP, 0, $aExtraParams);
// Search results

View File

@@ -358,6 +358,7 @@ class DBBackup
$sPortOption = self::GetMysqliCliSingleOption('port', $this->iDBPort);
$sTlsOptions = self::GetMysqlCliTlsOptions($this->oConfig);
$sProtocolOption = self::GetMysqlCliTransportOption($this->sDBHost);
$sMysqlVersion = CMDBSource::GetDBVersion();
$bIsMysqlSupportUtf8mb4 = (version_compare($sMysqlVersion, self::MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS) === -1);
@@ -378,8 +379,8 @@ EOF;
// Note: opt implicitely sets lock-tables... which cancels the benefit of single-transaction!
// skip-lock-tables compensates and allows for writes during a backup
$sCommand = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption --user=$sUser $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables 2>&1";
$sCommandDisplay = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption --user=xxxxx $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables";
$sCommand = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption $sProtocolOption --user=$sUser $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables 2>&1";
$sCommandDisplay = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortOption $sProtocolOption --user=xxxxx $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables";
// Now run the command for real
$this->LogInfo("backup: generate data file with command: $sCommandDisplay");
@@ -467,8 +468,8 @@ EOF;
if ($oMysqli->connect_errno)
{
$sHost = is_null($this->iDBPort) ? $this->sDBHost : $this->sDBHost.' on port '.$this->iDBPort;
throw new BackupException("Cannot connect to the MySQL server '$sHost' (".$oMysqli->connect_errno.") ".$oMysqli->connect_error);
}
throw new MySQLException('Could not connect to the DB server '.$oMysqli->connect_errno.' (mysql errno: '.$oMysqli->connect_error, array('host' => $sHost, 'user' => $sUser));
}
if (!$oMysqli->select_db($this->sDBName))
{
throw new BackupException("The database '$this->sDBName' does not seem to exist");
@@ -576,6 +577,28 @@ EOF;
return ' --'.$sCliArgName.'='.self::EscapeShellArg($sData);
}
/**
* Define if we should force a transport option
*
* @param string $sHost
*
* @return string .
* @since 2.7.9 3.0.4 3.1.1 N°6123
*/
public static function GetMysqlCliTransportOption(string $sHost)
{
$sTransportOptions = '';
/** N°6123 As we're using a --port option, if we use localhost as host,
* MariaDB > 10.6 will implicitly change its protocol from socket to tcp and throw a warning **/
if($sHost === 'localhost'){
$sTransportOptions = '--protocol=tcp';
}
return $sTransportOptions;
}
/**
* @return string the command to launch mysqldump (without its params)
*/

View File

@@ -1449,17 +1449,12 @@ EOF
}
$sMethods .= "\n $sCallbackFct\n\n";
}
if (strpos($sCallback, '::') === false) {
$sEventListener = '[$this, \''.$sCallback.'\']';
} else {
$sEventListener = "'$sCallback'";
}
$sListenerRank = (float)($oListener->GetChildText('rank', '0'));
$sEvents .= <<<PHP
// listenerId = $sListenerId
Combodo\iTop\Service\Events\EventService::RegisterListener("$sEventName", $sEventListener, \$this->m_sObjectUniqId, [], null, $sListenerRank, '$sModuleRelativeDir');
\$this->RegisterCRUDListener("$sEventName", '$sCallback', $sListenerRank, '$sModuleRelativeDir');
PHP;
}
}
@@ -2060,7 +2055,6 @@ EOF
$this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir);
$this->CompileCommonProperty('edit_mode', $oField, $aParameters, $sModuleRelativeDir);
$this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir);
$this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir);
$this->CompileCommonProperty('with_php_constraint', $oField, $aParameters, $sModuleRelativeDir, false);
$aParameters['depends_on'] = $sDependencies;
} elseif ($sAttType == 'AttributeLinkedSet') {

View File

@@ -892,7 +892,10 @@ class iTopDesignFormat
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field/values/value");
foreach ($oNodeList as $oNode) {
$sCode = $oNode->textContent;
$oNode->textContent = '';
// N°6562 textContent is readonly, see https://www.php.net/manual/en/class.domnode.php#95545
// $oNode->textContent = '';
// N°6562 to update text node content we must use the node methods !
$oNode->removeChild($oNode->firstChild);
$oCodeNode = $oNode->ownerDocument->createElement("code", $sCode);
$oNode->appendChild($oCodeNode);
}
@@ -982,7 +985,14 @@ class iTopDesignFormat
if ($oStyleNode) {
$this->DeleteNode($oStyleNode);
}
$oNode->textContent = $sCode;
// N°6562 textContent is readonly, see https://www.php.net/manual/en/class.domnode.php#95545
// $oNode->textContent = $sCode;
// N°6562 to update text node content we must use the node methods !
// we are using DOMDocument::createTextNode instead of new DOMText because elements created using the constructor are read only
// see https://www.php.net/manual/en/domelement.construct.php
$oTextContentNode = $this->oDocument->createTextNode($sCode);
$oNode->appendChild($oTextContentNode);
}
}
// - Style

View File

@@ -9,6 +9,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\Dashlet;
use Combodo\iTop\Application\UI\Base\tJSRefreshCallback;
use utils;
class DashletBadge extends DashletContainer
{
@@ -29,6 +30,11 @@ class DashletBadge extends DashletContainer
protected $iCount;
/** @var string */
protected $sClassLabel;
/**
* @var string
* @since 3.1.1 3.2.0
*/
protected $sClassDescription;
/** @var string */
protected $sCreateActionUrl;
@@ -62,6 +68,7 @@ class DashletBadge extends DashletContainer
$this->sCreateActionUrl = $sCreateActionUrl;
$this->sCreateActionLabel = $sCreateActionLabel;
$this->aRefreshParams = $aRefreshParams;
$this->sClassDescription = '';
}
@@ -185,6 +192,37 @@ class DashletBadge extends DashletContainer
return $this;
}
/**
* @return string
* @since 3.1.1 3.2.0
*/
public function GetClassDescription(): string
{
return $this->sClassDescription;
}
/**
* @param string $sClassDescription
*
* @return DashletBadge
* @since 3.1.1 3.2.0
*/
public function SetClassDescription(string $sClassDescription)
{
$this->sClassDescription = $sClassDescription;
return $this;
}
/**
* @return bool
* @since 3.1.1
*/
public function HasClassDescription(): bool
{
return utils::IsNotNullOrEmptyString($this->sClassDescription);
}
public function GetJSRefresh(): string
{
return "$('#".$this->sId."').block();

View File

@@ -55,11 +55,13 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
public const UI_BLOCK_CLASS_NAME = DataTable::class;
/**
* If inside an iTop object, you can use {@see cmdbAbstractObject::DisplaySet()}
*
* @api
* @param \WebPage $oPage
* @param string $sListId
* @param \DBObjectSet $oSet
* @param array $aExtraParams
* @param array $aExtraParams See possible values in {@see self::RenderDataTable()}
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
* @throws \ApplicationException
@@ -84,11 +86,13 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
}
/**
* If inside an iTop object, you can use {@see cmdbAbstractObject::DisplaySet()}
*
* @api
* @param \WebPage $oPage
* @param string $sListId
* @param DBObjectSet $oSet
* @param array $aExtraParams
* @param array $aExtraParams See possible values in {@see self::RenderDataTable()}
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
* @throws \ArchivedObjectException
@@ -117,7 +121,12 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
* @param \WebPage $oPage
* @param string $sListId
* @param \DBObjectSet $oSet
* @param array $aExtraParams
* @param array $aExtraParams example keys used in this method :
* - toolkit_menu = boolean
* - surround_with_panel = boolean : if true adds the standard class panel (icon, title, ...)
* - panel_title = string
* - panel_title_is_html = boolean
* - panel_icon = string : class icon (for example from {@see MetaModel::GetClassIcon()})
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
* @throws \ArchivedObjectException

View File

@@ -70,6 +70,9 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
/** @var AttributeLinkedSet $oAttDef attribute link set */
protected AttributeLinkedSet $oAttDef;
/** @var bool $bIsAttEditable Is attribute editable */
protected bool $bIsAttEditable;
/** @var string $sTargetClass links target classname */
protected string $sTargetClass;
@@ -119,11 +122,12 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
private function Init()
{
$this->sTargetClass = $this->GetTargetClass();
$this->InitIsAttEditable();
// User rights
$this->bIsAllowCreate = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES;
$this->bIsAllowModify = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES;
$this->bIsAllowDelete = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES;
$this->bIsAllowCreate = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES;
$this->bIsAllowModify = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES;
$this->bIsAllowDelete = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES;
}
@@ -196,6 +200,26 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
$this->AddSubBlock($oBlock->GetRenderContent($oPage, $this->GetExtraParam(), $this->sTableId));
}
/**
* @return void
* @throws \CoreException
*/
private function InitIsAttEditable(): void
{
$iFlags = 0;
if ($this->oDbObject->IsNew())
{
$iFlags = $this->oDbObject->GetInitialStateAttributeFlags($this->sAttCode);
}
else
{
$iFlags = $this->oDbObject->GetAttributeFlags($this->sAttCode);
}
$this->bIsAttEditable = !($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE | OPT_ATT_HIDDEN));
}
/**
* GetTableId.
*

View File

@@ -733,6 +733,7 @@ class AjaxRenderController
} else {
$oFullSetFilter = new DBObjectSearch($sRemoteClass);
}
$oFullSetFilter->SetShowObsoleteData(utils::ShowObsoleteData());
$oWidget->DoAddObjects($oPage, $iMaxAddedId, $oFullSetFilter, $oObj);
$oKPI->ComputeAndReport('Data write');
}

View File

@@ -74,6 +74,7 @@ class OAuthClientProviderFactory
* @return AccessTokenInterface
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException
*/
public static function GetAccessTokenFromCode(OAuthClient $oOAuthClient, $sCode)
{
@@ -109,7 +110,7 @@ class OAuthClientProviderFactory
/**
* @param \DBObject $oOAuthClient
*
* @return mixed
* @return OAuthClientProviderAbstract
* @throws \ArchivedObjectException
* @throws \CoreException
*/

View File

@@ -0,0 +1,125 @@
<?php
/**
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core\Kpi;
class KpiLogData
{
const TYPE_REPORT = 'report';
const TYPE_STATS = 'stats';
const TYPE_REQUEST = 'request';
/** @var string */
public $sType;
/** @var string */
public $sOperation;
/** @var string */
public $sArguments;
/** @var float */
public $fStartTime;
/** @var float */
public $fStopTime;
/** @var string */
public $sExtension;
/** @var int */
public $iInitialMemory;
/** @var int */
public $iCurrentMemory;
/** @var int */
public $iPeakMemory;
/** @var array */
public $aData;
/**
* @param string $sType
* @param string $sOperation
* @param string $sArguments
* @param float $fStartTime
* @param float $fStopTime
* @param string $sExtension
* @param int $iInitialMemory
* @param int $iCurrentMemory
* @param array $aData
*/
public function __construct($sType, $sOperation, $sArguments, $fStartTime, $fStopTime, $sExtension, $iInitialMemory = 0, $iCurrentMemory = 0, $iPeakMemory = 0, $aData = [])
{
$this->sType = $sType;
$this->sOperation = $sOperation;
$this->sArguments = @iconv(mb_detect_encoding($sArguments, mb_detect_order(), true), 'UTF-8', $sArguments);
$this->fStartTime = $fStartTime;
$this->fStopTime = $fStopTime;
$this->sExtension = $sExtension;
$this->iInitialMemory = $iInitialMemory;
$this->iCurrentMemory = $iCurrentMemory;
$this->iPeakMemory = $iPeakMemory;
$this->aData = $aData;
}
/**
* Return the CSV Header
*
* @return string
*/
public static function GetCSVHeader()
{
return "Type,Operation,Arguments,StartTime,StopTime,Duration,Extension,InitialMemory,CurrentMemory,PeakMemory";
}
/**
* Return the CSV line for the values
* @return string
*/
public function GetCSV()
{
$fDuration = sprintf('%01.4f', $this->fStopTime - $this->fStartTime);
$sType = $this->RemoveQuotes($this->sType);
$sOperation = $this->RemoveQuotes($this->sOperation);
$sArguments = $this->RemoveQuotes($this->sArguments);
$sExtension = $this->RemoveQuotes($this->sExtension);
return "\"$sType\",\"$sOperation\",\"$sArguments\",$this->fStartTime,$this->fStopTime,$fDuration,\"$sExtension\",$this->iInitialMemory,$this->iCurrentMemory,$this->iPeakMemory";
}
private function RemoveQuotes(string $sEntry): string
{
return str_replace('"', "'", $sEntry);
}
/**
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oOther
*
* @return float
*/
public function Compare(KpiLogData $oOther): float
{
if ($oOther->fStartTime > $this->fStartTime) {
return -1;
}
return 1;
}
public function Contains(KpiLogData $oOther): bool
{
if ($oOther->fStartTime < $this->fStartTime) {
return false;
}
if ($oOther->fStartTime > $this->fStopTime) {
return false;
}
return true;
}
public function __toString()
{
return "$this->sType:$this->sOperation:$this->sArguments";
}
public function GetUUID(): string
{
return sha1($this->__toString());
}
}

View File

@@ -320,12 +320,19 @@ EOF
if ($this->oField->GetCurrentValue() !== null && $this->oField->GetCurrentValue() !== 0 && $this->oField->GetCurrentValue() !== '')
{
// Note : AllowAllData set to true here instead of checking scope's flag because we are displaying a value that has been set and validated
$oFieldValue = MetaModel::GetObject($sFieldValueClass, $this->oField->GetCurrentValue(), true, true);
$oFieldValue = MetaModel::GetObjectWithArchive($sFieldValueClass, $this->oField->GetCurrentValue(), true, true);
$sFieldHtmlValue = $oFieldValue->GetName();
$sFieldUrl = ApplicationContext::MakeObjectUrl($sFieldValueClass, $this->oField->GetCurrentValue());
if(!empty($sFieldUrl))
if($oFieldValue->IsArchived())
{
$sFieldHtmlValue = '<a href="'.$sFieldUrl.'" data-toggle="itop-portal-modal">'.$sFieldHtmlValue.'</a>';
$sFieldHtmlValue = '<span class="text_decoration"><span class="fas fa-archive"></span></span>' . $sFieldHtmlValue;
}
else
{
$sFieldUrl = ApplicationContext::MakeObjectUrl($sFieldValueClass, $this->oField->GetCurrentValue());
if (!empty($sFieldUrl))
{
$sFieldHtmlValue = '<a href="' . $sFieldUrl . '" data-toggle="itop-portal-modal">' . $sFieldHtmlValue . '</a>';
}
}
}
else

View File

@@ -10,6 +10,7 @@ use Closure;
use Combodo\iTop\Service\Events\Description\EventDescription;
use ContextTag;
use CoreException;
use DBObject;
use Exception;
use ExecutionKPI;
use ReflectionClass;
@@ -53,6 +54,12 @@ final class EventService
/**
* Register a callback for a specific event
*
* **Warning** : be ultra careful on memory footprint ! each callback will be saved in {@see aEventListeners}, and a callback is
* made of the whole object instance and the method name ({@link https://www.php.net/manual/en/language.types.callable.php}).
* For example to register on DBObject instances, you should better use {@see DBObject::RegisterCRUDListener()}
*
* @uses aEventListeners
*
* @api
* @param string $sEvent corresponding event
* @param callable $callback The callback to call
@@ -61,8 +68,12 @@ final class EventService
* @param array|string|null $context context filter
* @param float $fPriority optional priority for callback order
*
* @return string Id of the registration
* @return string registration identifier
*
* @see DBObject::RegisterCRUDListener() to register in DBObject instances instead, to reduce memory footprint (callback saving)
*
* @since 3.1.0 method creation
* @since 3.1.0-3 3.1.1 3.2.0 N°6716 PHPDoc change to warn on memory footprint, and {@see DBObject::RegisterCRUDListener()} alternative
*/
public static function RegisterListener(string $sEvent, callable $callback, $sEventSource = null, array $aCallbackData = [], $context = null, float $fPriority = 0.0, $sModuleId = ''): string
{

View File

@@ -0,0 +1,214 @@
<?php
/**
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service\Module;
use MetaModel;
use ReflectionClass;
use ReflectionMethod;
use utils;
class ModuleService
{
/** @var ModuleService */
private static $oInstance;
private function __construct()
{
}
public static function GetInstance(): ModuleService
{
if (!isset(static::$oInstance)) {
static::$oInstance = new ModuleService();
}
return static::$oInstance;
}
/**
* Get a "signature" of the method of an extension in the form of: "[module-name] class::method()"
*
* @param object|string $object Object or class
* @param string $sMethod
*
* @return string
* @throws \ReflectionException
*/
public function GetModuleMethodSignature($object, string $sMethod): string
{
$sSignature = '';
$oReflectionMethod = new ReflectionMethod($object, $sMethod);
$oReflectionClass = $oReflectionMethod->getDeclaringClass();
$sExtension = $this->GetModuleNameFromObject($oReflectionClass->getName());
if (strlen($sExtension) !== 0) {
$sSignature .= '['.$sExtension.'] ';
}
$sSignature .= $oReflectionClass->getShortName().'::'.$sMethod.'()';
return $sSignature;
}
/**
* Get the module name from an object or class
*
* @param object|string $object
*
* @return string
* @throws \ReflectionException
*/
public function GetModuleNameFromObject($object): string
{
$oReflectionClass = new ReflectionClass($object);
$sPath = str_replace('\\', '/', $oReflectionClass->getFileName());
$sPattern = str_replace('\\', '/', '@'.APPROOT.'env-'.utils::GetCurrentEnvironment()).'/(?<ext>.+)/@U';
if (preg_match($sPattern, $sPath, $aMatches) !== false) {
if (isset($aMatches['ext'])) {
return $aMatches['ext'];
}
}
return '';
}
/**
* **Warning** : returned result can be invalid as we're using backtrace to find the module dir name
*
* @param int $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
*
* @return string the relative (to MODULESROOT) path of the root directory of the module containing the file where the call to
* this function is made
* or an empty string if no such module is found (or not called within a module file)
*
* @uses \debug_backtrace()
*/
public function GetCurrentModuleDir(int $iCallDepth): string
{
$sCurrentModuleDir = '';
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
foreach(GetModulesInfo() as $sModuleName => $aInfo)
{
if ($aInfo['root_dir'] !== '')
{
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
{
$sCurrentModuleDir = basename($sRootDir);
break;
}
}
}
return $sCurrentModuleDir;
}
/**
* **Warning** : as this method uses {@see GetCurrentModuleDir} it produces hazardous results.
* You should better uses directly {@see GetAbsoluteUrlModulesRoot} and add the module dir name yourself ! See N°4573
*
* @return string the base URL for all files in the current module from which this method is called
* or an empty string if no such module is found (or not called within a module file)
* @throws \Exception
*
* @uses GetCurrentModuleDir
*/
public function GetCurrentModuleUrl(int $iCallDepth = 0): string
{
$sDir = $this->GetCurrentModuleDir(1 + $iCallDepth);
if ( $sDir !== '')
{
return utils::GetAbsoluteUrlModulesRoot().'/'.$sDir;
}
return '';
}
/**
* @param string $sProperty The name of the property to retrieve
* @param mixed $defaultValue
*
* @return mixed the value of a given setting for the current module
*/
public function GetCurrentModuleSetting(string $sProperty, $defaultValue = null)
{
$sModuleName = $this->GetCurrentModuleName(1);
return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultValue);
}
/**
* @param string $sModuleName
*
* @return string|NULL compiled version of a given module, as it was seen by the compiler
*/
public function GetCompiledModuleVersion(string $sModuleName): ?string
{
$aModulesInfo = GetModulesInfo();
if (array_key_exists($sModuleName, $aModulesInfo))
{
return $aModulesInfo[$sModuleName]['version'];
}
return null;
}
/**
* Returns the name of the module containing the file where the call to this function is made
* or an empty string if no such module is found (or not called within a module file)
*
* @param int $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
*
* @return string
*/
public function GetCurrentModuleName(int $iCallDepth = 0): string
{
$sCurrentModuleName = '';
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
return $this->GetModuleNameFromPath($sCallerFile);
}
private function GetModuleNameFromPath($sPath)
{
foreach (GetModulesInfo() as $sModuleName => $aInfo) {
if ($aInfo['root_dir'] !== '') {
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
if (substr($sPath, 0, strlen($sRootDir)) === $sRootDir) {
return $sModuleName;
}
}
}
return '';
}
/**
* Get the extension code from the call stack.
* Scan the call stack until a module is found.
*
* @param int $iLevelsToIgnore
*
* @return string module name
*/
public function GetModuleNameFromCallStack(int $iLevelsToIgnore = 0): string
{
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$aCallStack = array_slice($aCallStack, $iLevelsToIgnore);
foreach ($aCallStack as $aCallInfo) {
$sFile = realpath(empty($aCallInfo['file']) ? '' : $aCallInfo['file']);
$sModuleName = $this->GetModuleNameFromPath($sFile);
if (strlen($sModuleName) > 0) {
return $sModuleName;
}
}
return '';
}
}

View File

@@ -2162,7 +2162,9 @@ class SynchroReplica extends DBObject implements iDisplay
// it will be deleted by the mean of a trigger too
protected function DBDeleteSingleObject()
{
$oKPI = new ExecutionKPI();
$this->OnDelete();
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
if (!MetaModel::DBIsReadOnly())
{

View File

@@ -3,7 +3,16 @@
{% apply spaceless %}
<div class="ibo-dashlet-badge--body{% if oUIBlock.IsHidden() %} ibo-is-hidden{% endif %}" id="{{ oUIBlock.GetId() }}"
data-role="ibo-dashlet-badge--body"
data-tooltip-content="{{ oUIBlock.GetClassLabel() }}">
{% if oUIBlock.HasClassDescription() %}
{# Display both class name and description as the name could be truncated if too long #}
data-tooltip-content="{{ '<div class="ibo-dashlet-badge--body--tooltip-title">'|escape }}{{ oUIBlock.GetClassLabel() }}{{ '</div><div class="ibo-dashlet-badge--body--tooltip-description">'|escape }}{{ oUIBlock.GetClassDescription() }}{{ '</div>'|escape }}"
data-tooltip-html-enabled="true"
{% else %}
{# Display only class name as it could be truncated if too long #}
data-tooltip-content="{{ oUIBlock.GetClassLabel() }}"
{% endif %}
{# Delay display to avoid having all tooltips appearing when mouse is just passing through the tabs #}
data-tooltip-show-delay="300">
<div class="ibo-dashlet-badge--icon-container">
{# Mind the empty "alt" attribute https://www.w3.org/WAI/tutorials/images/decorative/ #}
<img class="ibo-dashlet-badge--icon" src="{{ oUIBlock.GetClassIconUrl() }}" alt="">

View File

@@ -23,16 +23,16 @@
{% if aAction.confirmation is defined %}
// Prepare confirmation title
let sTitle = `{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s|raw }}`;
let sTitle = '{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s }}';
{% if aAction.confirmation.title is defined %}
sTitle = `{{ aAction.confirmation.title|dict_s|raw }}`;
sTitle = '{{ aAction.confirmation.title|dict_s }}';
{% endif %}
sTitle = sTitle.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);
// Prepare confirmation message
let sMessage = `{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s|raw }}`;
let sMessage = '{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s }}';
{% if aAction.confirmation.message is defined %}
sMessage = `{{ aAction.confirmation.message|dict_s|raw }}`;
sMessage = '{{ aAction.confirmation.message|dict_s }}';
{% endif %}
sMessage = sMessage.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);

View File

@@ -1,3 +1,8 @@
[infra]
php_version=7.4-apache
; N°6629 perf bug on some tests on mariadb for now, so specifying MySQL
db_version=5.7
[itop]
itop_setup=tests/setup_params/default-params.xml
itop_backup=tests/backups/backup-itop.tar.gz

View File

@@ -1,7 +1,122 @@
# PHP unitary tests
## Where should I add my test?
- Covers an iTop PHP class or method?
- Most likely in "unitary-tests".
- Covers the consistency of some data through the app?
- Most likely in "integration-tests".
- Most likely in "integration-tests".
## How do I make sure that my tests are efficient?
### Derive from the relevant test class
Whenever possible keep it the most simple, hence you should first
attempt to derive from `TestCase`.
Then, you might need to derive from `ItopTestCase`.
Finally, as a last resort, you will use `ItopDataTestCase`.
### Determine the most relevant isolation configuration
Should you have opted for `ItopDataTestCase`, then you will have to follow these steps:
1) Build you test class until it is successfull, without process isolation.
2) Run the whole test suite [unitary-tests](unitary-tests)
3) If a false-positive appears, then you will start troubleshooting. One advise: be positive!
### Leave the place clean
To check your code against polluting coding patterns, run the test [integration-tests/DetectStaticPollutionTest.php](integration-tests/DetectStaticPollutionTest.php)
It will tell you if something is wrong, either in your code, or anywhere else in the tests.
Fortunately, it will give you an alternative.
Detected patterns:
* ContextTag::addTag()
* EventService::RegisterListener()
* Dict::Add()
By the way, some patterns do not pollute, because they are handled by the test framework:
* Configuration : automatically reset after test class execution
* UserRights : a logoff is performed after each test execution
* Dict::SetUserLanguage: the user language is reset after each test execution
See also `@beforeClass` and `@afterClass` to handle cleanup.
If you can't, then ok you will have to isolate it!
## Tips
### Memory limit
As the tests are run in the same process, memory usage
may become an issue as soon as tests are all executed at once.
Fix that in the XML configuration in the PHP section
```xml
<ini name="memory_limit" value="512M"/>
```
### Understand tests interactions
With PHPStorm, select two tests, right click to get the context menu, then `run`.
You will have both tests executed and you will be able to figure out if the first one has an impact on the second one.
### About process isolation
#### Isolation with PHPUnit
By default, tests are run in a single process launched by PHPUnit.
If process isolation is configured for some tests, then those tests
will be executed in a separate process. The main process will
continue executing non isolated tests.
#### Cost of isolation
The cost of isolating a very basic `TestCase` is approximately 4 ms.
The cost of isolating an `ItopDataTestCase` is approximately 800 ms.
### Isolation within iTop
#### At the test level (preferred)
Add annotation `@runInSeparateProcess`
Each and every test case will run in a separate
process.
Note : before N°6658 (3.0.4 / 3.1.1 / 3.2.0) we were also adding the `@backupGlobals disabled`
and `@preserveGlobalState disabled` annotations. This is no longer necessary as the first has this default value
already, and the second one is now set in iTopTestCase as a PHP class attribute.
#### At the test class level
Add annotation `@runTestsInSeparateProcesses`
Each and every test case in the class will run in a separate
process.
#### Globally (never do that)
Set it into [phpunit.xml.dist](phpunit.xml.dist)
### Further enhancements
The annotation [`@runClassInSeparateProcess`](https://docs.phpunit.de/en/10.0/attributes.html?highlight=runclassinseparateprocess#runclassinseparateprocess) is supposed to do the perfect job, but it is buggy [(See Issue 5230)](https://github.com/sebastianbergmann/phpunit/issues/5230) and it has
the exact same effect as `@runTestsInSeparateProcesses`.
Note : this option is documented only in the [attributes part of the documentation](https://docs.phpunit.de/en/10.0/attributes.html).
### Traps
#### When it is a matter of stars
```php
/*
* @runTestsInSeparateProcesses
```
This won't work because the comment MUST start with `/**` (two stars) to be considerer by PHPUnit.
#### SetupBeforeClass called more often than expected
`setupBeforeClass` is called once for the class **in a given process**.
Therefore, if the tests are isolated, then `setupBeforeClass` will be called as often as `setUp`.
This has been proven with [`runClassInSeparateProcessTest.php`](experiments/runClassInSeparateProcessTest.php)

View File

@@ -2,5 +2,12 @@
"require-dev": {
"phpunit/phpunit" : "^9",
"sempro/phpunit-pretty-print": "^1.4"
},
"autoload": {
"psr-4": {
"Combodo\\iTop\\Test\\UnitTest\\": "src/BaseTestCase/",
"Combodo\\iTop\\Test\\UnitTest\\Hook\\": "src/Hook/",
"Combodo\\iTop\\Test\\UnitTest\\Service\\": "src/Service/"
}
}
}

View File

@@ -0,0 +1 @@
This directory aims at providing experimental proof of the mechanics of PHPUnit

View File

@@ -0,0 +1,64 @@
<?php
namespace Combodo\iTop\Test\UnitTest;
/**
* Shows that
* 1) the option runClassInSeparateProcess is equivalent to runTestsInSeparateProcesses
* 2) setUpBeforeClass is called within each spawned process (the main one, then in eventuel subprocesses)
* 3) setUp behaves as expected, i.e. called one within the same process as the test itself
*
* @preserveGlobalState disabled
* @runClassInSeparateProcess
*/
class runClassInSeparateProcessTest extends ItopDataTestCase
{
static public function setUpBeforeClass(): void
{
parent::setUpBeforeClass(); // TODO: Change the autogenerated stub
file_put_contents(
dirname(__FILE__).'/pid.txt',
getmypid().';'.static::class.';'.__METHOD__."\n",
FILE_APPEND);
}
protected function LogPid()
{
file_put_contents(
dirname(__FILE__).'/pid.txt',
getmypid().';'.static::class.';'.$this->getName()."\n",
FILE_APPEND);
}
function testA()
{
$this->LogPid();
static::assertTrue(true);
}
function testB()
{
$this->LogPid();
static::assertTrue(true);
}
/**
* @dataProvider CProvider
*/
function testC($i)
{
$this->LogPid();
static::assertTrue(true);
}
function CProvider()
{
return [
[1],
[1],
[1],
[1],
];
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Combodo\iTop\Test\UnitTest;
use PHPUnit\Framework\TestCase;
/**
* Shows that tearDown is called after a fatal error within a test
*/
class tearDownAfterFailureTest extends TestCase
{
static $bIsCorrectlyInitialized = true;
protected function tearDown(): void
{
parent::tearDown();
static::$bIsCorrectlyInitialized = true;
}
function testIsInitializedAndChangeIt()
{
static::assertTrue(static::$bIsCorrectlyInitialized);
static::$bIsCorrectlyInitialized = false;
$this->expectException('Exception');
throw new \Exception('hello');
}
function testIsStillInitialized()
{
static::assertTrue(static::$bIsCorrectlyInitialized);
}
function testFailingDueToUnexpectedException()
{
static::$bIsCorrectlyInitialized = false;
This_Is_Not_A_Function_And_Causes_A_Fatal_Error();
}
function testIsStillInitializedAnyway()
{
static::assertTrue(static::$bIsCorrectlyInitialized);
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* Copyright (C) 2013-2023 Combodo SARL
* This file is part of iTop.
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Test\UnitTest\Integration;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use Dict;
use const APPROOT;
/**
* As {@see DictionariesConsistencyTest}, we are testing dict files, but the ones that are compiled, so we cannot be in the beforeSetup group !
*/
class CompiledDictionariesConsistencyTest extends ItopTestCase
{
/**
* make sure N°5305 dictionary changes (CSV import ergonomy) are still here and UI remains unbroken for any lang
*
* One of the things checked is the number of parameters in the dict value. This is for now crashing the app (N°5491)
* and we have multiple inconsistencies in our existing dict files... So it is complicated to have a generic test for all files !
* At least we are protecting those new entries...
*/
public function testImportCsvMessageStillOk()
{
$aFailedLabels = [];
$aLabelsToTest = [
'UI:CSVReport-Value-SetIssue' => [],
'UI:CSVReport-Value-ChangeIssue' => ['arg1'],
'UI:CSVReport-Value-NoMatch' => ['arg1'],
'UI:CSVReport-Value-NoMatch-PossibleValues' => ['arg1', 'arg2'],
'UI:CSVReport-Value-NoMatch-NoObject' => ['arg1'],
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => ['arg1'],
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => ['arg1'],
];
$sCompiledLanguagesFilePath = APPROOT . 'env-' . \utils::GetCurrentEnvironment() . '/dictionaries/languages.php';
$this->assertFileExists($sCompiledLanguagesFilePath, 'We must have an existing compiled language.php file in the current env !');
require_once($sCompiledLanguagesFilePath);
$this->assertNotEmpty(Dict::GetLanguages(), 'the languages.php file exists but didn\'t load any language');
foreach (glob(APPROOT . 'env-' . \utils::GetCurrentEnvironment() . '/dictionaries/*.dict.php') as $sDictFile) {
if (preg_match('/.*\\/(.*).dict.php/', $sDictFile, $aMatches)) {
$sLangCode = $aMatches[1];
$sLanguageCode = strtoupper(str_replace('-', ' ', $sLangCode));
Dict::SetUserLanguage($sLanguageCode);
foreach ($aLabelsToTest as $sLabelKey => $aLabelArgs) {
echo "Testing $sDictFile, label $sLabelKey with " . \var_export($aLabelArgs, true) . "\n";
try {
$sLabelValue = Dict::Format($sLabelKey, ...$aLabelArgs);
//$this->debug($sLabelValue);
} catch (\ValueError $e) {
$aFailedLabels[] = $sLabelKey;
$this->debug([
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'label_name' => $sLabelKey,
'label_args' => $aLabelArgs,
]);
}
}
$this->assertEquals([], $aFailedLabels, "$sDictFile : test fail for lang $sLangCode and labels (" . implode(", ", $aFailedLabels) . ')');
}
}
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Combodo\iTop\Test\UnitTest;
use PHPUnit\Framework\TestCase;
use GlobIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RecursiveRegexIterator;
use RegexIterator;
/**
* Performs code static analysis to detect patterns that will change the values of static data and therefor could affect other tests while running them in a single process
*
* @runClassInSeparateProcess
* @preserveGlobalState disabled
*/
class detectStaticPollutionTest extends TestCase
{
protected function FindMatches($sFile, $sFileContents, $sRegexp)
{
$aRes = [];
foreach (explode("\n", $sFileContents) as $iLine => $sLine) {
if (preg_match_all($sRegexp, $sLine, $aMatches, PREG_PATTERN_ORDER)) {
$sLine = $iLine + 1;
$aRes[] = "$sFile:$sLine";
}
}
return $aRes;
}
/**
* @dataProvider PollutingPatterns
* @param $sPattern
*
* @return void
*/
function testDetectPolluters($sPattern, $sFix)
{
$sScannedDir = dirname(__FILE__).'/../unitary-tests';
$aPolluters = [];
$oDirectory = new RecursiveDirectoryIterator($sScannedDir);
$Iterator = new RecursiveIteratorIterator($oDirectory);
foreach (new RegexIterator($Iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH) as $aMatch) {
$sFile = $aMatch[0];
if(is_file($sFile)) {
$sFileContents = file_get_contents($sFile);
if (preg_match_all($sPattern, $sFileContents, $keys, PREG_PATTERN_ORDER)) {
$aPolluters = array_merge($aPolluters, $this->FindMatches($sFile, $sFileContents, $sPattern));
}
}
}
$iPolluters = count($aPolluters);
static::assertTrue($iPolluters === 0, "Found polluter(s) for pattern $sPattern, $sFix:\n".implode("\n", $aPolluters));
}
public function PollutingPatterns()
{
return [
'ContextTags' => ['/ContextTag::AddContext/i', 'Use new ContextTag() instead'],
'Dict::Add' => ['/Dict::Add/i', 'TODO: implement a facade into ItopDataTestCase'],
'EventService::RegisterListener' => ['/EventService::RegisterListener/i', 'Use ItopDataTestCase::EventService_RegisterListener instead'],
];
}
}

View File

@@ -0,0 +1,242 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Integration;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
class DictionariesConsistencyAfterSetupTest extends ItopTestCase
{
//used by testDictEntryValues
//to filter false positive broken traductions
private static $aLabelCodeNotToCheck = [
//use of Dict::S not Format
"UI:Audit:PercentageOk",
//unused dead labels
"Class:DatacenterDevice/Attribute:redundancy/count",
"Class:DatacenterDevice/Attribute:redundancy/disabled",
"Class:DatacenterDevice/Attribute:redundancy/percent",
"Class:TriggerOnThresholdReached/Attribute:threshold_index+"
];
public function FormatProvider(){
return [
'key does not exist in dictionnary' => [
'sTemplate' => null,
'sExpectedTraduction' => 'ITOP::DICT:FORMAT:BROKEN:KEY - 1',
],
'traduction that breaks expected nb of arguments' => [
'sTemplate' => 'toto %1$s titi %2$s',
'sExpectedTraduction' => 'ITOP::DICT:FORMAT:BROKEN:KEY - 1',
],
'traduction ok' => [
'sTemplate' => 'toto %1$s titi',
'sExpectedTraduction' => 'toto 1 titi',
],
];
}
/**
* @param $sTemplate : if null it will not create dict entry
* @since 2.7.10 N°5491 - Inconsistent dictionary entries regarding arguments to pass to Dict::Format
* @dataProvider FormatProvider
*/
public function testFormatWithOneArgumentAndCustomKey(?string $sTemplate, $sExpectedTranslation){
//tricky way to mock GetLabelAndLangCode behavior via connected user language
$sLangCode = \Dict::GetUserLanguage();
$aDictByLang = $this->GetNonPublicStaticProperty(\Dict::class, 'm_aData');
$sDictKey = 'ITOP::DICT:FORMAT:BROKEN:KEY';
if (! is_null($sTemplate)){
$aDictByLang[$sLangCode][$sDictKey] = $sTemplate;
}
$this->SetNonPublicStaticProperty(\Dict::class, 'm_aData', $aDictByLang);
$this->assertEquals($sExpectedTranslation, \Dict::Format($sDictKey, "1"));
}
//test works after setup (no annotation @beforesetup)
//even if it does not extend ItopDataTestCase
private function ReadDictKeys($sLangCode) : array {
\Dict::InitLangIfNeeded($sLangCode);
$aDictEntries = $this->GetNonPublicStaticProperty(\Dict::class, 'm_aData');
return $aDictEntries[$sLangCode];
}
/**
* foreach dictionnary label map (key/value) it counts the number argument that should be passed to use Dict::Format
* examples:
* for "gabu zomeu" label there are no args
* for "shadok %1 %2 %3" there are 3 args
*
* limitation: there is no validation check for "%3 itop %2 combodo" which seems unconsistent
* @param $aDictEntry
*
* @return array
*/
private function GetKeyArgCountMap($aDictEntry) {
$aKeyArgsCount = [];
foreach ($aDictEntry as $sKey => $sValue){
$aKeyArgsCount[$sKey] = $this->countArg($sValue);
}
ksort($aKeyArgsCount);
return $aKeyArgsCount;
}
private function countArg($sLabel) {
$iMaxIndex = 0;
if (preg_match_all("/%(\d+)/", $sLabel, $aMatches)){
$aSubMatches = $aMatches[1];
if (is_array($aSubMatches)){
foreach ($aSubMatches as $aCurrentMatch){
$iIndex = $aCurrentMatch;
$iMaxIndex = ($iMaxIndex < $iIndex) ? $iIndex : $iMaxIndex;
}
}
} else if ((false !== strpos($sLabel, "%s"))
|| (false !== strpos($sLabel, "%d"))
){
$iMaxIndex = 1;
}
return $iMaxIndex;
}
/**
* Warning: hardcoded list of languages
* It is hard to have it dynamically via Dict::GetLanguages as for each lang Dict::Init should be called first
**/
public function LangCodeProvider(){
return [
'cs' => [ 'CS CZ' ],
'da' => [ 'DA DA' ],
'de' => [ 'DE DE' ],
'en' => [ 'EN US' ],
'es' => [ 'ES CR' ],
'fr' => [ 'FR FR' ],
'hu' => [ 'HU HU' ],
'it' => [ 'IT IT' ],
'ja' => [ 'JA JP' ],
'nl' => [ 'NL NL' ],
'pt' => [ 'PT BR' ],
'ru' => [ 'RU RU' ],
'sk' => [ 'SK SK' ],
'tr' => [ 'TR TR' ],
'zh' => [ 'ZH CN' ],
];
}
/**
* compare en and other dictionaries and check that for all labels there is the same number of arguments
* if not Dict::Format could raise an exception for some languages. translation should be done again...
* @dataProvider LangCodeProvider
*/
public function testDictEntryValues($sLanguageCodeToTest)
{
$sReferenceLangCode = 'EN US';
$aReferenceLangDictEntry = $this->ReadDictKeys($sReferenceLangCode);
$aDictEntry = $this->ReadDictKeys($sLanguageCodeToTest);
$aKeyArgsCountMap = [];
$aKeyArgsCountMap[$sReferenceLangCode] = $this->GetKeyArgCountMap($aReferenceLangDictEntry);
//$aKeyArgsCountMap[$sCode] = $this->GetKeyArgCountMap($aDictEntry);
//set user language
$this->SetNonPublicStaticProperty(\Dict::class, 'm_sCurrentLanguage', $sLanguageCodeToTest);
$aMismatchedKeys = [];
foreach ($aKeyArgsCountMap[$sReferenceLangCode] as $sKey => $iExpectedNbOfArgs){
if (in_array($sKey, self::$aLabelCodeNotToCheck)){
//false positive: do not test
continue;
}
if (array_key_exists($sKey, $aDictEntry)){
$aPlaceHolders = [];
for ($i=0; $i<$iExpectedNbOfArgs; $i++){
$aPlaceHolders[]=$i;
}
$sLabelTemplate = $aDictEntry[$sKey];
try{
vsprintf($sLabelTemplate, $aPlaceHolders);
} catch(\Throwable $e){
$sError = $e->getMessage();
if (array_key_exists($sError, $aMismatchedKeys)){
$aMismatchedKeys[$sError][$sKey] = $iExpectedNbOfArgs;
} else {
$aMismatchedKeys[$sError] = [$sKey => $iExpectedNbOfArgs];
}
}
}
}
$iCount = 0;
foreach ($aMismatchedKeys as $sError => $aKeys){
var_dump($sError);
foreach ($aKeys as $sKey => $iExpectedNbOfArgs) {
$iCount++;
if ($sReferenceLangCode === $sLanguageCodeToTest) {
var_dump([
'key label' => $sKey,
'expected nb of expected args' => $iExpectedNbOfArgs,
"key value in $sLanguageCodeToTest" => $aDictEntry[$sKey],
]);
} else {
var_dump([
'key label' => $sKey,
'expected nb of expected args' => $iExpectedNbOfArgs,
"key value in $sLanguageCodeToTest" => $aDictEntry[$sKey],
"key value in $sReferenceLangCode" => $aReferenceLangDictEntry[$sKey],
]);
}
}
}
$sErrorMsg = sprintf("%s broken propertie(s) on $sLanguageCodeToTest dictionaries! either change the dict value in $sLanguageCodeToTest or add it in ignored label list (cf aLabelCodeNotToCheck)", $iCount);
$this->assertEquals([], $aMismatchedKeys, $sErrorMsg);
}
public function VsprintfProvider(){
return [
'not enough args' => [
"sLabelTemplate" => "$1%s",
"aPlaceHolders" => [],
],
'exact nb of args' => [
"sLabelTemplate" => "$1%s",
"aPlaceHolders" => ["1"],
],
'too much args' => [
"sLabelTemplate" => "$1%s",
"aPlaceHolders" => ["1", "2"],
],
'\"% ok\" without args' => [
"sLabelTemplate" => "% ok",
"aPlaceHolders" => [],
],
'\"% ok $1%s\" without args' => [
"sLabelTemplate" => "% ok",
"aPlaceHolders" => ['1'],
],
];
}
/**
* @dataProvider VsprintfProvider
public function testVsprintf($sLabelTemplate, $aPlaceHolders){
try{
$this->markTestSkipped("usefull to check a specific PHP version behavior");
vsprintf($sLabelTemplate, $aPlaceHolders);
$this->assertTrue(true);
} catch(\Throwable $e) {
$this->assertTrue(false, "label \'" . $sLabelTemplate . " failed with " . var_export($aPlaceHolders, true) );
}
}
*/
}

View File

@@ -16,9 +16,9 @@
namespace Combodo\iTop\Test\UnitTest\Integration;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use Dict;
/**
* For tests on compiled dict files, see {@see CompiledDictionariesConsistencyTest}
* @group beforeSetup
*/
class DictionariesConsistencyTest extends ItopTestCase
@@ -32,6 +32,8 @@ class DictionariesConsistencyTest extends ItopTestCase
*/
public function testDictionariesLanguage($sDictFile): void
{
// In iTop the language available list is dynamically made during setup, depending on the dict files found
// Here we are using a fixed list
$aPrefixToLanguageData = array(
'cs' => array('CS CZ', 'Czech', 'Čeština'),
'da' => array('DA DA', 'Danish', 'Dansk'),
@@ -96,10 +98,12 @@ class DictionariesConsistencyTest extends ItopTestCase
{
$this->setUp();
$sAppRoot = $this->GetAppRoot();
$aDictFiles = array_merge(
glob(APPROOT.'datamodels/2.x/*/*.dict*.php'), // legacy form in modules
glob(APPROOT.'datamodels/2.x/*/dictionaries/*.dict*.php'), // modern form in modules
glob(APPROOT.'dictionaries/*.dict*.php') // framework
glob($sAppRoot.'datamodels/2.x/*/*.dict*.php'), // legacy form in modules
glob($sAppRoot.'datamodels/2.x/*/dictionaries/*.dict*.php'), // modern form in modules
glob($sAppRoot.'dictionaries/*.dict*.php') // framework
);
$aTestCases = array();
foreach ($aDictFiles as $sDictFile) {
@@ -150,67 +154,4 @@ class DictionariesConsistencyTest extends ItopTestCase
$sMessage = "File `{$sDictFile}` syntax didn't matched expectations\nparsing results=".var_export($output, true);
self::assertEquals($bIsSyntaxValid, $bDictFileSyntaxOk, $sMessage);
}
/**
* @dataProvider ImportCsvMessageStillOkProvider
* make sure N°5305 dictionary changes are still here and UI remains unbroken for any lang
*/
public function testImportCsvMessageStillOk($sLangCode, $sDictFile)
{
$aFailedLabels = [];
$aLabelsToTest = [
'UI:CSVReport-Value-SetIssue' => [],
'UI:CSVReport-Value-ChangeIssue' => [ 'arg1' ],
'UI:CSVReport-Value-NoMatch' => [ 'arg1' ],
'UI:CSVReport-Value-NoMatch-PossibleValues' => [ 'arg1', 'arg2' ],
'UI:CSVReport-Value-NoMatch-NoObject' => [ 'arg1' ],
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => [ 'arg1' ],
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => [ 'arg1' ],
];
$sLanguageCode = strtoupper(str_replace('-', ' ', $sLangCode));
require_once(APPROOT.'env-'.\utils::GetCurrentEnvironment().'/dictionaries/languages.php');
Dict::SetUserLanguage($sLanguageCode);
foreach ($aLabelsToTest as $sLabelKey => $aLabelArgs){
try{
$sLabelValue = Dict::Format($sLabelKey, ...$aLabelArgs);
//$this->debug($sLabelValue);
} catch (\ValueError $e){
$aFailedLabels[] = $sLabelKey;
$this->debug([
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'label_name' => $sLabelKey,
'label_args' =>$aLabelArgs,
]);
}
}
$this->assertEquals([], $aFailedLabels, "test fail for lang $sLangCode and labels (" . implode(", ", $aFailedLabels) . ')');
}
public function ImportCsvMessageStillOkProvider(){
return $this->GetDictFiles();
}
/**
* return a map linked to *.dict.php files that are generated after setup
* each entry key is lang code (example 'en')
* each value is an array with lang code (again) and dict file path
* @return array
*/
private function GetDictFiles() : array {
$aDictFiles = [];
foreach (glob(APPROOT.'env-'.\utils::GetCurrentEnvironment().'/dictionaries/*.dict.php') as $sDictFile){
if (preg_match('/.*\\/(.*).dict.php/', $sDictFile, $aMatches)){
$sLangCode = $aMatches[1];
$aDictFiles[$sLangCode] = [
'lang' => $sLangCode,
'file' => $sDictFile
];
}
}
return $aDictFiles;
}
}

View File

@@ -22,6 +22,7 @@ use utils;
/**
* @package Combodo\iTop\Test\UnitTest\Setup
* @group beforeSetup
*/
class iTopModulesPhpVersionIntegrationTest extends ItopTestCase {
/**

View File

@@ -24,6 +24,7 @@ use iTopDesignFormat;
* @covers iTopDesignFormat
*
* @package Combodo\iTop\Test\UnitTest\Setup
* @group beforeSetup
*/
class iTopModulesXmlVersionIntegrationTest extends ItopTestCase
{
@@ -71,11 +72,13 @@ class iTopModulesXmlVersionIntegrationTest extends ItopTestCase
{
static::setUp();
$sPath = APPROOT.'datamodels/2.x/*/datamodel.*.xml';
$sAppRoot = $this->GetAppRoot();
$sPath = $sAppRoot.'datamodels/2.x/*/datamodel.*.xml';
$aXmlFiles = glob($sPath);
$aXmlFiles[] = APPROOT.'core/datamodel.core.xml';
$aXmlFiles[] = APPROOT.'application/datamodel.application.xml';
$aXmlFiles[] = $sAppRoot.'core/datamodel.core.xml';
$aXmlFiles[] = $sAppRoot.'application/datamodel.application.xml';
$aTestCases = array();
foreach ($aXmlFiles as $sXmlFile) {

View File

@@ -20,6 +20,7 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase;
/**
* @package Combodo\iTop\Test\UnitTest\Setup
* @group beforeSetup
*/
class iTopXmlVersionIntegrationTest extends ItopTestCase
{

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
bootstrap="unittestautoload.php"
backupGlobals="true"
colors="true"
columns="120"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnRisky="false"
stopOnSkipped="false"
verbose="true"
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"/>
<ini name="display_errors" value="On"/>
<ini name="log_errors" value="On"/>
<ini name="html_errors" value="Off"/>
<env name="PHPUNIT_PRETTY_PRINT_PROGRESS" value="true"/>
</php>
<testsuites>
<!-- Unitary tests -->
<testsuite name="Perf">
<directory>perf-tests</directory>
</testsuite>
</testsuites>
<!-- Code coverage white list -->
<filter>
<whitelist>
<file>../../core/apc-emulation.php</file>
<file>../../core/ormlinkset.class.inc.php</file>
<file>../../datamodels/2.x/itop-tickets/main.itop-tickets.php</file>
</whitelist>
</filter>
</phpunit>

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