Compare commits

..

255 Commits

Author SHA1 Message Date
odain
7e8c963c4d fix restore info logging 2024-02-08 16:52:32 +01:00
denis.flaven@combodo.com
dcb40a832f N°7017 - Prevent race conditions when rebuilding cache 2024-01-02 17:08:03 +01:00
jf-cbd
998586a14c Merge remote-tracking branch 'origin/saas/3.1' into saas/3.1 2023-11-30 09:49:02 +01:00
odain
c61565101e adapt tests for saas targets 2023-11-29 16:09:36 +01:00
jf-cbd
d1ece12c44 Removed useless call to GetCSSFromSASS that creates troubles (log error + unecessary compiling time) if iTop can't write in css/backoffice/ directory 2023-11-29 15:30:33 +01:00
jf-cbd
fc2ba866f2 N°6852 - Missing configuration 'forgot_password_from' 2023-11-28 10:06:36 +01:00
odain
6a0695f347 N°6901 - Enable tracking of iTop active sessions (monitoring) 2023-11-27 13:21:06 +01:00
odain-cbd
8854f159a2 N°6901 - Enable tracking of iTop active sessions (monitoring) (#568)
Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
Co-authored-by: Romain Quetiez <romain.quetiez@combodo.com>
2023-11-27 13:20:53 +01:00
odain
02a293c464 N°6949 - Run subset of itop core unit tests after the setup when validating a module github code 2023-11-27 13:20:18 +01:00
odain
84f36eeebb N°6949 - Run subset of itop core unit tests after the setup when validating a module github code 2023-11-27 13:19:35 +01:00
odain-cbd
ff9827ad17 N°6824 - Notification with current_contact placeholder trigger hundred of email sent (#562)
Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
Co-authored-by: Thomas Casteleyn <thomas.casteleyn@super-visions.com>
Co-authored-by: Romain Quetiez <romain.quetiez@combodo.com>
2023-10-31 16:21:55 +01:00
odain
bc5db3cd7d Merge branch 'support/3.1' into saas/3.1 2023-09-18 13:22:31 +02:00
odain
220be64f6e N°4762 - empty menu log set to debug instead of error 2023-09-18 13:21:56 +02:00
Pierre Goiffon
9a8e9a0b01 N°5491 Fix HU iTopUpdate:Error:MissingFile dict key 2023-09-18 08:45:45 +02:00
vdumas
f6a7e6b4e1 N°3506 - allow_target_creation based on UR_ACTION_MODIFY 2023-09-15 16:40:20 +02:00
Stephen Abello
2829fb291f Update contribution stickers with 2023 design (better late than never 🙃) 2023-09-15 15:43:50 +02:00
odain
6110abfc7f Merge branch 'support/3.0' into support/3.1 2023-09-15 10:08:37 +02:00
odain
6046f44f56 Merge branch 'support/2.7' into support/3.0 2023-09-15 10:08:08 +02:00
odain
6c6131ce03 N°5491 - test enhancement to reduce false positive 2023-09-15 10:07:42 +02:00
Stephen Abello
178c922407 Merge branch 'support/3.0' into support/3.1 2023-09-15 09:57:59 +02:00
Stephen Abello
343e87a8d4 N°6581 - Security hardening 2023-09-15 09:55:51 +02:00
Stephen Abello
d7e5d6fb7f N°6734 - Fix not being able to add dashlets to a dashboard when iBackofficeDictEntriesExtension is used and its JS files are loaded through ajax calls 2023-09-14 17:25:00 +02:00
Pierre Goiffon
993606f102 Merge branch 'support/3.1.0' into support/3.1
# Conflicts:
#	setup/itopdesignformat.class.inc.php
2023-09-14 16:54:28 +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
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
odain
f22921d8f8 Merge branch 'support/3.1' into saas/3.1 2023-09-13 10:37:07 +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
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
odain
235c63147f Merge branch 'saas/3.1.0' into saas/3.1 2023-09-05 15:23:11 +02:00
odain
5485b0d9b0 Merge branch 'feature/5324-powerportaluser-repairprofiles' into saas/3.1.0 2023-09-05 15:05:53 +02:00
odain
cc56413ee7 5324-add debug logs 2023-09-05 15:03:59 +02:00
odain
b3820560f1 5324-make test work with iTop core profiles AND saas targets 2023-09-05 15:03:46 +02:00
odain
144ca490ed 5324-adapt test 2023-09-05 13:35:12 +02:00
odain
70d271fcb1 Merge branch 'feature/5324-powerportaluser-repairprofiles' into saas/3.1.0 2023-09-05 13:12:11 +02:00
odain
7443fdd525 5324-userfriendly messages 2023-09-05 13:11:44 +02:00
odain
ff351d6b4b 5324-when calling FireEvent throw first exception raised instead of last one 2023-09-05 13:10:59 +02:00
odain
6f6b385550 5324-fix reentrance in case of object creation 2023-09-05 13:10:13 +02:00
odain
1d525e92e8 Merge branch 'feature/5324-powerportaluser-repairprofiles' into saas/3.1.0 2023-09-04 10:41:31 +02:00
odain
f25e9045c9 5324-fix no profile name provided during ci errors 2023-09-04 10:41:12 +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
odain
0b5a9aa5da Merge branch 'feature/5324-powerportaluser-repairprofiles' into saas/3.1.0 2023-09-01 16:46:42 +02:00
odain
03568f2fa5 5324-handle error message differently 2023-09-01 16:46:28 +02:00
odain
965659cdb4 N°6643-fix deadlock log 2023-09-01 16:16:42 +02:00
odain
d07747d82d Merge branch 'feature/5324-powerportaluser-repairprofiles' into saas/3.1.0 2023-09-01 16:02:20 +02:00
odain
15900720c8 5324-handle forgotten usecases 2023-09-01 15:33:42 +02:00
odain
6d3f7f4976 5324-enhance CRUD to avoid collision/reentrance when using events on links 2023-09-01 15:33:11 +02:00
odain
6dc88c372b Merge branch 'support/3.1.0' into feature/5324-powerportaluser-repairprofiles 2023-09-01 15:15:16 +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
odain
ebab4f8f96 fix merge of N°5324 power portal branch 2023-08-18 10:53:03 +02:00
odain
ac792e7a4c Merge branch 'feature/5324-powerportaluser-repairprofiles' into saas/3.1.0 2023-08-18 10:45:24 +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
odain
204a6d8e51 fix merge: broken render.php in case of exception - broken impact analysis tab for ex. 2023-08-10 12:06:34 +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
odain
7670c8723a fix merge 3.1 by moving test that remained in /test 2023-08-09 16:13:43 +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
odain
4c26a7a682 N°6640 - Broken unattended setup with XML backup configuration 2023-08-08 15:41:12 +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
odain
bcb110d99e N°6640 - Broken unattended setup with XML backup configuration 2023-08-08 15:00:14 +02:00
odain
446a13ad72 N°6640 - Broken unattended setup with XML backup configuration 2023-08-08 14:38:25 +02:00
odain
87ecbe9d37 N°6167 - Change welcome pop up and reactivate it on every instance - fix merge missing dependency 2023-08-08 11:54:31 +02:00
odain
5bceee3bdb Fix merge: 5619/5620/6293 dev lost because due to folder sources/Application renaming 2023-08-08 11:36:27 +02:00
odain
1378afbfa2 Merge branch 'saas/3.0' into saas/3.1.0 2023-08-08 11:35:27 +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
Molkobain
58f4d3b53c Remove "is_link" fixes as they have been done in Done in 12dbd0e 2023-06-29 10:05:41 +02:00
odain
427fc6f9f9 5324-Guillaume s feedback in PR 2023-06-28 21:31:11 +02:00
odain
07eadb3ea7 N°5324 -rename and move conf parameter to security.single-profile-completion + display warningmessage 2023-06-28 14:41:20 +02:00
odain
97f4818076 5324- Guillaume PR feedbacks 2023-06-26 15:01:57 +02:00
odain
ad46d47e21 N°5324 - repairing or warning profiles conf 2023-06-21 23:44:39 +02:00
odain
cd3f7d7ead N°5324 - disable repairment with backoffice and a customized portal 2023-06-21 21:42:40 +02:00
odain
c6b203fc4e N°5324 - make EVENT_DB_LINKS_CHANGED event work with URP_UserProfile by adding is-link property 2023-06-14 15:14:55 +02:00
odain
b8d04e40e4 N°5324 - repair profiles by default or if configured for customer with multiple profiles + move dedicated test in UserProfilesEventListenerTest.php 2023-06-14 15:13:29 +02:00
odain
d8c7888eac N°5324 - [ERGO] Avoid to have users with non-standalone power portal profile only 2023-06-13 15:39:34 +02:00
denis.flaven@combodo.com
9d6f4569ef Adapt Welcome Popup API for the 3.0.x branch 2023-05-30 08:00:32 +02:00
denis.flaven@combodo.com
1e41e805a2 API for Welcome Popup 2023-05-25 18:04:40 +02:00
odain
bd1e4389f7 N°6293 - [ERGO] Symplify avatar menu - first prototype to sort user menus after setup on page loading 2023-05-22 16:44:57 +02:00
odain
b059fb72a2 Merge branch 'support/3.0' into saas/3.0 2023-05-22 14:08:02 +02:00
odain
1b3b2e8a69 N°6171 - Password Expiration: can expire mode has no effect on user who have never changed their password 2023-05-05 11:44:26 +02:00
jf-cbd
6b448e29f5 PR fix 2023-04-19 10:46:36 +02:00
jf-cbd
7cb6af0a2b fix for 6179 (with description instead of tooltip)
PR fix
2023-04-19 10:46:36 +02:00
jf-cbd
ddc9952ec1 N°6179 - Tooltip attribute in field component (in Twig) 2023-04-13 15:21:11 +02:00
denis.flaven@combodo.com
a1a9ffe192 Merge remote-tracking branch 'origin/feature/6133-add-extra-files-to-backup-and-restore' into saas/3.0 2023-04-06 10:51:29 +02:00
denis.flaven@combodo.com
7728082c00 Merge remote-tracking branch 'origin/feature/6133-add-extra-files-to-backup-and-restore' into saas/3.0 2023-04-04 15:55:09 +02:00
denis.flaven@combodo.com
970183ef45 Merge remote-tracking branch 'origin/feature/6132-disabling-tabs-dynamically' into saas/3.0 2023-04-03 14:02:19 +02:00
denis.flaven@combodo.com
6c2db1e687 Fixed tab activation afeter re-enabling. 2023-04-03 13:45:32 +02:00
denis.flaven@combodo.com
32d74fbc8e Merge branch 'feature/6132-disabling-tabs-dynamically' into saas/3.0 2023-03-30 17:08:02 +02:00
Molkobain
477f2f51e9 Update code to match conventions 2023-03-30 16:50:08 +02:00
denis.flaven@combodo.com
94ea8e60e8 Typo! 2023-03-30 14:29:29 +02:00
denis.flaven@combodo.com
b9a00b15f5 Disable tabs by ID instead of index
Disabled tabs are visible (with a 'not-allowed' cursor)  instead of being hidden from the extra tabs menu.
2023-03-30 14:16:26 +02:00
Denis
e87f5af465 Apply suggestions from code review
JS cleanup after review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2023-03-30 13:25:55 +02:00
denis.flaven@combodo.com
97d717b016 Merge from support/3.0.2 2023-03-29 16:56:42 +02:00
denis.flaven@combodo.com
251fd3c67b N°6132 - disable tabs dynamically 2023-03-28 15:07:43 +02:00
odain
7b0a569c64 N°4762 - menu compilation: fix ci (merge issue) 2023-03-07 14:07:17 +01:00
odain
7176bc8686 N°4762 - menu compilation: fix broken menus scenario via AVA6 delta XML
N°4762-enhance test
2023-03-07 09:49:21 +01:00
Denis
9c0b906ded N°5922 - Fix plus button semantic on ext. key widget (#448)
* N°5922 - Enhance plus button on extkeywidget

* Properly reset the target class when closing the dialog

* Make icon buttons as actual clickable links for BeHat

* Apply suggestions from code review

Review by Guillaume. Thanks!

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
2023-03-03 14:12:09 +01:00
odain
0533916dad 4762-menu compilation rework after brainstorming 2023-03-02 08:49:41 +01:00
odain
60b08586c2 N°4762-fix fresh install setup crash 2023-02-24 10:11:17 +01:00
odain
28df2942e4 N°6022 - Make synchro scripts work by http via token authentication with SYNCHRO scopes 2023-02-24 09:11:43 +01:00
odain
cd48d2ad37 N°4762 - Designer customization of menus moved in itop-structure crashs in iTop 3.0 2023-02-24 09:01:10 +01:00
odain
9db2205241 N°5891 - fix and enable tests 2023-02-16 16:59:43 +01:00
odain
045985cd5b 5891-renable badly named tests 2023-02-16 15:56:49 +01:00
odain
809b371520 N°5753 - add config parameter allow_rest_services_via_tokens to bypass rest secure profile option 2022-12-21 15:23:49 +01:00
odain
9bbc7342b8 enhance test framework: let AddProfileToUser work on any User not only UserLocal 2022-12-21 15:22:52 +01:00
odain
973c435138 Revert "N°5753 - exposer l'API Rest dans le SaaS - ugly way of passing API scope to rest.php during login"
This reverts commit 49748a0374.
2022-12-21 14:39:03 +01:00
denis.flaven@combodo.com
1c3dfd6491 Merge branch 5620 - also hide favorites orgs from preferences 2022-12-19 15:57:44 +01:00
denis.flaven@combodo.com
9400b697eb N°5620 Also hide the favorite orgs in preferences 2022-12-19 15:55:52 +01:00
odain
49748a0374 N°5753 - exposer l'API Rest dans le SaaS - ugly way of passing API scope to rest.php during login 2022-12-19 14:22:45 +01:00
odain
163276a6c2 N°5620-merge fix 2022-11-23 09:24:21 +01:00
odain
9b0c2f7324 Merge branch 'feature/5620-hide-org-filter-menu' into saas/3.0 2022-11-22 13:47:33 +01:00
odain
e1807f598f N°5620-fix ci 2022-11-22 13:46:33 +01:00
Molkobain
02e63fff64 Add PHPDoc 2022-11-22 13:28:02 +01:00
odain
0864f05d9f N°5620 - conf param renaming + backward compatibility test 2022-11-22 08:25:43 +01:00
denis.flaven@combodo.com
1bbcd9656a N°5619 - fixed crash when no provider at all! 2022-11-22 08:25:43 +01:00
odain
34d8e52c22 N°5620 - remove debug log 2022-11-22 08:25:27 +01:00
odain
ac7309e48c N°5620 - Hide the organization filter with a conf parameter 2022-11-22 08:25:27 +01:00
odain
c8fade6013 5620-simplify test to avoid regression in other test sections linked to MetaModel use 2022-11-22 07:42:03 +01:00
odain
ad052dd861 N°5620-renaming IsOrgMenuFilterAllowed<-IsSiloSelectionEnabled and made public 2022-11-22 07:31:12 +01:00
odain
a5ea868609 fix test 2022-11-21 10:41:03 +01:00
odain
0b03b3ef4d N°5620 - conf param renaming + backward compatibility test 2022-11-21 09:56:56 +01:00
odain
174cace20a N°5620 - remove debug log 2022-11-15 09:55:12 +01:00
odain
e3f5dbfc80 N°5620 - Hide the organization filter with a conf parameter 2022-11-15 09:49:36 +01:00
denis.flaven@combodo.com
40e24c25a2 N°5619 - hide newsroom menu when no provider 2022-11-15 09:17:27 +01:00
odain
c6e4466c53 ci: fix ItopDataTestCase CreateUser contactid unset 2022-10-26 14:03:11 +02:00
odain
6638eb4adc ci: adapt impersonate test to any friendlyname output 2022-10-26 09:50:56 +02:00
odain
eb40968e34 ci: add CreateContactlessUser method in test framework 2022-10-25 09:26:57 +02:00
odain
0001e8ffc4 💚 use new ci validation 2020-10-09 10:13:51 +02:00
237 changed files with 8280 additions and 2605 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

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

@@ -161,4 +161,4 @@ We have one sticker per contribution type. You might get multiple stickers with
Here is the design of each stickers for year 2022:
![iTop stickers 2022](.doc/contributing-guide/2022.contributing-stickers-side-by-side.png)
![iTop stickers 2023](.doc/contributing-guide/2023.contributing-stickers-side-by-side.png)

View File

@@ -228,7 +228,7 @@ class URP_UserProfile extends UserRightsBaseClassGUI
"db_table" => "priv_urp_userprofile",
"db_key_field" => "id",
"db_finalclass_field" => "",
"is_link" => true, /** @since 3.1.0 N°6482 */
"is_link" => true, /** @since 3.1.0 N°6482 N°5324 */
'uniqueness_rules' => array(
'no_duplicate' => array(
'attributes' => array(

View File

@@ -23,7 +23,7 @@ define('PORTAL_PROFILE_NAME', 'Portal user');
class UserRightsBaseClassGUI extends cmdbAbstractObject
{
// Whenever something changes, reload the privileges
protected function AfterInsert()
{
UserRights::FlushPrivileges();
@@ -43,7 +43,7 @@ class UserRightsBaseClassGUI extends cmdbAbstractObject
class UserRightsBaseClass extends DBObject
{
// Whenever something changes, reload the privileges
protected function AfterInsert()
{
UserRights::FlushPrivileges();
@@ -100,7 +100,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
$this->m_bCheckReservedNames = false;
}
protected static $m_aActions = array(
UR_ACTION_READ => 'Read',
UR_ACTION_MODIFY => 'Modify',
@@ -113,7 +113,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
protected static $m_aCacheActionGrants = null;
protected static $m_aCacheStimulusGrants = null;
protected static $m_aCacheProfiles = null;
public static function DoCreateProfile($sName, $sDescription, $bReservedName = false)
{
if (is_null(self::$m_aCacheProfiles))
@@ -125,7 +125,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
{
self::$m_aCacheProfiles[$oProfile->Get('name')] = $oProfile->GetKey();
}
}
}
$sCacheKey = $sName;
if (isset(self::$m_aCacheProfiles[$sCacheKey]))
@@ -137,17 +137,17 @@ class URP_Profiles extends UserRightsBaseClassGUI
$oNewObj->Set('description', $sDescription);
if ($bReservedName)
{
$oNewObj->DisableCheckOnReservedNames();
$oNewObj->DisableCheckOnReservedNames();
}
$iId = $oNewObj->DBInsertNoReload();
self::$m_aCacheProfiles[$sCacheKey] = $iId;
self::$m_aCacheProfiles[$sCacheKey] = $iId;
return $iId;
}
public static function DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true)
{
$sAction = self::$m_aActions[$iAction];
if (is_null(self::$m_aCacheActionGrants))
{
self::$m_aCacheActionGrants = array();
@@ -157,7 +157,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
{
self::$m_aCacheActionGrants[$oGrant->Get('profileid').'-'.$oGrant->Get('action').'-'.$oGrant->Get('class')] = $oGrant->GetKey();
}
}
}
$sCacheKey = "$iProfile-$sAction-$sClass";
if (isset(self::$m_aCacheActionGrants[$sCacheKey]))
@@ -171,10 +171,10 @@ class URP_Profiles extends UserRightsBaseClassGUI
$oNewObj->Set('class', $sClass);
$oNewObj->Set('action', $sAction);
$iId = $oNewObj->DBInsertNoReload();
self::$m_aCacheActionGrants[$sCacheKey] = $iId;
self::$m_aCacheActionGrants[$sCacheKey] = $iId;
return $iId;
}
public static function DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass)
{
if (is_null(self::$m_aCacheStimulusGrants))
@@ -186,7 +186,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
{
self::$m_aCacheStimulusGrants[$oGrant->Get('profileid').'-'.$oGrant->Get('stimulus').'-'.$oGrant->Get('class')] = $oGrant->GetKey();
}
}
}
$sCacheKey = "$iProfile-$sStimulusCode-$sClass";
if (isset(self::$m_aCacheStimulusGrants[$sCacheKey]))
@@ -199,13 +199,13 @@ class URP_Profiles extends UserRightsBaseClassGUI
$oNewObj->Set('class', $sClass);
$oNewObj->Set('stimulus', $sStimulusCode);
$iId = $oNewObj->DBInsertNoReload();
self::$m_aCacheStimulusGrants[$sCacheKey] = $iId;
self::$m_aCacheStimulusGrants[$sCacheKey] = $iId;
return $iId;
}
/*
* Create the built-in Administrator profile with its reserved name
*/
*/
public static function DoCreateAdminProfile()
{
self::DoCreateProfile(ADMIN_PROFILE_NAME, 'Has the rights on everything (bypassing any control)', true /* reserved name */);
@@ -213,7 +213,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
/*
* Overload the standard behavior to preserve reserved names
*/
*/
public function DoCheckToWrite()
{
parent::DoCheckToWrite();
@@ -255,7 +255,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
}
}
function DoShowGrantSumary($oPage)
{
if ($this->GetRawName() == "Administrator")
@@ -267,7 +267,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
// Note: for sure, we assume that the instance is derived from UserRightsProfile
$oUserRights = UserRights::GetModuleInstance();
$aDisplayData = array();
foreach (MetaModel::GetClasses('bizmodel') as $sClass)
{
@@ -284,7 +284,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
}
}
$sStimuli = implode(', ', $aStimuli);
$aDisplayData[] = array(
'class' => MetaModel::GetName($sClass),
'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Read'),
@@ -296,7 +296,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
'stimuli' => $sStimuli,
);
}
$aDisplayConfig = array();
$aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+'));
$aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+'));
@@ -334,7 +334,7 @@ class URP_UserProfile extends UserRightsBaseClassGUI
"db_table" => "priv_urp_userprofile",
"db_key_field" => "id",
"db_finalclass_field" => "",
"is_link" => true, /** @since 3.1.0 N°6482 */
"is_link" => true, /** @since 3.1.0 N°6482 N°5324 */
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -611,7 +611,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oSearch->AllowAllData();
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
$oSearch->AddConditionExpression($oCondition);
$oUserOrgSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
while ($oUserOrg = $oUserOrgSet->Fetch())
{
@@ -633,7 +633,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oSearch->AllowAllData();
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
$oSearch->AddConditionExpression($oCondition);
$this->m_aUserProfiles[$iUser] = array();
$oUserProfileSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
while ($oUserProfile = $oUserProfileSet->Fetch())
@@ -648,7 +648,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
public function ResetCache()
{
// Loaded by Load cache
$this->m_aProfiles = null;
$this->m_aProfiles = null;
$this->m_aUserProfiles = array();
$this->m_aUserOrgs = array();
@@ -658,7 +658,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
// Loaded on demand (time consuming as compared to the others)
$this->m_aClassActionGrants = null;
$this->m_aClassStimulusGrants = null;
$this->m_aObjectActionGrants = array();
}
@@ -694,10 +694,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
}
$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles"));
$this->m_aProfiles = array();
$this->m_aProfiles = array();
while ($oProfile = $oProfileSet->Fetch())
{
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
}
$this->m_aClassStimulusGrants = array();
@@ -871,7 +871,7 @@ exit;
$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes;
return $aRes;
}
public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null)
{
$this->LoadCache();
@@ -1009,8 +1009,8 @@ exit;
/**
* Find out which attribute is corresponding the the dimension 'owner org'
* returns null if no such attribute has been found (no filtering should occur)
*/
* returns null if no such attribute has been found (no filtering should occur)
*/
public static function GetOwnerOrganizationAttCode($sClass)
{
$sAttCode = null;

View File

@@ -22,9 +22,9 @@ define('ADMIN_PROFILE_ID', 1);
class UserRightsBaseClass extends cmdbAbstractObject
{
// Whenever something changes, reload the privileges
// Whenever something changes, reload the privileges
protected function AfterInsert()
{
UserRights::FlushPrivileges();
@@ -78,7 +78,7 @@ class URP_Profiles extends UserRightsBaseClass
function GetGrantAsHtml($oUserRights, $sClass, $sAction)
{
$oGrant = $oUserRights->GetClassActionGrant($this->GetKey(), $sClass, $sAction);
if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes'))
if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes'))
{
return '<span style="background-color: #ddffdd;">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>';
}
@@ -87,7 +87,7 @@ class URP_Profiles extends UserRightsBaseClass
return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
}
}
function DoShowGrantSumary($oPage)
{
if ($this->GetRawName() == "Administrator")
@@ -99,7 +99,7 @@ class URP_Profiles extends UserRightsBaseClass
// Note: for sure, we assume that the instance is derived from UserRightsProjection
$oUserRights = UserRights::GetModuleInstance();
$aDisplayData = array();
foreach (MetaModel::GetClasses('bizmodel') as $sClass)
{
@@ -116,7 +116,7 @@ class URP_Profiles extends UserRightsBaseClass
}
}
$sStimuli = implode(', ', $aStimuli);
$aDisplayData[] = array(
'class' => MetaModel::GetName($sClass),
'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Read'),
@@ -128,7 +128,7 @@ class URP_Profiles extends UserRightsBaseClass
'stimuli' => $sStimuli,
);
}
$aDisplayConfig = array();
$aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+'));
$aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+'));
@@ -277,7 +277,7 @@ class URP_UserProfile extends UserRightsBaseClass
"db_table" => "priv_urp_userprofile",
"db_key_field" => "id",
"db_finalclass_field" => "",
"is_link" => true, /** @since 3.1.0 N°6482 */
"is_link" => true, /** @since 3.1.0 N°6482 N°5324 */
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -356,7 +356,7 @@ class URP_ProfileProjection extends UserRightsBaseClass
{
$aRes = array($oUser->Get($sColumn));
}
}
elseif (($sExpr == '<any>') || ($sExpr == ''))
{
@@ -427,14 +427,14 @@ class URP_ClassProjection extends UserRightsBaseClass
{
$aRes = array($oObject->Get($sColumn));
}
}
elseif (($sExpr == '<any>') || ($sExpr == ''))
{
$aRes = null;
}
elseif (strtolower(substr($sExpr, 0, 6)) == 'select')
{
{
$sColumn = $this->Get('attribute');
// SELECT...
$oValueSetDef = new ValueSetObjects($sExpr, $sColumn, array(), true /*allow all data*/);
@@ -585,14 +585,14 @@ class UserRightsProjection extends UserRightsAddOnAPI
$oContact->Set('org_id', $iOrgId);
$oContact->Set('email', 'my.email@foo.org');
$iContactId = $oContact->DBInsertNoReload();
$oUser = new UserLocal();
$oUser->Set('login', $sAdminUser);
$oUser->Set('password', $sAdminPwd);
$oUser->Set('contactid', $iContactId);
$oUser->Set('language', $sLanguage); // Language was chosen during the installation
$iUserId = $oUser->DBInsertNoReload();
// Add this user to the very specific 'admin' profile
$oUserProfile = new URP_UserProfile();
$oUserProfile->Set('userid', $iUserId);
@@ -643,24 +643,24 @@ class UserRightsProjection extends UserRightsAddOnAPI
// Could be loaded in a shared memory (?)
$oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Dimensions"));
$this->m_aDimensions = array();
$this->m_aDimensions = array();
while ($oDimension = $oDimensionSet->Fetch())
{
$this->m_aDimensions[$oDimension->GetKey()] = $oDimension;
$this->m_aDimensions[$oDimension->GetKey()] = $oDimension;
}
$oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_ClassProjection"));
$this->m_aClassProjs = array();
$this->m_aClassProjs = array();
while ($oClassProj = $oClassProjSet->Fetch())
{
$this->m_aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj;
$this->m_aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj;
}
$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles"));
$this->m_aProfiles = array();
$this->m_aProfiles = array();
while ($oProfile = $oProfileSet->Fetch())
{
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
}
$oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserProfile"));
@@ -676,10 +676,10 @@ class UserRightsProjection extends UserRightsAddOnAPI
}
$oProProSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_ProfileProjection"));
$this->m_aProPros = array();
$this->m_aProPros = array();
while ($oProPro = $oProProSet->Fetch())
{
$this->m_aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro;
$this->m_aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro;
}
/*
@@ -707,7 +707,7 @@ exit;
// Authorize any for this dimension, then no additional criteria is required
continue;
}
// 1 - Get class projection info
//
$oExpression = null;
@@ -731,13 +731,13 @@ exit;
}
elseif (strtolower(substr($sExpr, 0, 6)) == 'select')
{
throw new CoreException('Sorry, projections by the mean of OQL are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr));
throw new CoreException('Sorry, projections by the mean of OQL are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr));
}
else
{
// Constant value(s)
// unsupported
throw new CoreException('Sorry, constant projections are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr));
throw new CoreException('Sorry, constant projections are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr));
// $aRes = explode(';', trim($sExpr));
}
@@ -866,7 +866,7 @@ exit;
$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iObjectRef][$iActionCode] = $aRes;
return $aRes;
}
public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null)
{
if (is_null($oInstanceSet))
@@ -934,7 +934,7 @@ exit;
}
else
{
$iInstancePermission = UR_ALLOWED_NO;
$iInstancePermission = UR_ALLOWED_NO;
}
if (isset($iGlobalPermission))
@@ -1140,7 +1140,7 @@ exit;
}
protected $m_aMatchingProfiles = array(); // cache of the matching profiles for a given user/object
protected function GetMatchingProfiles($oUser, $sClass, /*DBObject*/ $oObject = null)
{
$iUser = $oUser->GetKey();
@@ -1186,7 +1186,7 @@ exit;
@$aProfileRes[$iProfile] += 1;
}
}
$aRes = array();
$iDimCount = count($this->m_aDimensions);
foreach ($aProfileRes as $iProfile => $iMatches)
@@ -1200,7 +1200,7 @@ exit;
// store into the cache
$this->m_aMatchingProfiles[$iUser][$sClass][$iObjectRef] = $aRes;
return $aRes;
return $aRes;
}
public function FlushPrivileges()

View File

@@ -2246,3 +2246,70 @@ interface iModuleExtension
*/
public function __construct();
}
/**
* Interface to provide messages to be displayed in the "Welcome Popup"
*
* @api
* @private
* @since 3.1.0
*/
interface iWelcomePopup
{
// Importance for ordering messages
// Just two levels since less important messages have nothing to do in the welcome popup
const IMPORTANCE_CRITICAL = 0;
const IMPORTANCE_HIGH = 1;
/**
* @return [['importance' => IMPORTANCE_CRITICAL|IMPORTANCE_HIGH, 'id' => '...', 'title' => '', 'html' => '', 'twig' => '']]
*/
public function GetMessages();
/**
* The message specified by the given Id has been acknowledged by the current user
* @param string $sMessageId
*/
public function AcknowledgeMessage(string $sMessageId): void;
}
/**
* Inherit from this class to provide messages to be displayed in the "Welcome Popup"
*
* @api
* @since 3.1.0
*/
abstract class AbstractWelcomePopup implements iWelcomePopup
{
public function GetMessages()
{
return [];
}
public function AcknowledgeMessage(string $sMessageId): void
{
return;
}
}
/**
* 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(APPROOT.$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;
}
@@ -1136,7 +1141,7 @@ JS
$oToolbar->AddSubBlock($oActionButton);
$aActions = array();
$sFile = addslashes($this->sDefinitionFile);
$sFile = addslashes(utils::LocalPath($this->sDefinitionFile));
$sJSExtraParams = json_encode($aExtraParams);
if ($this->HasCustomDashboard()) {
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:EditCustom'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)");

View File

@@ -40,6 +40,36 @@
<presentation/>
<methods/>
</class>
<class id="WelcomePopupAcknowledge" _delta="define">
<parent>DBObject</parent>
<properties>
<comment>/* Acknowledge welcome popup messages */</comment>
<abstract>false</abstract>
<category></category>
<key_type>autoincrement</key_type>
<db_table>priv_welcome_popup_acknowledge</db_table>
</properties>
<fields>
<field id="message_uuid" xsi:type="AttributeString">
<sql>message_uuid</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
</field>
<field id="user_id" xsi:type="AttributeExternalKey">
<sql>user_id</sql>
<target_class>User</target_class>
<is_null_allowed>false</is_null_allowed>
<on_target_delete>DEL_SILENT</on_target_delete>
</field>
<field id="acknowledge_date" xsi:type="AttributeDateTime">
<sql>acknowledge_date</sql>
<default_value/>
<is_null_allowed>false</is_null_allowed>
</field>
</fields>
<presentation/>
<methods/>
</class>
</classes>
<portals>
<portal id="backoffice" _delta="define">

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

@@ -248,6 +248,7 @@ class LoginWebPage extends NiceWebPage
$oEmail = new Email();
$oEmail->SetRecipientTO($sTo);
$sFrom = MetaModel::GetConfig()->Get('forgot_password_from');
$sFrom = utils::IsNullOrEmptyString($sFrom) ? MetaModel::GetConfig()->Get('email_default_sender_address') : $sFrom;
$oEmail->SetRecipientFrom($sFrom);
$oEmail->SetSubject(Dict::S('UI:ResetPwd-EmailSubject', $oUser->Get('login')));
$sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken);

View File

@@ -9,6 +9,7 @@ use Combodo\iTop\Application\Helper\WebResourcesHelper;
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/application/template.class.inc.php');
require_once(APPROOT."/application/user.dashboard.class.inc.php");
require_once(APPROOT."/setup/parentmenunodecompiler.class.inc.php");
/**
@@ -103,7 +104,7 @@ class ApplicationMenu
{
self::$sFavoriteSiloQuery = $sOQL;
}
/**
* Get the query used to limit the list of displayed organizations in the drop-down menu
* @return string The OQL query returning a list of Organization objects
@@ -273,12 +274,23 @@ class ApplicationMenu
continue;
}
$aSubMenuNodes = static::GetSubMenuNodes($sMenuGroupIdx, $aExtraParams);
if (! ParentMenuNodeCompiler::$bUseLegacyMenuCompilation && !($oMenuNode instanceof ShortcutMenuNode)){
if (is_array($aSubMenuNodes) && 0 === sizeof($aSubMenuNodes)){
IssueLog::Debug('Empty menu node not displayed', LogChannels::CONSOLE, [
'menu_node_class' => get_class($oMenuNode),
'menu_node_label' => $oMenuNode->GetLabel(),
]);
continue;
}
}
$aMenuGroups[] = [
'sId' => $oMenuNode->GetMenuID(),
'sIconCssClasses' => $oMenuNode->GetDecorationClasses(),
'sInitials' => $oMenuNode->GetInitials(),
'sTitle' => $oMenuNode->GetTitle(),
'aSubMenuNodes' => static::GetSubMenuNodes($sMenuGroupIdx, $aExtraParams),
'aSubMenuNodes' => $aSubMenuNodes,
];
}
@@ -536,7 +548,7 @@ EOF
return -1;
}
/**
* Retrieves the currently active menu (if any, otherwise the first menu is the default)
* @return string The Id of the currently active menu
@@ -544,7 +556,7 @@ EOF
public static function GetActiveNodeId()
{
$oAppContext = new ApplicationContext();
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
if ($sMenuId === null)
{
$sMenuId = self::GetDefaultMenuId();
@@ -654,7 +666,7 @@ abstract class MenuNode
/**
* Stimulus to check: if the user can 'apply' this stimulus, then she/he can see this menu
*/
*/
protected $m_aEnableStimuli;
/**
@@ -814,7 +826,7 @@ abstract class MenuNode
{
return false;
}
/**
* Add a limiting display condition for the same menu node. The conditions will be combined with a AND
* @param $oMenuNode MenuNode Another definition of the same menu node, with potentially different access restriction
@@ -987,7 +999,7 @@ class TemplateMenuNode extends MenuNode
* @var string
*/
protected $sTemplateFile;
/**
* Create a menu item based on a custom template and inserts it into the application's main menu
* @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
@@ -1058,7 +1070,7 @@ class OQLMenuNode extends MenuNode
* @var bool|null
*/
protected $bSearchFormOpen;
/**
* Extra parameters to be passed to the display block to fine tune its appearence
*/
@@ -1091,7 +1103,7 @@ class OQLMenuNode extends MenuNode
// Enhancement: we could set as the "enable" condition that the user has enough rights to "read" the objects
// of the class specified by the OQL...
}
/**
* Set some extra parameters to be passed to the display block to fine tune its appearence
* @param array $aParams paramCode => value. See DisplayBlock::GetDisplay for the meaning of the parameters
@@ -1111,7 +1123,7 @@ class OQLMenuNode extends MenuNode
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
ContextTag::AddContext(ContextTag::TAG_OBJECT_SEARCH);
$oTag = new ContextTag(ContextTag::TAG_OBJECT_SEARCH);
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
OQLMenuNode::RenderOQLSearch
(
@@ -1120,7 +1132,7 @@ class OQLMenuNode extends MenuNode
'Menu_'.$this->GetMenuId(),
$this->bSearch, // Search pane
$this->bSearchFormOpen, // Search open
$oPage,
$oPage,
array_merge($this->m_aParams, $aExtraParams),
true
);
@@ -1354,10 +1366,10 @@ class NewObjectMenuNode extends MenuNode
{
// Enable this menu, only if the current user has enough rights to create such an object, or an object of
// any child class
$aSubClasses = MetaModel::EnumChildClasses($this->sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
$bActionIsAllowed = false;
foreach($aSubClasses as $sCandidateClass)
{
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
@@ -1366,7 +1378,7 @@ class NewObjectMenuNode extends MenuNode
break; // Enough for now
}
}
return $bActionIsAllowed;
return $bActionIsAllowed;
}
/**
@@ -1508,7 +1520,7 @@ class DashboardMenuNode extends MenuNode
throw new Exception("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
}
}
}
/**
@@ -1549,7 +1561,7 @@ class ShortcutContainerMenuNode extends MenuNode
$sName = $this->GetMenuId().'_'.$oShortcut->GetKey();
new ShortcutMenuNode($sName, $oShortcut, $this->GetIndex(), $fRank++);
}
// Complete the tree
//
parent::PopulateChildMenus();

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

@@ -1,29 +0,0 @@
<div style="width:100%;background: #fff url(../images/welcome.jpg) top left no-repeat;">
<style>
.welcome_popup_cell {
vertical-align:top;
width:50%;
border:0px solid #000;
-moz-border-radius:10px;
padding:5px;
text-align:left;
}
tr td.welcome_popup_cell, tr td.welcome_popup_cell ul {
font-size:10pt;
}
</style>
<p></p>
<p></p>
<p style="text-align:left; font-size:32px;padding-left:400px;padding-top:40px;margin-bottom:30px;margin-top:0;color:#FFFFFF;"><itopstring>UI:WelcomeMenu:Title</itopstring></p>
<p></p>
<table border="0" style="padding:10px;border-spacing: 10px;width:100%">
<tr>
<td class="welcome_popup_cell">
<itopstring>UI:WelcomeMenu:LeftBlock</itopstring>
</td>
<td class="welcome_popup_cell">
<itopstring>UI:WelcomeMenu:RightBlock</itopstring>
</td>
</tr>
</table>
</div>

View File

@@ -163,7 +163,7 @@ class UIExtKeyWidget
$oPage->add_linked_script('../js/extkeywidget.js');
$oPage->add_linked_script('../js/forms-json-utils.js');
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_MODIFY) && $bAllowTargetCreation);
$bExtensions = true;
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';

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.',
@@ -1185,6 +1193,30 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'sessions_tracking.enabled' => [
'type' => 'bool',
'description' => 'Whether or not the whole mechanism to track active sessions is enabled. See PHP session.gc_maxlifetime setting to configure session expiration.',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'sessions_tracking.gc_threshold' => [
'type' => 'integer',
'description' => 'fallback in case cron is not active: probability in percent that session files are cleanup during any itop request (100 means always)',
'default' => 1,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'sessions_tracking.gc_duration_in_seconds' => [
'type' => 'integer',
'description' => 'fallback in case cron is not active: when a cleanup is triggered cleanup duration will not exceed this duration (in seconds).',
'default' => 1,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'transaction_storage' => [
'type' => 'string',
'description' => 'The type of mechanism to use for storing the unique identifiers for transactions (Session|File).',
@@ -1345,6 +1377,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'navigation_menu.sorted_popup_user_menu_items' => [
'type' => 'array',
'description' => 'Sort user menu items after setup on page load',
'default' => [],
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'quick_create.enabled' => [
'type' => 'bool',
'description' => 'Whether or not the quick create is enabled',
@@ -1627,6 +1667,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.single_profile_completion' => [
'type' => 'array',
'description' => 'Non standalone profiles can be completed by other profiles via this configuration. default configuration is equivalent to [\'Portal power user\' => \'Portal user\'] configuration. unless you have specific portal customization.',
'default' => null,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'behind_reverse_proxy' => [
'type' => 'bool',
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',

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

@@ -59,7 +59,7 @@ require_once('mutex.class.inc.php');
/**
* A persistent object, as defined by the metamodel
* A persistent object, as defined by the metamodel
*
* @package iTopORM
* @api
@@ -307,9 +307,9 @@ abstract class DBObject implements iDisplay
/**
* Whether the object is already persisted in DB or not.
*
*
* @api
*
*
* @return bool
*/
public function IsNew()
@@ -319,9 +319,9 @@ abstract class DBObject implements iDisplay
/**
* Returns an Id for memory objects
*
*
* @internal
*
*
* @param string $sClass
*
* @return int
@@ -358,7 +358,7 @@ abstract class DBObject implements iDisplay
$sRet .= "<b title=\"$sRootClass\">$sClass</b>::$iPKey ($sFriendlyname)<br/>\n";
return $sRet;
}
/**
* Alias of DBObject::Reload()
*
@@ -381,7 +381,7 @@ abstract class DBObject implements iDisplay
*
* @internal
* @see m_bFullyLoaded
*
*
* @return bool
* @throws CoreException
*/
@@ -504,7 +504,7 @@ abstract class DBObject implements iDisplay
{
$aAttList = $aAttToLoad[$sClassAlias];
}
foreach($aAttList as $sAttCode=>$oAttDef)
{
// Skip links (could not be loaded by the mean of this query)
@@ -564,7 +564,7 @@ abstract class DBObject implements iDisplay
$bFullyLoaded = false;
}
}
// Load extended data
if ($aExtendedDataSpec != null)
{
@@ -588,7 +588,7 @@ abstract class DBObject implements iDisplay
*
* @internal
* @see Set()
*
*
* @param string $sAttCode
* @param mixed $value
*/
@@ -793,11 +793,11 @@ abstract class DBObject implements iDisplay
/**
* Get the label of an attribute.
*
*
* Shortcut to the field's AttributeDefinition->GetLabel()
*
* @api
*
*
* @param string $sAttCode
*
* @return string
@@ -870,7 +870,7 @@ abstract class DBObject implements iDisplay
*
* @internal
* @see Get
*
*
* @param string $sAttCode
*
* @return int|mixed|null
@@ -975,7 +975,7 @@ abstract class DBObject implements iDisplay
* Returns the default value of the $sAttCode.
*
* Returns the default value of the given attribute.
*
*
* @internal
*
* @param string $sAttCode
@@ -996,12 +996,12 @@ abstract class DBObject implements iDisplay
* @internal
*
* @return array|null
*/
*/
public function GetExtendedData()
{
return $this->m_aExtendedData;
}
/**
* Set the HighlightCode
*
@@ -1023,7 +1023,7 @@ abstract class DBObject implements iDisplay
{
$fCurrentRank = $aHighlightScale[$this->m_sHighlightCode]['rank'];
}
if (array_key_exists($sCode, $aHighlightScale))
{
$fRank = $aHighlightScale[$sCode]['rank'];
@@ -1033,13 +1033,13 @@ abstract class DBObject implements iDisplay
}
}
}
/**
* Get the current HighlightCode
*
*
* @internal
* @used-by DBObject::ComputeHighlightCode()
*
*
* @return string|null The Hightlight code (null if none set, meaning rank = 0)
*/
protected function GetHighlightCode()
@@ -1088,7 +1088,7 @@ abstract class DBObject implements iDisplay
* corresponding to the external key and getting the value from it
*
* UNUSED ?
*
*
* @internal
* @todo: check if this is dead code.
*
@@ -1152,12 +1152,14 @@ abstract class DBObject implements iDisplay
return; //skip!
}
$this->FireEventComputeValues();
$oKPI = new ExecutionKPI();
$this->ComputeValues();
$oKPI->ComputeStatsForExtension($this, 'ComputeValues');
}
/**
* @api
*
*
* @param string $sAttCode
* @param bool $bLocalize
*
@@ -1203,11 +1205,11 @@ abstract class DBObject implements iDisplay
/**
* Get the value as it must be in the edit areas (forms)
*
*
* Makes a raw text representation of the value.
*
* @internal
*
*
* @param string $sAttCode
*
* @return int|mixed|string
@@ -1237,7 +1239,7 @@ abstract class DBObject implements iDisplay
else
{
$sEditValue = 0;
}
}
}
else
{
@@ -1253,14 +1255,14 @@ abstract class DBObject implements iDisplay
/**
* Get $sAttCode formatted as XML
*
*
* The returned value is a text that is suitable for insertion into an XML node.
* Depending on the type of attribute, the returned text is either:
* * A literal, with XML entities already escaped,
* * XML
*
* @api
*
*
* @param string $sAttCode
* @param bool $bLocalize
*
@@ -1298,10 +1300,10 @@ abstract class DBObject implements iDisplay
}
/**
*
*
* @see GetAsHTML()
* @see GetOriginal()
*
*
* @param string $sAttCode
* @param bool $bLocalize
*
@@ -1476,7 +1478,7 @@ abstract class DBObject implements iDisplay
/**
* @internal
*
*
* @param string $sClass
*
* @return mixed
@@ -1531,7 +1533,7 @@ abstract class DBObject implements iDisplay
* Get the id
*
* @api
*
*
* @return string|null
*/
public function GetKey()
@@ -1542,7 +1544,7 @@ abstract class DBObject implements iDisplay
/**
* Primary key Setter
* Usable only for not yet persisted DBObjects
*
*
* @internal
*
* @param int $iNewKey the desired identifier
@@ -1555,7 +1557,7 @@ abstract class DBObject implements iDisplay
{
throw new CoreException("An object id must be an integer value ($iNewKey)");
}
if ($this->m_bIsInDB && !empty($this->m_iKey) && ($this->m_iKey != $iNewKey))
{
throw new CoreException("Changing the key ({$this->m_iKey} to $iNewKey) on an object (class {".get_class($this).") wich already exists in the Database");
@@ -1565,7 +1567,7 @@ abstract class DBObject implements iDisplay
/**
* Get the icon representing this object
*
*
* @api
*
* @param boolean $bImgTag If true the result is a full IMG tag (or an empty string if no icon is defined)
@@ -1655,7 +1657,7 @@ abstract class DBObject implements iDisplay
*
* Returns the label as defined in the dictionary for the language of the current user
*
* @api
* @api
*
* @return string (empty for default name scheme)
*/
@@ -1722,7 +1724,7 @@ abstract class DBObject implements iDisplay
/**
* Helper to get the state
*
*
* @api
*
* @return mixed|string '' if no state attribute, object representing its value otherwise
@@ -1744,9 +1746,9 @@ abstract class DBObject implements iDisplay
/**
* Get the label (raw text) of the current state
* helper for MetaModel::GetStateLabel()
*
*
* @api
*
*
* @return mixed|string
*
* @throws ArchivedObjectException
@@ -1793,7 +1795,7 @@ abstract class DBObject implements iDisplay
* Define attributes read-only from the end-user perspective
*
* @return array|null List of attcodes
*/
*/
public static function GetReadOnlyAttributes()
{
return null;
@@ -1802,14 +1804,14 @@ abstract class DBObject implements iDisplay
/**
* Get predefined objects
*
*
* The predefined objects will be synchronized with the DB at each install/upgrade
* As soon as a class has predefined objects, then nobody can create nor delete objects
*
* @internal
*
* @return array An array of id => array of attcode => php value(so-called "real value": integer, string, ormDocument, DBObjectSet, etc.)
*/
*/
public static function GetPredefinedObjects()
{
return null;
@@ -1938,7 +1940,7 @@ abstract class DBObject implements iDisplay
* Note: Attributes (and flags) from the target state and the transition are combined.
*
* @internal
*
*
* @param string $sStimulus
* @param string $sOriginState Default is current state
*
@@ -2145,7 +2147,7 @@ abstract class DBObject implements iDisplay
/**
* @internal
*
*
* @throws \CoreException
* @throws \OQLException
*
@@ -2488,7 +2490,6 @@ abstract class DBObject implements iDisplay
{
$this->m_aCheckIssues = array();
$oKPI = new ExecutionKPI();
if ($bDoComputeValues) {
$this->DoComputeValues();
}
@@ -2498,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;
@@ -2517,7 +2519,7 @@ abstract class DBObject implements iDisplay
*
* an array of displayable error is added in {@see DBObject::$m_aDeleteIssues}
*
* @internal
* @internal
*
* @param \DeletionPlan $oDeletionPlan
*
@@ -2634,7 +2636,7 @@ abstract class DBObject implements iDisplay
{
// The value is a scalar, the comparison must be 100% strict
if($this->m_aOrigValues[$sAtt] !== $proposedValue)
{
{
//echo "$sAtt:<pre>\n";
//var_dump($this->m_aOrigValues[$sAtt]);
//var_dump($proposedValue);
@@ -2756,7 +2758,7 @@ abstract class DBObject implements iDisplay
/**
* Used only by insert, Meant to be overloaded
*
*
* @overwritable-hook You can extend this method in order to provide your own logic.
*/
protected function OnObjectKeyReady()
@@ -2864,7 +2866,7 @@ abstract class DBObject implements iDisplay
// fields in first array, values in the second
$aFieldsToWrite = array();
$aValuesToWrite = array();
if (!empty($this->m_iKey) && ($this->m_iKey >= 0))
{
// Add it to the list of fields to write
@@ -2873,7 +2875,7 @@ abstract class DBObject implements iDisplay
}
$aHierarchicalKeys = array();
foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) {
// Skip this attribute if not defined in this table
if ((!MetaModel::IsAttributeOrigin($sTableClass, $sAttCode) && !$oAttDef->CopyOnAllTables())
@@ -2883,7 +2885,7 @@ abstract class DBObject implements iDisplay
$aAttColumns = $oAttDef->GetSQLValues($this->m_aCurrValues[$sAttCode]);
foreach($aAttColumns as $sColumn => $sValue)
{
$aFieldsToWrite[] = "`$sColumn`";
$aFieldsToWrite[] = "`$sColumn`";
$aValuesToWrite[] = CMDBSource::Quote($sValue);
}
if ($oAttDef->IsHierarchicalKey())
@@ -2907,7 +2909,7 @@ abstract class DBObject implements iDisplay
self::$m_aBulkInsertCols[$sClass][$sTable] = implode(', ', $aFieldsToWrite);
}
self::$m_aBulkInsertItems[$sClass][$sTable][] = '('.implode (', ', $aValuesToWrite).')';
$iNewKey = 999999; // TODO - compute next id....
}
else
@@ -2992,7 +2994,7 @@ abstract class DBObject implements iDisplay
// fields in first array, values in the second
$aFieldsToWrite = array();
$aValuesToWrite = array();
if (!empty($this->m_iKey) && ($this->m_iKey >= 0))
{
// Add it to the list of fields to write
@@ -3027,7 +3029,7 @@ abstract class DBObject implements iDisplay
$aAttColumns = $oAttDef->GetSQLValues($value);
foreach($aAttColumns as $sColumn => $sValue)
{
$aFieldsToWrite[] = "`$sColumn`";
$aFieldsToWrite[] = "`$sColumn`";
$aValuesToWrite[] = CMDBSource::Quote($sValue);
}
if ($oAttDef->IsHierarchicalKey())
@@ -3122,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();
@@ -3165,6 +3169,10 @@ abstract class DBObject implements iDisplay
// First query built upon on the root class, because the ID must be created first
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
//since N°5324: issue with test and db links events
$this->SetReadOnly('No modification allowed during transaction');
MetaModel::StartReentranceProtection($this);
// Then do the leaf class, if different from the root class
if ($sClass != $sRootClass) {
$this->DBInsertSingleTable($sClass);
@@ -3178,7 +3186,9 @@ abstract class DBObject implements iDisplay
$this->DBInsertSingleTable($sParentClass);
}
$oKPI = new ExecutionKPI();
$this->OnObjectKeyReady();
$oKPI->ComputeStatsForExtension($this, 'OnObjectKeyReady');
$this->UpdateCurrentObjectInCrudStack();
$this->DBWriteLinks();
@@ -3214,6 +3224,7 @@ abstract class DBObject implements iDisplay
}
}
$this->SetReadWrite();
$this->m_bIsInDB = true;
$this->m_bDirty = false;
foreach ($this->m_aCurrValues as $sAttCode => $value) {
@@ -3224,7 +3235,7 @@ abstract class DBObject implements iDisplay
}
// Prevent DBUpdate at this point (reentrance protection)
MetaModel::StartReentranceProtection($this);
//MetaModel::StartReentranceProtection($this);
try {
$this->PostInsertActions();
@@ -3255,7 +3266,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);
@@ -3301,7 +3314,7 @@ abstract class DBObject implements iDisplay
$this->RecordObjCreation();
return $ret;
}
/**
* This function is automatically called after cloning an object with the "clone" PHP language construct
* The purpose of this method is to reset the appropriate attributes of the object in
@@ -3353,7 +3366,9 @@ abstract class DBObject implements iDisplay
try {
$this->DoComputeValues();
$this->ComputeStopWatchesDeadline(false);
$oKPI = new ExecutionKPI();
$this->OnUpdate();
$oKPI->ComputeStatsForExtension($this, 'OnUpdate');
$this->FireEventBeforeWrite();
@@ -3390,6 +3405,7 @@ abstract class DBObject implements iDisplay
}
}
$this->SetReadOnly('No modification allowed during transaction');
$iTransactionRetry = 1;
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
if ($bIsTransactionEnabled) {
@@ -3504,6 +3520,8 @@ abstract class DBObject implements iDisplay
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
// new values are already in the object (call {@see DBObject::Get()} to get them)
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
$this->SetReadWrite();
$this->m_bDirty = false;
$this->m_aTouchedAtt = array();
$this->m_aModifiedAtt = array();
@@ -3567,7 +3585,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));
@@ -3794,7 +3814,9 @@ abstract class DBObject implements iDisplay
return;
}
$oKPI = new ExecutionKPI();
$this->OnDelete();
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
// Activate any existing trigger
$sClass = get_class($this);
@@ -3902,7 +3924,9 @@ abstract class DBObject implements iDisplay
}
$this->FireEventAfterDelete();
$oKPI = new ExecutionKPI();
$this->AfterDelete();
$oKPI->ComputeStatsForExtension($this, 'AfterDelete');
$this->m_bIsInDB = false;
@@ -3917,7 +3941,7 @@ abstract class DBObject implements iDisplay
* First, checks if the object can be deleted regarding database integrity.
* If the answer is yes, it performs any required cleanup (delete other objects or reset external keys) in addition to the object
* deletion.
*
*
* @api
*
* @param \DeletionPlan $oDeletionPlan Do not use: aims at dealing with recursion
@@ -4340,7 +4364,7 @@ abstract class DBObject implements iDisplay
*
* @api
*
*/
*/
public function Reset($sAttCode)
{
$this->Set($sAttCode, $this->GetDefaultValue($sAttCode));
@@ -4352,7 +4376,7 @@ abstract class DBObject implements iDisplay
* Suitable for use as a lifecycle action
*
* @api
*/
*/
public function Copy($sDestAttCode, $sSourceAttCode)
{
$oTypeValueToCopy = MetaModel::GetAttributeDef(get_class($this), $sSourceAttCode);
@@ -4682,7 +4706,7 @@ abstract class DBObject implements iDisplay
{
throw new CoreException("Unknown attribute '$sExtKeyAttCode' for the class ".get_class($this));
}
$oKeyAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyAttCode);
if (!$oKeyAttDef instanceof AttributeExternalKey)
{
@@ -4700,14 +4724,14 @@ abstract class DBObject implements iDisplay
$ret = $oRemoteObj->GetForTemplate($sRemoteAttCode);
}
}
else
else
{
switch($sPlaceholderAttCode)
{
case 'id':
$ret = $this->GetKey();
break;
case 'name()':
$ret = $this->GetName();
break;
@@ -4894,7 +4918,7 @@ abstract class DBObject implements iDisplay
if ($oOwner)
{
$sLinkSetOwnerClass = get_class($oOwner);
$oMyChangeOp = MetaModel::NewObject($sChangeOpClass);
$oMyChangeOp->Set("objclass", $sLinkSetOwnerClass);
$oMyChangeOp->Set("objkey", $iLinkSetOwnerId);
@@ -4921,7 +4945,7 @@ abstract class DBObject implements iDisplay
{
/** @var \AttributeLinkedSet $oLinkSet */
if (($oLinkSet->GetTrackingLevel() & LINKSET_TRACKING_LIST) == 0) continue;
$iLinkSetOwnerId = $this->Get($sExtKeyAttCode);
$oMyChangeOp = $this->PrepareChangeOpLinkSet($iLinkSetOwnerId, $oLinkSet, 'CMDBChangeOpSetAttributeLinksAddRemove');
if ($oMyChangeOp)
@@ -4991,7 +5015,7 @@ abstract class DBObject implements iDisplay
// Keep track of link changes
//
if (($oLinkSet->GetTrackingLevel() & LINKSET_TRACKING_DETAILS) == 0) continue;
$iLinkSetOwnerId = $this->Get($sExtKeyAttCode);
$oMyChangeOp = $this->PrepareChangeOpLinkSet($iLinkSetOwnerId, $oLinkSet, 'CMDBChangeOpSetAttributeLinksTune');
if ($oMyChangeOp)
@@ -5140,7 +5164,7 @@ abstract class DBObject implements iDisplay
$this->FireEventCheckToDelete($oDeletionPlan);
$this->DoCheckToDelete($oDeletionPlan);
$oDeletionPlan->SetDeletionIssues($this, $this->m_aDeleteIssues, $this->m_bSecurityIssue);
$aDependentObjects = $this->GetReferencingObjects(true /* allow all data */);
// Getting and setting time limit are not symmetric:
@@ -5322,7 +5346,7 @@ abstract class DBObject implements iDisplay
$aSynchroClasses[] = $sTarget;
}
}
foreach($aSynchroClasses as $sClass)
{
if ($this instanceof $sClass)

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

@@ -101,7 +101,7 @@ EOF;
EOF;
SetupUtils::builddir(dirname($sFilePath));
file_put_contents($sFilePath, $content);
file_put_contents($sFilePath, $content, LOCK_EX);
}
}
Dict::SetUserLanguage($sUserLang);

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

@@ -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.
@@ -576,6 +576,11 @@ class LogChannels
public const DATATABLE = 'Datatable';
public const DEADLOCK = 'DeadLock';
/**
* @var string Everything related to PHP sessions tracking
* @since 3.1.1 3.2.0 N°6901
*/
public const SESSIONTRACKER = 'SessionTracker';
/**
* @var string Everything related to the datamodel CRUD
@@ -1138,7 +1143,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

@@ -1241,7 +1241,7 @@ abstract class MetaModel
}
$sTable = self::DBGetTable($sClass);
// Could be completed later with all the classes that are using a given table
// Could be completed later with all the classes that are using a given table
if (!array_key_exists($sTable, $aTables)) {
$aTables[$sTable] = array();
}
@@ -3522,7 +3522,7 @@ abstract class MetaModel
}
// Set the "host class" as soon as possible, since HierarchicalKeys use it for their 'target class' as well
// and this needs to be know early (for Init_IsKnowClass 19 lines below)
// and this needs to be know early (for Init_IsKnowClass 19 lines below)
$oAtt->SetHostClass($sTargetClass);
// Some attributes could refer to a class
@@ -3564,7 +3564,7 @@ abstract class MetaModel
self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt;
self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass;
// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used
// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used
}
/**
@@ -3764,7 +3764,7 @@ abstract class MetaModel
self::$m_aStimuli[$sTargetClass][$oStimulus->GetCode()] = $oStimulus;
// I wanted to simplify the syntax of the declaration of objects in the biz model
// Therefore, the reference to the host class is set there
// Therefore, the reference to the host class is set there
$oStimulus->SetHostClass($sTargetClass);
}
@@ -4219,40 +4219,77 @@ abstract class MetaModel
}
else
{
$aCurrentUser = array();
$aCurrentContact = array();
$aCurrentUser = [];
$aCurrentContact = [];
foreach ($aExpectedArgs as $expression)
{
$aName = explode('->', $expression->GetName());
if ($aName[0] == 'current_contact_id') {
$aPlaceholders['current_contact_id'] = UserRights::GetContactId();
}
if ($aName[0] == 'current_user') {
} else if ($aName[0] == 'current_user') {
array_push($aCurrentUser, $aName[1]);
}
if ($aName[0] == 'current_contact') {
} else if ($aName[0] == 'current_contact') {
array_push($aCurrentContact, $aName[1]);
}
}
if (count($aCurrentUser) > 0) {
$oUser = UserRights::GetUserObject();
$aPlaceholders['current_user->object()'] = $oUser;
foreach ($aCurrentUser as $sField) {
$aPlaceholders['current_user->'.$sField] = $oUser->Get($sField);
}
static::FillObjectPlaceholders($aPlaceholders, 'current_user', UserRights::GetUserObject(), $aCurrentUser);
}
if (count($aCurrentContact) > 0) {
$oPerson = UserRights::GetContactObject();
$aPlaceholders['current_contact->object()'] = $oPerson;
foreach ($aCurrentContact as $sField) {
$aPlaceholders['current_contact->'.$sField] = $oPerson->Get($sField);
}
static::FillObjectPlaceholders($aPlaceholders, 'current_contact', UserRights::GetContactObject(), $aCurrentContact);
}
}
return $aPlaceholders;
}
/**
* @since 3.1.1 N°6824
* @param array $aPlaceholders
* @param string $sPlaceHolderPrefix
* @param ?\DBObject $oObject
* @param array $aCurrentUser
*
* @return void
*
*/
private static function FillObjectPlaceholders(array &$aPlaceholders, string $sPlaceHolderPrefix, ?\DBObject $oObject, array $aCurrentUser) : void {
$sPlaceHolderKey = $sPlaceHolderPrefix."->object()";
if (is_null($oObject)){
$aContext = [
"current_user_id" => UserRights::GetUserId(),
"null object type" => $sPlaceHolderPrefix,
"fields" => $aCurrentUser,
];
IssueLog::Warning("Unresolved placeholders due to null object in current context", null,
$aContext);
$aPlaceholders[$sPlaceHolderKey] = \Dict::Format("Core:Placeholder:CannotBeResolved", $sPlaceHolderKey);
foreach ($aCurrentUser as $sField) {
$sPlaceHolderKey = $sPlaceHolderPrefix . "->$sField";
$aPlaceholders[$sPlaceHolderKey] = \Dict::Format("Core:Placeholder:CannotBeResolved", $sPlaceHolderKey);
}
} else {
$aPlaceholders[$sPlaceHolderKey] = $oObject;
foreach ($aCurrentUser as $sField) {
$sPlaceHolderKey = $sPlaceHolderPrefix . "->$sField";
if (false === MetaModel::IsValidAttCode(get_class($oObject), $sField)){
$aContext = [
"current_user_id" => UserRights::GetUserId(),
"obj_class" => get_class($oObject),
"placeholder" => $sPlaceHolderKey,
"invalid_field" => $sField,
];
IssueLog::Warning("Unresolved placeholder due to invalid attribute", null,
$aContext);
$aPlaceholders[$sPlaceHolderKey] = \Dict::Format("Core:Placeholder:CannotBeResolved", $sPlaceHolderKey);
continue;
}
$aPlaceholders[$sPlaceHolderKey] = $oObject->Get($sField);
}
}
}
/**
* @param \DBSearch $oFilter
*
@@ -6298,6 +6335,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 +6420,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');
@@ -6470,7 +6516,7 @@ abstract class MetaModel
$aCache['m_aExtensionClassNames'] = self::$m_aExtensionClassNames;
$aCache['m_Category2Class'] = self::$m_Category2Class;
$aCache['m_aRootClasses'] = self::$m_aRootClasses; // array of "classname" => "rootclass"
$aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass")
$aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass")
$aCache['m_aChildClasses'] = self::$m_aChildClasses; // array of ("classname" => array of "childclass")
$aCache['m_aClassParams'] = self::$m_aClassParams; // array of ("classname" => array of class information)
$aCache['m_aAttribDefs'] = self::$m_aAttribDefs; // array of ("classname" => array of attributes)
@@ -6495,6 +6541,7 @@ abstract class MetaModel
CMDBSource::InitFromConfig(self::$m_oConfig);
// Later when timezone implementation is correctly done: CMDBSource::SetTimezone($sDBTimezone);
ExecutionKPI::InitStats();
}
/**
@@ -6526,6 +6573,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 +6867,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;
@@ -7567,6 +7623,20 @@ abstract class MetaModel
return false;
}
/**
* @since 3.1.0 N°5324: to ease reentrance checks when using events on links (to avoid reentering if main link object ongoing operation)
*/
public static function GetReentranceObjectByChildClass(string $sParentClass, $sKey)
{
foreach (self::EnumChildClasses($sParentClass, ENUM_CHILD_CLASSES_ALL, false) as $sChildClass){
if (self::GetReentranceObject($sChildClass, $sKey)){
return true;
}
}
return false;
}
/**
* @param \DBObject $oObject
*
@@ -7608,14 +7678,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 +7697,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

@@ -51,4 +51,4 @@ tr.ibo-csv-import--row-added td {
font-size: $ibo-csv-import--download-file--font-size;
color: $ibo-csv-import--download-file--color;
margin: $ibo-csv-import--download-file--margin;
}
}

View File

@@ -17,7 +17,9 @@ $ibo-welcome-popup--text--options--bottom: 10px !default;
#welcome_popup{
display: flex;
}
.ibo-welcome-popup--columns{
display: flex;
}
.ibo-welcome-popup--image{
display: flex;
@@ -44,7 +46,39 @@ $ibo-welcome-popup--text--options--bottom: 10px !default;
}
}
}
.ibo-welcome-popup--text--options{
position: absolute;
bottom: $ibo-welcome-popup--text--options--bottom;
.ibo-welcome-popup--dialog {
width: 60rem;
}
.ibo-welcome-popup--content {
width: 100%;
.ibo-welcome-popup--message {
width: 100%;
min-height: 12rem;
}
.ibo-welcome-popup--button {
width: 100%;
text-align: center;
padding-top: 1rem;
position: absolute;
bottom: 4.5rem;
}
}
.ibo-welcome-popup--indicators {
width: 100%;
display: block;
text-align: center;
padding-top: 1.5rem;
padding-bottom: 0;
height: 3rem;
.ibo-welcome-popup--indicator {
width: 1rem;
height: 1rem;
border-radius: 0.5rem;
background-color: $ibo-color-secondary-600;
display: inline-block;
cursor: pointer;
}
.ibo-welcome-popup--active {
background-color: $ibo-color-information-600 !important;
}
}

File diff suppressed because one or more lines are too long

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

@@ -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.
@@ -34,7 +34,7 @@ class DBRestore extends DBBackup
protected function LogInfo($sMsg)
{
//IssueLog::Info('non juste info: '.$sMsg);
IssueLog::Info('non juste info: '.$sMsg);
}
protected function LogError($sMsg)
@@ -91,7 +91,7 @@ class DBRestore extends DBBackup
{
$this->LogError("mysql said: $sLine");
}
if (count($aOutput) == 1)
if (count($aOutput) == 1)
{
$sMoreInfo = trim($aOutput[0]);
}
@@ -187,7 +187,7 @@ class DBRestore extends DBBackup
@chmod($sConfigFile, 0770); // Allow overwriting the file
rename($sDataDir.'/config-itop.php', $sConfigFile);
@chmod($sConfigFile, 0440); // Read-only
$aExtraFiles = $this->ListExtraFiles($sDataDir);
foreach($aExtraFiles as $sSourceFilePath => $sDestinationFilePath) {
SetupUtils::builddir(dirname($sDestinationFilePath));
@@ -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

@@ -88,7 +88,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
// Errors
'iTopUpdate:Error:MissingFunction' => 'Lehetetlen elindítani a frissítést, hiányzó funkció',
'iTopUpdate:Error:MissingFile' => 'Hiányzó fájl: %1$',
'iTopUpdate:Error:MissingFile' => 'Hiányzó fájl: %1$s',
'iTopUpdate:Error:CorruptedFile' => 'A %1$s fájl sérült',
'iTopUpdate:Error:BadFileFormat' => 'A frissítési fájl nem zip fájl',
'iTopUpdate:Error:BadFileContent' => 'A frissítési fájl nem alkalmazás archívum',

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

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.1">
<itop_design version="3.1">
<classes/>
<user_rights>
<groups>
@@ -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 -->
@@ -544,5 +546,6 @@
<groups/>
</profile>
</profiles>
<dictionaries/>
</user_rights>
</itop_design>

View File

@@ -0,0 +1,13 @@
<?php
/**
* Localized data
*
* @copyright Copyright (C) Combodo SARL 2022
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('EN US', 'English', 'English', array(
'Class:User/NonStandaloneProfileWarning' => 'Profile %1$s cannot be standalone. You should add other profiles to user %2$s otherwise you may encounter access issue with this user.',
'Class:User/NonStandaloneProfileWarning-ReparationMessage' => 'Profile %1$s cannot be standalone. User %2$s has been completed with another profile: %3$s.',
));

View File

@@ -0,0 +1,13 @@
<?php
/**
* Localized data
*
* @copyright Copyright (C) Combodo SARL 2022
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('FR FR', 'French', 'Français', array(
'Class:User/NonStandaloneProfileWarning' => 'Le profil %1$s ne peut être seul. Sans le rajout d\'autres profiles, l\'utilisateur %2$s peut rencontrer des problèmes dans iTop.',
'Class:User/NonStandaloneProfileWarning-ReparationMessage' => 'Le profil %1$s ne peut être seul. Le user %2$s a été complété par le profil %3$s.',
));

View File

@@ -36,6 +36,7 @@ SetupWebPage::AddModule(
// Components
//
'datamodel' => array(
'src/UserProfilesEventListener.php'
),
'webservice' => array(
//'webservices.itop-profiles-itil.php',

View File

@@ -0,0 +1,394 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\ItilProfiles;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\Events\iEventServiceSetup;
use Exception;
use IssueLog;
use LogChannels;
define('POWER_USER_PORTAL_PROFILE_NAME', 'Portal power user');
/**
* Class UserProfilesEventListener
*
* @package Combodo\iTop\Core\EventListener
* @since 3.1 N°5324 - Avoid to have users with non-standalone power portal profile only
*
*/
class UserProfilesEventListener implements iEventServiceSetup
{
const USERPROFILE_REPAIR_ITOP_PARAM_NAME = 'security.single_profile_completion';
private $bIsRepairmentEnabled = false;
//map: non standalone profile name => repairing profile id
private $aNonStandaloneProfilesMap = [];
/**
* @inheritDoc
*/
public function RegisterEventsAndListeners()
{
$this->Init();
if (false === $this->bIsRepairmentEnabled){
IssueLog::Debug('UserProfilesEventListener bIsRepairmentEnabled disabled', LogChannels::DM_CRUD);
return;
}
$aEventSource = [\User::class, \UserExternal::class, \UserInternal::class];
EventService::RegisterListener(
EVENT_DB_BEFORE_WRITE,
[$this, 'OnUserEdition'],
$aEventSource
);
EventService::RegisterListener(
EVENT_DB_BEFORE_WRITE,
[ $this, 'OnUserProfileEdition' ],
[ \URP_UserProfile::class ],
[],
null
);
EventService::RegisterListener(
EVENT_DB_CHECK_TO_DELETE,
[ $this, 'OnUserProfileLinkDeletion' ],
[ \URP_UserProfile::class ],
[],
null
);
}
public function IsRepairmentEnabled() : bool
{
return $this->bIsRepairmentEnabled;
}
public function OnUserEdition(EventData $oEventData): void {
/** @var \User $oObject */
$oUser = $oEventData->Get('object');
try {
$this->ValidateThenRepairOrWarn($oUser);
} catch (Exception $e) {
IssueLog::Error('Exception occurred on RepairProfiles', LogChannels::DM_CRUD, [
'user_class' => get_class($oUser),
'user_id' => $oUser->GetKey(),
'exception_message' => $e->getMessage(),
'exception_stacktrace' => $e->getTraceAsString(),
]);
if ($e instanceof \CoreCannotSaveObjectException){
throw $e;
}
}
}
public function OnUserProfileEdition(EventData $oEventData): void {
$oURP_UserProfile = $oEventData->Get('object');
try {
$iUserId = $oURP_UserProfile->Get('userid');
$oUser = \MetaModel::GetReentranceObjectByChildClass(\User::class, $iUserId);
if (false !== $oUser){
IssueLog::Debug('OnUserProfileEdition user already being edited', LogChannels::DM_CRUD);
//user edition: handled by other event
return;
}
$oUser = \MetaModel::GetObject(\User::class, $iUserId);
$aChanges = $oURP_UserProfile->ListChanges();
if (array_key_exists('userid', $aChanges)) {
IssueLog::Debug('OnUserProfileEdition userid changed', LogChannels::DM_CRUD);
$iUserId = $oURP_UserProfile->GetOriginal('userid');
$oPreviousUser = \MetaModel::GetObject(\User::class, $iUserId);
$oProfileLinkSet = $oPreviousUser->Get('profile_list');
$oProfileLinkSet->Rewind();
$iCount = 0;
$sSingleProfileName = null;
while ($oCurrentURP_UserProfile = $oProfileLinkSet->Fetch()) {
$sNewUserId = $oCurrentURP_UserProfile->Get('userid');
$sOriginalUserId = $oCurrentURP_UserProfile->GetOriginal('userid');
if ($sNewUserId !== $sOriginalUserId) {
$sRemovedProfileId = $oCurrentURP_UserProfile->GetOriginal('profileid');
IssueLog::Debug('OnUserProfileEdition profile moved does not count', LogChannels::DM_CRUD, [
'URP_UserProfile' => $oURP_UserProfile->GetKey(),
'sRemovedProfileId' => $sRemovedProfileId,
'sNewUserId' => $sNewUserId,
'sOriginalUserId' => $sOriginalUserId,
]);
continue;
}
$iCount++;
if ($iCount > 1){
IssueLog::Debug('OnUserProfileEdition more than one user', LogChannels::DM_CRUD);
//more than one profile: no repairment needed
return;
}
$sSingleProfileName = $oCurrentURP_UserProfile->Get('profile');
}
$this->RepairProfileChangesOrWarn($oPreviousUser, $sSingleProfileName, $oURP_UserProfile, $sRemovedProfileId);
} else if (array_key_exists('profileid', $aChanges)){
IssueLog::Debug('OnUserProfileEdition profileid changed', LogChannels::DM_CRUD);
$oCurrentUserProfileSet = $oUser->Get('profile_list');
if ($oCurrentUserProfileSet->Count() === 1){
$oProfile = $oCurrentUserProfileSet->Fetch();
$this->RepairProfileChangesOrWarn($oUser, $oProfile->Get('profile'), $oURP_UserProfile, $oProfile->GetOriginal("profileid"));
}
}
} catch (Exception $e) {
IssueLog::Error('OnUserProfileEdition Exception', LogChannels::DM_CRUD, [
'user_id' => $iUserId,
'lnk_id' => $oURP_UserProfile->GetKey(),
'exception_message' => $e->getMessage(),
'exception_stacktrace' => $e->getTraceAsString(),
]);
if ($e instanceof \CoreCannotSaveObjectException){
throw $e;
}
}
}
public function OnUserProfileLinkDeletion(EventData $oEventData): void {
$oURP_UserProfile = $oEventData->Get('object');
try {
$iUserId = $oURP_UserProfile->Get('userid');
$oUser = \MetaModel::GetReentranceObjectByChildClass(\User::class, $iUserId);
if (false !== $oUser){
IssueLog::Debug('OnUserProfileLinkDeletion user being deleted already', LogChannels::DM_CRUD);
//user edition: handled by other event
return;
}
$oUser = \MetaModel::GetObject(\User::class, $iUserId);
/** @var \DeletionPlan $oDeletionPlan */
$oDeletionPlan = $oEventData->Get('deletion_plan');
$aDeletedURP_UserProfiles = [];
if (! is_null($oDeletionPlan)){
$aListDeletes = $oDeletionPlan->ListDeletes();
if (array_key_exists(\URP_UserProfile::class, $aListDeletes)) {
foreach ($aListDeletes[\URP_UserProfile::class] as $iId => $aDeletes) {
$aDeletedURP_UserProfiles []= $iId;
}
}
}
$oProfileLinkSet = $oUser->Get('profile_list');
$oProfileLinkSet->Rewind();
$sSingleProfileName = null;
$iCount = 0;
while ($oCurrentURP_UserProfile = $oProfileLinkSet->Fetch()) {
if (in_array($oCurrentURP_UserProfile->GetKey(), $aDeletedURP_UserProfiles)) {
continue;
}
$iCount++;
if ($iCount > 1){
IssueLog::Debug('OnUserProfileLinkDeletion more than one profile', LogChannels::DM_CRUD);
//more than one profile: no repairment needed
return;
}
$sSingleProfileName = $oCurrentURP_UserProfile->Get('profile');
}
$this->RepairProfileChangesOrWarn($oUser, $sSingleProfileName, $oURP_UserProfile, $oURP_UserProfile->Get('profileid'), true);
} catch (Exception $e) {
IssueLog::Error('OnUserProfileLinkDeletion Exception', LogChannels::DM_CRUD, [
'user_id' => $iUserId,
'profile_id' => $oURP_UserProfile->Get('profileid'),
'exception_message' => $e->getMessage(),
'exception_stacktrace' => $e->getTraceAsString(),
]);
}
}
/**
* @param $aPortalDispatcherData: passed only for testing purpose
*
* @return void
* @throws \ConfigException
* @throws \CoreException
*/
public function Init($aPortalDispatcherData=null) : void {
if (is_null($aPortalDispatcherData)){
$aPortalDispatcherData = \PortalDispatcherData::GetData();
}
$aNonStandaloneProfiles = \utils::GetConfig()->Get(self::USERPROFILE_REPAIR_ITOP_PARAM_NAME, null);
//When there are several customized portals on an itop, choosing a specific profile means choosing which portal user will access
//In that case, itop administrator has to specify it via itop configuration. we dont use default profiles repairment otherwise
if (is_null($aNonStandaloneProfiles)){
if (count($aPortalDispatcherData) > 2){
IssueLog::Debug('Init repairment disabled as there are more than 2 portals (extended customer should decide on their own)', LogChannels::DM_CRUD);
$this->bIsRepairmentEnabled = false;
return;
}
$aPortalNames = array_keys($aPortalDispatcherData);
sort($aPortalNames);
if ($aPortalNames !== ['backoffice', 'itop-portal']){
IssueLog::Debug('Init repairment disabled there is a custom portal', LogChannels::DM_CRUD, [$aPortalNames]);
$this->bIsRepairmentEnabled = false;
return;
}
}
if (is_null($aNonStandaloneProfiles)){
//default configuration in the case there are no customized portals
$aNonStandaloneProfiles = [ POWER_USER_PORTAL_PROFILE_NAME => PORTAL_PROFILE_NAME ];
}
if (! is_array($aNonStandaloneProfiles)){
\IssueLog::Error(sprintf("%s is badly configured. it should be an array.", self::USERPROFILE_REPAIR_ITOP_PARAM_NAME),LogChannels::DM_CRUD, [self::USERPROFILE_REPAIR_ITOP_PARAM_NAME => $aNonStandaloneProfiles]);
$this->bIsRepairmentEnabled = false;
return;
}
if (empty($aNonStandaloneProfiles)){
//Feature specifically disabled in itop configuration
IssueLog::Debug('Init repairment disabled by conf on purpose', LogChannels::DM_CRUD);
$this->bIsRepairmentEnabled = false;
return;
}
$this->FetchRepairingProfileIds($aNonStandaloneProfiles);
}
public function FetchRepairingProfileIds(array $aNonStandaloneProfiles) : void {
$aProfiles = [];
try {
$aProfilesToSearch = array_unique(array_values($aNonStandaloneProfiles));
if(($iIndex = array_search(null, $aProfilesToSearch)) !== false) {
unset($aProfilesToSearch[$iIndex]);
}
if (1 === count($aProfilesToSearch)){
$sInCondition = sprintf('"%s"', array_pop($aProfilesToSearch));
} else {
$sInCondition = sprintf('"%s"', implode('","', $aProfilesToSearch));
}
$sOql = "SELECT URP_Profiles WHERE name IN ($sInCondition)";
$oSearch = \DBSearch::FromOQL($sOql);
$oSearch->AllowAllData();
$oSet = new \DBObjectSet($oSearch);
while(($oProfile = $oSet->Fetch()) != null) {
$sProfileName = $oProfile->Get('name');
$aProfiles[$sProfileName] = $oProfile->GetKey();
}
$this->aNonStandaloneProfilesMap = [];
foreach ($aNonStandaloneProfiles as $sNonStandaloneProfileName => $sRepairProfileName) {
if (is_null($sRepairProfileName)) {
$this->aNonStandaloneProfilesMap[$sNonStandaloneProfileName] = null;
continue;
}
if (! array_key_exists($sRepairProfileName, $aProfiles)) {
throw new \Exception(sprintf("%s is badly configured. profile $sRepairProfileName does not exist.", self::USERPROFILE_REPAIR_ITOP_PARAM_NAME));
}
$this->aNonStandaloneProfilesMap[$sNonStandaloneProfileName] = [ 'name' => $sRepairProfileName, 'id' => $aProfiles[$sRepairProfileName]];
}
$this->bIsRepairmentEnabled = true;
} catch (\Exception $e) {
IssueLog::Error('Exception when searching user portal profile', LogChannels::DM_CRUD, [
'exception_message' => $e->getMessage(),
'exception_stacktrace' => $e->getTraceAsString(),
'aProfiles' => $aProfiles,
'aNonStandaloneProfiles' => $aNonStandaloneProfiles,
]);
$this->bIsRepairmentEnabled = false;
}
}
public function ValidateThenRepairOrWarn(\User $oUser) : void
{
$oCurrentUserProfileSet = $oUser->Get('profile_list');
if ($oCurrentUserProfileSet->Count() === 1){
IssueLog::Debug('ValidateThenRepairOrWarn one profile found', LogChannels::DM_CRUD);
$oProfile = $oCurrentUserProfileSet->Fetch();
$this->RepairUserChangesOrWarn($oUser, $oProfile->Get('profile'));
}
}
public function RepairUserChangesOrWarn(\User $oUser, string $sSingleProfileName) : void {
IssueLog::Debug('RepairUserChangesOrWarn', LogChannels::DM_CRUD,
[
'aNonStandaloneProfilesMap' => $this->aNonStandaloneProfilesMap,
'sSingleProfileName' => $sSingleProfileName
]
);
if (array_key_exists($sSingleProfileName, $this->aNonStandaloneProfilesMap)) {
$aRepairingProfileInfo = $this->aNonStandaloneProfilesMap[$sSingleProfileName];
if (is_null($aRepairingProfileInfo)){
$sMessage = \Dict::Format("Class:User/NonStandaloneProfileWarning", $sSingleProfileName, $oUser->Get('friendlyname'));
throw new \CoreCannotSaveObjectException(array('issues' => [$sMessage], 'class' => get_class($oUser), 'id' => $oUser->GetKey()));
} else {
//Completing profiles profiles by adding repairing one : by default portal user to a power portal user
$oUserProfile = new \URP_UserProfile();
$oUserProfile->Set('profileid', $aRepairingProfileInfo['id']);
$oCurrentUserProfileSet = $oUser->Get('profile_list');
$oCurrentUserProfileSet->AddItem($oUserProfile);
$oUser->Set('profile_list', $oCurrentUserProfileSet);
$sMessage = \Dict::Format("Class:User/NonStandaloneProfileWarning-ReparationMessage", $sSingleProfileName, $oUser->Get('friendlyname'), $aRepairingProfileInfo['name']);
$oUser::SetSessionMessage(get_class($oUser), $oUser->GetKey(), 1, $sMessage, 'WARNING', 1);
}
}
}
public function RepairProfileChangesOrWarn(\User $oUser, ?string $sSingleProfileName, \URP_UserProfile $oURP_UserProfile, string $sRemovedProfileId, $bIsRemoval=false) : void {
IssueLog::Debug('RepairUserChangesOrWarn', LogChannels::DM_CRUD,
[
'aNonStandaloneProfilesMap' => $this->aNonStandaloneProfilesMap,
'sSingleProfileName' => $sSingleProfileName
]
);
if (is_null($sSingleProfileName)){
return;
}
if (array_key_exists($sSingleProfileName, $this->aNonStandaloneProfilesMap)) {
$aRepairingProfileInfo = $this->aNonStandaloneProfilesMap[$sSingleProfileName];
if (is_null($aRepairingProfileInfo)
|| ($aRepairingProfileInfo['id'] === $sRemovedProfileId) //cannot repair by readding same remove profile as it will raise uniqueness rule
){
$sMessage = \Dict::Format("Class:User/NonStandaloneProfileWarning", $sSingleProfileName, $oUser->Get('friendlyname'));
if ($bIsRemoval){
$oURP_UserProfile->AddDeleteIssue($sMessage);
} else {
throw new \CoreCannotSaveObjectException(array('issues' => [$sMessage], 'class' => get_class($oURP_UserProfile), 'id' => $oURP_UserProfile->GetKey()));
}
} else {
//Completing profiles profiles by adding repairing one : by default portal user to a power portal user
$oUserProfile = new \URP_UserProfile();
$oUserProfile->Set('profileid', $aRepairingProfileInfo['id']);
$oCurrentUserProfileSet = $oUser->Get('profile_list');
$oCurrentUserProfileSet->AddItem($oUserProfile);
$oUser->Set('profile_list', $oCurrentUserProfileSet);
$oUser->DBWrite();
$sMessage = \Dict::Format("Class:User/NonStandaloneProfileWarning-ReparationMessage", $sSingleProfileName, $oUser->Get('friendlyname'), $aRepairingProfileInfo['name']);
$oURP_UserProfile::SetSessionMessage(get_class($oURP_UserProfile), $oURP_UserProfile->GetKey(), 1, $sMessage, 'WARNING', 1);
}
}
}
}

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

@@ -49,6 +49,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:AttributeTagSet' => 'List of tags',
'Core:AttributeTagSet+' => '',
'Core:AttributeSet:placeholder' => 'click to add',
'Core:Placeholder:CannotBeResolved' => '(%1$s : cannot be resolved)',
'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)',

View File

@@ -452,6 +452,7 @@ Dict::Add('EN US', 'English', 'English', array(
We hope youll enjoy this version as much as we enjoyed imagining and creating it.</div>
<div>Customize your '.ITOP_APPLICATION.' preferences for a personalized experience.</div>',
'UI:WelcomePopup:Button:Acknowledge' => 'Ok, discard this message',
'UI:WelcomeMenu:AllOpenRequests' => 'Open requests: %1$d',
'UI:WelcomeMenu:MyCalls' => 'My requests',
'UI:WelcomeMenu:OpenIncidents' => 'Open incidents: %1$d',
@@ -837,7 +838,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

@@ -444,6 +444,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
Esperamos distrute de esta versión tanto como nosotros la imaginamos y creamos.</div>
<div>Configure las preferencias de '.ITOP_APPLICATION.' para una experiencia personalizada.</div>',
'UI:WelcomePopup:Button:Acknowledge' => 'Ok, descartar este mensaje',
'UI:WelcomeMenu:AllOpenRequests' => 'Requerimientos Abiertos: %1$d',
'UI:WelcomeMenu:MyCalls' => 'Mis Requerimientos',
'UI:WelcomeMenu:OpenIncidents' => 'Incidentes Abiertos: %1$d',

View File

@@ -39,6 +39,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Core:AttributeTagSet' => 'Liste d\'étiquettes',
'Core:AttributeTagSet+' => '',
'Core:AttributeSet:placeholder' => 'cliquer pour ajouter',
'Core:Placeholder:CannotBeResolved' => '(%1$s : non remplacé)',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s de la classe %3$s)',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s d\'une sous-classe)',

View File

@@ -433,6 +433,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
Nous espérons que vous aimerez cette version autant que nous avons eu du plaisir à l\'imaginer et à la créer.</div>
<div>Configurez vos préférences '.ITOP_APPLICATION.' pour une expérience personnalisée.</div>',
'UI:WelcomePopup:Button:Acknowledge' => 'Ok, supprimer ce message',
'UI:WelcomeMenu:AllOpenRequests' => 'Requêtes en cours: %1$d',
'UI:WelcomeMenu:MyCalls' => 'Mes appels support',
'UI:WelcomeMenu:OpenIncidents' => 'Incidents en cours: %1$d',

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -332,11 +332,14 @@ $(function()
oParams.dashlet_class = sDashletClass;
oParams.dashlet_id = sDashletId;
oParams.dashlet_type = options.dashlet_type;
oParams.ajax_promise_id = 'ajax_promise_' + sDashletId;
var me = this;
$.post(this.options.render_to, oParams, function (data) {
me.ajax_div.html(data);
me.add_dashlet_finalize(options, sDashletId, sDashletClass);
me.mark_as_modified();
window[oParams.ajax_promise_id].then(function(){
me.add_dashlet_finalize(options, sDashletId, sDashletClass);
me.mark_as_modified();
});
});
},
on_dashlet_moved: function (oDashlet, oReceiver, bRefresh) {

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

@@ -14,6 +14,7 @@ return array(
'AbstractPortalUIExtension' => $baseDir . '/application/applicationextension.inc.php',
'AbstractPreferencesExtension' => $baseDir . '/application/applicationextension.inc.php',
'AbstractWeeklyScheduledProcess' => $baseDir . '/core/backgroundprocess.inc.php',
'AbstractWelcomePopup' => $baseDir . '/application/applicationextension.inc.php',
'Action' => $baseDir . '/core/action.class.inc.php',
'ActionChecker' => $baseDir . '/core/userrights.class.inc.php',
'ActionEmail' => $baseDir . '/core/action.class.inc.php',
@@ -363,6 +364,8 @@ return array(
'Combodo\\iTop\\Application\\UI\\Links\\Set\\LinkSetUIBlockFactory' => $baseDir . '/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => $baseDir . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php',
'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => $baseDir . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php',
'Combodo\\iTop\\Application\\WelcomePopup\\DefaultWelcomePopup' => $baseDir . '/sources/Application/WelcomePopup/DefaultWelcomePopup.php',
'Combodo\\iTop\\Application\\WelcomePopup\\WelcomePopupService' => $baseDir . '/sources/Application/WelcomePopup/WelcomePopupService.php',
'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php',
'Combodo\\iTop\\Controller\\AbstractController' => $baseDir . '/sources/Controller/AbstractController.php',
'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php',
@@ -372,6 +375,7 @@ return array(
'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => $baseDir . '/sources/Controller/OAuth/OAuthLandingController.php',
'Combodo\\iTop\\Controller\\PreferencesController' => $baseDir . '/sources/Controller/PreferencesController.php',
'Combodo\\iTop\\Controller\\TemporaryObjects\\TemporaryObjectController' => $baseDir . '/sources/Controller/TemporaryObjects/TemporaryObjectController.php',
'Combodo\\iTop\\Controller\\WelcomePopupController' => $baseDir . '/sources/Controller/WelcomePopupController.php',
'Combodo\\iTop\\Controller\\iController' => $baseDir . '/sources/Controller/iController.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php',
@@ -383,6 +387,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 +466,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',
@@ -470,6 +476,8 @@ return array(
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectManager' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectManager.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectRepository' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectRepository.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectsEvents' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectsEvents.php',
'Combodo\\iTop\\SessionTracker\\SessionGC' => $baseDir . '/sources/SessionTracker/SessionGC.php',
'Combodo\\iTop\\SessionTracker\\SessionHandler' => $baseDir . '/sources/SessionTracker/SessionHandler.php',
'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Config' => $baseDir . '/core/config.class.inc.php',
@@ -2952,6 +2960,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',
@@ -2982,6 +2991,7 @@ return array(
'iTopWebPage' => $baseDir . '/sources/Application/WebPage/iTopWebPage.php',
'iTopWizardWebPage' => $baseDir . '/sources/Application/WebPage/iTopWizardWebPage.php',
'iTopXmlException' => $baseDir . '/application/exceptions/iTopXmlException.php',
'iWelcomePopup' => $baseDir . '/application/applicationextension.inc.php',
'iWorkingTimeComputer' => $baseDir . '/core/computing.inc.php',
'lnkAuditCategoryToAuditDomain' => $baseDir . '/application/audit.domain.class.inc.php',
'lnkTriggerAction' => $baseDir . '/core/trigger.class.inc.php',

View File

@@ -378,6 +378,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'AbstractPortalUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'AbstractPreferencesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'AbstractWeeklyScheduledProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
'AbstractWelcomePopup' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'Action' => __DIR__ . '/../..' . '/core/action.class.inc.php',
'ActionChecker' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',
'ActionEmail' => __DIR__ . '/../..' . '/core/action.class.inc.php',
@@ -727,6 +728,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Application\\UI\\Links\\Set\\LinkSetUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => __DIR__ . '/../..' . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php',
'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => __DIR__ . '/../..' . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php',
'Combodo\\iTop\\Application\\WelcomePopup\\DefaultWelcomePopup' => __DIR__ . '/../..' . '/sources/Application/WelcomePopup/DefaultWelcomePopup.php',
'Combodo\\iTop\\Application\\WelcomePopup\\WelcomePopupService' => __DIR__ . '/../..' . '/sources/Application/WelcomePopup/WelcomePopupService.php',
'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php',
'Combodo\\iTop\\Controller\\AbstractController' => __DIR__ . '/../..' . '/sources/Controller/AbstractController.php',
'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php',
@@ -736,6 +739,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthLandingController.php',
'Combodo\\iTop\\Controller\\PreferencesController' => __DIR__ . '/../..' . '/sources/Controller/PreferencesController.php',
'Combodo\\iTop\\Controller\\TemporaryObjects\\TemporaryObjectController' => __DIR__ . '/../..' . '/sources/Controller/TemporaryObjects/TemporaryObjectController.php',
'Combodo\\iTop\\Controller\\WelcomePopupController' => __DIR__ . '/../..' . '/sources/Controller/WelcomePopupController.php',
'Combodo\\iTop\\Controller\\iController' => __DIR__ . '/../..' . '/sources/Controller/iController.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php',
@@ -747,6 +751,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 +830,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',
@@ -834,6 +840,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectManager' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectManager.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectRepository' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectRepository.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectsEvents' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectsEvents.php',
'Combodo\\iTop\\SessionTracker\\SessionGC' => __DIR__ . '/../..' . '/sources/SessionTracker/SessionGC.php',
'Combodo\\iTop\\SessionTracker\\SessionHandler' => __DIR__ . '/../..' . '/sources/SessionTracker/SessionHandler.php',
'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',
@@ -3316,6 +3324,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',
@@ -3346,6 +3355,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'iTopWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWebPage.php',
'iTopWizardWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWizardWebPage.php',
'iTopXmlException' => __DIR__ . '/../..' . '/application/exceptions/iTopXmlException.php',
'iWelcomePopup' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iWorkingTimeComputer' => __DIR__ . '/../..' . '/core/computing.inc.php',
'lnkAuditCategoryToAuditDomain' => __DIR__ . '/../..' . '/application/audit.domain.class.inc.php',
'lnkTriggerAction' => __DIR__ . '/../..' . '/core/trigger.class.inc.php',

View File

@@ -5,7 +5,7 @@
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'dbf3393c9729a20f0bf389d343507238d61fef56',
'reference' => '204a6d8e51619cd669c6d9d7722edaa36d1c3394',
'name' => 'combodo/itop',
'dev' => true,
),
@@ -25,7 +25,7 @@
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'dbf3393c9729a20f0bf389d343507238d61fef56',
'reference' => '204a6d8e51619cd669c6d9d7722edaa36d1c3394',
'dev_requirement' => false,
),
'combodo/tcpdf' => array(

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

@@ -20,6 +20,7 @@ use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Controller\Base\Layout\ObjectController;
use Combodo\iTop\Service\Router\Router;
use Combodo\iTop\Application\WelcomePopup\WelcomePopupService;
/**
* Displays a popup welcome message, once per session at maximum
@@ -29,19 +30,18 @@ use Combodo\iTop\Service\Router\Router;
*
* @return void
*/
function DisplayWelcomePopup(WebPage $oP)
function DisplayWelcomePopup(WebPage $oP): void
{
if (!Session::IsSet('welcome'))
{
// Check, only once per session, if the popup should be displayed...
// If the user did not already ask for hiding it forever
$bPopup = appUserPreferences::GetPref('welcome_popup', true);
if ($bPopup)
$oWelcomePopupService = new WelcomePopupService();
$aMessages = $oWelcomePopupService->GetMessages();
if (count($aMessages) > 0)
{
TwigHelper::RenderIntoPage($oP, APPROOT.'/', 'templates/pages/backoffice/welcome_popup/welcome_popup');
Session::Set('welcome', 'ok');
TwigHelper::RenderIntoPage($oP, APPROOT.'/', 'templates/pages/backoffice/welcome_popup/welcome_popup', ['messages' => $aMessages]);
}
}
Session::Set('welcome', 'ok'); // Try just once per session
}
}
/**
@@ -66,7 +66,7 @@ function ApplyNextAction(Webpage $oP, CMDBObject $oObj, $sNextAction)
}
// Get the list of missing mandatory fields for the target state, considering only the changes from the previous form (i.e don't prompt twice)
$aExpectedAttributes = $oObj->GetTransitionAttributes($sNextAction);
if (count($aExpectedAttributes) == 0)
{
// If all the mandatory fields are already present, just apply the transition silently...
@@ -85,7 +85,7 @@ function ApplyNextAction(Webpage $oP, CMDBObject $oObj, $sNextAction)
// redirect to the 'stimulus' action
$oAppContext = new ApplicationContext();
//echo "<p>Missing Attributes <pre>".print_r($aExpectedAttributes, true)."</pre></p>\n";
$oP->add_header('Location: '.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=stimulus&class='.get_class($oObj).'&stimulus='.$sNextAction.'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink());
}
}
@@ -243,7 +243,7 @@ function DisplayMultipleSelectionForm(WebPage $oP, DBSearch $oFilter, string $sN
$aExtraParams['surround_with_panel'] = true;
if(array_key_exists('icon', $aDisplayParams)){
$aExtraParams['panel_icon'] = $aDisplayParams['icon'];
}
}
if(array_key_exists('title', $aDisplayParams)){
$aExtraParams['panel_title'] = $aDisplayParams['title'];
}
@@ -292,7 +292,7 @@ function DisplayNavigatorGroupTab($oP)
}
/***********************************************************************************
*
*
* Main user interface page starts here
*
***********************************************************************************/
@@ -700,7 +700,7 @@ try
break;
///////////////////////////////////////////////////////////////////////////////////////////
/** @deprecated 3.1.0 Use the "object.new" route instead */
// Kept for backward compatibility
case 'new': // Form to create a new object
@@ -1638,4 +1638,4 @@ class UI
);
cmdbAbstractObject::DoBulkModify($oP, $sClass, $aSelectedObj, 'preview_or_modify_all', $bPreview, $sCancelUrl, $aContext);
}
}
}

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

@@ -17,6 +17,7 @@ use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
use Combodo\iTop\Service\Router\Router;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
use Combodo\iTop\Controller\WelcomePopupController;
require_once('../approot.inc.php');
@@ -68,7 +69,7 @@ try
break;
default:
ContextTag::AddContext(ContextTag::TAG_CONSOLE);
$oTag = new ContextTag(ContextTag::TAG_CONSOLE);
}
// First check if we can redirect the route to a dedicated controller
@@ -201,10 +202,10 @@ try
$oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates);
$oAppContext = new ApplicationContext();
$aPrefillFormParam = array(
'user' => Session::Get("auth_user"),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'user' => Session::Get("auth_user"),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'source_obj' => $oObj
);
$aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array());
@@ -276,10 +277,10 @@ try
$oPage->SetContentType('text/html');
$oAppContext = new ApplicationContext();
$aPrefillFormParam = array(
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'source_obj' => $oObj,
);
$aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass());
@@ -303,10 +304,10 @@ try
}
$oAppContext = new ApplicationContext();
$aPrefillFormParam = array(
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'source_obj' => $oObj,
);
$aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass());
@@ -418,10 +419,10 @@ try
$iInputId = utils::ReadParam('iInputId', '');
$sAttCode = utils::ReadParam('sAttCode', '');
$sJson = utils::ReadParam('json', '', false, 'raw_data');
$bTargetClassSelected = utils::ReadParam('bTargetClassSelected', '', false, 'raw_data');
// Building form, if target class has child classes we ask the user for the desired leaf class, unless we've already done just that
$bTargetClassSelected = utils::ReadParam('bTargetClassSelected', '', false, 'raw_data');
// Building form, if target class has child classes we ask the user for the desired leaf class, unless we've already done just that
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false);
if(!$bTargetClassSelected && MetaModel::HasChildrenClasses($sTargetClass)){
if (!$bTargetClassSelected && MetaModel::HasChildrenClasses($sTargetClass)) {
$oWidget->GetClassSelectionForm($oPage);
} else {
$aPrefillFormParam = array();
@@ -430,11 +431,11 @@ try
$oObj = $oWizardHelper->GetTargetObject();
$oAppContext = new ApplicationContext();
$aPrefillFormParam = array(
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'source_obj' => $oObj,
'origin' => 'console'
'origin' => 'console'
);
} else {
// Search form: no current object
@@ -527,7 +528,8 @@ try
$oObj->Set($sAttCode, $defaultValue);
}
$sFormPrefix = $oWizardHelper->GetFormPrefix();
$aExpectedAttributes = ($oWizardHelper->GetStimulus() === null) ? array() : $oObj->GetTransitionAttributes($oWizardHelper->GetStimulus(), $oWizardHelper->GetInitialState());
$aExpectedAttributes = ($oWizardHelper->GetStimulus() === null) ? array() : $oObj->GetTransitionAttributes($oWizardHelper->GetStimulus(),
$oWizardHelper->GetInitialState());
foreach ($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) {
$sId = $oWizardHelper->GetIdForField($sAttCode);
if ($sId != '') {
@@ -573,7 +575,8 @@ try
$sTargetState = utils::ReadParam('target_state', '');
$iTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id');
$oObj->Set(MetaModel::GetStateAttributeCode($sClass), $sTargetState);
cmdbAbstractObject::DisplayCreationForm($oPage, $sClass, $oObj, array(), array('action' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php', 'transaction_id' => $iTransactionId));
cmdbAbstractObject::DisplayCreationForm($oPage, $sClass, $oObj, array(),
array('action' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php', 'transaction_id' => $iTransactionId));
break;
// DisplayBlock
@@ -600,8 +603,7 @@ try
} else {
try {
$oFilter = DBSearch::unserialize($sFilter);
}
catch (CoreException $e) {
} catch (CoreException $e) {
$sFilter = utils::HtmlEntities($sFilter);
$oPage->p("Invalid query (invalid filter) : <code>$sFilter</code>");
IssueLog::Error("ajax.render operation='ajax', invalid DBSearch filter param : $sFilter");
@@ -666,7 +668,8 @@ try
$aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs);
$aResult['js'] = 'charts['.$iRefresh.'].load({json: '.str_replace('"', '\'', $oBlock->sJson).
',keys: { x: \'label\', value: [\'value\']'.
'},onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'', $oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})';
'},onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'',
$oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})';
break;
case 'pie':
@@ -677,7 +680,8 @@ try
$aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs);
$aResult['js'] = 'charts['.$iRefresh.'].load({columns: '.str_replace('"', '\'', $oBlock->sJSColumns).
',names: '.str_replace('"', '\'', $oBlock->sJSNames).
',onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'', $oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})';
',onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'',
$oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})';
break;
}
} else {
@@ -777,14 +781,14 @@ try
$oFilter = new DBObjectSearch($sClass);
$oSet = new CMDBObjectSet($oFilter);
$sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array(
'currentId' => $currentId,
'baseClass' => $sRootClass,
'action' => $sAction,
'table_id' => $sTableId,
'selection_mode' => $sSelectionMode,
'currentId' => $currentId,
'baseClass' => $sRootClass,
'action' => $sAction,
'table_id' => $sTableId,
'selection_mode' => $sSelectionMode,
'result_list_outer_selector' => $sResultListOuterSelector,
'cssCount' => $scssCount,
'table_inner_id' => $sTableInnerId
'cssCount' => $scssCount,
'table_inner_id' => $sTableInnerId
));
$oPage->add($sHtml);
break;
@@ -821,13 +825,13 @@ try
TemporaryObjectManager::GetInstance()->CancelAllTemporaryObjects($iTransactionId);
IssueLog::Trace('on_form_cancel', $sObjClass, array(
'$iObjKey' => $iObjKey,
'$iObjKey' => $iObjKey,
'$sTransactionId' => $iTransactionId,
'$sTempId' => $sTempId,
'$sToken' => $sToken,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
'$sTempId' => $sTempId,
'$sToken' => $sToken,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
break;
@@ -879,11 +883,9 @@ try
$oDoc = utils::ReadPostedDocument('dashboard_upload_file');
$oDashboard->FromXml($oDoc->GetData());
$oDashboard->Save();
}
catch (DOMException $e) {
} catch (DOMException $e) {
$aResult = array('error' => Dict::S('UI:Error:InvalidDashboardFile'));
}
catch (Exception $e) {
} catch (Exception $e) {
$aResult = array('error' => $e->getMessage());
}
} else {
@@ -1054,7 +1056,8 @@ EOF
$oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); // in ajax web page add_script has the same effect as add_ready_script
// but is executed BEFORE all 'ready_scripts'
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property'));
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php',
array('operation' => 'update_dashlet_property'));
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, '.itop-dashboard'));
$sHtml = str_replace("\n", '', $sHtml);
$sHtml = str_replace("\r", '', $sHtml);
@@ -1113,7 +1116,8 @@ EOF
}
if ($oDashlet->IsFormRedrawNeeded()) {
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php',
array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true, '.itop-dashboard'));
$sHtml = str_replace("\n", '', $sHtml);
$sHtml = str_replace("\r", '', $sHtml);
@@ -1359,7 +1363,8 @@ JS
}
$sFullTextJS = addslashes($sFullText);
$bEnableEnlarge = array_key_exists($sClassName, $aAccelerators) && array_key_exists('query', $aAccelerators[$sClassName]);
$bEnableEnlarge = array_key_exists($sClassName, $aAccelerators) && array_key_exists('query',
$aAccelerators[$sClassName]);
if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('enable_enlarge', $aAccelerators[$sClassName])) {
$bEnableEnlarge &= $aAccelerators[$sClassName]['enable_enlarge'];
}
@@ -1393,9 +1398,11 @@ EOF;
$oPage->add("<div class=\"search-class-result search-class-$sClassName\">\n");
$oPage->add("<div class=\"page_header\">\n");
if (array_key_exists($sClassName, $aAccelerators)) {
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName)).$sEnlargeButton."</h2>\n");
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found',
count($aLeafs), Metamodel::GetName($sClassName)).$sEnlargeButton."</h2>\n");
} else {
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName))."</h2>\n");
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found',
count($aLeafs), Metamodel::GetName($sClassName))."</h2>\n");
}
$oPage->add("</div>\n");
$oLeafsFilter->AddCondition('id', $aLeafs, 'IN');
@@ -1411,7 +1418,8 @@ EOF;
if (array_key_exists($sClassName, $aAccelerators)) {
$oPage->add("<div class=\"search-class-result search-class-$sClassName\">\n");
$oPage->add("<div class=\"page_header\">\n");
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', 0, Metamodel::GetName($sClassName)).$sEnlargeButton."</h2>\n");
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found',
0, Metamodel::GetName($sClassName)).$sEnlargeButton."</h2>\n");
$oPage->add("</div>\n");
$oPage->add("</div>\n");
$oPage->p('&nbsp;'); // Some space ?
@@ -1491,7 +1499,8 @@ EOF
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
$oSet = new DBObjectSet($oFilter);
$oPage->add("<div class=\"page_header\">\n");
$oPage->add("<h2>".MetaModel::GetClassIcon($sClass)."&nbsp;<span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sClass))."</h2>\n");
$oPage->add("<h2>".MetaModel::GetClassIcon($sClass)."&nbsp;<span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found',
$oSet->Count(), Metamodel::GetName($sClass))."</h2>\n");
$oPage->add("</div>\n");
if ($oSet->Count() > 0) {
$aLeafs = array();
@@ -1579,10 +1588,11 @@ EOF
$oPage->add('<div class="statistics"><div class="stats-toggle closed">'.Dict::S('ExcelExport:Statistics').'<div class="stats-data"></div></div></div>');
$oPage->add('</div>');
$aLabels = array(
'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'),
'cancel_button' => Dict::S('UI:Button:Cancel'),
'export_button' => Dict::S('ExcelExporter:ExportButton'),
'download_button' => Dict::Format('ExcelExporter:DownloadButton', 'export.xlsx'), //TODO: better name for the file (based on the class of the filter??)
'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'),
'cancel_button' => Dict::S('UI:Button:Cancel'),
'export_button' => Dict::S('ExcelExporter:ExportButton'),
'download_button' => Dict::Format('ExcelExporter:DownloadButton', 'export.xlsx'),
//TODO: better name for the file (based on the class of the filter??)
);
$sJSLabels = json_encode($aLabels);
$sFilter = addslashes($sFilter);
@@ -1694,7 +1704,8 @@ EOF
if ($sDirection == 'up') {
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
} else {
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts);
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects,
$aContexts);
}
// Remove excluded classes from the graph
@@ -1754,9 +1765,11 @@ EOF
$sIconUrl = MetaModel::GetClassIcon($sListClass, false);
$sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl);
$oTitle = new Html("<img src=\"$sIconUrl\" style=\"vertical-align:middle;width: 24px; height: 24px;\"/> ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass)));*/
$oTitle = new Html(Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass)));
$oTitle = new Html(Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(),
Metamodel::GetName($sListClass)));
$oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2));
$oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet, array('table_id' => $sSourceClass.'_'.$sRelation.'_'.$sDirection.'_'.$sListClass)));
$oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet,
array('table_id' => $sSourceClass.'_'.$sRelation.'_'.$sDirection.'_'.$sListClass)));
}
// Then the content of the groups (one table per group)
@@ -1768,8 +1781,10 @@ EOF
$sListClass = get_class(current($aObjects));
$oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
$sIconUrl = MetaModel::GetClassIcon($sListClass, false);
$sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl);
$oTitle = new Html("<img src=\"$sIconUrl\" style=\"vertical-align:middle;width: 24px; height: 24px;\"/> ".Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)), Metamodel::GetName($sListClass));
$sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/',
$sIconUrl);
$oTitle = new Html("<img src=\"$sIconUrl\" style=\"vertical-align:middle;width: 24px; height: 24px;\"/> ".Dict::Format('UI:RelationGroupNumber_N',
(1 + $idx)), Metamodel::GetName($sListClass));
$oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2));
$oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet));
@@ -1843,7 +1858,8 @@ EOF
if ($sDirection == 'up') {
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
} else {
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts);
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects,
$aContexts);
}
// Remove excluded classes from the graph
@@ -1874,13 +1890,15 @@ EOF
$oSearch = new DBObjectSearch($sListClass);
$oSearch->AddCondition('id', $aDefinition['keys'], 'IN');
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral(Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)), 1, "relation_group_$idx"));
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral(Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)), 1,
"relation_group_$idx"));
$oBlock = new DisplayBlock($oSearch, 'list');
$oBlock->Display($oPage, 'group_'.$iBlock++, array(
'surround_with_panel' => true,
'panel_class' => $sListClass,
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aDefinition['keys']), Metamodel::GetName($sListClass)),
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
'panel_class' => $sListClass,
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aDefinition['keys']),
Metamodel::GetName($sListClass)),
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
));
}
break;
@@ -1894,11 +1912,12 @@ EOF
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
$oBlock = new DisplayBlock($oSearch, 'list');
$oBlock->Display($oPage, 'list_'.$iBlock++, array(
'table_id' => 'ImpactAnalysis_'.$sListClass,
'table_id' => 'ImpactAnalysis_'.$sListClass,
'surround_with_panel' => true,
'panel_class' => $sListClass,
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aKeys), Metamodel::GetName($sListClass)),
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
'panel_class' => $sListClass,
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aKeys),
Metamodel::GetName($sListClass)),
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
));
}
break;
@@ -1950,7 +1969,8 @@ EOF
$sContextKey = 'itop-tickets/relation_context/'.$sClass.'/'.$sRelation.'/'.$sDirection;
$oAppContext = new ApplicationContext();
$oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId, $sContextKey, array('this' => $oTicket));
$oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId, $sContextKey,
array('this' => $oTicket));
break;
case 'export_build':
@@ -2000,7 +2020,7 @@ EOF
$aLockData = iTopOwnershipLock::IsLocked($sObjClass, $iObjKey);
$aResult = [
'locked' => $aLockData['locked'],
'locked' => $aLockData['locked'],
'message' => '',
];
@@ -2087,11 +2107,11 @@ EOF
$aResult = array(
'uploaded' => 0,
'fileName' => '',
'url' => '',
'icon' => '',
'msg' => '',
'att_id' => 0,
'preview' => 'false',
'url' => '',
'icon' => '',
'msg' => '',
'att_id' => 0,
'preview' => 'false',
);
$sObjClass = stripslashes(utils::ReadParam('obj_class', '', false, 'class'));
@@ -2126,20 +2146,19 @@ EOF
}
IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array(
'$operation' => $operation,
'$aResult' => $aResult,
'secret' => $oAttachment->Get('secret'),
'temp_id' => $sTempId,
'item_class' => $sObjClass,
'user' => UserRights::GetUser(),
'$operation' => $operation,
'$aResult' => $aResult,
'secret' => $oAttachment->Get('secret'),
'temp_id' => $sTempId,
'item_class' => $sObjClass,
'user' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
} else {
$aResult['error'] = $oDoc->GetFileName().' is not a valid image format.';
}
}
catch (FileUploadException $e) {
} catch (FileUploadException $e) {
$aResult['error'] = $e->GetMessage();
}
}
@@ -2156,8 +2175,8 @@ EOF
if (!InlineImage::IsImage($sDocMimeType)) {
LogErrorMessage('CKE : error when uploading image in ajax.render.php, not an image',
array(
'operation' => 'cke_upload_and_browse',
'class' => $sObjClass,
'operation' => 'cke_upload_and_browse',
'class' => $sObjClass,
'ImgMimeType' => $sDocMimeType,
));
} else {
@@ -2174,22 +2193,21 @@ EOF
$iAttId = $oAttachment->DBInsert();
IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array(
'$operation' => $operation,
'secret' => $oAttachment->Get('secret'),
'temp_id' => $sTempId,
'item_class' => $sObjClass,
'user' => UserRights::GetUser(),
'$operation' => $operation,
'secret' => $oAttachment->Get('secret'),
'temp_id' => $sTempId,
'item_class' => $sObjClass,
'user' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
}
}
catch (FileUploadException $e) {
} catch (FileUploadException $e) {
LogErrorMessage('CKE : error when uploading image in ajax.render.php, exception occured',
array(
'operation' => 'cke_upload_and_browse',
'class' => $sObjClass,
'operation' => 'cke_upload_and_browse',
'class' => $sObjClass,
'exceptionMsg' => $e,
));
}
@@ -2303,7 +2321,8 @@ $('.img-picker').magnificPopup({type: 'image', closeOnContentClick: true });
EOF
);
$sOQL = "SELECT InlineImage WHERE ((temp_id = :temp_id) OR (item_class = :obj_class AND item_id = :obj_id))";
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('temp_id' => $sTempId, 'obj_class' => $sClass, 'obj_id' => $iObjectId));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(),
array('temp_id' => $sTempId, 'obj_class' => $sClass, 'obj_id' => $iObjectId));
$oPage->add("<div><fieldset><legend>$sAvailableImagesLegend</legend>");
if ($oSet->Count() == 0) {
@@ -2360,7 +2379,8 @@ EOF
$aTriggerMentionedSearches = [];
$aTriggerSetParams = array('class_list' => MetaModel::EnumParentClasses($sHostClass, ENUM_PARENT_CLASSES_ALL));
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"), array(), $aTriggerSetParams);
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"),
array(), $aTriggerSetParams);
/** @var \TriggerOnObjectMention $oTrigger */
while ($oTrigger = $oTriggerSet->Fetch()) {
$sTriggerMentionedOQL = $oTrigger->Get('mentioned_filter');
@@ -2394,7 +2414,8 @@ EOF
// Add condition to filter on the friendlyname
$oSearch->AddConditionExpression(
new BinaryExpression(new FieldExpression('friendlyname', $sSearchMainClassAlias), 'LIKE', new VariableExpression('needle'))
new BinaryExpression(new FieldExpression('friendlyname', $sSearchMainClassAlias), 'LIKE',
new VariableExpression('needle'))
);
$oSet = new DBObjectSet($oSearch, [], $aSearchParams);
@@ -2413,8 +2434,8 @@ EOF
$sObjectClass = get_class($oObject);
$iObjectId = $oObject->GetKey();
$aMatch = [
'class' => $sObjectClass,
'id' => $iObjectId,
'class' => $sObjectClass,
'id' => $iObjectId,
'friendlyname' => $oObject->Get('friendlyname'),
];
@@ -2423,7 +2444,8 @@ EOF
/** @var \ormDocument $oImage */
$oImage = $oObject->Get($sObjectImageAttCode);
if (!$oImage->IsEmpty()) {
$aMatch['picture_style'] = "background-image: url('".$oImage->GetDisplayURL($sObjectClass, $iObjectId, $sObjectImageAttCode)."')";
$aMatch['picture_style'] = "background-image: url('".$oImage->GetDisplayURL($sObjectClass, $iObjectId,
$sObjectImageAttCode)."')";
$aMatch['initials'] = '';
} else {
// If no image found, fallback on initials
@@ -2460,8 +2482,7 @@ EOF
$aRenderRes = $oRenderer->Render($aRequestedFields);
$aResult['form']['updated_fields'] = $aRenderRes;
}
catch (Exception $e) {
} catch (Exception $e) {
$aResult['error'] = $e->getMessage();
}
$oPage->SetData($aResult);
@@ -2478,10 +2499,9 @@ EOF
$oController = new PreferencesController();
$aResult = $oController->SetUserPicture();
$aResult['success'] = true;
}
catch (Exception $oException) {
} catch (Exception $oException) {
$aResult = [
'success' => false,
'success' => false,
'error_message' => $oException->getMessage(),
];
}
@@ -2500,10 +2520,9 @@ EOF
$aResult = [
'success' => true,
];
}
catch (Exception $oException) {
} catch (Exception $oException) {
$aResult = [
'success' => false,
'success' => false,
'error_message' => $oException->getMessage(),
];
}
@@ -2516,10 +2535,9 @@ EOF
try {
$oController = new ActivityPanelController();
$aResult = $oController->AddCaseLogsEntries();
}
catch (Exception $oException) {
} catch (Exception $oException) {
$aResult = [
'success' => false,
'success' => false,
'error_message' => $oException->getMessage(),
];
}
@@ -2532,10 +2550,9 @@ EOF
try {
$oController = new ActivityPanelController();
$aResult = $oController->LoadMoreEntries();
}
catch (Exception $oException) {
} catch (Exception $oException) {
$aResult = [
'success' => false,
'success' => false,
'error_message' => $oException->getMessage(),
];
}
@@ -2551,6 +2568,24 @@ EOF
$oAjaxRenderController->GetMenusCount($oPage);
break;
//--------------------------------
// WelcomePopupMenu
//--------------------------------
case 'welcome_popup_acknowledge_message':
$oPage = new JsonPage();
try {
$oController = new WelcomePopupController();
$oController->AcknowledgeMessage();
$aResult = ['success' => true];
} catch (Exception $oException) {
$aResult = [
'success' => false,
'error_message' => $oException->getMessage(),
];
}
$oPage->SetData($aResult);
break;
//--------------------------------
// Object
//--------------------------------

View File

@@ -141,55 +141,58 @@ JS
//
//////////////////////////////////////////////////////////////////////////
$oFavoriteOrganizationsBlock = new Panel(Dict::S('UI:FavoriteOrganizations'), array(), 'grey', 'ibo-favorite-organizations');
$oFavoriteOrganizationsBlock->SetSubTitle(Dict::S('UI:FavoriteOrganizations+'));
$oFavoriteOrganizationsBlock->AddCSSClass('ibo-datatable-panel');
$oFavoriteOrganizationsForm = new Form();
$oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsForm);
// Favorite organizations: the organizations listed in the drop-down menu
$sOQL = ApplicationMenu::GetFavoriteSiloQuery();
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$bIsSiloSelectionEnabled = MetaModel::GetConfig()->Get('navigation_menu.show_organization_filter');
if ($bIsSiloSelectionEnabled)
{
$oFavoriteOrganizationsBlock = new Panel(Dict::S('UI:FavoriteOrganizations'), array(), 'grey', 'ibo-favorite-organizations');
$oFavoriteOrganizationsBlock->SetSubTitle(Dict::S('UI:FavoriteOrganizations+'));
$oFavoriteOrganizationsBlock->AddCSSClass('ibo-datatable-panel');
$oFavoriteOrganizationsForm = new Form();
$oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsForm);
// Favorite organizations: the organizations listed in the drop-down menu
$sOQL = ApplicationMenu::GetFavoriteSiloQuery();
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$aFavoriteOrgs = appUserPreferences::GetPref('favorite_orgs', null);
$aFavoriteOrgs = appUserPreferences::GetPref('favorite_orgs', null);
$sIdFavoriteOrganizations = 1;
$oFavoriteOrganizationsForm->AddSubBlock($oBlock->GetDisplay($oP, $sIdFavoriteOrganizations, [
'menu' => false,
'selection_mode' => true,
'selection_type' => 'multiple',
'table_id' => 'user_prefs',
'surround_with_panel' => false,
'selected_rows' => $aFavoriteOrgs,
]));
$oFavoriteOrganizationsForm->AddSubBlock($oAppContext->GetForFormBlock());
$sIdFavoriteOrganizations = 1;
$oFavoriteOrganizationsForm->AddSubBlock($oBlock->GetDisplay($oP, $sIdFavoriteOrganizations, [
'menu' => false,
'selection_mode' => true,
'selection_type' => 'multiple',
'table_id' => 'user_prefs',
'surround_with_panel' => false,
'selected_rows' => $aFavoriteOrgs,
]));
$oFavoriteOrganizationsForm->AddSubBlock($oAppContext->GetForFormBlock());
// Button toolbar
$oFavoriteOrganizationsToolBar = ToolbarUIBlockFactory::MakeForButton(null, ['ibo-is-fullwidth']);
$oFavoriteOrganizationsForm->AddSubBlock($oFavoriteOrganizationsToolBar);
// Button toolbar
$oFavoriteOrganizationsToolBar = ToolbarUIBlockFactory::MakeForButton(null, ['ibo-is-fullwidth']);
$oFavoriteOrganizationsForm->AddSubBlock($oFavoriteOrganizationsToolBar);
// - Cancel button
$oFavoriteOrganizationsCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'));
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsCancelButton);
$oFavoriteOrganizationsCancelButton->SetOnClickJsCode("window.location.href = '$sURL'");
// - Submit button
$oFavoriteOrganizationsSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), 'operation', 'apply', true);
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsSubmitButton);
// - Cancel button
$oFavoriteOrganizationsCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'));
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsCancelButton);
$oFavoriteOrganizationsCancelButton->SetOnClickJsCode("window.location.href = '$sURL'");
// - Submit button
$oFavoriteOrganizationsSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), 'operation', 'apply', true);
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsSubmitButton);
// TODO 3.0 have this code work again, currently it prevents the display of favorite organizations and shortcuts.
// if ($aFavoriteOrgs == null) {
// // All checked
// $oP->add_ready_script(
// <<<JS
// $('#$sIdFavoriteOrganizations.checkAll').prop('checked', true);
// checkAllDataTable('datatable_$sIdFavoriteOrganizations',true,'$sIdFavoriteOrganizations');
//JS
// );
//
// }
$oContentLayout->AddMainBlock($oFavoriteOrganizationsBlock);
// TODO 3.0 have this code work again, currently it prevents the display of favorite organizations and shortcuts.
// if ($aFavoriteOrgs == null) {
// // All checked
// $oP->add_ready_script(
// <<<JS
// $('#$sIdFavoriteOrganizations.checkAll').prop('checked', true);
// checkAllDataTable('datatable_$sIdFavoriteOrganizations',true,'$sIdFavoriteOrganizations');
//JS
// );
//
// }
$oContentLayout->AddMainBlock($oFavoriteOrganizationsBlock);
}
//////////////////////////////////////////////////////////////////////////
//
// Shortcuts

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

@@ -149,7 +149,7 @@ class DBBackup
* @param string|null $sNameSpec Name and path, eventually containing itop placeholders + time formatting following the strftime() format {@link https://www.php.net/manual/fr/function.strftime.php}
* @param \DateTime|null $oDateTime Date time to use for the name
*
* @return string Name of the backup file WITHOUT the file extension (eg. `.tar.gz`)
* @return ?string Name of the backup file WITHOUT the file extension (eg. `.tar.gz`)
* @since 3.1.0 N°5279 Add $oDateTime parameter
*/
public function MakeName(?string $sNameSpec = null, ?DateTime $oDateTime = null)
@@ -363,6 +363,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);
@@ -383,8 +384,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");
@@ -472,8 +473,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");
@@ -581,6 +582,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)
*/

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