Compare commits

..

168 Commits

Author SHA1 Message Date
Pierre Goiffon
ec1dcc8df6 💡 N°3513 PHPDoc 2021-09-23 14:42:16 +02:00
Pierre Goiffon
47ed863da9 N°4215 N°3513 Fix DB errors fetch from the wrong object n°2 2021-09-23 14:32:43 +02:00
Pierre Goiffon
88290f9e91 N°4215 N°3513 Fix DB errors fetch from the wrong object 2021-09-23 13:55:23 +02:00
Pierre Goiffon
cfdbc8ae62 N°4215 When checking for TLS cnx, don't set anymore CMDBSource mysql attributes ! 2021-09-23 11:59:44 +02:00
Pierre Goiffon
aaa8f6d311 N°4215 Fix call to a function on null error when setting TLS connection in the setup
Regression introduced by b1ca1f2630 / N°3513
2021-09-22 15:59:39 +02:00
Molkobain
da217a1cb3 N°4161 - Fix ManageBrick crash when no item listed 2021-07-12 18:10:22 +02:00
Pierre Goiffon
d90b1a3d82 🐛 N°4020 Fix syntax error for PHP < 7.1
`syntax error, unexpected 'const' (T_CONST), expecting variable (T_VARIABLE) in /var/www/itop274/setup/compiler.class.inc.php on line 61`
Was added in 1059befa
2021-07-02 09:26:03 +02:00
Pierre Goiffon
3694108f42 N°3870 updateLicenses : fix generating wrong product names on Windows
Was including paths fragments.

Example :
<product scope="datamodels">C:\Dev\wamp64\www\itop-dev\.make\license/../..//datamodels/2.x/authent-cas/vendor/apereo/phpcas</product>

Instead of :
<product scope="datamodels">apereo/phpcas</product>
2021-07-01 17:20:55 +02:00
Pierre Goiffon
8cf75f826f 🔨 updateLicenses : add logs and replace rm -f by unlink() 2021-07-01 15:30:33 +02:00
Pierre Goiffon
ad9726b64c 🔧 .editorConfig : restore old ij_visual_guides value
Was overwritten by mistake in 19505649
2021-06-30 10:24:19 +02:00
Pierre Goiffon
e32e275f40 🎨 Align dataprovider elements 2021-06-29 16:45:34 +02:00
Pierre Goiffon
195056492e 🔧 .editorConfig : enable var alignement 2021-06-29 16:45:18 +02:00
Pierre Goiffon
af338de17f 🔨 Changelog generation script 2021-06-29 11:05:59 +02:00
Pierre Goiffon
a6aa183e26 🔖 Prepare 2.7.5 2021-06-28 15:03:17 +02:00
Pierre Goiffon
b5074c4cee N°3806 Fix saying memory_limit isn't enough in CLI scripts and setup
This was caused by the strict comparison in \utils::IsMemoryLimitOk for the special "-1" value of memory_limit, which was added in c2f5cafa.
Fix was to change \utils::ConvertToBytes : it was returning original value when input was numeric (so if input is '-1' output was '-1')  now it always returns an int (input '-1' output -1)
2021-06-28 14:53:39 +02:00
acognet
8b9589744b N°4020 - timeout on updating the hierarchical key during setup - php 5.6 compatibility 2021-06-28 13:56:49 +02:00
Pierre Goiffon
8259a79cd2 🎨 Factorize LogAPI channels value in LogChannels class 2021-06-25 17:13:35 +02:00
acognet
a23ea9a01f N°3678 - Portal : a modification of field cannot hide another one 2021-06-24 14:11:41 +02:00
Eric
949b213f9d N°3513 - revert crud sequence 2021-06-24 13:32:43 +02:00
acognet
a940adc4ba N°3678 - Portal : a modification of field cannot hide another one 2021-06-24 10:35:01 +02:00
Pierre Goiffon
5d994edd62 N°4012 Fix debug trace syntax
Thanks @Molkobain !
2021-06-23 17:26:34 +02:00
Eric
b1ca1f2630 N°3513 - ObjectFormManager : remove transaction
* Add automatic tests
* Fix object consistency
2021-06-22 16:09:41 +02:00
acognet
58e315d7f6 N°4020 - timeout on updating the hierarchical key during setup - fix git ignore 2021-06-21 17:16:08 +02:00
acognet
1059befa39 N°4020 - timeout on updating the hierarchical key during setup 2021-06-21 16:39:58 +02:00
Eric
0f5130611d Fix log API tests 2021-06-21 16:07:36 +02:00
Eric
a1271da74a N°3513 - ObjectFormManager : remove transaction 2021-06-21 15:03:17 +02:00
Denis
0d40235791 🗃️ N°3968 Fix mutex being silently released after connection timeout (#209)
Mutex are using their own DB connection
Because the `wait_timeout` isn't specified when opening the connection, it could be closed before we released the lock : if so the lock is silently released !
We are now setting this variable directly when opening the connection to avoid such case (setting 86400s, so 1 day : this should be enough !)
2021-06-21 12:33:15 +02:00
Pierre Goiffon
dd63f2b817 N°4012 Debug trace for objects lists in portal : ManageBrick and BrowseBrick
'portal' channel, debug level
2021-06-18 10:58:04 +02:00
Pierre Goiffon
8f84c3b84b 🎨 LogAPI code formatting 2021-06-18 10:58:04 +02:00
Pierre Goiffon
00c58bb245 💡 updateLicenses.php : copy phpdoc from develop branch 2021-06-17 17:11:30 +02:00
Eric
3a876d5c75 Log complete error on Memory Limit or max execution time 2021-06-11 09:36:01 +02:00
Pierre Goiffon
147916062b 🎨 Fix local variable names
Was copy/paste and wasn't consistent with the functionnality :/
2021-06-10 16:28:17 +02:00
Pierre Goiffon
0de6f98add 🔊 When ordering objects search list, log problems due to low max_input_vars php.ini option (#211)
iTop is sending large volume of data to the server, and those data can be truncated due to this php.ini option.
Now we are checking for common issues : if the data is truncated a log is done (IssueLog, warning level, no channel) and the corresponding column is set as not selected.
User will still see an inconsistent result, but we won't have PHP notices generated anymore, and a log can tell what caused the issue.
2021-06-08 18:43:43 +02:00
Pierre Goiffon
a076792e77 ⬆️ N°3973 Update update pear/archive_tar 2021-06-08 18:16:12 +02:00
acognet
2d2a6857de N°3797 - [MariaDB] crash while converting utf8mb4 + add index 2021-06-04 09:10:28 +02:00
Pierre Goiffon
373641e01d 🎨 Code formatting 2021-06-03 17:31:34 +02:00
Eric
d11eceac62 N°4052 - OQL parser limit for huge request - fix CI 2021-06-03 17:07:17 +02:00
Eric
3965806fa0 N°4052 - OQL parser limit for huge request - add better feedback 2021-06-03 16:49:21 +02:00
Pierre Goiffon
2625d2da80 💡 PHPDoc 2021-06-03 15:29:13 +02:00
Eric
02d32a556d N°4052 - OQL parser limit for huge request 2021-06-02 15:15:00 +02:00
Eric
71fcc6f026 N°4031 - OQL Error when AttributeObjectKey is used in JOIN condition 2021-06-02 14:45:03 +02:00
Purple Grape
7168860a0b 🌐 Improved chinese for 2.7 (#218) 2021-06-02 08:06:16 +02:00
acognet
684c88e0b8 N°4038 - Pop up erreur portail utilisateur 2021-05-31 23:43:58 +02:00
Eric
84741c19f0 N°4002 - code hardening (merge from support/2.6) 2021-05-27 16:36:00 +02:00
Eric
86f649affc N°4002 - code hardening 2021-05-27 16:13:27 +02:00
Eric
4f5c987d8b N°4002 - code hardening 2021-05-27 15:57:04 +02:00
Eric
e7b5953feb documentation 2021-05-27 11:52:38 +02:00
Eric
e441e5e78a documentation 2021-05-27 11:49:32 +02:00
Eric
6be9a87c15 N°3952 - code hardening (merged from support/2.6) 2021-05-27 09:55:16 +02:00
Eric
43daa2ef08 N°3952 - code hardening 2021-05-27 09:29:50 +02:00
Pierre Goiffon
caa2a05bf4 🔧 restore .editorconfig
Was moved to /test by mistake
And also editorconfig syntax was removed :(
Those mistakes were done in 7f15eed9
Thanks Molkobain, good catch !
2021-05-26 15:19:40 +02:00
Pierre Goiffon
fc39d8aca9 💡 PHPDoc type hinting 2021-05-26 14:24:29 +02:00
Eric
cf12578289 N°3452 - DB tools : better formatting 2021-05-26 12:14:14 +02:00
Pierre Goiffon
44952d1ea0 Fix \UtilsTest::testIsMemoryLimit 2021-05-25 17:04:32 +02:00
Pierre Goiffon
7f15eed9a8 🔧 Update .editorconfig
* Preserve XML line breaks
* Add markdown
* Add editorconfig
2021-05-25 15:50:23 +02:00
BGdu38
c2f5cafaf3 Avoid setting memory_limit to lower value than the one already configured (#215)
Some scripts are setting the memory_limit PHP option : setup, csvimport and XLSX export. This was done to avoid crashing when dealing with such large amount of data.
But sometimes we were setting the value without any prior check, so we could actually lower the memory_limit value :/

Now this memory_limit change is done using \utils::SetMinMemoryLimit, which will call ini_set if and only if the current value is lower than the one to be set.

Setup calls (setup/ajax.dataloader.php and webservices/backoffice.dataloader.php) were left as is as they weren't subject to this bug, and also they are more complex (logging done on each case).
2021-05-25 12:03:19 +02:00
Pierre Goiffon
81822efa0f 💡 PHPDoc for \DBObject::ApplyStimulus 2021-05-25 10:52:12 +02:00
Pierre Goiffon
923a025f1c 🌐 N°4017 Report pt_br translations for TTO/TTR made in combodo-dispatch-incident
Those dict keys were wrongly duplicated in this module, but they are core iTop !
Original commit 75f00d993c
Thanks to @rokam !
2021-05-20 15:51:21 +02:00
Pierre Goiffon
a4b6f4e37c 👥 Add @rokam to contributors list
Thanks to him for his PT-BR translations !
2021-05-20 15:44:24 +02:00
Pierre Goiffon
f0c73451a2 🌐 N°4017 Report pt_br translations for TTO/TTR made in combodo-dispatch-userrequest
Those dict keys were wrongly duplicated in this module, but they are core iTop !
Original commit 9725897623
Thanks to @rokam !
2021-05-20 15:30:00 +02:00
acognet
db6e813cba N°3945 - Password database is visible in the setup process 2021-05-18 17:34:57 +02:00
Pierre Goiffon
d74e3e6b42 💡 ItopTestCase : some PHPDoc 2021-05-18 17:16:04 +02:00
acognet
b740cb2afd N°2540 - prevent the mysql password to appear on misconfigured servers 2021-05-12 08:17:35 +02:00
Molkobain
6ad3c40c42 Merge branch 'support/2.7.4' into support/2.7 2021-05-11 12:14:36 +02:00
Molkobain
f49c8ce188 Merge branch 'support/2.7.3' into support/2.7.4 2021-05-11 12:13:29 +02:00
Molkobain
acf828b72e N°3995 - Portal: Fix loader not displaying in BrowseBrick for tree/mosaic modes with huge amount of data 2021-05-11 12:10:31 +02:00
Pierre Goiffon
bac92716f3 📄 Update Font Awesome license
Was duplicated...
And also not the correct license !
Source for new license text : https://github.com/FortAwesome/Font-Awesome/blob/master/LICENSE.txt

(modification already committed to develop : f8f9c2d7)
2021-05-04 08:58:34 +02:00
bruno-ds
07257cc2d2 N°3671 - fix a code comment too evasive
thanks @Hipska
2021-04-21 09:25:10 +02:00
Pierre Goiffon
87ba67225a 💡 Add since phpdoc tags for \DeadLockLog::Log 2021-04-16 08:36:21 +02:00
Pierre Goiffon
2ad3b3c27e 🎨 Fix \DeadLockLog::Log parameter name + PHPDoc
Thanks @Hipska !
2021-04-15 15:03:13 +02:00
Molkobain
92a640e41a Merge branch 'support/2.7.4' into support/2.7 2021-04-15 10:37:17 +02:00
Molkobain
842df7646b Merge branch 'support/2.7.3' into support/2.7.4 2021-04-15 10:35:56 +02:00
Molkobain
01b38d2ed6 N°3869 - Portal: Fix the Notice "Undefined index: max_display_limit" 2021-04-15 10:34:46 +02:00
Molkobain
9af4846372 N°3810 - Avoid syntax highlighting that shouldn't take place 2021-03-23 16:39:22 +01:00
bruno-ds
91fc2d2e2b N°3671 - reformat tests 2021-03-17 08:46:41 +01:00
Pierre Goiffon
2432ff77a3 💡 More details in PHPDoc for \utils::RealPath 2021-03-17 08:34:25 +01:00
odain
d229e08f02 prepare iTop release 2.7.4 2021-03-16 09:00:06 +01:00
odain
b5adb2e82b N°3671: fix and make test more lisible 2021-03-15 19:01:09 +01:00
odain
386c90c601 N°3668 - URL direct error: renamed trust_proxies<-behind_reverse_proxy 2021-03-15 14:56:16 +01:00
odain
5d0c61178b N°3671 : persist absolute URL when setup context (force trustproxy enabled) 2021-03-15 14:34:56 +01:00
odain
3bcae734e5 N°3671 : persist absolute URL when setup context (force trustproxy enabled) 2021-03-15 12:27:05 +01:00
odain
842e8f9e01 php doc 2021-03-10 15:32:44 +01:00
odain
52cd4f7c5e N°3788 - timeout/excessive duration during MTP - fix PostDbCreation 2021-03-09 11:44:25 +01:00
odain
995619af9b N°3788 - timeout/excessive duration during MTP - increase timeout from 5 to 30s 2021-03-09 08:27:33 +01:00
odain
c842162fe2 N°3788 - timeout/excessive duration during MTP 2021-03-09 08:13:57 +01:00
odain
83f99642e0 N°3793 - Cleanup of orphan CMDBChange can hang the setup 2021-03-08 11:38:20 +01:00
bruno-ds
ae6a264d6d N°3671 - fix typo in HTTP header name 2021-03-05 16:57:03 +01:00
bruno-ds
a06bf6ea7c coding convention (thanks @molkobain) 2021-03-05 09:20:04 +01:00
bruno-ds
bb8d4a92cb fix an indentation problem (thanks @Hipska) 2021-03-04 09:56:05 +01:00
bruno-ds
1429792690 N°3668 - fix an improper redirection to the homepage when iTop is behind a reverse proxy 2021-03-04 09:39:48 +01:00
bruno-ds
1f26b59d90 N°3671 - add an API endpoint (it will be used by N°3668 and N°3760) + some code cleanup asked by @molkobain 2021-03-04 09:32:13 +01:00
bruno-ds
7b093a6bba N°3671 - app_root_url: handle reverse proxies during the setup and preserve existing configuration during an upgrade. 2021-03-03 11:55:18 +01:00
odain
d4607ee815 N°3065 - Failed enum comparison when values contains parenthesis : add a warning 2021-03-02 07:33:36 +01:00
odain
5c0e92d51a N°3065 - Failed enum comparison when values contains parenthesis 2021-03-01 17:06:49 +01:00
bruno-ds
cd4b3fdaab N°3764 - fix CI 2021-03-01 16:27:40 +01:00
bruno-ds
0030d5c2b8 N°3764 - add transactions_gc_threshold in order to tune CSRF token GC load 2021-03-01 15:30:40 +01:00
bruno-ds
95a0efedcf N°3728 - security hardening 2021-03-01 15:28:34 +01:00
bruno-ds
13a1d32f56 N°3453 - portal export header fields are now localized 2021-02-26 11:47:34 +01:00
Pierre Goiffon
35155e4b7a 💡 N°3065 comments modifications 2021-02-26 10:06:29 +01:00
Eric
77710f1613 Revert "#1946 Fix Twig templates logging too much"
This reverts commit 2763b991
2021-02-25 17:57:39 +01:00
Eric
2763b99142 #1946 Fix Twig templates logging too much 2021-02-25 14:44:16 +01:00
bruno-ds
db13c105ad N°3473 - PHPdoc
as requested by @piRGoif
2021-02-24 17:38:54 +01:00
bruno-ds
2276539f24 N°3430 - code cleanup 2021-02-24 16:50:14 +01:00
bruno-ds
9b7cd20d47 N°3473 - security hardening 2021-02-24 16:50:13 +01:00
bruno-ds
e1d644c33b Merge remote-tracking branch 'origin/support/2.7' into support/2.7 2021-02-24 12:06:40 +01:00
bruno-ds
c601082a5e 3548 - disable core update if a file integrity problem is detected 2021-02-24 12:05:11 +01:00
Molkobain
5836be7131 Fix unit test 2021-02-24 09:49:16 +01:00
Molkobain
6f40bb4c35 Change check level to "warning" in order to keep consistency with the others 2021-02-24 09:29:42 +01:00
bruno-ds
241bd1cdeb N°3430 - code cleanup
- during the code review @dflaven preferred the reference rather than the return alternative
 - typo
2021-02-22 09:43:42 +01:00
Molkobain
71c5f47cd8 PHPDoc 2021-02-19 09:37:53 +01:00
odain
74246a8278 N°3065 - Failed enum comparison when values contains parenthesis - enhance db model parsing used during setup comparison with expected one to generate SQL migration queries 2021-02-18 18:24:09 +01:00
bruno-ds
c450c9426c Merge remote-tracking branch 'origin/support/2.7' into support/2.7 2021-02-18 16:29:44 +01:00
odain
46f9fe743c fix ci: adapt test to make sure config date_and_time is set properly before 2021-02-18 16:27:38 +01:00
odain
c31df5fff3 fix ci: adapt test to make sure config date_and_time is set properly before 2021-02-18 16:07:37 +01:00
Pierre Goiffon
6e0af1a3b7 💡 Add variable typing 2021-02-18 13:21:06 +01:00
bruno-ds
e9e18513be N°3430 - fix preference page's warning and add missing token generation
- fix the warning (ajax call interrupted) if preference form ajax call is way faster than the one of the 2 other by adding a new timeout_duration option before the redirect.
2021-02-18 12:18:38 +01:00
Molkobain
9d2fc883b8 Fix test name 2021-02-17 10:31:59 +01:00
odain
913ea0cef2 N°3412 - Command Injection vulnerability in the Setup Wizard - renaming 2021-02-17 10:22:21 +01:00
odain
82ba7f25b0 N°3412 - Command Injection vulnerability in the Setup Wizard - do not use escapeshellcmd before execution in Windows envt 2021-02-17 10:18:28 +01:00
odain
bb877a244b N°3412 - Command Injection vulnerability in the Setup Wizard - do not use escapeshellcmd before execution in Windows envt 2021-02-17 10:09:39 +01:00
odain
a12959d60e N°3412 - Command Injection vulnerability in the Setup Wizard - handle empty path 2021-02-17 07:50:18 +01:00
bruno-ds
83434b5506 N°3430 - add translations 2021-02-16 17:42:11 +01:00
bruno-ds
dcd4abe72b N°3430 - security hardening 2021-02-16 17:33:49 +01:00
odain
571520815a N°3412 - Command Injection vulnerability in the Setup Wizard - include test to CI 2021-02-16 17:25:45 +01:00
odain
e9cff0920b N°3412 - Command Injection vulnerability in the Setup Wizard - fix test and code 2021-02-16 17:12:41 +01:00
odain
905ee19519 N°3412 - Command Injection vulnerability in the Setup Wizard 2021-02-16 15:57:12 +01:00
bruno-ds
0b95220d1b N°3466 - Add (missing) translations 2021-02-16 09:46:04 +01:00
bruno-ds
e1b2a767f5 N°3142 - fix typos 2021-02-15 17:49:25 +01:00
bruno-ds
3058b2eb00 N°3142 - Add (missing) translations 2021-02-15 17:08:47 +01:00
Molkobain
38bc2d9d58 🔧 Change max line length in .editorConfig 2021-02-15 13:49:39 +01:00
Eric
c8e8778d7b N°3468 - Fix extension.xml preventing extensions installation
(cherry picked from commit 92c8af1b19)
2021-02-11 17:48:29 +01:00
bruno-ds
656fa3208a N°3721 - revert the feature (will only be available on the 3.0) 2021-02-10 15:33:01 +01:00
bruno-ds
f647ce61c2 N°3721 - toolkit's "update iTop" with the "Create symbolic links" option checked now empty the compiled directory as expected 2021-02-10 14:34:21 +01:00
Eric
6b76e5a853 N°3618 - Count on union with different conditions fails (php doc) 2021-02-08 09:39:24 +01:00
Eric
dbb6e43751 N°3618 - Count on union with different conditions fails (Fix unit tests) 2021-02-05 11:28:40 +01:00
Eric
f07f0ba1c7 N°3618 - Count on union with different conditions fails (Fix multi-column attributes sql generation) 2021-02-05 10:15:14 +01:00
Pierre Goiffon
a5894c1a4c Rename \Combodo\iTop\Test\UnitTest\ItopTestCase::InvokeInvisible* to InvokeNonPublic* 2021-02-04 09:41:56 +01:00
Eric
e06996a2e4 N°3660 - Fix JOIN without condition on child joined table is ignored (check done in optimizer) 2021-02-03 15:58:54 +01:00
Eric
2f0e7c6d29 N°3586 - Fix login window not correctly displayed 2021-02-03 09:15:56 +01:00
Eric
7115a6ae7d N°3660 - Fix JOIN without condition on child joined table is ignored (remove unnecessary check) 2021-02-03 09:07:59 +01:00
Pierre Goiffon
765560d1f5 ItopTestCase : helpers to call invisble methods 2021-02-02 17:57:40 +01:00
Eric
bc024d9ed0 N°3660 - Fix JOIN without condition on child joined table is ignored 2021-02-02 17:30:03 +01:00
jbostoen
37a4a3eb47 🌐 Fix typo in Dutch translations (#189)
Co-authored-by: jbostoen <->
2021-02-01 16:21:10 +01:00
Pierre Goiffon
54e9bd5c8e Merge branch 'support/2.6' into support/2.7
# Conflicts:
#	.editorconfig
2021-01-25 09:13:43 +01:00
Pierre Goiffon
066a6d8b36 🔧 Use same .editorconfig in all supported branches 2021-01-25 09:12:38 +01:00
jbostoen
4123c6213d 🌐 NL : distinguish between approval (goedkeuring) <=> acceptance (acceptatie) (#182)
Co-authored-by: jbostoen <->
2020-12-21 14:08:29 +01:00
Pierre Goiffon
8265b9b034 N°3416 fix PHPDoc 2020-12-10 18:10:45 +01:00
Pierre Goiffon
c4756e8cec Upgrade version n° 2020-12-08 18:47:24 +01:00
odain
37351d6b3e N°3464: fix ci 2020-12-07 16:23:17 +01:00
odain
57a085eec1 N°3464: move fix in itop-fence + fix/enhance rest api test 2020-12-07 15:56:35 +01:00
odain
0019595923 N°3464: fix ci 2020-12-07 00:44:39 +01:00
odain
4d61c14f80 N°3464 add test in phpunit.xml.dit to validate the fix 2020-12-07 00:12:31 +01:00
odain
cf1b613923 N°3464 REST comment field not working anymore 2020-12-06 23:54:27 +01:00
Pierre Goiffon
1304e2eb2d N°3416 Updates after code review v2 :) 2020-12-04 08:51:07 +01:00
Pierre Goiffon
3cf16627c1 Merge remote-tracking branch 'origin/support/2.6' into support/2.7
# Conflicts:
#	pages/ajax.render.php
2020-12-03 18:18:36 +01:00
Pierre Goiffon
4aaa237bf9 🔖 Prepare 2.7.3 version 2020-12-03 18:15:58 +01:00
Pierre Goiffon
cece15d10c N°3416 Updates after code review
Many thanks @bruno-ds !
* add comments to explain intentions
* fix indentations
2020-12-03 17:45:44 +01:00
Pierre Goiffon
aa15e009cb 🔖 Prepare 2.7.2-2 version 2020-12-03 10:05:37 +01:00
Pierre Goiffon
b9ca2ac13d N°3416 Fix DocumentFile preview not working anymore
Was caused by X-Frame-Options http header added with N°3317

(cherry picked from commit 35d77ff642)

# Conflicts:
#	pages/ajax.render.php
2020-12-03 08:20:51 +01:00
Pierre Goiffon
80e1e0e61a N°3426 Fix no navigation menu on User object creation
Caused by a typo in js/forms-json-utils.js
Thanks @Molkobain !
2020-12-02 18:02:00 +01:00
Pierre Goiffon
ecebe4ecd5 N°3416 XFrame and cache headers optimizations
* Remove XFrame header set in \WebPage::no_cache : not this method responsability, was confusing :/
* Remove no_cache() calls when already set in page constructor (ajax_page mainly)
* Also calls everywhere the \WebPage::no_cache method instead of setting headers manually
2020-12-02 17:19:05 +01:00
Pierre Goiffon
8bfcb14d0c N°3416 XFrame-Options header is now set using a config parameter, defaults to SAMEORIGIN
Also adds an indirection (\WebPage::add_xframe_options) to set header
2020-12-02 17:17:11 +01:00
Molkobain
1cf1473d6b N°3469 - Fix variable declaration (let => var) 🤭 2020-12-02 17:01:00 +01:00
Molkobain
aa43425df3 N°3469 - Portal: Fix modal created without an ID 2020-12-02 16:59:39 +01:00
Pierre Goiffon
35d77ff642 N°3416 Fix DocumentFile preview not working anymore
Was caused by X-Frame-Options http header added with N°3317
2020-12-02 15:44:58 +01:00
acognet
539fa43503 N°3461 - Setup Broken with Chrome v87 2020-11-30 18:27:25 +01:00
acognet
eb537f45f4 N°3421 - Attributes of class Person are not accessible from :current_contact in portal anymore. Only attributes of class Contact are. 2020-11-30 09:24:35 +01:00
acognet
a2a4cd4e7a N°3426 - Wrong tab is displayed when a creation or modification form is invalidated 2020-11-27 15:20:20 +01:00
177 changed files with 7732 additions and 3896 deletions

View File

@@ -6,19 +6,20 @@ end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 140
max_line_length = 300
tab_width = 4
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_visual_guides = 80,120
ij_visual_guides = 300
ij_wrap_on_typing = true
[*.css]
indent_style = tab
ij_smart_tabs = true
ij_visual_guides = none
ij_css_align_closing_brace_with_properties = false
ij_css_blank_lines_around_nested_selector = 1
ij_css_blank_lines_between_blocks = 1
@@ -38,7 +39,9 @@ ij_css_use_double_quotes = true
ij_css_value_alignment = do_not_align
[*.scss]
indent_style = tab
indent_size = 2
tab_width = 2
ij_visual_guides = none
ij_scss_align_closing_brace_with_properties = false
ij_scss_blank_lines_around_nested_selector = 1
ij_scss_blank_lines_between_blocks = 1
@@ -58,8 +61,8 @@ ij_scss_use_double_quotes = true
ij_scss_value_alignment = 0
[*.twig]
indent_style = tab
ij_smart_tabs = true
ij_visual_guides = none
ij_wrap_on_typing = false
ij_twig_keep_indents_on_empty_lines = false
ij_twig_spaces_inside_comments_delimiters = true
@@ -67,6 +70,7 @@ ij_twig_spaces_inside_delimiters = true
ij_twig_spaces_inside_variable_delimiters = true
[.editorconfig]
ij_visual_guides = none
ij_editorconfig_align_group_field_declarations = false
ij_editorconfig_space_after_colon = false
ij_editorconfig_space_after_comma = true
@@ -74,17 +78,19 @@ ij_editorconfig_space_before_colon = false
ij_editorconfig_space_before_comma = false
ij_editorconfig_spaces_around_assignment_operators = true
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul,phpunit.xml.dist}]
[{*.ant, *.fxml, *.jhm, *.jnlp, *.jrxml, *.rng, *.tld, *.wsdl, *.xml, *.xsd, *.xsl, *.xslt, *.xul, phpunit.xml.dist}]
indent_size = 2
tab_width = 2
ij_smart_tabs = true
ij_visual_guides = none
ij_wrap_on_typing = false
ij_xml_align_attributes = true
ij_xml_align_text = false
ij_xml_attribute_wrap = normal
ij_xml_block_comment_at_first_column = true
ij_xml_keep_blank_lines = 2
ij_xml_keep_indents_on_empty_lines = false
ij_xml_keep_line_breaks = false
ij_xml_keep_line_breaks = true
ij_xml_keep_line_breaks_in_text = true
ij_xml_keep_whitespaces = false
ij_xml_keep_whitespaces_around_cdata = preserve
@@ -93,21 +99,24 @@ ij_xml_line_comment_at_first_column = true
ij_xml_space_after_tag_name = false
ij_xml_space_around_equals_in_attribute = false
ij_xml_space_inside_empty_tag = false
ij_xml_text_wrap = normal
ij_xml_text_wrap = off
[{*.bash,*.sh,*.zsh}]
indent_size = 2
tab_width = 2
ij_visual_guides = none
ij_shell_binary_ops_start_line = false
ij_shell_keep_column_alignment_padding = false
ij_shell_minify_program = false
ij_shell_redirect_followed_by_space = false
ij_shell_switch_cases_indented = false
ij_shell_use_unix_line_separator = true
[{*.cjs,*.js}]
indent_style = tab
ij_continuation_indent_size = 4
ij_smart_tabs = true
ij_visual_guides = none
ij_javascript_align_imports = false
ij_javascript_align_multiline_array_initializer_expression = false
ij_javascript_align_multiline_binary_operation = false
@@ -141,7 +150,7 @@ ij_javascript_chained_call_dot_on_new_line = true
ij_javascript_class_brace_style = end_of_line
ij_javascript_comma_on_new_line = false
ij_javascript_do_while_brace_force = always
ij_javascript_else_on_new_line = true
ij_javascript_else_on_new_line = false
ij_javascript_enforce_trailing_comma = keep
ij_javascript_extends_keyword_wrap = off
ij_javascript_extends_list_wrap = off
@@ -271,17 +280,17 @@ ij_javascript_while_brace_force = always
ij_javascript_while_on_new_line = false
ij_javascript_wrap_comments = false
[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}]
[{*.ctp, *.hphp, *.inc, *.module, *.php, *.php4, *.php5, *.phtml}]
indent_style = tab
ij_continuation_indent_size = 4
ij_smart_tabs = true
ij_wrap_on_typing = false
ij_php_align_assignments = false
ij_php_align_assignments = true
ij_php_align_class_constants = false
ij_php_align_group_field_declarations = false
ij_php_align_inline_comments = false
ij_php_align_key_value_pairs = false
ij_php_align_multiline_array_initializer_expression = false
ij_php_align_key_value_pairs = true
ij_php_align_multiline_array_initializer_expression = true
ij_php_align_multiline_binary_operation = false
ij_php_align_multiline_chained_methods = false
ij_php_align_multiline_extends_list = false
@@ -297,6 +306,7 @@ ij_php_array_initializer_new_line_after_left_brace = true
ij_php_array_initializer_right_brace_on_new_line = true
ij_php_array_initializer_wrap = on_every_item
ij_php_assignment_wrap = off
ij_php_attributes_wrap = off
ij_php_author_weight = 8
ij_php_binary_operation_sign_on_next_line = false
ij_php_binary_operation_wrap = off
@@ -385,6 +395,7 @@ ij_php_new_line_after_php_opening_tag = false
ij_php_null_type_position = in_the_end
ij_php_package_weight = 28
ij_php_param_weight = 5
ij_php_parameters_attributes_wrap = off
ij_php_parentheses_expression_new_line_after_left_paren = false
ij_php_parentheses_expression_right_paren_on_new_line = false
ij_php_phpdoc_blank_line_before_tags = true
@@ -406,6 +417,7 @@ ij_php_see_weight = 3
ij_php_since_weight = 28
ij_php_sort_phpdoc_elements = true
ij_php_space_after_colon = true
ij_php_space_after_colon_in_named_argument = true
ij_php_space_after_colon_in_return_type = true
ij_php_space_after_comma = true
ij_php_space_after_for_semicolon = true
@@ -419,6 +431,7 @@ ij_php_space_before_catch_parentheses = true
ij_php_space_before_class_left_brace = true
ij_php_space_before_closure_left_parenthesis = true
ij_php_space_before_colon = true
ij_php_space_before_colon_in_named_argument = false
ij_php_space_before_colon_in_return_type = false
ij_php_space_before_comma = false
ij_php_space_before_do_left_brace = true
@@ -486,6 +499,7 @@ ij_php_while_on_new_line = false
[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,composer.lock,jest.config}]
indent_size = 2
ij_visual_guides = none
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true
@@ -500,6 +514,7 @@ ij_json_wrap_long_lines = false
[{*.htm,*.html,*.sht,*.shtm,*.shtml}]
indent_style = tab
ij_smart_tabs = true
ij_visual_guides = none
ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
ij_html_align_attributes = true
ij_html_align_text = false
@@ -527,10 +542,30 @@ ij_html_space_inside_empty_tag = false
ij_html_text_wrap = normal
ij_html_uniform_ident = false
[{*.markdown,*.md}]
ij_visual_guides = none
ij_markdown_force_one_space_after_blockquote_symbol = true
ij_markdown_force_one_space_after_header_symbol = true
ij_markdown_force_one_space_after_list_bullet = true
ij_markdown_force_one_space_between_words = true
ij_markdown_keep_indents_on_empty_lines = false
ij_markdown_max_lines_around_block_elements = 1
ij_markdown_max_lines_around_header = 1
ij_markdown_max_lines_between_paragraphs = 1
ij_markdown_min_lines_around_block_elements = 1
ij_markdown_min_lines_around_header = 1
ij_markdown_min_lines_between_paragraphs = 1
[{*.yaml,*.yml}]
indent_size = 2
ij_visual_guides = none
ij_yaml_align_values_properties = do_not_align
ij_yaml_autoinsert_sequence_marker = true
ij_yaml_block_mapping_on_new_line = false
ij_yaml_indent_sequence_value = true
ij_yaml_keep_indents_on_empty_lines = false
ij_yaml_keep_line_breaks = true
ij_yaml_space_before_colon = true
ij_yaml_sequence_on_new_line = false
ij_yaml_space_before_colon = false
ij_yaml_spaces_within_braces = true
ij_yaml_spaces_within_brackets = true

View File

@@ -1,7 +1,22 @@
<?php
/**
* script used to sort license file (usefull for autogeneration)
* Example: php
* script used to sort license file (useful for autogeneration)
*
* Requirements :
* * bash (on Windows, use Git Bash)
* * composer (if you use the phar version, mind to create a `Composer` alias !)
* * JQ command
* to install on Windows :
* `curl -L -o /usr/bin/jq.exe https://github.com/stedolan/jq/releases/latest/download/jq-win64.exe`
* this is a Windows port : https://stedolan.github.io/jq/
*
* Licenses sources :
* * `composer licenses --format json` (see https://getcomposer.org/doc/03-cli.md#licenses)
* * keep every existing nodes with `/licenses/license[11]/product/@scope` not in ['lib', 'datamodels']
* ⚠ If licenses were added manually, they might be removed by this tool ! Be very careful to check for the result before pushing !
*
* To launch, check requirements and run `php updateLicenses.php`
* The target license file path is in `$xmlFilePath`
*/
$iTopFolder = __DIR__ . "/../../" ;
@@ -51,39 +66,83 @@ function get_license_nodes($file_path)
$xp = new DOMXPath($dom);
$licenseList = $xp->query('/licenses/license');
$licenses = iterator_to_array($licenseList);
$licenses = iterator_to_array($licenseList);
usort($licenses, 'sort_by_product');
return $licenses;
}
/** @noinspection SuspiciousAssignmentsInspection */
function fix_product_name(DOMNode &$oProductNode)
{
$sProductNameOrig = $oProductNode->nodeValue;
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//lib/symfony/polyfill-ctype`
$sProductNameFixed = remove_dir_from_string($sProductNameOrig, 'lib/');
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//datamodels/2.x/authent-cas/vendor/apereo/phpcas`
$sProductNameFixed = remove_dir_from_string($sProductNameFixed, 'vendor/');
$oProductNode->nodeValue = $sProductNameFixed;
}
function remove_dir_from_string($sString, $sNeedle)
{
if (strpos($sString, $sNeedle) === false) {
return $sString;
}
$sStringTmp = strstr($sString, $sNeedle);
$sStringFixed = str_replace($sNeedle, '', $sStringTmp);
// DEBUG trace O:)
// echo "$sNeedle = $sString => $sStringFixed\n";
return $sStringFixed;
}
$old_licenses = get_license_nodes($xmlFilePath);
//generate file with updated licenses
$generated_license_file_path = __DIR__."/provfile.xml";
exec("bash " . __DIR__ . "/gen-community-license.sh $iTopFolder > ". $generated_license_file_path);
echo "- Generating licences...";
exec("bash ".__DIR__."/gen-community-license.sh $iTopFolder > ".$generated_license_file_path);
echo "OK!\n";
echo "- Get licenses nodes...";
$new_licenses = get_license_nodes($generated_license_file_path);
exec("rm -f ". $generated_license_file_path);
unlink($generated_license_file_path);
foreach ($old_licenses as $b) {
$aProductNode = get_product_node($b);
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels" )
{
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels") {
$new_licenses[] = $b;
}
}
usort($new_licenses, 'sort_by_product');
echo "OK!\n";
echo "- Overwritting Combodo license file...";
$new_dom = new DOMDocument("1.0");
$new_dom->formatOutput = true;
$root = $new_dom->createElement("licenses");
$new_dom->appendChild($root);
foreach ($new_licenses as $b) {
$node = $new_dom->importNode($b,true);
$root->appendChild($new_dom->importNode($b,true));
$node = $new_dom->importNode($b, true);
// N°3870 fix when running script in Windows
// fix should be in gen-community-license.sh but it is easier to do it here !
if (strncasecmp(PHP_OS, 'WIN', 3) === 0) {
$oProductNodeOrig = get_product_node($node);
fix_product_name($oProductNodeOrig);
}
$root->appendChild($node);
}
$new_dom->save($xmlFilePath);
$new_dom->save($xmlFilePath);
echo "OK!\n";

View File

@@ -0,0 +1,77 @@
<?php
/**
* Usage :
* `php changelog.php 2.7.4`
*
* As argument is passed the git ref (tag name or sha1) we want to use as reference
*
* Outputs :
*
* 1. List of bugs as CSV :
* bug ref;link
* Example :
* <code>
* Bug_ref;Bug_URL;sha1
* 1234;https://support.combodo.com/pages/UI.php?operation=details&class=Bug&id=1234;949b213f9|b1ca1f263|a1271da74
* </code>
*
* 2. List of commits sha1/message without bug ref
* Example :
* <code>
* sha1;subject
* a6aa183e2;:bookmark: Prepare 2.7.5
* </code>
*/
if (count($argv) === 1) {
echo '⚠ You must pass the base tag/sha1 as parameter';
exit(1);
}
$sBaseReference = $argv[1];
//--- Get log
$sGitLogCommand = 'git log --decorate --pretty="%h;%s" --date-order --no-merges '.$sBaseReference.'..HEAD';
$sGitLogRaw = shell_exec($sGitLogCommand);
//--- Analyze log
$aGitLogLines = preg_split('/\n/', trim($sGitLogRaw));;
$aLogLinesWithBugRef = [];
$aLogLineNoBug = [];
foreach ($aGitLogLines as $sLogLine) {
$sBugRef = preg_match('/[nN]°(\d{3,4})/', $sLogLine, $aLineBugRef);
if (($sBugRef === false) || empty($aLineBugRef)) {
$aLogLineNoBug[] = $sLogLine;
continue;
}
$iBugId = $aLineBugRef[1];
$sSha = substr($sLogLine, 0, 9);
if (array_key_exists($iBugId, $aLogLinesWithBugRef)) {
$aBugShaRefs = $aLogLinesWithBugRef[$iBugId];
$aBugShaRefs[] = $sSha;
$aLogLinesWithBugRef[$iBugId] = $aBugShaRefs;
} else {
$aLogLinesWithBugRef[$iBugId] = [$sSha];
}
}
$aBugsList = array_keys($aLogLinesWithBugRef);
sort($aBugsList, SORT_NUMERIC);
//-- Output results
echo "# Bugs included\n";
echo "Bug_ref;Bug_URL;sha1\n";
foreach ($aBugsList as $sBugRef) {
$sShaRefs = implode('|', $aLogLinesWithBugRef[$sBugRef]);
echo "{$sBugRef};https://support.combodo.com/pages/UI.php?operation=details&class=Bug&id={$sBugRef};$sShaRefs\n";
}
echo "\n";
echo "# Logs line without bug referenced\n";
echo "sha1;subject\n";
foreach ($aLogLineNoBug as $sLogLine) {
echo "$sLogLine\n";
}

View File

@@ -123,8 +123,9 @@ We would like to give a special thank you to the people from the community who c
- Lassiter, Dennis
- Lazcano, Federico
- Lucas, Jonathan
- Malik, Remie
- Rosenke, Stephan
- Malik, Remie
- Mindêllo de Andrade, Lucas (a.k.a @rokam)
- Rosenke, Stephan
- Seki, Shoji
- Shilov, Vladimir
- Tulio, Marco

View File

@@ -41,10 +41,8 @@ class ajax_page extends WebPage implements iTabbedPage
parent::__construct($s_title, $bPrintable);
$this->m_sReadyScript = "";
//$this->add_header("Content-type: text/html; charset=utf-8");
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$this->m_oTabs = new TabManager();
$this->sContentType = 'text/html';
$this->sContentDisposition = 'inline';

View File

@@ -72,15 +72,14 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
/**
* @inheritDoc
*/
public abstract function ListSupportedLoginModes();
abstract public function ListSupportedLoginModes();
/**
* @inheritDoc
*/
public function LoginAction($sLoginState, &$iErrorCode)
{
switch ($sLoginState)
{
switch ($sLoginState) {
case LoginWebPage::LOGIN_STATE_START:
return $this->OnStart($iErrorCode);

View File

@@ -3256,16 +3256,15 @@ EOF
*/
public function DisplayDocumentInline(WebPage $oPage, $sAttCode)
{
/** @var \ormDocument $oDoc */
$oDoc = $this->Get($sAttCode);
$sClass = get_class($this);
$Id = $this->GetKey();
switch ($oDoc->GetMainMimeType())
{
switch ($oDoc->GetMainMimeType()) {
case 'text':
case 'html':
$data = $oDoc->GetData();
switch ($oDoc->GetMimeType())
{
switch ($oDoc->GetMimeType()) {
case 'text/xml':
$oPage->add("<iframe id='preview_$sAttCode' src=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" width=\"100%\" height=\"400\">Loading...</iframe>\n");
break;

View File

@@ -32,10 +32,8 @@ class CSVPage extends WebPage
function __construct($s_title) {
parent::__construct($s_title);
$this->add_header("Content-type: text/plain; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
//$this->add_header("Content-Transfer-Encoding: binary");
}

View File

@@ -459,17 +459,21 @@ EOF
$sAttType = $aTargetAttCodes[$sTargetAttCode];
$sExtFieldAttCode = $sTargetAttCode;
}
if (is_a($sAttType, 'AttributeLinkedSet', true))
{
continue;
}
if (is_a($sAttType, 'AttributeFriendlyName', true))
{
continue;
}
if (is_a($sAttType, 'AttributeOneWayPassword', true))
{
continue;
$aForbidenAttType = [
'AttributeLinkedSet',
'AttributeFriendlyName',
'iAttributeNoGroupBy', //we cannot only use iAttributeNoGroupBy since this method is also used by the designer who do not have access to the classes' PHP reflection API. So the known classes has to be listed altogether
'AttributeOneWayPassword',
'AttributeEncryptedString',
'AttributePassword',
];
foreach ($aForbidenAttType as $sForbidenAttType) {
if (is_a($sAttType, $sForbidenAttType, true))
{
continue 2;
}
}
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);

View File

@@ -446,8 +446,21 @@ class DisplayBlock
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
}
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
switch($this->m_sStyle)
{
switch($this->m_sStyle) {
case 'list_search':
case 'list':
break;
default:
// N°3473: except for 'list_search' and 'list' (which have more granularity, see the other switch below),
// refuse to render if the user is not allowed to see the class.
if (! UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) {
$sHtml .= $oPage->GetP(Dict::Format('UI:Error:ReadNotAllowedOn_Class', $this->m_oSet->GetClass()));
return $sHtml;
}
}
switch ($this->m_sStyle) {
case 'count':
if (isset($aExtraParams['group_by']))
{

View File

@@ -60,8 +60,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
// Create a breadcrumb entry for the current page, but get its title as late as possible (page title could be changed later)
$this->bBreadCrumbEnabled = true;
}
else
{
else {
$this->bBreadCrumbEnabled = false;
}
@@ -71,10 +70,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->m_aMessages = array();
$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
$this->add_header("Content-type: text/html; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$this->add_linked_stylesheet("../css/jquery.treeview.css");
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
@@ -357,14 +354,15 @@ JS
);
// Highlight code content created with CKEditor
// Note: We check for the <code> tag inside the <pre> tag to only target code from CKEditor, otherwise we might highlight some others things. See N°3810
$this->add_ready_script(
<<<JS
// Highlight code content for HTML AttributeText
$("[data-attribute-type='AttributeText'] .HTML pre").each(function(i, block) {
$("[data-attribute-type='AttributeText'] .HTML pre > code").parent().each(function(i, block) {
hljs.highlightBlock(block);
});
// Highlight code content for CaseLogs
$("[data-attribute-type='AttributeCaseLog'] .caselog_entry_html pre").each(function(i, block) {
$("[data-attribute-type='AttributeCaseLog'] .caselog_entry_html pre > code").parent().each(function(i, block) {
hljs.highlightBlock(block);
});
JS

View File

@@ -84,10 +84,8 @@ class LoginWebPage extends NiceWebPage
parent::__construct($sTitle);
$this->SetStyleSheet();
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
}
public function SetStyleSheet()
@@ -687,7 +685,7 @@ class LoginWebPage extends NiceWebPage
public static function HTTPReload()
{
$sOriginURL = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$sOriginURL = utils::GetCurrentAbsoluteUrl();
if (!utils::StartsWith($sOriginURL, utils::GetAbsoluteUrlAppRoot()))
{
// If the found URL does not start with the configured AppRoot URL

View File

@@ -45,7 +45,7 @@ register_shutdown_function(function()
$sMessage = substr($sMessage, 0, $iStackTracePos);
}
}
IssueLog::error($sMessage);
IssueLog::error($sMessage, null, $err);
if (strpos($err['message'], 'Allowed memory size of') !== false)
{
$sLimit = ini_get('memory_limit');

View File

@@ -297,11 +297,19 @@ class privUITransactionFile
* Cleanup old transactions which have been pending since more than 24 hours
* Use filemtime instead of filectime since filectime may be affected by operations on the directory (like changing the access rights)
*/
protected static function CleanupOldTransactions()
protected static function CleanupOldTransactions($sTransactionDir = null)
{
$iLimit = time() - 24*3600;
$iThreshold = (int) MetaModel::GetConfig()->Get('transactions_gc_threshold');
$iThreshold = min(100, $iThreshold);
$iThreshold = max(1, $iThreshold);
if ((100 != $iThreshold) && (rand(1, 100) > $iThreshold)) {
return;
}
clearstatcache();
$aTransactions = glob(APPROOT.'data/transactions/*-*');
$iLimit = time() - 24*3600;
$sPattern = $sTransactionDir ? "$sTransactionDir/*" : APPROOT.'data/transactions/*';
$aTransactions = glob($sPattern);
foreach($aTransactions as $sFileName)
{
if (filemtime($sFileName) < $iLimit)

View File

@@ -563,48 +563,93 @@ class utils
public static function ReadFromFile($sFileName)
{
if (!file_exists($sFileName)) return false;
if (!file_exists($sFileName)) {
return false;
}
return file_get_contents($sFileName);
}
/**
* Helper function to convert a value expressed in a 'user friendly format'
* as in php.ini, e.g. 256k, 2M, 1G etc. Into a number of bytes
* @param mixed $value The value as read from php.ini
* @return number
* @param mixed $value The value as read from php.ini (eg 256k, 2M, 1G etc.)
*
* @return int conversion to number of bytes
*
* @since 2.7.5 3.0.0 convert to int numeric values
*
* @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes Shorthand bytes value reference in PHP.net FAQ
*/
public static function ConvertToBytes( $value )
public static function ConvertToBytes($value)
{
$iReturn = $value;
if ( !is_numeric( $value ) )
{
$iLength = strlen( $value );
$iReturn = substr( $value, 0, $iLength - 1 );
$sUnit = strtoupper( substr( $value, $iLength - 1 ) );
switch ( $sUnit )
{
case 'G':
$iReturn *= 1024;
case 'M':
$iReturn *= 1024;
case 'K':
$iReturn *= 1024;
}
}
return $iReturn;
}
/**
* Checks if the memory limit is at least what is required
*
* @param int $memoryLimit set limit in bytes
* @param int $requiredLimit required limit in bytes
* @return bool
*/
public static function IsMemoryLimitOk($memoryLimit, $requiredLimit)
{
return ($memoryLimit >= $requiredLimit) || ($memoryLimit == -1);
}
if (!is_numeric($value)) {
$iLength = strlen($value);
$iReturn = substr($value, 0, $iLength - 1);
$sUnit = strtoupper(substr($value, $iLength - 1));
switch ($sUnit) {
case 'G':
$iReturn *= 1024;
case 'M':
$iReturn *= 1024;
case 'K':
$iReturn *= 1024;
}
} else {
$iReturn = (int)$value;
}
return $iReturn;
}
/**
* Checks if the memory limit is at least what is required
*
* @param int $iMemoryLimit set limit in bytes, use {@link utils::ConvertToBytes()} to convert current php.ini value
* @param int $iRequiredLimit required limit in bytes
*
* @return bool
*/
public static function IsMemoryLimitOk($iMemoryLimit, $iRequiredLimit)
{
if ($iMemoryLimit === -1) {
// -1 means : no limit (see https://www.php.net/manual/fr/ini.core.php#ini.memory-limit)
return true;
}
return ($iMemoryLimit >= $iRequiredLimit);
}
/**
* Set memory_limit to required value
*
* @param string $sRequiredLimit required limit, for example '512M'
*
* @return bool|null null if nothing was done, true if modifying memory_limit was successful, false otherwise
*
* @uses utils::ConvertToBytes()
* @uses \ini_get('memory_limit')
* @uses \ini_set()
* @uses utils::ConvertToBytes()
*
* @since 2.7.5 N°3806
*/
public static function SetMinMemoryLimit($sRequiredLimit)
{
$iRequiredLimit = static::ConvertToBytes($sRequiredLimit);
$sMemoryLimit = trim(ini_get('memory_limit'));
if (empty($sMemoryLimit)) {
// On some PHP installations, memory_limit does not exist as a PHP setting!
// (encountered on a 5.2.0 under Windows)
// In that case, ini_set will not work
return false;
}
$iMemoryLimit = static::ConvertToBytes($sMemoryLimit);
if (static::IsMemoryLimitOk($iMemoryLimit, $iRequiredLimit)) {
return null;
}
return ini_set('memory_limit', $iRequiredLimit);
}
/**
* Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount.
@@ -733,7 +778,7 @@ class utils
* @throws \ConfigException
* @throws \CoreException
*
* @since 2.7.0 N°2478 always call {@link MetaModel::GetConfig} first, cache is only set when loading from disk
* @since 2.7.0 N°2478 this method will now always call {@link MetaModel::GetConfig} first, and cache in this class is only set when loading from disk
*/
public static function GetConfig()
{
@@ -782,22 +827,42 @@ class utils
}
}
/**
* @return bool The boolean value of the conf. "behind_reverse_proxy" (except if there is no REMOTE_ADDR int his case, it return false)
*
* @since 2.7.4
*/
public static function IsProxyTrusted()
{
if (empty($_SERVER['REMOTE_ADDR'])) {
return false;
}
$bTrustProxies = (bool) self::GetConfig()->Get('behind_reverse_proxy');
return $bTrustProxies;
}
/**
* Returns the absolute URL to the application root path
*
* @param bool $bForceTrustProxy
*
* @return string The absolute URL to the application root, without the first slash
*
* @throws \Exception
*
* @since 2.7.4 $bForceTrustProxy param added
*/
public static function GetAbsoluteUrlAppRoot()
public static function GetAbsoluteUrlAppRoot($bForceTrustProxy = false)
{
static $sUrl = null;
if ($sUrl === null)
if ($sUrl === null || $bForceTrustProxy)
{
$sUrl = self::GetConfig()->Get('app_root_url');
if ($sUrl == '')
{
$sUrl = self::GetDefaultUrlAppRoot();
$sUrl = self::GetDefaultUrlAppRoot($bForceTrustProxy);
}
elseif (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1)
{
@@ -821,31 +886,116 @@ class utils
* For most usages, when an root url is needed, use utils::GetAbsoluteUrlAppRoot() instead as uses this only as a fallback when the
* app_root_url conf parameter is not defined.
*
* @param bool $bForceTrustProxy
*
* @return string
*
* @throws \Exception
*/
public static function GetDefaultUrlAppRoot()
*
* @since 2.7.4 $bForceTrustProxy param added
*/
public static function GetDefaultUrlAppRoot($bForceTrustProxy = false)
{
$sAbsoluteUrl = self::GetCurrentAbsoluteUrl($bForceTrustProxy, true);
$sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']);
$sAppRoot = realpath(APPROOT);
return self::GetAppRootUrl($sCurrentScript, $sAppRoot, $sAbsoluteUrl);
}
/**
* Build the current absolute URL from the server's variables.
*
* For almost every usage, you should use the more secure utils::GetAbsoluteUrlAppRoot() : instead of reading the current uri, it provide you the configured application's root URL (this is done during the setup and chn be changed in the configuration file)
*
* @see utils::GetAbsoluteUrlAppRoot
*
* @param bool $bForceTrustProxy
* @param bool $bTrimQueryString
*
* @return string
*
* @since 2.7.4
*/
public static function GetCurrentAbsoluteUrl($bForceTrustProxy = false, $bTrimQueryString = false)
{
// Build an absolute URL to this page on this server/port
$sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
$sProtocol = self::IsConnectionSecure() ? 'https' : 'http';
$iPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
if ($sProtocol == 'http')
{
$sServerName = self::GetServerName($bForceTrustProxy);
$bIsSecure = self::IsConnectionSecure($bForceTrustProxy);
$sProtocol = $bIsSecure ? 'https' : 'http';
$iPort = self::GetServerPort($bForceTrustProxy);
if ($bIsSecure) {
$sPort = ($iPort == 443) ? '' : ':'.$iPort;
} else {
$sPort = ($iPort == 80) ? '' : ':'.$iPort;
}
else
{
$sPort = ($iPort == 443) ? '' : ':'.$iPort;
$sPath = self::GetRequestUri($bForceTrustProxy);
if ($bTrimQueryString) {
// remove all the parameters from the query string
$iQuestionMarkPos = strpos($sPath, '?');
if ($iQuestionMarkPos !== false) {
$sPath = substr($sPath, 0, $iQuestionMarkPos);
}
}
$sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}";
return $sAbsoluteUrl;
}
/**
* @param bool $bForceTrustProxy
*
* @return string
*
* @since 2.7.4
*/
public static function GetServerName($bForceTrustProxy = false)
{
$bTrustProxy = $bForceTrustProxy || self::IsProxyTrusted();
$sServerName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
if ($bTrustProxy) {
$sServerName = isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : $sServerName;
}
return $sServerName;
}
/**
* @param bool $bForceTrustProxy
*
* @return int|mixed
* @since 2.7.4
*/
public static function GetServerPort($bForceTrustProxy = false)
{
$bTrustProxy = $bForceTrustProxy || self::IsProxyTrusted();
$sServerPort = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
if ($bTrustProxy) {
$sServerPort = isset($_SERVER['HTTP_X_FORWARDED_PORT']) ? $_SERVER['HTTP_X_FORWARDED_PORT'] : $sServerPort;
}
return $sServerPort;
}
/**
* @return string
*
* @since 2.7.4
*/
public static function GetRequestUri()
{
// $_SERVER['REQUEST_URI'] is empty when running on IIS
// Let's use Ivan Tcholakov's fix (found on www.dokeos.com)
if (!empty($_SERVER['REQUEST_URI']))
{
$sPath = $_SERVER['REQUEST_URI'];
}
else
if (empty($_SERVER['REQUEST_URI']))
{
$sPath = $_SERVER['SCRIPT_NAME'];
if (!empty($_SERVER['QUERY_STRING']))
@@ -856,18 +1006,7 @@ class utils
}
$sPath = $_SERVER['REQUEST_URI'];
// remove all the parameters from the query string
$iQuestionMarkPos = strpos($sPath, '?');
if ($iQuestionMarkPos !== false)
{
$sPath = substr($sPath, 0, $iQuestionMarkPos);
}
$sAbsoluteUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}";
$sCurrentScript = realpath($_SERVER['SCRIPT_FILENAME']);
$sAppRoot = realpath(APPROOT);
return self::GetAppRootUrl($sCurrentScript, $sAppRoot, $sAbsoluteUrl);
return $sPath;
}
/**
@@ -911,19 +1050,36 @@ class utils
/**
* Helper to handle the variety of HTTP servers
* See N°286 (fixed in [896]), and N°634 (this fix)
*
*
* Though the official specs says 'a non empty string', some servers like IIS do set it to 'off' !
* nginx set it to an empty string
* Others might leave it unset (no array entry)
*/
public static function IsConnectionSecure()
* Others might leave it unset (no array entry)
*
* @param bool $bForceTrustProxy
*
* @return bool
*
* @since 2.7.4 reverse proxies handling
*/
public static function IsConnectionSecure($bForceTrustProxy = false)
{
$bSecured = false;
if (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off'))
$bTrustProxy = $bForceTrustProxy || self::IsProxyTrusted();
if ($bTrustProxy && !empty($_SERVER['HTTP_X_FORWARDED_PROTO']))
{
$bSecured = ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
}
elseif ($bTrustProxy && !empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL']))
{
$bSecured = ($_SERVER['HTTP_X_FORWARDED_PROTOCOL'] === 'https');
}
elseif ((!empty($_SERVER['HTTPS'])) && (strtolower($_SERVER['HTTPS']) != 'off'))
{
$bSecured = true;
}
return $bSecured;
}
@@ -2221,8 +2377,11 @@ class utils
}
/**
* @param string $sPath for example '/var/www/html/itop/data/backups/manual/itop_27-2019-10-03_15_35.tar.gz'
* @param string $sBasePath for example '/var/www/html/itop/data/'
* @param string $sPath for example `/var/www/html/itop/data/backups/manual/itop_27-2019-10-03_15_35.tar.gz`
* **Warning**, if path is a symlink, it will be resolved !
* So `C:\Dev\wamp64\www\itop-dev/env-production/itop-hub-connector/land.php`
* Will become `C:\Dev\wamp64\www\itop-dev\datamodels\2.x\itop-hub-connector\land.php`
* @param string $sBasePath for example `/var/www/html/itop/data/`
*
* @return bool|string false if path :
* * invalid
@@ -2230,7 +2389,10 @@ class utils
* * not contained in base path
* Otherwise return the real path (see realpath())
*
* @uses \realpath()
* @uses static::StartsWith
* @since 2.6.5 2.7.0 N°2538
* @since 2.7.5 details in PHPDoc about symlink resolution
*/
final public static function RealPath($sPath, $sBasePath)
{
@@ -2337,4 +2499,13 @@ class utils
$e = new CoreException($sMessage, null, '', $oException);
throw $e;
}
/**
* @return bool : indicate whether we run under a windows environnement or not
* @since 2.7.4 : N°3412
*/
public static function IsWindowsEnvironment(){
return (substr(PHP_OS,0,3) === 'WIN');
}
}

View File

@@ -482,6 +482,23 @@ class WebPage implements Page
$this->a_headers[] = $s_header;
}
/**
* @param string|null $sHeaderValue for example `SAMESITE`. If null will set the header using the config parameter value.
*
* @since 2.7.3 3.0.0 N°3416
* @uses security_header_xframe config parameter
* @uses \utils::GetConfig()
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
*/
public function add_xframe_options($sHeaderValue = null)
{
if (is_null($sHeaderValue)) {
$sHeaderValue = utils::GetConfig()->Get('security_header_xframe');
}
$this->add_header('X-Frame-Options: '.$sHeaderValue);
}
/**
* Add needed headers to the page so that it will no be cached
*/
@@ -490,7 +507,6 @@ class WebPage implements Page
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
}
/**

View File

@@ -43,10 +43,8 @@ class XMLPage extends WebPage
$this->m_bPassThrough = $bPassThrough;
$this->m_bHeaderSent = false;
$this->add_header("Content-type: text/xml; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$this->add_header("Content-location: export.xml");
}

View File

@@ -12,7 +12,7 @@
"ext-soap": "*",
"combodo/tcpdf": "6.3.5",
"nikic/php-parser": "^3.1",
"pear/archive_tar": "1.4.10",
"pear/archive_tar": "1.4.13",
"pelago/emogrifier": "2.1.0",
"scssphp/scssphp": "1.0.6",
"swiftmailer/swiftmailer": "5.4.12",

29
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "27af144ea2acf2c138f587052a4ceddc",
"content-hash": "8c7f3127435b1afb67965369c50d0898",
"packages": [
{
"name": "combodo/tcpdf",
@@ -168,16 +168,16 @@
},
{
"name": "pear/archive_tar",
"version": "1.4.10",
"version": "1.4.13",
"source": {
"type": "git",
"url": "https://github.com/pear/Archive_Tar.git",
"reference": "bbb4f10f71a1da2715ec6d9a683f4f23c507a49b"
"reference": "2b87b41178cc6d4ad3cba678a46a1cae49786011"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pear/Archive_Tar/zipball/bbb4f10f71a1da2715ec6d9a683f4f23c507a49b",
"reference": "bbb4f10f71a1da2715ec6d9a683f4f23c507a49b",
"url": "https://api.github.com/repos/pear/Archive_Tar/zipball/2b87b41178cc6d4ad3cba678a46a1cae49786011",
"reference": "2b87b41178cc6d4ad3cba678a46a1cae49786011",
"shasum": ""
},
"require": {
@@ -230,7 +230,21 @@
"archive",
"tar"
],
"time": "2020-09-15T14:13:23+00:00"
"support": {
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Archive_Tar",
"source": "https://github.com/pear/Archive_Tar"
},
"funding": [
{
"url": "https://github.com/mrook",
"type": "github"
},
{
"url": "https://www.patreon.com/michielrook",
"type": "patreon"
}
],
"time": "2021-02-16T10:50:50+00:00"
},
{
"name": "pear/console_getopt",
@@ -2590,5 +2604,6 @@
"platform-dev": [],
"platform-overrides": {
"php": "5.6.0"
}
},
"plugin-api-version": "2.0.0"
}

View File

@@ -103,6 +103,14 @@ define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu
define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place
define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place
/**
* Attributes implementing this interface won't be accepted as `group by` field
* @since 2.7.4 N°3473
*/
interface iAttributeNoGroupBy
{
//no method, just a contract on implement
}
/**
* Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.)
@@ -3761,7 +3769,7 @@ class AttributeFinalClass extends AttributeString
*
* @package iTopORM
*/
class AttributePassword extends AttributeString
class AttributePassword extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
@@ -3837,7 +3845,7 @@ class AttributePassword extends AttributeString
*
* @package iTopORM
*/
class AttributeEncryptedString extends AttributeString
class AttributeEncryptedString extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
@@ -9217,7 +9225,7 @@ class AttributeSubItem extends AttributeDefinition
/**
* One way encrypted (hashed) password
*/
class AttributeOneWayPassword extends AttributeDefinition
class AttributeOneWayPassword extends AttributeDefinition implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;

View File

@@ -39,6 +39,7 @@ class MySQLException extends CoreException
*/
public function __construct($sIssue, $aContext, $oException = null, $oMysqli = null)
{
if ($oException != null)
{
$aContext['mysql_errno'] = $oException->getCode();
@@ -58,6 +59,11 @@ class MySQLException extends CoreException
$aContext['mysql_error'] = CMDBSource::GetError();
}
parent::__construct($sIssue, $aContext);
//if is connection error, don't log the default message with password in
if (mysqli_connect_errno()) {
error_log($this->message);
error_reporting(0);
}
}
}
@@ -148,6 +154,17 @@ class CMDBSource
/** @var mysqli $m_oMysqli */
protected static $m_oMysqli;
/**
* The mysqli object is really hard to mock ! This attribute is used only in certain methods, so that we can mock only a very little subset of the mysqli object.
* We are setting it in {@see Init}, by default it is a copy of {@see $m_oMysqli}
* The mock can be injected using the setter {@see SetMySQLiForQuery}
*
* @var mysqli $oMySQLiForQuery
* @see GetMySQLiForQuery
* @see SetMySQLiForQuery
* @since 2.7.5 N°3513 new var to allow mock in tests ({@see \Combodo\iTop\Test\UnitTest\Core\TransactionsTest})
*/
protected static $oMySQLiForQuery;
/**
* @var int number of level for nested transactions : 0 if no transaction was ever opened, +1 for each 'START TRANSACTION' sent
@@ -214,6 +231,7 @@ class CMDBSource
self::$m_sDBTlsCA = empty($sTlsCA) ? null : $sTlsCA;
self::$m_oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
self::SetMySQLiForQuery(self::$m_oMysqli);
}
/**
@@ -227,12 +245,12 @@ class CMDBSource
*
* @return \mysqli
* @throws \MySQLException
*
* @uses IsOpenedDbConnectionUsingTls when asking for a TLS connection, to check if it was really opened using TLS
*/
public static function GetMysqliInstance(
$sDbHost, $sUser, $sPwd, $sSource = '', $bTlsEnabled = false, $sTlsCa = null, $bCheckTlsAfterConnection = false
) {
$oMysqli = null;
$sServer = null;
$iPort = null;
self::InitServerAndPort($sDbHost, $sServer, $iPort);
@@ -262,7 +280,7 @@ class CMDBSource
}
catch(mysqli_sql_exception $e)
{
throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser), $e);
throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser),$e);
}
if ($bTlsEnabled
@@ -331,41 +349,41 @@ class CMDBSource
* parameters were used.<br>
* This method can be called to ensure that the DB connection really uses TLS.
*
* <p>We're using this object connection : {@link self::$m_oMysqli}
* <p>We're using our own mysqli instance to do the check as this check is done when creating the mysqli instance : the consumer
* might want a dedicated object, and if so we don't want to overwrite the one saved in CMDBSource !<br>
* This is the case for example with {@see \iTopMutex} !
*
* @param \mysqli $oMysqli
*
* @return boolean true if the connection was really established using TLS
* @return boolean true if the connection was really established using TLS, false otherwise
* @throws \MySQLException
*
* @used-by GetMysqliInstance
* @uses IsMySqlVarNonEmpty
* @uses 'ssl_version' MySQL var
* @uses 'ssl_cipher' MySQL var
*/
private static function IsOpenedDbConnectionUsingTls($oMysqli)
{
if (self::$m_oMysqli == null)
{
self::$m_oMysqli = $oMysqli;
}
$bNonEmptySslVersionVar = self::IsMySqlVarNonEmpty('ssl_version');
$bNonEmptySslCipherVar = self::IsMySqlVarNonEmpty('ssl_cipher');
$bNonEmptySslVersionVar = self::IsMySqlVarNonEmpty('ssl_version', $oMysqli);
$bNonEmptySslCipherVar = self::IsMySqlVarNonEmpty('ssl_cipher', $oMysqli);
return ($bNonEmptySslVersionVar && $bNonEmptySslCipherVar);
}
/**
* @param string $sVarName
* @param mysqli $oMysqli connection to use for the query
*
* @return bool
* @throws \MySQLException
*
* @uses SHOW STATUS queries
* @uses 'SHOW SESSION STATUS' queries
*/
private static function IsMySqlVarNonEmpty($sVarName)
private static function IsMySqlVarNonEmpty($sVarName, $oMysqli)
{
try
{
$sResult = self::QueryToScalar("SHOW SESSION STATUS LIKE '$sVarName'", 1);
$sResult = self::QueryToScalar("SHOW SESSION STATUS LIKE '$sVarName'", 1, $oMysqli);
}
catch (MySQLQueryHasNoResultException $e)
{
@@ -530,6 +548,24 @@ class CMDBSource
return self::$m_oMysqli;
}
/**
* @return
*/
private static function GetMySQLiForQuery()
{
return self::$oMySQLiForQuery;
}
/**
* Used for test purpose (mysqli mock)
* @param $oMySQLi
*/
private static function SetMySQLiForQuery($oMySQLi)
{
self::$oMySQLiForQuery = $oMySQLi;
}
public static function GetErrNo()
{
if (self::$m_oMysqli->errno != 0)
@@ -665,10 +701,15 @@ class CMDBSource
*/
private static function DBQuery($sSql)
{
$sShortSQL = substr(preg_replace("/\s+/", " ", substr($sSql, 0, 180)), 0, 150);
if (substr_compare($sShortSQL, "SELECT", 0, strlen("SELECT")) !== 0) {
IssueLog::Trace("$sShortSQL", 'cmdbsource');
}
$oKPI = new ExecutionKPI();
try
{
$oResult = self::$m_oMysqli->query($sSql);
$oResult = self::GetMySQLiForQuery()->query($sSql);
}
catch (mysqli_sql_exception $e)
{
@@ -680,7 +721,7 @@ class CMDBSource
{
$aContext = array('query' => $sSql);
$iMySqlErrorNo = self::$m_oMysqli->errno;
$iMySqlErrorNo = self::GetMySQLiForQuery()->errno;
$aMySqlHasGoneAwayErrorCodes = MySQLHasGoneAwayException::getErrorCodes();
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes))
{
@@ -702,7 +743,7 @@ class CMDBSource
private static function LogDeadLock(Exception $e)
{
// checks MySQL error code
$iMySqlErrorNo = self::$m_oMysqli->errno;
$iMySqlErrorNo = self::GetMySQLiForQuery()->errno;
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK)))
{
return;
@@ -710,7 +751,7 @@ class CMDBSource
// Get error info
$sUser = UserRights::GetUser();
$oError = self::$m_oMysqli->query('SHOW ENGINE INNODB STATUS');
$oError = self::GetMySQLiForQuery()->query('SHOW ENGINE INNODB STATUS');
if ($oError !== false)
{
$aData = $oError->fetch_all(MYSQLI_ASSOC);
@@ -732,7 +773,7 @@ class CMDBSource
);
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
IssueLog::Error($sMessage, 'DeadLock', $e->getMessage());
IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage());
}
/**
@@ -748,10 +789,15 @@ class CMDBSource
*/
private static function StartTransaction()
{
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
$bHasExistingTransactions = self::IsInsideTransaction();
if (!$bHasExistingTransactions)
{
IssueLog::Trace("START TRANSACTION $sCaller", 'cmdbsource');
self::DBQuery('START TRANSACTION');
} else {
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") START TRANSACTION $sCaller", 'cmdbsource');
}
self::AddTransactionLevel();
@@ -769,9 +815,12 @@ class CMDBSource
*/
private static function Commit()
{
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
if (!self::IsInsideTransaction())
{
// should not happen !
IssueLog::Error("No Transaction COMMIT $sCaller", 'cmdbsource');
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
}
@@ -779,8 +828,10 @@ class CMDBSource
if (self::IsInsideTransaction())
{
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") COMMIT $sCaller", 'cmdbsource');
return;
}
IssueLog::Trace("COMMIT $sCaller", 'cmdbsource');
self::DBQuery('COMMIT');
}
@@ -799,17 +850,22 @@ class CMDBSource
*/
private static function Rollback()
{
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
if (!self::IsInsideTransaction())
{
// should not happen !
IssueLog::Error("No Transaction ROLLBACK $sCaller", 'cmdbsource');
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
}
self::RemoveLastTransactionLevel();
if (self::IsInsideTransaction())
{
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") ROLLBACK $sCaller", 'cmdbsource');
return;
}
IssueLog::Trace("ROLLBACK $sCaller", 'cmdbsource');
self::DBQuery('ROLLBACK');
}
@@ -859,6 +915,17 @@ class CMDBSource
self::$m_iTransactionLevel = 0;
}
public static function IsDeadlockException(Exception $e)
{
while ($e instanceof Exception) {
if (($e instanceof MySQLException) && ($e->getCode() == 1213)) {
return true;
}
$e = $e->getPrevious();
}
return false;
}
/**
*
* @deprecated 2.7.0 N°1627 use ItopCounter instead
@@ -912,17 +979,21 @@ class CMDBSource
/**
* @param string $sSql
* @param int $iCol beginning at 0
* @param mysqli $oMysqli if not null will query using this connection, otherwise will use {@see GetMySQLiForQuery}
*
* @return string corresponding cell content on the first line
* @throws \MySQLException
* @throws \MySQLQueryHasNoResultException
* @since 2.7.5-2 2.7.6 3.0.0 N°4215 new optional mysqli param
*/
public static function QueryToScalar($sSql, $iCol = 0)
public static function QueryToScalar($sSql, $iCol = 0, $oMysqli = null)
{
$oMysqliToQuery = (is_null($oMysqli)) ? self::GetMySQLiForQuery() : $oMysqli;
$oKPI = new ExecutionKPI();
try
{
$oResult = self::$m_oMysqli->query($sSql);
$oResult = $oMysqliToQuery->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -962,7 +1033,7 @@ class CMDBSource
$oKPI = new ExecutionKPI();
try
{
$oResult = self::$m_oMysqli->query($sSql);
$oResult = self::GetMySQLiForQuery()->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -1044,7 +1115,7 @@ class CMDBSource
{
try
{
$oResult = self::$m_oMysqli->query($sSql);
$oResult = self::GetMySQLiForQuery()->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -1181,17 +1252,24 @@ class CMDBSource
}
/**
* There may have some differences between DB : for example in MySQL 5.7 we have "INT", while in MariaDB >= 10.2 you get "int DEFAULT 'NULL'"
* There may have some differences between DB ! For example in :
* * MySQL 5.7 we have `INT`
* * MariaDB >= 10.2 you get `int DEFAULT 'NULL'`
*
* We still do a case sensitive comparison for enum values !
* We still need to do a case sensitive comparison for enum values !
*
* A better solution would be to generate SQL field definitions ({@link GetFieldSpec} method) based on the DB used... But for
* now (N°2490 / SF #1756 / PR #91) we did implement this simpler solution
*
* @param string $sItopGeneratedFieldType
* @see GetFieldDataTypeAndOptions extracts all info from the SQL field definition
*
* @param string $sDbFieldType
*
* @param string $sItopGeneratedFieldType
*
* @return bool true if same type and options (case sensitive comparison only for type options), false otherwise
*
* @throws \CoreException
* @since 2.7.0 N°2490
*/
public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType)
@@ -1239,24 +1317,68 @@ class CMDBSource
}
/**
* @param string $sCompleteFieldType sql field type, for example 'VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
* @see \self::GetEnumOptions() specific processing for ENUM fields
*
* @param string $sCompleteFieldType sql field type, for example `VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0`
*
* @return string[] consisting of 3 items :
* 1. data type : for example 'VARCHAR'
* 2. type value : for example '255'
* 3. other options : for example ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
* 1. data type : for example `VARCHAR`
* 2. type value : for example `255`
* 3. other options : for example `CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0`
*
* @throws \CoreException
*/
private static function GetFieldDataTypeAndOptions($sCompleteFieldType)
{
preg_match('/^([a-zA-Z]+)(\(([^\)]+)\))?( .+)?/', $sCompleteFieldType, $aMatches);
$sDataType = isset($aMatches[1]) ? $aMatches[1] : '';
if (strcasecmp($sDataType, 'ENUM') === 0){
try{
return self::GetEnumOptions($sDataType, $sCompleteFieldType);
}catch(CoreException $e){
//do nothing ; especially do not block setup.
IssueLog::Warning("enum was not parsed properly: $sCompleteFieldType. it should not happen during setup.");
}
}
$sTypeOptions = isset($aMatches[2]) ? $aMatches[3] : '';
$sOtherOptions = isset($aMatches[4]) ? $aMatches[4] : '';
return array($sDataType, $sTypeOptions, $sOtherOptions);
}
/**
* @param string $sDataType for example `ENUM`
* @param string $sCompleteFieldType Example:
* `ENUM('CSP A','CSP (aaaa) M','NA','OEM(ROC)','OPEN(VL)','RETAIL (Boite)') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`
*
* @return string[] consisting of 3 items :
* 1. data type : ENUM or enum here
* 2. type value : in-between EUM parenthesis
* 3. other options : for example ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
* @throws \CoreException
* @since 2.7.4 N°3065 specific processing for enum fields : fix no alter table when enum values containing parenthesis
* Handle ENUM options
*/
private static function GetEnumOptions($sDataType, $sCompleteFieldType)
{
$iFirstOpeningParenthesis = strpos($sCompleteFieldType, '(');
$iLastEndingParenthesis = strrpos($sCompleteFieldType, ')');
if ($iFirstOpeningParenthesis === false || $iLastEndingParenthesis === false ){
//should never happen as GetFieldDataTypeAndOptions regexp matched.
//except if regexp is modiied/broken somehow one day...
throw new CoreException("GetEnumOptions issue with $sDataType parsing : " . $sCompleteFieldType);
}
$sTypeOptions = substr($sCompleteFieldType, $iFirstOpeningParenthesis + 1, $iLastEndingParenthesis - 1);
$sOtherOptions = substr($sCompleteFieldType, $iLastEndingParenthesis + 1);
return array($sDataType, $sTypeOptions, $sOtherOptions);
}
/**
* @param string $sTable
* @param string $sField
@@ -1480,7 +1602,7 @@ class CMDBSource
$sSql = "SELECT * FROM `$sTable`";
try
{
$oResult = self::$m_oMysqli->query($sSql);
$oResult = self::GetMySQLiForQuery()->query($sSql);
}
catch(mysqli_sql_exception $e)
{

View File

@@ -1065,6 +1065,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'transactions_gc_threshold' => array(
'type' => 'integer',
'description' => 'probability in percent for the garbage collector to be triggered (100 mean always)',
'default' => 10, // added in itop 2.7.4, before the GC was always called
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'log_transactions' => array(
'type' => 'bool',
'description' => 'Whether or not to enable the debug log for the transactions.',
@@ -1249,6 +1257,22 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'security_header_xframe' => [
'type' => 'string',
'description' => 'Value of the X-Frame-Options HTTP header sent by iTop. Possible values : DENY, SAMEORIGIN, or empty string.',
'default' => 'SAMEORIGIN',
'value' => '',
'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)',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => true,
],
);

View File

@@ -149,14 +149,14 @@ class CoreCannotSaveObjectException extends CoreException
*
* @param array $aContextData containing at least those keys : issues, id, class
*/
public function __construct($aContextData)
public function __construct($aContextData, $oPrevious = null)
{
$this->aIssues = $aContextData['issues'];
$this->iObjectId = $aContextData['id'];
$this->sObjectClass = $aContextData['class'];
$sIssues = implode(', ', $this->aIssues);
parent::__construct($sIssues, $aContextData);
parent::__construct($sIssues, $aContextData, '', $oPrevious);
}
/**

View File

@@ -2739,50 +2739,72 @@ abstract class DBObject implements iDisplay
}
}
$iTransactionRetry = 1;
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
try
if ($bIsTransactionEnabled)
{
if ($bIsTransactionEnabled)
{
CMDBSource::Query('START TRANSACTION');
}
// First query built upon on the root class, because the ID must be created first
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
// Then do the leaf class, if different from the root class
if ($sClass != $sRootClass)
{
$this->DBInsertSingleTable($sClass);
}
// Then do the other classes
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
{
if ($sParentClass == $sRootClass)
{
continue;
}
$this->DBInsertSingleTable($sParentClass);
}
$this->OnObjectKeyReady();
$this->DBWriteLinks();
$this->WriteExternalAttributes();
if ($bIsTransactionEnabled)
{
CMDBSource::Query('COMMIT');
}
// TODO Deep clone this object before the transaction (to use it in case of rollback)
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
$iTransactionRetryCount = 1;
$iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
$iTransactionRetry = $iTransactionRetryCount;
}
catch (Exception $e)
{
if ($bIsTransactionEnabled)
{
CMDBSource::Query('ROLLBACK');
while ($iTransactionRetry > 0) {
try {
$iTransactionRetry--;
if ($bIsTransactionEnabled) {
CMDBSource::Query('START TRANSACTION');
}
// First query built upon on the root class, because the ID must be created first
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
// Then do the leaf class, if different from the root class
if ($sClass != $sRootClass) {
$this->DBInsertSingleTable($sClass);
}
// Then do the other classes
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) {
if ($sParentClass == $sRootClass) {
continue;
}
$this->DBInsertSingleTable($sParentClass);
}
$this->OnObjectKeyReady();
$this->DBWriteLinks();
$this->WriteExternalAttributes();
if ($bIsTransactionEnabled) {
CMDBSource::Query('COMMIT');
}
break;
}
catch (Exception $e) {
IssueLog::Error($e->getMessage());
if ($bIsTransactionEnabled)
{
CMDBSource::Query('ROLLBACK');
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
{
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
if ($iTransactionRetry > 0)
{
// wait and retry
IssueLog::Error("Insert TRANSACTION Retrying...");
usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
continue;
}
else
{
IssueLog::Error("Insert Deadlock TRANSACTION prevention failed.");
}
}
}
throw $e;
}
throw $e;
}
$this->m_bIsInDB = true;
@@ -3123,13 +3145,11 @@ abstract class DBObject implements iDisplay
array(), $aParams);
while ($oTrigger = $oSet->Fetch())
{
/** @var \Trigger $oTrigger */
try
{
/** @var \TriggerOnObjectUpdate $oTrigger */
try {
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch(Exception $e)
{
catch (Exception $e) {
utils::EnrichRaisedException($oTrigger, $e);
}
}
@@ -3158,9 +3178,11 @@ abstract class DBObject implements iDisplay
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
if ($bIsTransactionEnabled)
{
$iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
// TODO Deep clone this object before the transaction (to use it in case of rollback)
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
$iTransactionRetryCount = 1;
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
$iTransactionRetry = $iIsTransactionRetryCount;
$iTransactionRetry = $iTransactionRetryCount;
}
while ($iTransactionRetry > 0)
{
@@ -3228,13 +3250,6 @@ abstract class DBObject implements iDisplay
$this->DBWriteLinks();
$this->WriteExternalAttributes();
// 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->m_bDirty = false;
$this->m_aTouchedAtt = array();
$this->m_aModifiedAtt = array();
if (count($aChanges) != 0)
{
$this->RecordAttChanges($aChanges, $aOriginalValues);
@@ -3248,18 +3263,18 @@ abstract class DBObject implements iDisplay
}
catch (MySQLException $e)
{
IssueLog::Error($e->getMessage());
if ($bIsTransactionEnabled)
{
CMDBSource::Query('ROLLBACK');
if ($e->getCode() == 1213)
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
{
// Deadlock found when trying to get lock; try restarting transaction
IssueLog::Error($e->getMessage());
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
if ($iTransactionRetry > 0)
{
// wait and retry
IssueLog::Error("Update TRANSACTION Retrying...");
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry));
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
continue;
}
else
@@ -3273,10 +3288,11 @@ abstract class DBObject implements iDisplay
'id' => $this->GetKey(),
'class' => get_class($this),
'issues' => $aErrors
));
), $e);
}
catch (CoreCannotSaveObjectException $e)
{
IssueLog::Error($e->getMessage());
if ($bIsTransactionEnabled)
{
CMDBSource::Query('ROLLBACK');
@@ -3285,6 +3301,7 @@ abstract class DBObject implements iDisplay
}
catch (Exception $e)
{
IssueLog::Error($e->getMessage());
if ($bIsTransactionEnabled)
{
CMDBSource::Query('ROLLBACK');
@@ -3298,6 +3315,13 @@ 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->m_bDirty = false;
$this->m_aTouchedAtt = array();
$this->m_aModifiedAtt = array();
try
{
$this->AfterUpdate();
@@ -3496,9 +3520,11 @@ abstract class DBObject implements iDisplay
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
if ($bIsTransactionEnabled)
{
$iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
$iTransactionRetry = $iIsTransactionRetryCount;
// TODO Deep clone this object before the transaction (to use it in case of rollback)
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
$iTransactionRetryCount = 1;
$iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
$iTransactionRetry = $iTransactionRetryCount;
}
while ($iTransactionRetry > 0)
{
@@ -3521,18 +3547,18 @@ abstract class DBObject implements iDisplay
}
catch (MySQLException $e)
{
IssueLog::Error($e->getMessage());
if ($bIsTransactionEnabled)
{
CMDBSource::Query('ROLLBACK');
if ($e->getCode() == 1213)
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
{
// Deadlock found when trying to get lock; try restarting transaction
IssueLog::Error($e->getMessage());
if ($iTransactionRetry > 0)
{
// wait and retry
IssueLog::Error("Delete TRANSACTION Retrying...");
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry));
usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
continue;
}
else
@@ -3709,16 +3735,22 @@ abstract class DBObject implements iDisplay
/**
* Apply a stimulus (workflow)
*
* @api
*
* @param string $sStimulusCode
* @param bool $bDoNotWrite
*
*
* @api
*
* @param string $sStimulusCode
* @param bool $bDoNotWrite if true we won't save the object !
*
* @return bool
*
*
* @throws CoreException
* @throws CoreUnexpectedValue
*
* @uses \AttributeStopWatch::Start
* @uses \AttributeStopWatch::Stop
* @uses \DBObject::DBWrite
* @uses \TriggerOnStateLeave::DoActivate
* @uses \TriggerOnStateEnter::DoActivate
*/
public function ApplyStimulus($sStimulusCode, $bDoNotWrite = false)
{
@@ -3842,8 +3874,7 @@ abstract class DBObject implements iDisplay
}
}
if (!$bDoNotWrite)
{
if (!$bDoNotWrite) {
$this->DBWrite();
}
@@ -3851,30 +3882,26 @@ abstract class DBObject implements iDisplay
$aParams = array(
'class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL),
'previous_state' => $sPreviousState,
'new_state' => $sNewState);
'new_state' => $sNewState,
);
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class IN (:class_list) AND t.state=:previous_state"), array(), $aParams);
while ($oTrigger = $oSet->Fetch())
{
/** @var \Trigger $oTrigger */
try
{
while ($oTrigger = $oSet->Fetch()) {
/** @var \TriggerOnStateLeave $oTrigger */
try {
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch(Exception $e)
{
catch (Exception $e) {
utils::EnrichRaisedException($oTrigger, $e);
}
}
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class IN (:class_list) AND t.state=:new_state"), array(), $aParams);
while ($oTrigger = $oSet->Fetch())
{
/** @var \Trigger $oTrigger */
try{
while ($oTrigger = $oSet->Fetch()) {
/** @var \TriggerOnStateEnter $oTrigger */
try {
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch(Exception $e)
{
catch (Exception $e) {
utils::EnrichRaisedException($oTrigger, $e);
}
}

View File

@@ -1004,10 +1004,8 @@ abstract class DBSearch
}
/**
* Generate a SQL query from the current search
*
* @internal
*
* Generate a SQL query from the current search
*
* @param array $aOrderBy Array of '[<classalias>.]attcode' => bAscending
* @param array $aArgs
* @param null $aAttToLoad
@@ -1015,12 +1013,16 @@ abstract class DBSearch
* @param int $iLimitCount
* @param int $iLimitStart
* @param bool $bGetCount
* @param bool $bBeautifulSQL
*
* @return string
* @throws CoreException
* @throws Exception
* @throws MissingQueryArgument
* @throws \ConfigException
* @throws \CoreException
* @throws \MissingQueryArgument
* @internal
*
*/
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false)
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulSQL = true)
{
// Check the order by specification, and prefix with the class alias
// and make sure that the ordering columns are going to be selected
@@ -1085,8 +1087,7 @@ abstract class DBSearch
}
try
{
// $bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
$sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, true);
$sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, $bBeautifulSQL);
if ($sClassAlias == '_itop_')
{
IssueLog::Info('SQL Query (_itop_): '.$sRes);
@@ -1186,6 +1187,30 @@ abstract class DBSearch
}
}
}
if (is_array($aGroupByExpr))
{
foreach($aGroupByExpr as $sAlias => $oGroupByExp)
{
/** @var \Expression $oGroupByExp */
$aFields = $oGroupByExp->ListRequiredFields();
foreach($aFields as $sFieldAlias)
{
$aMatches = array();
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
{
$sFieldClass = $this->GetClassName($aMatches[1]);
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
if ( $oAttDef instanceof iAttributeNoGroupBy)
{
throw new Exception("Grouping on '$sFieldClass' fields is not supported.");
}
}
}
}
}
$oSQLQuery = $oSearch->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, null, $aSelectExpr);
$oSQLQuery->SetSourceOQL($oSearch->ToOQL());

View File

@@ -176,31 +176,29 @@ class InlineImage extends DBObject
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
$oSearch = DBObjectSearch::FromOQL($sOQL);
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
$aInlineImagesId = array();
while($oInlineImage = $oSet->Fetch())
{
$aInlineImagesId[] = $oInlineImage->GetKey();
$aInlineImagesId = array();
while ($oInlineImage = $oSet->Fetch()) {
$aInlineImagesId[] = $oInlineImage->GetKey();
$oInlineImage->SetItem($oObject);
$oInlineImage->Set('temp_id', '');
$oInlineImage->DBUpdate();
}
IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', 'InlineImage', array(
'$sObjectClass' => get_class($oObject),
'$sTransactionId' => $iTransactionId,
'$sTempId' => $sTempId,
'$aInlineImagesId' => $aInlineImagesId,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', LogChannels::INLINE_IMAGE, array(
'$sObjectClass' => get_class($oObject),
'$sTransactionId' => $iTransactionId,
'$sTempId' => $sTempId,
'$aInlineImagesId' => $aInlineImagesId,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
}
else
{
IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', 'InlineImage', array(
'$sObjectClass' => get_class($oObject),
'$sTransactionId' => $iTransactionId,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
else {
IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', LogChannels::INLINE_IMAGE, array(
'$sObjectClass' => get_class($oObject),
'$sTransactionId' => $iTransactionId,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
}
}
@@ -220,12 +218,12 @@ class InlineImage extends DBObject
$aInlineImagesId[] = $oInlineImage->GetKey();
$oInlineImage->DBDelete();
}
IssueLog::Trace('OnFormCancel', 'InlineImage', array(
'$sTempId' => $sTempId,
'$aInlineImagesId' => $aInlineImagesId,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
IssueLog::Trace('OnFormCancel', LogChannels::INLINE_IMAGE, array(
'$sTempId' => $sTempId,
'$aInlineImagesId' => $aInlineImagesId,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
}
/**
@@ -565,17 +563,17 @@ JS
protected function AfterInsert()
{
IssueLog::Trace(__METHOD__, 'InlineImage', array(
'id' => $this->GetKey(),
'expire' => $this->Get('expire'),
'temp_id' => $this->Get('temp_id'),
'item_class' => $this->Get('item_class'),
'item_id' => $this->Get('item_id'),
'item_org_id' => $this->Get('item_org_id'),
'secret' => $this->Get('secret'),
'user' => $sUser = UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
'id' => $this->GetKey(),
'expire' => $this->Get('expire'),
'temp_id' => $this->Get('temp_id'),
'item_class' => $this->Get('item_class'),
'item_id' => $this->Get('item_id'),
'item_org_id' => $this->Get('item_org_id'),
'secret' => $this->Get('secret'),
'user' => $sUser = UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
parent::AfterInsert();
@@ -583,17 +581,17 @@ JS
protected function AfterUpdate()
{
IssueLog::Trace(__METHOD__, 'InlineImage', array(
'id' => $this->GetKey(),
'expire' => $this->Get('expire'),
'temp_id' => $this->Get('temp_id'),
'item_class' => $this->Get('item_class'),
'item_id' => $this->Get('item_id'),
'item_org_id' => $this->Get('item_org_id'),
'secret' => $this->Get('secret'),
'user' => $sUser = UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
'id' => $this->GetKey(),
'expire' => $this->Get('expire'),
'temp_id' => $this->Get('temp_id'),
'item_class' => $this->Get('item_class'),
'item_id' => $this->Get('item_id'),
'item_org_id' => $this->Get('item_org_id'),
'secret' => $this->Get('secret'),
'user' => $sUser = UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
parent::AfterUpdate();
@@ -601,17 +599,17 @@ JS
protected function AfterDelete()
{
IssueLog::Trace(__METHOD__, 'InlineImage', array(
'id' => $this->GetKey(),
'expire' => $this->Get('expire'),
'temp_id' => $this->Get('temp_id'),
'item_class' => $this->Get('item_class'),
'item_id' => $this->Get('item_id'),
'item_org_id' => $this->Get('item_org_id'),
'secret' => $this->Get('secret'),
'user' => $sUser = UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
'id' => $this->GetKey(),
'expire' => $this->Get('expire'),
'temp_id' => $this->Get('temp_id'),
'item_class' => $this->Get('item_class'),
'item_id' => $this->Get('item_id'),
'item_org_id' => $this->Get('item_org_id'),
'secret' => $this->Get('secret'),
'user' => $sUser = UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
parent::AfterDelete();

View File

@@ -2602,5 +2602,18 @@ class DBObjectSearch extends DBSearch
return $oExpression;
}
/**
* @param array $aAttCodes array of attCodes to search into
* @param string $sNeedle one word to be searched
*
* @throws \CoreException
*/
public function AddCondition_FullTextOnAttributes(array $aAttCodes, $sNeedle)
{
}
public function ListParameters()
{
return $this->GetCriteria()->ListParameters();
}
}

View File

@@ -398,10 +398,10 @@ class MonthlyRotatingLogFileNameBuilder extends RotatingLogFileNameBuilder
*/
protected function GetFileSuffix($oDate)
{
$sWeekYear = $oDate->format('o');
$sWeekNumber = $oDate->format('m');
$sMonthYear = $oDate->format('o');
$sMonthNumber = $oDate->format('m');
return $sWeekYear.'-month'.$sWeekNumber;
return $sMonthYear.'-month'.$sMonthNumber;
}
/**
@@ -502,6 +502,7 @@ class FileLog
protected function Write($sText, $sLevel = '', $sChannel = '', $aContext = array())
{
$sTextPrefix = empty($sLevel) ? '' : (str_pad($sLevel, 7).' | ');
$sTextPrefix .= str_pad(UserRights::GetUserId(), 5)." | ";
$sTextSuffix = empty($sChannel) ? '' : " | $sChannel";
$sText = "{$sTextPrefix}{$sText}{$sTextSuffix}";
$sLogFilePath = $this->oFileNameBuilder->GetLogFilePath();
@@ -516,12 +517,9 @@ class FileLog
{
flock($hLogFile, LOCK_EX);
$sDate = date('Y-m-d H:i:s');
if (empty($aContext))
{
if (empty($aContext)) {
fwrite($hLogFile, "$sDate | $sText\n");
}
else
{
} else {
$sContext = var_export($aContext, true);
fwrite($hLogFile, "$sDate | $sText\n$sContext\n");
}
@@ -532,6 +530,21 @@ class FileLog
}
}
/**
* Simple enum like class to factorize channels values as constants
* Channels are used especially as parameters in {@see \LogAPI} methods
*
* @since 2.7.5 3.0.0 N°4012
*/
class LogChannels
{
const DEADLOCK = 'DeadLock';
const INLINE_IMAGE = 'InlineImage';
const PORTAL = 'portal';
}
abstract class LogAPI
{
const CHANNEL_DEFAULT = '';
@@ -543,11 +556,11 @@ abstract class LogAPI
const LEVEL_DEBUG = 'Debug';
const LEVEL_TRACE = 'Trace';
/**
* @var string default log level, can be overrided
* @see GetMinLogLevel
* @var string default log level, can be overrided
* @since 2.7.1 N°2977
*/
const LEVEL_DEFAULT = self::LEVEL_OK;
const LEVEL_DEFAULT = self::LEVEL_OK;
protected static $aLevelsPriority = array(
self::LEVEL_ERROR => 400,
@@ -604,36 +617,29 @@ abstract class LogAPI
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array())
{
if (! static::$m_oFileLog)
{
if (!static::$m_oFileLog) {
return;
}
if (! isset(self::$aLevelsPriority[$sLevel]))
{
if (!isset(self::$aLevelsPriority[$sLevel])) {
IssueLog::Error("invalid log level '{$sLevel}'");
return;
}
if (is_null($sChannel))
{
if (is_null($sChannel)) {
$sChannel = static::CHANNEL_DEFAULT;
}
$sMinLogLevel = self::GetMinLogLevel($sChannel);
if ($sMinLogLevel === false || $sMinLogLevel === 'false')
{
if ($sMinLogLevel === false || $sMinLogLevel === 'false') {
return;
}
if (is_string($sMinLogLevel))
{
if (! isset(self::$aLevelsPriority[$sMinLogLevel]))
{
if (is_string($sMinLogLevel)) {
if (!isset(self::$aLevelsPriority[$sMinLogLevel])) {
throw new Exception("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority)));
}
elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel])
{
} elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel]) {
//priority too low regarding the conf, do not log this
return;
}
@@ -740,7 +746,7 @@ class DeadLockLog extends LogAPI
return self::CHANNEL_WAIT_TIMEOUT;
break;
case 1213:
return self::CHANNEL_DEADLOCK_FOUND;
return self::CHANNEL_DEADLOCK_FOUND;
break;
default:
return self::CHANNEL_DEFAULT;
@@ -749,17 +755,21 @@ class DeadLockLog extends LogAPI
}
/**
* @param int $iMySQLErrNo will be converted to channel using {@link GetChannelFromMysqlErrorNo}
* @param string $sLevel
* @param string $sMessage
* @param null $iMysqlErroNo
* @param int $iMysqlErrorNumber will be converted to channel using {@link GetChannelFromMysqlErrorNo}
* @param array $aContext
*
* @throws \Exception
* @noinspection PhpParameterNameChangedDuringInheritanceInspection
*
* @since 2.7.1 method creation
* @since 2.7.5 3.0.0 rename param names and fix phpdoc (thanks Hipska !)
*/
public static function Log($iMySQLErrNo, $sMessage, $iMysqlErroNo = null, $aContext = array())
public static function Log($sLevel, $sMessage, $iMysqlErrorNumber = null, $aContext = array())
{
$sChannel = self::GetChannelFromMysqlErrorNo($iMysqlErroNo);
parent::Log($iMySQLErrNo, $sMessage, $sChannel, $aContext);
$sChannel = self::GetChannelFromMysqlErrorNo($iMysqlErrorNumber);
parent::Log($sLevel, $sMessage, $sChannel, $aContext);
}
}

View File

@@ -4166,11 +4166,7 @@ abstract class MetaModel
}
if (count($aCurrentUser) > 0)
{
$oSearch = DBObjectSearch::FromOQL("SELECT User WHERE id = :id");
$oSearch->AllowAllData();
$oSet = new DBObjectSet($oSearch, array(), array('id' => UserRights::GetUserId()));
$oSet->OptimizeColumnLoad($aCurrentUser);
$oUser = $oSet->fetch();
$oUser = MetaModel::GetObject("User", UserRights::GetUserId(),true,true);
$aPlaceholders['current_user->object()'] = $oUser;
foreach ($aCurrentUser as $sField)
{
@@ -4179,10 +4175,7 @@ abstract class MetaModel
}
if (count($aCurrentContact) > 0)
{
$oSearch = DBObjectSearch::FromOQL("SELECT Contact WHERE id = :id");
$oSet = new DBObjectSet($oSearch, array(), array('id' => UserRights::GetContactId()));
$oSet->OptimizeColumnLoad(['Contact' => $aCurrentContact]);
$oUser = $oSet->fetch();
$oUser = MetaModel::GetObject("Person", UserRights::GetContactId(),true,true);
foreach ($aCurrentContact as $sField)
{
$aPlaceholders['current_contact->'.$sField] = $oUser->Get($sField);
@@ -5373,7 +5366,7 @@ abstract class MetaModel
$aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` ADD $sKeyFieldDefinition";
if (!$bTableToCreate)
{
$aAlterTableItems[$sTable][$sKeyField] = "ADD $sKeyFieldDefinition";
$aAlterTableItems[$sTable]['field'][$sKeyField] = "ADD $sKeyFieldDefinition";
}
}
else
@@ -5386,7 +5379,7 @@ abstract class MetaModel
$aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)";
if (!$bTableToCreate)
{
$aAlterTableItems[$sTable][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
$aAlterTableItems[$sTable]['field'][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
}
}
if (self::IsAutoIncrementKey($sClass) && !CMDBSource::IsAutoIncrement($sTable, $sKeyField))
@@ -5395,7 +5388,7 @@ abstract class MetaModel
$aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` $sKeyFieldDefinition";
if (!$bTableToCreate)
{
$aAlterTableItems[$sTable][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
$aAlterTableItems[$sTable]['field'][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
}
}
}
@@ -5458,7 +5451,7 @@ abstract class MetaModel
}
else
{
$aAlterTableItems[$sTable][$sField] = "ADD $sFieldDefinition";
$aAlterTableItems[$sTable]['field'][$sField] = "ADD $sFieldDefinition";
$aAdditionalRequests = self::GetAdditionalRequestAfterAlter($sClass, $sTable, $sField);
if (!empty($aAdditionalRequests))
{
@@ -5502,7 +5495,7 @@ abstract class MetaModel
}
else
{
$aAlterTableItems[$sTable][] = "ADD $sIndexType `$sIndexName` ($sColumns)";
$aAlterTableItems[$sTable]['index'][] = "ADD $sIndexType `$sIndexName` ($sColumns)";
}
}
@@ -5543,7 +5536,7 @@ abstract class MetaModel
if (CMDBSource::HasIndex($sTable, $sField))
{
$aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` DROP INDEX `$sIndexName`";
$aAlterTableItems[$sTable][] = "DROP INDEX `$sIndexName`";
$aAlterTableItems[$sTable]['index'][] = "DROP INDEX `$sIndexName`";
}
$sSugFixAfterChange = "ALTER TABLE `$sTable` ADD $sIndexType `$sIndexName` ($sColumns)";
$sAlterTableItemsAfterChange = "ADD $sIndexType `$sIndexName` ($sColumns)";
@@ -5552,17 +5545,12 @@ abstract class MetaModel
// The field already exists, does it have the relevant properties?
//
$bToBeChanged = false;
$sActualFieldSpec = CMDBSource::GetFieldSpec($sTable, $sField);
if (!CMDBSource::IsSameFieldTypes($sDBFieldSpec, $sActualFieldSpec))
{
$bToBeChanged = true;
$aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found <code>$sActualFieldSpec</code> while expecting <code>$sDBFieldSpec</code>";
}
if ($bToBeChanged)
{
$aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` $sFieldDefinition";
$aAlterTableItems[$sTable][$sField] = "CHANGE `$sField` $sFieldDefinition";
$aAlterTableItems[$sTable]['field'][$sField] = "CHANGE `$sField` $sFieldDefinition";
}
// Create indexes (external keys only... so far)
@@ -5577,7 +5565,7 @@ abstract class MetaModel
}
else
{
$aAlterTableItems[$sTable][] = $sAlterTableItemsAfterChange;
$aAlterTableItems[$sTable]['index'][] = $sAlterTableItemsAfterChange;
}
}
}
@@ -5637,9 +5625,12 @@ abstract class MetaModel
{
$aAlterTableItems[$sTable] = array();
}
array_unshift($aAlterTableItems[$sTable], "DROP INDEX `$sIndexId`");
if (isset($aAlterTableItems[$sTable]['index']))
{
array_unshift($aAlterTableItems[$sTable]['index'], "DROP INDEX `$sIndexId`");
}
}
$aAlterTableItems[$sTable][] = "ADD INDEX `$sIndexId` ($sColumns)";
$aAlterTableItems[$sTable]['index'][] = "ADD INDEX `$sIndexId` ($sColumns)";
}
}
}
@@ -5657,7 +5648,7 @@ abstract class MetaModel
// without specifying the value of this unknown column
$sFieldDefinition = "`$sField` ".CMDBSource::GetFieldType($sTable, $sField).' NULL';
$aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` $sFieldDefinition";
$aAlterTableItems[$sTable][$sField] = "CHANGE `$sField` $sFieldDefinition";
$aAlterTableItems[$sTable]['field'][$sField] = "CHANGE `$sField` $sFieldDefinition";
}
$aSugFix[$sClass][$sAttCode][] = "-- Recommended action: ALTER TABLE `$sTable` DROP `$sField`";
}
@@ -5676,7 +5667,10 @@ abstract class MetaModel
{
$aAlterTableItems[$sTable] = array();
}
array_unshift($aAlterTableItems[$sTable], "DROP INDEX `$sIndexId`");
if (isset($aAlterTableItems[$sTable]['index']))
{
array_unshift($aAlterTableItems[$sTable]['index'], "DROP INDEX `$sIndexId`");
}
}
}
@@ -5708,8 +5702,16 @@ abstract class MetaModel
}
foreach ($aAlterTableItems as $sTable => $aChangeList)
{
$sChangeList = implode(', ', $aChangeList);
$aCondensedQueries[] = "ALTER TABLE `$sTable` $sChangeList";
if (isset($aAlterTableItems[$sTable]['field']))
{
$sChangeList = implode(', ', $aChangeList['field']);
$aCondensedQueries[] = "ALTER TABLE `$sTable` $sChangeList";
}
if (isset($aAlterTableItems[$sTable]['index']))
{
$sChangeList = implode(', ', $aChangeList['index']);
$aCondensedQueries[] = "ALTER TABLE `$sTable` $sChangeList";
}
// Add request right after the ALTER TABLE
if (isset($aPostTableAlteration[$sTable]))
{

View File

@@ -242,6 +242,8 @@ class iTopMutex
*
* @throws \Exception
* @throws \MySQLException
*
* @since 2.7.5 3.0.0 N°3968 specify `wait_timeout` for the mutex dedicated connection
*/
public function InitMySQLSession()
{
@@ -254,10 +256,36 @@ class iTopMutex
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
if (!$this->hDBLink)
{
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().')');
}
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,
// since the lock will be released if/when the connection times out.
// Source https://dev.mysql.com/doc/refman/5.7/en/locking-functions.html :
// > A lock obtained with GET_LOCK() is released explicitly by executing RELEASE_LOCK() or implicitly when your session terminates
//
// BEWARE: If you want to check the value of this variable, when run from an interactive console `SHOW VARIABLES LIKE 'wait_timeout'`
// will actually returns the value of the variable `interactive_timeout` which may be quite different.
$sSql = "SHOW VARIABLES LIKE 'wait_timeout'";
$result = mysqli_query($this->hDBLink, $sSql);
if (!$result) {
throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')');
}
if ($aRow = mysqli_fetch_array($result, MYSQLI_BOTH)) {
$iTimeout = (int)$aRow[1];
} else {
mysqli_free_result($result);
throw new Exception("No result for query '".$sSql."'");
}
mysqli_free_result($result);
if ($iTimeout < 86400) {
$result = mysqli_query($this->hDBLink, 'SET SESSION wait_timeout=86400');
if ($result === false) {
throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')');
}
}
}

View File

@@ -1672,6 +1672,39 @@ class FieldExpression extends UnaryExpression
// Has been resolved into an SQL expression
class FieldExpressionResolved extends FieldExpression
{
protected $m_aAdditionalExpressions;
public function __construct($mExpression, $sParent = '')
{
$this->m_aAdditionalExpressions = array();
if (is_array($mExpression))
{
foreach ($mExpression as $sSuffix => $sExpression)
{
if ($sSuffix == '')
{
$sName = $sExpression;
}
$this->m_aAdditionalExpressions[$sSuffix] = new FieldExpressionResolved($sExpression, $sParent);
}
}
else
{
$sName = $mExpression;
}
parent::__construct($sName, $sParent);
}
/**
* @return array of additional expressions for muti-column attributes
* @since 2.7.4
*/
public function AdditionalExpressions()
{
return $this->m_aAdditionalExpressions;
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}

View File

@@ -597,7 +597,7 @@ static public $yy_action = array(
** defined, then do no error processing.
*/
const YYNOCODE = 119;
const YYSTACKDEPTH = 100;
const YYSTACKDEPTH = 1000;
const YYNSTATE = 175;
const YYNRULE = 125;
const YYERRORSYMBOL = 76;
@@ -1175,6 +1175,10 @@ static public $yy_action = array(
}
/* Here code is inserted which will execute if the parser
** stack ever overflows */
#line 30 "..\oql-parser.y"
throw new OQLParserStackOverFlowException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
#line 1186 "..\oql-parser.php"
return;
}
$yytos = new OQLParser_yyStackEntry;
@@ -1474,116 +1478,116 @@ static public $yy_action = array(
** function yy_r0($yymsp){ ... } // User supplied code
** #line <lineno> <thisfile>
*/
#line 29 "..\oql-parser.y"
#line 37 "..\oql-parser.y"
function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor; }
#line 1483 "..\oql-parser.php"
#line 33 "..\oql-parser.y"
#line 1488 "..\oql-parser.php"
#line 41 "..\oql-parser.y"
function yy_r3(){
$this->_retvalue = new OqlUnionQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
}
#line 1488 "..\oql-parser.php"
#line 40 "..\oql-parser.y"
#line 1493 "..\oql-parser.php"
#line 48 "..\oql-parser.y"
function yy_r5(){
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor));
}
#line 1493 "..\oql-parser.php"
#line 43 "..\oql-parser.y"
#line 1498 "..\oql-parser.php"
#line 51 "..\oql-parser.y"
function yy_r6(){
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor));
}
#line 1498 "..\oql-parser.php"
#line 47 "..\oql-parser.y"
#line 1503 "..\oql-parser.php"
#line 55 "..\oql-parser.y"
function yy_r7(){
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -4]->minor);
}
#line 1503 "..\oql-parser.php"
#line 50 "..\oql-parser.y"
#line 1508 "..\oql-parser.php"
#line 58 "..\oql-parser.y"
function yy_r8(){
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -6]->minor);
}
#line 1508 "..\oql-parser.php"
#line 55 "..\oql-parser.y"
#line 1513 "..\oql-parser.php"
#line 63 "..\oql-parser.y"
function yy_r9(){
$this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor);
}
#line 1513 "..\oql-parser.php"
#line 58 "..\oql-parser.y"
#line 1518 "..\oql-parser.php"
#line 66 "..\oql-parser.y"
function yy_r10(){
array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
$this->_retvalue = $this->yystack[$this->yyidx + -2]->minor;
}
#line 1519 "..\oql-parser.php"
#line 63 "..\oql-parser.y"
#line 1524 "..\oql-parser.php"
#line 71 "..\oql-parser.y"
function yy_r11(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; }
#line 1522 "..\oql-parser.php"
#line 64 "..\oql-parser.y"
#line 1527 "..\oql-parser.php"
#line 72 "..\oql-parser.y"
function yy_r12(){ $this->_retvalue = null; }
#line 1525 "..\oql-parser.php"
#line 66 "..\oql-parser.y"
#line 1530 "..\oql-parser.php"
#line 74 "..\oql-parser.y"
function yy_r13(){
// insert the join statement on top of the existing list
array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor);
// and return the updated array
$this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
}
#line 1533 "..\oql-parser.php"
#line 72 "..\oql-parser.y"
#line 1538 "..\oql-parser.php"
#line 80 "..\oql-parser.y"
function yy_r14(){
$this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor);
}
#line 1538 "..\oql-parser.php"
#line 78 "..\oql-parser.y"
#line 1543 "..\oql-parser.php"
#line 86 "..\oql-parser.y"
function yy_r16(){
// create an array with one single item
$this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
}
#line 1544 "..\oql-parser.php"
#line 83 "..\oql-parser.y"
#line 1549 "..\oql-parser.php"
#line 91 "..\oql-parser.y"
function yy_r17(){
// create an array with one single item
$this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
}
#line 1550 "..\oql-parser.php"
#line 88 "..\oql-parser.y"
function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); }
#line 1553 "..\oql-parser.php"
#line 89 "..\oql-parser.y"
function yy_r19(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW', $this->yystack[$this->yyidx + 0]->minor); }
#line 1556 "..\oql-parser.php"
#line 90 "..\oql-parser.y"
function yy_r20(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
#line 1559 "..\oql-parser.php"
#line 91 "..\oql-parser.y"
function yy_r21(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW', $this->yystack[$this->yyidx + 0]->minor); }
#line 1562 "..\oql-parser.php"
#line 92 "..\oql-parser.y"
function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
#line 1565 "..\oql-parser.php"
#line 93 "..\oql-parser.y"
function yy_r23(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
#line 1568 "..\oql-parser.php"
#line 94 "..\oql-parser.y"
function yy_r24(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
#line 1571 "..\oql-parser.php"
#line 95 "..\oql-parser.y"
function yy_r25(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
#line 1574 "..\oql-parser.php"
#line 1555 "..\oql-parser.php"
#line 96 "..\oql-parser.y"
function yy_r26(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
#line 1577 "..\oql-parser.php"
function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); }
#line 1558 "..\oql-parser.php"
#line 97 "..\oql-parser.y"
function yy_r19(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW', $this->yystack[$this->yyidx + 0]->minor); }
#line 1561 "..\oql-parser.php"
#line 98 "..\oql-parser.y"
function yy_r27(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; }
#line 1580 "..\oql-parser.php"
function yy_r20(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
#line 1564 "..\oql-parser.php"
#line 99 "..\oql-parser.y"
function yy_r21(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW', $this->yystack[$this->yyidx + 0]->minor); }
#line 1567 "..\oql-parser.php"
#line 100 "..\oql-parser.y"
function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
#line 1570 "..\oql-parser.php"
#line 101 "..\oql-parser.y"
function yy_r23(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
#line 1573 "..\oql-parser.php"
#line 102 "..\oql-parser.y"
function yy_r24(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
#line 1576 "..\oql-parser.php"
#line 103 "..\oql-parser.y"
function yy_r31(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); }
#line 1583 "..\oql-parser.php"
function yy_r25(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
#line 1579 "..\oql-parser.php"
#line 104 "..\oql-parser.y"
function yy_r32(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; }
#line 1586 "..\oql-parser.php"
#line 105 "..\oql-parser.y"
function yy_r33(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
#line 1589 "..\oql-parser.php"
function yy_r26(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
#line 1582 "..\oql-parser.php"
#line 106 "..\oql-parser.y"
function yy_r27(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; }
#line 1585 "..\oql-parser.php"
#line 111 "..\oql-parser.y"
function yy_r31(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); }
#line 1588 "..\oql-parser.php"
#line 112 "..\oql-parser.y"
function yy_r32(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; }
#line 1591 "..\oql-parser.php"
#line 113 "..\oql-parser.y"
function yy_r33(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
#line 1594 "..\oql-parser.php"
#line 119 "..\oql-parser.y"
function yy_r37(){
if ($this->yystack[$this->yyidx + -1]->minor == 'MATCHES')
{
@@ -1594,44 +1598,44 @@ static public $yy_action = array(
$this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor);
}
}
#line 1601 "..\oql-parser.php"
#line 128 "..\oql-parser.y"
#line 1606 "..\oql-parser.php"
#line 136 "..\oql-parser.y"
function yy_r42(){
$this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor);
}
#line 1606 "..\oql-parser.php"
#line 131 "..\oql-parser.y"
#line 1611 "..\oql-parser.php"
#line 139 "..\oql-parser.y"
function yy_r43(){
$this->_retvalue = new NestedQueryOqlExpression($this->yystack[$this->yyidx + -1]->minor);
}
#line 1611 "..\oql-parser.php"
#line 146 "..\oql-parser.y"
#line 1616 "..\oql-parser.php"
#line 154 "..\oql-parser.y"
function yy_r47(){
$this->_retvalue = array();
}
#line 1616 "..\oql-parser.php"
#line 157 "..\oql-parser.y"
#line 1621 "..\oql-parser.php"
#line 165 "..\oql-parser.y"
function yy_r51(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
#line 1619 "..\oql-parser.php"
#line 170 "..\oql-parser.y"
#line 1624 "..\oql-parser.php"
#line 178 "..\oql-parser.y"
function yy_r61(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); }
#line 1622 "..\oql-parser.php"
#line 172 "..\oql-parser.y"
#line 1627 "..\oql-parser.php"
#line 180 "..\oql-parser.y"
function yy_r63(){ $this->_retvalue = new ScalarOqlExpression(null); }
#line 1625 "..\oql-parser.php"
#line 174 "..\oql-parser.y"
#line 1630 "..\oql-parser.php"
#line 182 "..\oql-parser.y"
function yy_r64(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); }
#line 1628 "..\oql-parser.php"
#line 175 "..\oql-parser.y"
#line 1633 "..\oql-parser.php"
#line 183 "..\oql-parser.y"
function yy_r65(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); }
#line 1631 "..\oql-parser.php"
#line 176 "..\oql-parser.y"
#line 1636 "..\oql-parser.php"
#line 184 "..\oql-parser.y"
function yy_r66(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; }
#line 1634 "..\oql-parser.php"
#line 179 "..\oql-parser.y"
#line 1639 "..\oql-parser.php"
#line 187 "..\oql-parser.y"
function yy_r67(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); }
#line 1637 "..\oql-parser.php"
#line 181 "..\oql-parser.y"
#line 1642 "..\oql-parser.php"
#line 189 "..\oql-parser.y"
function yy_r68(){
if ($this->yystack[$this->yyidx + 0]->minor[0] == '`')
{
@@ -1643,22 +1647,22 @@ static public $yy_action = array(
}
$this->_retvalue = new OqlName($name, $this->m_iColPrev);
}
#line 1650 "..\oql-parser.php"
#line 192 "..\oql-parser.y"
#line 1655 "..\oql-parser.php"
#line 200 "..\oql-parser.y"
function yy_r69(){$this->_retvalue=(int)$this->yystack[$this->yyidx + 0]->minor; }
#line 1653 "..\oql-parser.php"
#line 193 "..\oql-parser.y"
#line 1658 "..\oql-parser.php"
#line 201 "..\oql-parser.y"
function yy_r70(){$this->_retvalue=(int)-$this->yystack[$this->yyidx + 0]->minor; }
#line 1656 "..\oql-parser.php"
#line 194 "..\oql-parser.y"
#line 1661 "..\oql-parser.php"
#line 202 "..\oql-parser.y"
function yy_r71(){$this->_retvalue=new OqlHexValue($this->yystack[$this->yyidx + 0]->minor); }
#line 1659 "..\oql-parser.php"
#line 195 "..\oql-parser.y"
#line 1664 "..\oql-parser.php"
#line 203 "..\oql-parser.y"
function yy_r72(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); }
#line 1662 "..\oql-parser.php"
#line 198 "..\oql-parser.y"
#line 1667 "..\oql-parser.php"
#line 206 "..\oql-parser.y"
function yy_r73(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; }
#line 1665 "..\oql-parser.php"
#line 1670 "..\oql-parser.php"
/**
* placeholder for the left hand side in a reduce operation.
@@ -1759,6 +1763,10 @@ static public $yy_action = array(
}
/* Here code is inserted which will be executed whenever the
** parser fails */
#line 33 "..\oql-parser.y"
throw new OQLParserParseFailureException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
#line 1775 "..\oql-parser.php"
}
/**
@@ -1772,8 +1780,8 @@ static public $yy_action = array(
{
#line 25 "..\oql-parser.y"
throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
#line 1781 "..\oql-parser.php"
throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
#line 1791 "..\oql-parser.php"
}
/**
@@ -1940,19 +1948,47 @@ throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCo
} while ($yymajor != self::YYNOCODE && $this->yyidx >= 0);
}
}
#line 263 "..\oql-parser.y"
#line 271 "..\oql-parser.y"
class OQLParserException extends OQLException
{
public function __construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue)
{
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
}
}
class OQLParserSyntaxErrorException extends OQLParserException
{
public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue)
{
$sIssue = "Unexpected token $sTokenName";
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
}
}
class OQLParserStackOverFlowException extends OQLParserException
{
public function __construct($sInput, $iLine, $iCol)
{
$sIssue = "Stack overflow";
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
}
}
class OQLParserParseFailureException extends OQLParserException
{
public function __construct($sInput, $iLine, $iCol)
{
$sIssue = "Unexpected token $sTokenName";
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
}
}
class OQLParser extends OQLParserRaw
{
// dirty, but working for us (no other mean to get the final result :-(
@@ -2005,4 +2041,4 @@ class OQLParser extends OQLParserRaw
}
}
#line 2014 "..\oql-parser.php"
#line 2052 "..\oql-parser.php"

View File

@@ -23,7 +23,15 @@ later : solve the 2 remaining shift-reduce conflicts (JOIN)
%name OQLParser_
%declare_class {class OQLParserRaw}
%syntax_error {
throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
}
/* Bug N°4052 Parser stack size too small for huge OQL requests */
%stack_size 1000
%stack_overflow {
throw new OQLParserStackOverFlowException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
}
%parse_failure {
throw new OQLParserParseFailureException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
}
result ::= union(X). { $this->my_result = X; }
@@ -263,15 +271,43 @@ func_name(A) ::= F_INET_NTOA(X). { A=X; }
%code {
class OQLParserException extends OQLException
{
public function __construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue)
{
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
}
}
class OQLParserSyntaxErrorException extends OQLParserException
{
public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue)
{
$sIssue = "Unexpected token $sTokenName";
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
}
}
class OQLParserStackOverFlowException extends OQLParserException
{
public function __construct($sInput, $iLine, $iCol)
{
$sIssue = "Stack overflow";
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
}
}
class OQLParserParseFailureException extends OQLParserException
{
public function __construct($sInput, $iLine, $iCol)
{
$sIssue = "Unexpected token $sTokenName";
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
}
}
class OQLParser extends OQLParserRaw
{
// dirty, but working for us (no other mean to get the final result :-(

View File

@@ -1 +1 @@
2020-09-29
2021-06-03

View File

@@ -331,4 +331,12 @@ class OQLJoin
return $this->sRightField;
}
/**
* @return string
*/
public function GetLeftField()
{
return $this->sLeftField;
}
}

View File

@@ -50,8 +50,15 @@ class OQLClassTreeOptimizer
{
if ($oJoin->IsOutbound())
{
// The join is not used, remove from tree
$oCurrentClassNode->RemoveJoin($sLeftKey, $index);
// If joined class in not the same class than the external key target class
// then the join cannot be removed because it is used to filter the request
$sJoinedClass = $oJoin->GetOOQLClassNode()->GetNodeClass();
$sExtKeyAttCode = $oJoin->GetLeftField();
$oExtKeyAttDef = MetaModel::GetAttributeDef($oCurrentClassNode->GetNodeClass(), $sExtKeyAttCode);
if (($oExtKeyAttDef instanceof AttributeExternalKey) && ($sJoinedClass == $oExtKeyAttDef->GetTargetClass())) {
// The join is not used, remove from tree
$oCurrentClassNode->RemoveJoin($sLeftKey, $index);
}
}
else
{

View File

@@ -178,6 +178,14 @@ class QueryBuilderExpressions
foreach ($this->m_aSelectExpr as $sColAlias => $oExpr)
{
$this->m_aSelectExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
if ($this->m_aSelectExpr[$sColAlias] instanceof FieldExpressionResolved)
{
// Split the field with the relevant alias
foreach ($this->m_aSelectExpr[$sColAlias]->AdditionalExpressions() as $sSuffix => $oAdditionalExpr)
{
$this->m_aSelectExpr[$sColAlias.$sSuffix] = $oAdditionalExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
}
}
if ($this->m_aGroupByExpr)
{

View File

@@ -199,8 +199,8 @@ EOF
// Integration within MS-Excel web queries + HTTPS + IIS:
// MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS
// Then the fix is to force the reset of header values Pragma and Cache-control
$oPage->add_header("Pragma:", true);
$oPage->add_header("Cache-control:", true);
$oPage->add_header("Pragma:");
$oPage->add_header("Cache-control:");
}
public function GetHeader()

View File

@@ -239,24 +239,16 @@ class SQLObjectQueryBuilder
continue;
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr)
$oFieldSQLExp = new FieldExpressionResolved($oAttDef->GetSQLExpressions(), $sClassAlias);
/**
* @var string $sPluginClass
* @var iQueryModifier $oQueryModifier
*/
foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
{
if (!empty($sColId))
{
// Multi column attributes
$oBuild->m_oQBExpressions->AddSelect($sSelectedClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias));
}
$oFieldSQLExp = new FieldExpressionResolved($sSQLExpr, $sClassAlias);
/**
* @var string $sPluginClass
* @var iQueryModifier $oQueryModifier
*/
foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
{
$oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sClass, $sAttCode, $sColId, $oFieldSQLExp, $oBaseSQLQuery);
}
$aTranslation[$sClassAlias][$sAttCode.$sColId] = $oFieldSQLExp;
$oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sClass, $sAttCode, '', $oFieldSQLExp, $oBaseSQLQuery);
}
$aTranslation[$sClassAlias][$sAttCode] = $oFieldSQLExp;
}
// Translate the selected columns

View File

@@ -17,17 +17,17 @@
*/
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
$version: "v2.7.2";
$version: "v2.7.5";
$approot-relative: "../../../../../" !default; // relative to env-***/branding/themes/***/main.css
// Base colors
$gray-base: #000 !default;
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
$gray-dark: #444 !default;
$gray: #777 !default;
$gray-light: #808080 !default;
$gray-lighter: #ddd !default;
$gray-extra-light: #F1F1F1 !default;
$gray-base: #000 !default;
$gray-darker: lighten($gray-base, 13.5%) !default; // #222
$gray-dark: #444 !default;
$gray: #777 !default;
$gray-light: #808080 !default;
$gray-lighter: #ddd !default;
$gray-extra-light: #F1F1F1 !default;
$white: #FFFFFF !default;

View File

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

View File

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

View File

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

View File

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

View File

@@ -222,13 +222,11 @@ class DatabaseAnalyzer
{
$oFixSearch->AddCondition($sAttCode, $sValue, '=');
}
$aFixit[] = $oFixSearch->MakeSelectQuery().';';
$aFixit[] = $oFixSearch->MakeSelectQuery([], [], null, null, 0, 0, false, false).';';
$aFixit[] = "";
}
$aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'] = $aFixit;
}
return;
}
private function GetUniquenessRuleMessage($sUniquenessRuleId)
@@ -326,13 +324,8 @@ class DatabaseAnalyzer
{
$sField = MetaModel::DBGetClassField($sClass);
$sRootField = MetaModel::DBGetClassField($sRootClass);
$sSelWrongRecs = <<<SQL
SELECT `$sTable`.`$sKeyField` AS id
FROM `$sTable`
JOIN `$sRootTable` ON `$sRootTable`.`$sRootKey` = `$sTable`.`$sKeyField`
WHERE `$sTable`.`$sField` != `$sRootTable`.`$sRootField`
SQL;
// Copy the finalclass of the root table
$sSelWrongRecs = "SELECT `$sTable`.`$sKeyField` AS id FROM `$sTable` JOIN `$sRootTable` ON `$sRootTable`.`$sRootKey` = `$sTable`.`$sKeyField` WHERE `$sTable`.`$sField` != `$sRootTable`.`$sRootField`";
// Copy the final class of the root table
$sFixItRequest = "UPDATE `$sTable`,`$sRootTable` SET `$sTable`.`$sField` = `$sRootTable`.`$sRootField` WHERE `$sTable`.`$sKeyField` = `$sRootTable`.`$sRootKey`";
$this->ExecQuery($sSelWrongRecs, $sFixItRequest, Dict::Format('DBAnalyzer-Integrity-FinalClass', $sField, $sTable, $sRootTable), $sClass, $aErrorsAndFixes);
}
@@ -360,65 +353,79 @@ SQL;
// Note: a class/table may have an external key on itself
$sSelect = "SELECT DISTINCT `$sTable`.`$sKeyField` AS id, `$sTable`.`$sExtKeyField` AS value";
$sFilter = "FROM `$sTable` LEFT JOIN `$sRemoteTable` AS `{$sRemoteTable}_1` ON `$sTable`.`$sExtKeyField` = `{$sRemoteTable}_1`.`$sRemoteKey`";
$sFrom = "FROM `$sTable`";
$sJoin = "LEFT JOIN `$sRemoteTable` AS `{$sRemoteTable}_1` ON `$sTable`.`$sExtKeyField` = `{$sRemoteTable}_1`.`$sRemoteKey`";
$sFilter = $sFilter." WHERE `{$sRemoteTable}_1`.`$sRemoteKey` IS NULL";
$sFilter = "WHERE `{$sRemoteTable}_1`.`$sRemoteKey` IS NULL";
// Exclude the records pointing to 0/null from the errors (separate test below)
$sFilter .= " AND `$sTable`.`$sExtKeyField` IS NOT NULL";
$sFilter .= " AND `$sTable`.`$sExtKeyField` != 0";
$sSelWrongRecs = "$sSelect $sFilter";
$sSelWrongRecs = "$sSelect $sFrom $sJoin $sFilter";
$sErrorDesc = Dict::Format('DBAnalyzer-Integrity-InvalidExtKey', $sAttCode, $sTable, $sExtKeyField);
$this->ExecQuery($sSelWrongRecs, '', $sErrorDesc, $sClass, $aErrorsAndFixes);
$aFixIt = [];
// Fix it request needs the values of the enum to generate the requests
if (isset($aErrorsAndFixes[$sClass][$sErrorDesc]['values']))
{
$aFixIt = array();
$aFixIt[] = "-- Remove inconsistant entries:";
$sIds = implode(', ', array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']));
$aFixIt[] = "DELETE `$sTable` FROM `$sTable` WHERE `$sTable`.`$sExtKeyField` IN ($sIds)";
$aFixIt[] = "";
$aFixIt[] = "-- Or fix inconsistant values: Replace XXX with the appropriate value";
foreach (array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']) as $sKey)
{
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sExtKeyField` = XXX WHERE `$sTable`.`$sExtKeyField` = '$sKey'";
}
$aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'] = $aFixIt;
}
if (!$oAttDef->IsNullAllowed())
{
$sSelect = "SELECT DISTINCT `$sTable`.`$sKeyField` AS id";
$sDelete = "DELETE `$sTable`";
$sFilter = "FROM `$sTable` WHERE `$sTable`.`$sExtKeyField` IS NULL OR `$sTable`.`$sExtKeyField` = 0";
$sSelWrongRecs = "$sSelect $sFilter";
$sFixItRequest = "$sDelete $sFilter";
$sErrorDesc = Dict::Format('DBAnalyzer-Integrity-MissingExtKey', $sAttCode, $sTable, $sExtKeyField);
$this->ExecQuery($sSelWrongRecs, $sFixItRequest, $sErrorDesc, $sClass, $aErrorsAndFixes);
if (isset($aErrorsAndFixes[$sClass][$sErrorDesc]['count']) && ($aErrorsAndFixes[$sClass][$sErrorDesc]['count'] > 0))
{
$aFixIt = $aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'];
$aFixIt[] = "-- Alternate fix";
$aFixIt[] = "-- Replace XXX with the appropriate value";
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sExtKeyField` = XXX WHERE `$sTable`.`$sExtKeyField` IS NULL OR `$sTable`.`$sExtKeyField` = 0";
$aAdditionalFixIt = $this->GetSpecificExternalKeysFixItForNull($sTable, $sExtKeyField);
if ($oAttDef->IsNullAllowed()) {
$aFixIt[] = "-- Fix inconsistant values: remove the external key";
foreach (array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']) as $sKey) {
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sExtKeyField` = 0 WHERE `$sTable`.`$sExtKeyField` = '$sKey'";
}
} else {
$aAdditionalFixIt = $this->GetSpecificExternalKeysFixItForNull($sTable, $sExtKeyField, $sFilter, $sJoin);
foreach ($aAdditionalFixIt as $sFixIt)
{
$aFixIt[] = $sFixIt;
}
$aFixIt[] = "-- Alternate fix: remove inconsistant entries:";
$sIds = implode(', ', array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']));
$aFixIt[] = "DELETE `$sTable` FROM `$sTable` WHERE `$sTable`.`$sExtKeyField` IN ($sIds)";
$aFixIt[] = "-- Alternate fix: update inconsistant values: Replace XXX with the appropriate value";
foreach (array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']) as $sKey) {
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sExtKeyField` = XXX WHERE `$sTable`.`$sExtKeyField` = '$sKey'";
}
}
$aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'] = $aFixIt;
}
if (!$oAttDef->IsNullAllowed()) {
$sSelect = "SELECT DISTINCT `$sTable`.`$sKeyField` AS id";
$sDelete = "DELETE `$sTable`";
$sFrom = "FROM `$sTable`";
$sFilter = "WHERE `$sTable`.`$sExtKeyField` IS NULL OR `$sTable`.`$sExtKeyField` = 0";
$sSelWrongRecs = "$sSelect $sFrom $sFilter";
$sFixItRequest = "$sDelete $sFrom $sFilter";
$sErrorDesc = Dict::Format('DBAnalyzer-Integrity-MissingExtKey', $sAttCode, $sTable, $sExtKeyField);
$this->ExecQuery($sSelWrongRecs, '', $sErrorDesc, $sClass, $aErrorsAndFixes);
$aFixIt = [];
if (isset($aErrorsAndFixes[$sClass][$sErrorDesc]['count']) && ($aErrorsAndFixes[$sClass][$sErrorDesc]['count'] > 0))
{
$aAdditionalFixIt = $this->GetSpecificExternalKeysFixItForNull($sTable, $sExtKeyField, $sFilter);
foreach ($aAdditionalFixIt as $sFixIt)
{
$aFixIt[] = $sFixIt;
}
$aFixIt[] = "-- Alternate fix: remove inconsistant entries:";
$aFixIt[] = $sFixItRequest;
$aFixIt[] = "-- Alternate fix: replace XXX with the appropriate value";
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sExtKeyField` = XXX WHERE `$sTable`.`$sExtKeyField` IS NULL OR `$sTable`.`$sExtKeyField` = 0";
$aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'] = $aFixIt;
}
}
}
private function GetSpecificExternalKeysFixItForNull($sTable, $sExtKeyField)
private function GetSpecificExternalKeysFixItForNull($sTable, $sExtKeyField, $sFilter, $sJoin = '')
{
$aFixIt = array();
if ($sTable == 'ticket' && $sExtKeyField == 'org_id')
{
$aFixIt[] = "-- Alternate fix: set the ticket org to the caller org";
$aFixIt[] = "UPDATE ticket AS t JOIN contact AS c ON t.caller_id=c.id SET t.org_id=c.org_id WHERE t.org_id IS NULL OR t.org_id = 0";
$aFixIt[] = "UPDATE ticket JOIN contact AS c ON ticket.caller_id=c.id $sJoin SET ticket.org_id=c.org_id $sFilter";
}
return $aFixIt;
}
@@ -441,7 +448,8 @@ SQL;
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode);
if (!is_null($aAllowedValues) && count($aAllowedValues) > 0)
{
$sExpectedValues = implode(",", CMDBSource::Quote(array_keys($aAllowedValues), true));
$aAllowedValues = array_keys($aAllowedValues);
$sExpectedValues = implode(",", CMDBSource::Quote($aAllowedValues, true));
$aCols = $oAttDef->GetSQLExpressions(); // Workaround a PHP bug: sometimes issuing a Notice if invoking current(somefunc())
$sMyAttributeField = current($aCols); // get the first column for the moment
@@ -458,15 +466,18 @@ SQL;
if (isset($aErrorsAndFixes[$sClass][$sErrorDesc]['fixit']))
{
$aFixIt = $aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'];
$aFixIt[] = "-- Alternative: Replace 'XXX' with the appropriate value";
$aFixIt[] = "-- Alternative: Replace enums with the appropriate value";
}
else
{
$aFixIt = array("-- Replace 'XXX' with the appropriate value");
$aFixIt = ["-- Replace enums with the appropriate value"];
}
foreach (array_keys($aErrorsAndFixes[$sClass][$sErrorDesc]['values']) as $sKey)
{
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sMyAttributeField` = 'XXX' WHERE `$sTable`.`$sMyAttributeField` = '$sKey'";
foreach ($aAllowedValues as $sAllowedValue) {
$aFixIt[] = "-- Replace $sKey by $sAllowedValue";
$aFixIt[] = "UPDATE `$sTable` SET `$sTable`.`$sMyAttributeField` = '$sAllowedValue' WHERE `$sTable`.`$sMyAttributeField` = '$sKey'";
}
}
$aErrorsAndFixes[$sClass][$sErrorDesc]['fixit'] = $aFixIt;
}

View File

@@ -31,15 +31,16 @@ const MAX_RESULTS = 10;
* @param ApplicationContext $oAppContext
*
* @return \iTopWebPage
* @throws CoreException
* @throws DictExceptionMissingString
* @throws MySQLException
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \MySQLException
*/
function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppContext)
{
$iShowId = intval(utils::ReadParam('show_id', '0'));
$sErrorLabelSelection = utils::ReadParam('error_selection', '');
$sClassSelection = utils::ReadParam('class_selection', '');
$bVerbose = utils::ReadParam('verbose', 0);
if (!empty($sClassSelection))
{
$aClassSelection = explode(",", $sClassSelection);
@@ -109,11 +110,16 @@ function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppCon
if ($iShowId == 3)
{
DisplayInconsistenciesReport($aResults);
DisplayInconsistenciesReport($aResults, $bVerbose);
}
$oP->p(Dict::S('DBTools:ErrorsFound'));
if ($iShowId > 0) {
$oP->p(Dict::S('DBTools:Disclaimer'));
$oP->p(Dict::S('DBTools:Indication'));
}
$oP->add('<table class="listResults"><tr><th>'.Dict::S('DBTools:Class').'</th><th>'.Dict::S('DBTools:Count').'</th><th>'.Dict::S('DBTools:Error').'</th></tr>');
$bTable = true;
foreach($aResults as $sClass => $aErrorList)
@@ -148,7 +154,7 @@ function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppCon
$oP->p(Dict::S('DBTools:SQLquery'));
$sQuery = $aError['query'];
$oP->add('<div style="padding: 15px; background: #f1f1f1;">');
$oP->add('<code>'.$sQuery.'</code>');
$oP->add('<pre>'.$sQuery.'</pre>');
$oP->add('</div>');
if (isset($aError['fixit']))
@@ -163,27 +169,26 @@ function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppCon
$oP->add('<br></div>');
}
$oP->p(Dict::S('DBTools:SQLresult'));
$sQueryResult = '';
$iCount = count($aError['res']);
$iMaxCount = MAX_RESULTS;
foreach($aError['res'] as $aRes)
{
$iMaxCount--;
if ($iMaxCount < 0)
{
$sQueryResult .= 'Displayed '.MAX_RESULTS."/$iCount results.<br>";
break;
if ($bVerbose) {
$oP->p(Dict::S('DBTools:SQLresult'));
$sQueryResult = '';
$iCount = count($aError['res']);
$iMaxCount = MAX_RESULTS;
foreach ($aError['res'] as $aRes) {
$iMaxCount--;
if ($iMaxCount < 0) {
$sQueryResult .= 'Displayed '.MAX_RESULTS."/$iCount results.<br>";
break;
}
foreach ($aRes as $sKey => $sValue) {
$sQueryResult .= "'$sKey'='$sValue'&nbsp;";
}
$sQueryResult .= '<br>';
}
foreach($aRes as $sKey => $sValue)
{
$sQueryResult .= "'$sKey'='$sValue'&nbsp;";
}
$sQueryResult .= '<br>';
$oP->add('<div style="padding: 15px; background: #f1f1f1;">');
$oP->add('<pre>'.$sQueryResult.'</pre>');
$oP->add('</div>');
}
$oP->add('<div style="padding: 15px; background: #f1f1f1;">');
$oP->add('<code>'.$sQueryResult.'</code>');
$oP->add('</div>');
}
}
}
@@ -194,14 +199,15 @@ function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppCon
/**
* @param $aResults
* @param bool $bVerbose
*
* @return mixed
* @throws CoreException
* @throws DictExceptionMissingString
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
function DisplayInconsistenciesReport($aResults)
function DisplayInconsistenciesReport($aResults, $bVerbose = false)
{
$sReportFile = DBAnalyzerUtils::GenerateReport($aResults);
$sReportFile = DBAnalyzerUtils::GenerateReport($aResults, $bVerbose);
$sZipReport = "{$sReportFile}.zip";
$oArchive = new ZipArchive();
@@ -212,9 +218,9 @@ function DisplayInconsistenciesReport($aResults)
header('Content-Description: File Transfer');
header('Content-Type: multipart/x-zip');
header('Content-Disposition: inline; filename="'.basename($sZipReport).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Expires: 0');
header('Content-Length: '.filesize($sZipReport));
readfile($sZipReport);
unlink($sZipReport);

View File

@@ -28,6 +28,8 @@ Dict::Add('EN US', 'English', 'English', array(
'DBTools:Class' => 'Class',
'DBTools:Title' => 'Database Maintenance Tools',
'DBTools:ErrorsFound' => 'Errors Found',
'DBTools:Indication' => 'Important: after fixing errors in the database you\'ll have to run the analysis again as new inconsistencies will be generated',
'DBTools:Disclaimer' => 'DISCLAIMER: BACKUP YOUR DATABASE BEFORE RUNNING THE FIXES',
'DBTools:Error' => 'Error',
'DBTools:Count' => 'Count',
'DBTools:SQLquery' => 'SQL query',

View File

@@ -23,6 +23,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'DBTools:Class' => 'Classe',
'DBTools:Title' => 'Outils maintenance base de données',
'DBTools:ErrorsFound' => 'Erreurs trouvées',
'DBTools:Indication' => 'Important : après correction il est nécessaire de relancer l\'analyse car d\'autres inconsistances peuvent être générées par les modifications',
'DBTools:Disclaimer' => 'ATTENTION : EFFECTUEZ UNE SAUVEGARDE DE LA BASE AVANT D\'APPLIQUER LES CORRECTIONS',
'DBTools:Error' => 'Erreur',
'DBTools:Count' => 'Nombre',
'DBTools:SQLquery' => 'Requête SQL',
@@ -41,7 +43,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'DBTools:Inconsistencies' => 'Incohérences de base de données',
'DBAnalyzer-Integrity-OrphanRecord' => 'Enregistrement orphelin dans `%1$s`, il devrait avoir son équivalent dans la tableit `%2$s`',
'DBAnalyzer-Integrity-OrphanRecord' => 'Enregistrement orphelin dans `%1$s`, il devrait avoir son équivalent dans la table `%2$s`',
'DBAnalyzer-Integrity-InvalidExtKey' => 'Clef externe invalide %1$s (colonne: `%2$s.%3$s`)',
'DBAnalyzer-Integrity-MissingExtKey' => 'Clef externe manquante %1$s (colonne: `%2$s.%3$s`)',
'DBAnalyzer-Integrity-InvalidValue' => 'Valeur invalide pour %1$s (colonne: `%2$s.%3$s`)',

View File

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

View File

@@ -8,6 +8,7 @@
namespace Combodo\iTop\DBTools\Service;
use CoreException;
use Dict;
use DictExceptionMissingString;
use MetaModel;
@@ -20,29 +21,31 @@ class DBAnalyzerUtils
* @throws CoreException
* @throws DictExceptionMissingString
*/
public static function GenerateReport($aResults)
public static function GenerateReport($aResults, $bVerbose = false)
{
$sDBToolsFolder = str_replace("\\", '/', APPROOT.'log/');
$sReportFile = 'dbtools-report';
$fReport = fopen($sDBToolsFolder.$sReportFile.'.log', 'w');
fwrite($fReport, 'Database Maintenance tools: '.date('Y-m-d H:i:s')."\r\n");
fwrite($fReport, '-- Database Maintenance tools: '.date('Y-m-d H:i:s')."\r\n");
fwrite($fReport, "-- ".Dict::S('DBTools:Disclaimer')."\r\n");
fwrite($fReport, "-- ".Dict::S('DBTools:Indication')."\r\n");
foreach ($aResults as $sClass => $aErrorList)
{
fwrite($fReport, '');
foreach ($aErrorList as $sErrorLabel => $aError)
{
fwrite($fReport, "\r\n----------\r\n");
fwrite($fReport, 'Class: '.MetaModel::GetName($sClass).' ('.$sClass.")\r\n");
fwrite($fReport, "\r\n-- \r\n");
fwrite($fReport, '-- Class: '.MetaModel::GetName($sClass).' ('.$sClass.")\r\n");
$iCount = $aError['count'];
fwrite($fReport, 'Count: '.$iCount."\r\n");
fwrite($fReport, 'Error: '.$sErrorLabel."\r\n");
fwrite($fReport, '-- Count: '.$iCount."\r\n");
fwrite($fReport, '-- Error: '.$sErrorLabel."\r\n");
$sQuery = $aError['query'];
fwrite($fReport, 'Query: '.$sQuery."\r\n");
fwrite($fReport, '-- Query: '.$sQuery."\r\n");
if (isset($aError['fixit']))
{
fwrite($fReport, "\r\nFix it (indication):\r\n\r\n");
fwrite($fReport, "\r\n-- Fix it (indication):\r\n\r\n");
$aFixitQueries = $aError['fixit'];
foreach ($aFixitQueries as $sFixitQuery)
{
@@ -51,31 +54,26 @@ class DBAnalyzerUtils
fwrite($fReport, "\r\n");
}
$sQueryResult = '';
$aIdList = array();
foreach ($aError['res'] as $aRes)
{
foreach ($aRes as $sKey => $sValue)
{
$sQueryResult .= "'$sKey'='$sValue' ";
if ($sKey == 'id')
{
$aIdList[] = $sValue;
if ($bVerbose) {
$sQueryResult = '';
$aIdList = [];
foreach ($aError['res'] as $aRes) {
$sQueryResult .= " - ";
foreach ($aRes as $sKey => $sValue) {
$sQueryResult .= "'$sKey'='$sValue' ";
if ($sKey == 'id') {
$aIdList[] = $sValue;
}
}
}
$sQueryResult .= "\r\n";
fwrite($fReport, "-- Result: ".$sQueryResult);
$sIdList = '('.implode(',', $aIdList).')';
fwrite($fReport, "\r\n-- Ids: ".$sIdList."\r\n");
}
fwrite($fReport, "Result: \r\n".$sQueryResult);
$sIdList = '('.implode(',', $aIdList).')';
fwrite($fReport, 'Ids: '.$sIdList."\r\n");
}
}
fclose($fReport);
$sReportFile = $sDBToolsFolder.$sReportFile;
return $sReportFile;
return $sDBToolsFolder.$sReportFile;
}
}

View File

@@ -64,26 +64,26 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
// Lost attachments
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'DBTools:LostAttachments' => 'Lost attachments~~',
'DBTools:LostAttachments:Disclaimer' => 'Here you can search your database for lost or misplaced attachments. This is NOT a data recovery tool, is does not retrieve deleted data.~~',
'DBTools:LostAttachments' => '附件丢失',
'DBTools:LostAttachments:Disclaimer' => '您可以在数据库里搜索附件是否有丢失或误挪动. 这不是数据恢复工具, 无法恢复已删除的数据.',
'DBTools:LostAttachments:Button:Analyze' => '分析',
'DBTools:LostAttachments:Button:Restore' => '还原',
'DBTools:LostAttachments:Button:Restore:Confirm' => '该操作无法回退, 请确认是否继续还原.',
'DBTools:LostAttachments:Button:Busy' => '请稍后...',
'DBTools:LostAttachments:Step:Analyze' => 'First, search for lost/misplaced attachments by analyzing the database.~~',
'DBTools:LostAttachments:Step:Analyze' => '首先, 通过分析数据库来搜索丢失或误挪动的附件.',
'DBTools:LostAttachments:Step:AnalyzeResults' => '分析结果:',
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Great! Every thing seems to be at the right place.~~',
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Some attachments (%1$d) seem to be misplaced. Take a look at the following list and check the ones you would like to move.~~',
'DBTools:LostAttachments:Step:AnalyzeResults:None' => '非常好! 所有附件都是正常的.',
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => '某些附件 (%1$d) 看起来放错了位置. 请检查下面的列表并选择要挪动的文件.',
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => '文件名',
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => '当前位置',
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => '移动到...',
'DBTools:LostAttachments:Step:RestoreResults' => '还原结果:',
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d attachments were restored.~~',
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d 的附件已还原.',
'DBTools:LostAttachments:StoredAsInlineImage' => 'Stored as inline image~~',
'DBTools:LostAttachments:History' => 'Attachment "%1$s" restored with DB tools~~'
'DBTools:LostAttachments:History' => 'Attachment "附件 %1$s" 已被数据库工具恢复'
));

View File

@@ -61,7 +61,6 @@ try
LoginWebPage::DoLoginEx(null /* any portal */, false);
$oPage = new ajax_page("");
$oPage->no_cache();
$sOperation = utils::ReadParam('operation', '');

View File

@@ -19,7 +19,7 @@
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-attachments/2.7.1',
'itop-attachments/2.7.5',
array(
// Identification
//
@@ -80,6 +80,31 @@ if (!class_exists('AttachmentInstaller'))
return $oConfiguration;
}
/**
* @since 2.7.4 N°3788
* @param string $sTableName
* @param int $iBulkSize
*
* @return array
* @throws \CoreException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public static function GetOrphanAttachmentIds($sTableName, $iBulkSize){
$sSqlQuery = <<<SQL
SELECT id as attachment_id FROM `$sTableName` WHERE (`item_id`='' OR `item_id` IS NULL) LIMIT {$iBulkSize};
SQL;
/** @var \mysqli_result $oQueryResult */
$oQueryResult = CMDBSource::Query($sSqlQuery);
$aIds = [];
while($aRow = $oQueryResult->fetch_array()){
$aIds[] = $aRow['attachment_id'];
}
return $aIds;
}
/**
* Handler called before creating or upgrading the database schema
* @param $oConfiguration Config The new configuration of the application
@@ -97,10 +122,32 @@ if (!class_exists('AttachmentInstaller'))
$iCount = CMDBSource::QueryToScalar($sCountQuery);
if ($iCount > 0)
{
SetupPage::log_info("Cleanup of orphan attachments that cannot be migrated to the new ObjKey model: $iCount record(s) must be deleted.");
$sRepairQuery = "DELETE FROM `$sTableName` WHERE (`item_id`='' OR `item_id` IS NULL)";
$iRet = CMDBSource::Query($sRepairQuery); // Throws an exception in case of error
SetupPage::log_info("Cleanup of orphan attachments successfully completed.");
SetupPage::log_info("Cleanup of orphan attachments that cannot be migrated to the new ObjKey model: $iCount record(s) must be deleted.");
$iBulkSize = 100;
$iMaxDuration = 30;
$iDeletedCount = 0;
$fStartTime = microtime(true);
$aIds = self::GetOrphanAttachmentIds($sTableName, $iBulkSize);
while (count($aIds) !== 0) {
$sCleanupQuery = sprintf("DELETE FROM `$sTableName` WHERE `id` IN (%s)", implode(",", $aIds));
CMDBSource::Query($sCleanupQuery); // Throws an exception in case of error
$iDeletedCount += count($aIds);
$fElapsed = microtime(true) - $fStartTime;
if ($fElapsed > $iMaxDuration){
SetupPage::log_info(sprintf("Cleanup of orphan attachments interrupted after %.3f s. $iDeletedCount records were deleted among $iCount.", $fElapsed));
break;
}
$aIds = self::GetOrphanAttachmentIds($sTableName, $iBulkSize);
}
if (count($aIds) === 0){
SetupPage::log_info("Cleanup of orphan attachments successfully completed.");
}
}
else
{
@@ -108,7 +155,7 @@ if (!class_exists('AttachmentInstaller'))
}
}
}
/**
* Handler called after the creation/update of the database schema
* @param $oConfiguration Config The new configuration of the application
@@ -123,6 +170,7 @@ if (!class_exists('AttachmentInstaller'))
// Prerequisite: change null into 0 (workaround to the fact that we cannot use IS NULL in OQL)
SetupPage::log_info("Initializing attachment/item_org_id - null to zero");
$sTableName = MetaModel::DBGetTable('Attachment');
$sRepair = "UPDATE `$sTableName` SET `item_org_id` = 0 WHERE `item_org_id` IS NULL";
CMDBSource::Query($sRepair);
@@ -132,7 +180,13 @@ if (!class_exists('AttachmentInstaller'))
$iUpdated = 0;
while ($oAttachment = $oSet->Fetch())
{
if (empty($oAttachment->Get('item_class'))) {
//do not treat orphan attachment
continue;
}
$oContainer = MetaModel::GetObject($oAttachment->Get('item_class'), $oAttachment->Get('item_id'), false /* must be found */, true /* allow all data */);
if ($oContainer)
{
$oAttachment->SetItem($oContainer, true /*updateonchange*/);

View File

@@ -30,10 +30,10 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Attachment:Max_Ko' => '(最大文件尺寸: %1$s KB)',
'Attachments:NoAttachment' => '没有附件. ',
'Attachments:PreviewNotAvailable' => '该附件类型不支持预览.',
'Attachments:Error:FileTooLarge' => '上传的文件过大. %1$s~~',
'Attachments:Error:UploadedFileEmpty' => 'The received file is empty and cannot be attached.
Either you have pushed an empty file,
or ask your iTop administrator if the iTop server disk is full.~~',
'Attachments:Error:FileTooLarge' => '上传的文件过大. %1$s',
'Attachments:Error:UploadedFileEmpty' => '收到的文件为空,无法添加.
可能是因为您上传了一个空文件,,
或者咨询iTop 管理员确认iTop 服务器磁盘空间是否已满.',
'Attachments:Render:Icons' => '显示为图标',
'Attachments:Render:Table' => '显示为列表',
));
@@ -47,11 +47,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:Attachment+' => '',
'Class:Attachment/Attribute:expire' => '过期',
'Class:Attachment/Attribute:expire+' => '~~',
'Class:Attachment/Attribute:temp_id' => '临时 id',
'Class:Attachment/Attribute:temp_id' => '临时id',
'Class:Attachment/Attribute:temp_id+' => '~~',
'Class:Attachment/Attribute:item_class' => 'Item class~~',
'Class:Attachment/Attribute:item_class+' => '~~',
'Class:Attachment/Attribute:item_id' => 'Item~~',
'Class:Attachment/Attribute:item_id' => '项目',
'Class:Attachment/Attribute:item_id+' => '~~',
'Class:Attachment/Attribute:item_org_id' => 'Item organization~~',
'Class:Attachment/Attribute:item_org_id+' => '',

View File

@@ -51,7 +51,6 @@ function DisplayErrorAndDie($oPage, $sHtmlErrorMessage, $exitCode = null)
$sOperation = utils::ReadParam('operation', '');
$oPage = new ajax_page('');
$oPage->no_cache();
$oPage->SetContentType('text/html');

View File

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

View File

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

View File

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

View File

@@ -237,9 +237,9 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'Class:NormalChange' => 'Normale Change',
'Class:NormalChange+' => '',
'Class:NormalChange/Attribute:acceptance_date' => 'Datum goedkeuring',
'Class:NormalChange/Attribute:acceptance_date' => 'Datum acceptatie',
'Class:NormalChange/Attribute:acceptance_date+' => '',
'Class:NormalChange/Attribute:acceptance_comment' => 'Commentaar goedkeuring',
'Class:NormalChange/Attribute:acceptance_comment' => 'Commentaar acceptatie',
'Class:NormalChange/Attribute:acceptance_comment+' => '',
'Class:NormalChange/Stimulus:ev_validate' => 'Valideer',
'Class:NormalChange/Stimulus:ev_validate+' => '',

View File

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

View File

@@ -86,8 +86,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:Change/Attribute:category/Value:hardware+' => '硬件',
'Class:Change/Attribute:category/Value:network' => '网络',
'Class:Change/Attribute:category/Value:network+' => '网络',
'Class:Change/Attribute:category/Value:other' => '其',
'Class:Change/Attribute:category/Value:other+' => '其',
'Class:Change/Attribute:category/Value:other' => '其',
'Class:Change/Attribute:category/Value:other+' => '其',
'Class:Change/Attribute:category/Value:software' => '软件',
'Class:Change/Attribute:category/Value:software+' => '软件',
'Class:Change/Attribute:category/Value:system' => '系统',

View File

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

View File

@@ -365,8 +365,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:PhysicalDevice/Attribute:status/Value:obsolete+' => '废弃',
'Class:PhysicalDevice/Attribute:status/Value:production' => '生产',
'Class:PhysicalDevice/Attribute:status/Value:production+' => '生产',
'Class:PhysicalDevice/Attribute:status/Value:stock' => '闲置',
'Class:PhysicalDevice/Attribute:status/Value:stock+' => '闲置',
'Class:PhysicalDevice/Attribute:status/Value:stock' => '库存',
'Class:PhysicalDevice/Attribute:status/Value:stock+' => '库存',
'Class:PhysicalDevice/Attribute:brand_id' => '品牌',
'Class:PhysicalDevice/Attribute:brand_id+' => '',
'Class:PhysicalDevice/Attribute:brand_name' => '品牌名称',
@@ -868,8 +868,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:VirtualDevice/Attribute:status/Value:obsolete+' => '废弃',
'Class:VirtualDevice/Attribute:status/Value:production' => '生产',
'Class:VirtualDevice/Attribute:status/Value:production+' => '生产',
'Class:VirtualDevice/Attribute:status/Value:stock' => '闲置',
'Class:VirtualDevice/Attribute:status/Value:stock+' => '闲置',
'Class:VirtualDevice/Attribute:status/Value:stock' => '库存',
'Class:VirtualDevice/Attribute:status/Value:stock+' => '库存',
'Class:VirtualDevice/Attribute:logicalvolumes_list' => '逻辑卷',
'Class:VirtualDevice/Attribute:logicalvolumes_list+' => '该设备使用的所有逻辑卷',
));

View File

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

View File

@@ -75,6 +75,7 @@ Dict::Add('EN US', 'English', 'English', array(
'iTopUpdate:UI:CanCoreUpdate:Yes' => 'Application can be updated',
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => 'You must use the <a href="%1$s">setup</a> to update the application.<br />Some modified files were detected, a partial update cannot be executed.',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start',

View File

@@ -75,6 +75,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'iTopUpdate:UI:CanCoreUpdate:Yes' => 'L\'application peut être mise à jour',
'iTopUpdate:UI:CanCoreUpdate:No' => 'L\'application ne peut pas être mise à jour : %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Attention : la mise à jour de l\'application peut échouer : %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => 'Vous devez utiliser la page <a href="%1$s">d\'installation</a> pour mettre à jour l\'application.<br />Des fichiers modifiés ont été détectés, une mise à jour partielle ne peut pas être effectuée.',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Prêt pour l\\installation',

View File

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

View File

@@ -37,7 +37,8 @@ class AjaxController extends Controller
}
else
{
$aParams['sMessage'] = Dict::Format("iTopUpdate:UI:CanCoreUpdate:{$sCanUpdateCore}", $sMessage);
$sLink = utils::GetAbsoluteUrlAppRoot().'setup/';
$aParams['sMessage'] = Dict::Format('iTopUpdate:UI:CannotUpdateUseSetup', $sLink);
}
} catch (FileNotExistException $e)
{

View File

@@ -54,7 +54,7 @@
</fieldset>
<fieldset>
<fieldset id="form-update-outer">
<legend>{{ 'iTopUpdate:UI:SelectUpdateFile'|dict_s }}</legend>
<div class="details">

View File

@@ -22,6 +22,9 @@ $.ajax({
}
else
{
$("#check-update").prop("disabled", true);
$("#file").prop("disabled", true);
$('#form-update-outer').slideUp(600);
oRequirements.addClass("message_error");
}
}

View File

@@ -24,33 +24,33 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'iTopUpdate:UI:PageTitle' => '应用升级',
'itop-core-update:UI:SelectUpdateFile' => '应用升级',
'itop-core-update:UI:ConfirmUpdate' => ' 升级',
'itop-core-update:UI:UpdateCoreFiles' => 'Application Upgrade~~',
'iTopUpdate:UI:MaintenanceModeActive' => 'The application is currently under maintenance, no user can access the application. You have to run a setup or restore the application archive to return in normal mode.~~',
'itop-core-update:UI:UpdateDone' => 'Application Upgrade~~',
'itop-core-update:UI:UpdateCoreFiles' => '应用升级',
'iTopUpdate:UI:MaintenanceModeActive' => '应用正处于维护状态, 用户无法登录系统. 您必须运行安装向导或从备份中还原才能恢复到正常状态.',
'itop-core-update:UI:UpdateDone' => '应用升级',
'itop-core-update/Operation:SelectUpdateFile/Title' => 'Application Upgrade~~',
'itop-core-update/Operation:ConfirmUpdate/Title' => 'Confirm Application Upgrade~~',
'itop-core-update/Operation:UpdateCoreFiles/Title' => 'Application Upgrading~~',
'itop-core-update/Operation:UpdateDone/Title' => 'Application Upgrade Done~~',
'itop-core-update/Operation:SelectUpdateFile/Title' => '应用升级',
'itop-core-update/Operation:ConfirmUpdate/Title' => '确认升级',
'itop-core-update/Operation:UpdateCoreFiles/Title' => '应用升级进行中',
'itop-core-update/Operation:UpdateDone/Title' => '升级完毕',
'iTopUpdate:UI:SelectUpdateFile' => '请选择要上传的升级文件',
'iTopUpdate:UI:CheckUpdate' => '校验升级文件',
'iTopUpdate:UI:ConfirmInstallFile' => '即将安装 %1$s',
'iTopUpdate:UI:DoUpdate' => '升级',
'iTopUpdate:UI:CurrentVersion' => '当前已安装的版本',
'iTopUpdate:UI:NewVersion' => 'Newly installed version~~',
'iTopUpdate:UI:NewVersion' => '新安装的版本',
'iTopUpdate:UI:Back' => '返回',
'iTopUpdate:UI:Cancel' => '取消',
'iTopUpdate:UI:Continue' => '继续',
'iTopUpdate:UI:RunSetup' => 'Run Setup~~',
'iTopUpdate:UI:RunSetup' => '运行安装向导',
'iTopUpdate:UI:WithDBBackup' => '数据库备份',
'iTopUpdate:UI:WithFilesBackup' => '应用文件备份',
'iTopUpdate:UI:WithoutBackup' => 'No backup is planned~~',
'iTopUpdate:UI:WithoutBackup' => '暂时没有备份计划',
'iTopUpdate:UI:Backup' => '升级之前执行备份',
'iTopUpdate:UI:DoFilesArchive' => '打包应用文件',
'iTopUpdate:UI:UploadArchive' => 'Select a package to upload~~',
'iTopUpdate:UI:ServerFile' => 'Path of a package already on the server~~',
'iTopUpdate:UI:WarningReadOnlyDuringUpdate' => 'During the upgrade, the application will be read-only.~~',
'iTopUpdate:UI:UploadArchive' => '请选择要上传的包',
'iTopUpdate:UI:ServerFile' => '软件包已在服务器上',
'iTopUpdate:UI:WarningReadOnlyDuringUpdate' => '在升级期间, 应用会变成只读状态.',
'iTopUpdate:UI:Status' => '状态',
'iTopUpdate:UI:Action' => '升级',
@@ -74,7 +74,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'iTopUpdate:UI:CanCoreUpdate:Failed' => '文件系统检查失败',
'iTopUpdate:UI:CanCoreUpdate:Yes' => '应用无法升级',
'iTopUpdate:UI:CanCoreUpdate:No' => '应用无法升级: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
'iTopUpdate:UI:CanCoreUpdate:Warning' => '警告: 应用升级可能会失败: %1$s',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => '准备开始',
@@ -82,9 +82,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'iTopUpdate:UI:SetupMessage:Backup' => '数据库备份',
'iTopUpdate:UI:SetupMessage:FilesArchive' => '打包应用文件',
'iTopUpdate:UI:SetupMessage:CopyFiles' => '复制新文件',
'iTopUpdate:UI:SetupMessage:CheckCompile' => 'Check application upgrade~~',
'iTopUpdate:UI:SetupMessage:CheckCompile' => '检查更新',
'iTopUpdate:UI:SetupMessage:Compile' => '升级应用程序和数据库',
'iTopUpdate:UI:SetupMessage:UpdateDatabase' => 'Upgrade database~~',
'iTopUpdate:UI:SetupMessage:UpdateDatabase' => '升级数据库',
'iTopUpdate:UI:SetupMessage:ExitMaintenance' => '正在退出维护模式',
'iTopUpdate:UI:SetupMessage:UpdateDone' => '升级完成',
@@ -109,7 +109,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Menu:iTopUpdate+' => '应用升级',
// Missing itop entries
'Class:ModuleInstallation/Attribute:installed' => '安装',
'Class:ModuleInstallation/Attribute:installed' => '安装时间',
'Class:ModuleInstallation/Attribute:name' => '名称',
'Class:ModuleInstallation/Attribute:version' => '版本',
'Class:ModuleInstallation/Attribute:comment' => '备注',

View File

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

View File

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

View File

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

View File

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

View File

@@ -111,7 +111,6 @@ function DoBackup($sTargetFile)
function ReportStatus($sMessage, $bSuccess, $iErrorCode = 0, $aMoreFields = array())
{
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('application/json');
$aResult = array(
'code' => $iErrorCode,

View File

@@ -4,20 +4,18 @@ class HubConnectorPage extends NiceWebPage
{
public function __construct($sTitle)
{
parent::__construct($sTitle);
parent::__construct($sTitle);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->no_cache();
$this->add_xframe_options();
$sImagesDir = utils::GetAbsoluteUrlAppRoot().'images';
$sModuleImagesDir = utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/images';
$sUserPrefs = appUserPreferences::GetAsJSON();
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
$this->add_script(
<<<EOF
$sImagesDir = utils::GetAbsoluteUrlAppRoot().'images';
$sModuleImagesDir = utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/images';
$sUserPrefs = appUserPreferences::GetAsJSON();
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
$this->add_script(
<<<EOF
var oUserPreferences = $sUserPrefs;
EOF
);

View File

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

View File

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

View File

@@ -144,9 +144,9 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'Class:Incident/Attribute:cumulatedpending' => 'Pendências acumuladas',
'Class:Incident/Attribute:cumulatedpending+' => '',
'Class:Incident/Attribute:tto' => 'TTO',
'Class:Incident/Attribute:tto+' => '',
'Class:Incident/Attribute:tto+' => 'Tempo de atribuição',
'Class:Incident/Attribute:ttr' => 'TTR',
'Class:Incident/Attribute:ttr+' => '',
'Class:Incident/Attribute:ttr+' => 'Tempo de resolução',
'Class:Incident/Attribute:tto_escalation_deadline' => 'Prazo determinado TTO',
'Class:Incident/Attribute:tto_escalation_deadline+' => '',
'Class:Incident/Attribute:sla_tto_passed' => 'SLA TTO passou',

View File

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

View File

@@ -63,6 +63,8 @@ Dict::Add('EN US', 'English', 'English', array(
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Open</a> / <a href="%4$s" class="file_download_link">Download</a>',
'Portal:Calendar-FirstDayOfWeek' => 'en-us', //work with moment.js locales
'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost',
'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.',
'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.',
));
// UserProfile brick

View File

@@ -62,6 +62,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Portal:File:DisplayInfo+' => '%1$s (%2$s) <a href="%3$s" class="file_open_link" target="_blank">Ouvrir</a> / <a href="%4$s" class="file_download_link">Télécharger</a>',
'Portal:Calendar-FirstDayOfWeek' => 'fr', //work with moment.js locales
'Portal:Form:Close:Warning' => 'Voulez-vous quitter ce formulaire ? Les données saisies seront perdues',
'Portal:Error:ObjectCannotBeCreated' => 'Erreur: L\'objet n\'a pas été créé. Vérifiez les objets liés et les attachements avant de soumettre à nouveau le formulaire.',
'Portal:Error:ObjectCannotBeUpdated' => 'Erreur: L\'objet n\'a pas été modifié. Vérifiez les objets liés et les attachements avant de soumettre à nouveau le formulaire.',
));
// UserProfile brick

View File

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

View File

@@ -33,6 +33,7 @@ $(function()
category: 'redirect',
url: null,
modal: false,
timeout_duration: 400,
},
cancel_rule: {
category: 'close',
@@ -56,7 +57,7 @@ $(function()
this.options.submit_rule.url = this.options.submit_url;
if((this.options.cancel_url !== null) && (this.options.cancel_url !== ''))
this.options.cancel_rule.url = this.options.cancel_url;
this._super();
},
@@ -143,12 +144,14 @@ $(function()
// Determine where we go in case validation is successful
var sRuleType = me.options.submit_rule.category;
var bRedirectInModal = me.options.submit_rule.modal;
var iRedirectTimeout = me.options.submit_rule.timeout_duration;
var sRedirectUrl = me.options.submit_rule.url;
// - The validation might want us to be redirect elsewhere
if(oValidation.valid)
{
// Checking if we have to redirect to another page
// Typically this happens when applying a stimulus, we redirect to the transition form
// This code let the ajax response override the initial parameters
if(oValidation.redirection !== undefined)
{
var oRedirection = oValidation.redirection;
@@ -160,6 +163,10 @@ $(function()
{
sRedirectUrl = oRedirection.url;
}
if(oRedirection.timeout_duration !== undefined)
{
iRedirectTimeout = oRedirection.timeout_duration;
}
sRuleType = 'redirect';
}
}
@@ -241,7 +248,7 @@ $(function()
// Checking if we have to redirect to another page
if(sRuleType === 'redirect')
{
me._applyRedirectRule(sRedirectUrl, bRedirectInModal);
me._applyRedirectRule(sRedirectUrl, bRedirectInModal, iRedirectTimeout);
}
// Close rule only needs to be applied to non modal forms (modal is always closed on submit)
else if(sRuleType === 'close')
@@ -360,10 +367,13 @@ $(function()
{
$('#page_overlay').fadeOut(200);
},
_applyRedirectRule: function(sRedirectUrl, bRedirectInModal)
_applyRedirectRule: function(sRedirectUrl, bRedirectInModal, iRedirectTimeout)
{
var me = this;
//optional argument
iRedirectTimeout = (typeof iRedirectTimeout !== 'undefined' && iRedirectTimeout != null ) ? iRedirectTimeout : 400;
// Always close current modal
if(this.options.is_modal)
{
@@ -391,8 +401,8 @@ $(function()
// Showing loader while redirecting, otherwise user tend to click somewhere in the page.
// Note: We use a timeout because .always() is called right after here and will hide the loader
setTimeout(function(){ me._disableFormBeforeLoading(); }, 50);
// Redirecting after a few ms so the user can see what happend
setTimeout(function() { location.href = sRedirectUrl; }, 400);
// Redirecting after a few ms so the user can see what happened
setTimeout(function() { location.href = sRedirectUrl; }, iRedirectTimeout);
}
}
},

View File

@@ -106,7 +106,11 @@ var CombodoPortalToolbox = {
if (oOptions.base_modal.usage === 'clone')
{
oModalElem = oSelectorElem.clone();
oModalElem.attr('id', oOptions.id)
// Force modal to have an HTML ID, otherwise it can lead to complications, especially with the portal_leave_handle.js
// See N°3469
var sModalID = (oOptions.id !== null) ? oOptions.id : 'modal-with-generated-id-'+Date.now();
oModalElem.attr('id', sModalID)
.appendTo('body');
}
// - Get an existing modal in the DOM

View File

@@ -30,6 +30,8 @@ use DBObjectSearch;
use DBObjectSet;
use DBSearch;
use FieldExpression;
use IssueLog;
use LogChannels;
use MetaModel;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -65,6 +67,8 @@ class BrowseBrickController extends BrickController
*/
public function DisplayAction(Request $oRequest, $sBrickId, $sBrowseMode = null, $sDataLoading = null)
{
$sPortalId = $this->getParameter('combodo.portal.instance.id');
/** @var \Combodo\iTop\Portal\Helper\BrowseBrickHelper $oBrowseBrickHelper */
$oBrowseBrickHelper = $this->get('browse_brick');
/** @var \Combodo\iTop\Portal\Helper\RequestManipulatorHelper $oRequestManipulator */
@@ -266,8 +270,7 @@ class BrowseBrickController extends BrickController
// Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb
$aQueryParams = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetInternalParams();
// Note : $iSearchloopMax was initialized on the previous loop
for ($j = 0; $j <= $iSearchLoopMax; $j++)
{
for ($j = 0; $j <= $iSearchLoopMax; $j++) {
$aQueryParams['search_value_'.$j] = '%'.$aSearchValues[$j].'%';
}
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetInternalParams($aQueryParams);
@@ -277,12 +280,11 @@ class BrowseBrickController extends BrickController
$oQuery = $aLevelsProperties[$aLevelsPropertiesKeys[0]]['search'];
// Testing appropriate data loading mode if we are in auto
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO)
{
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO) {
// - Check how many records there is.
// - Update $sDataLoading with its new value regarding the number of record and the threshold
$oCountSet = new DBObjectSet($oQuery);
$fThreshold = (float)MetaModel::GetModuleSetting($this->getParameter('combodo.portal.instance.id'),
$fThreshold = (float)MetaModel::GetModuleSetting($sPortalId,
'lazy_loading_threshold');
$sDataLoading = ($oCountSet->Count() > $fThreshold) ? AbstractBrick::ENUM_DATA_LOADING_LAZY : AbstractBrick::ENUM_DATA_LOADING_FULL;
unset($oCountSet);
@@ -440,17 +442,21 @@ class BrowseBrickController extends BrickController
}
}
IssueLog::Debug('Portal BrowseBrick query', LogChannels::PORTAL, array(
'sPortalId' => $sPortalId,
'sBrickId' => $sBrickId,
'oql' => $oSet->GetFilter()->ToOQL(),
));
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
if ($oRequest->isXmlHttpRequest()) {
$aData = $aData + array(
'data' => $aItems,
'levelsProperties' => $aLevelsProperties,
);
$oResponse = new JsonResponse($aData);
}
else
{
} else {
$aData = $aData + array(
'oBrick' => $oBrick,
'sBrickId' => $sBrickId,

View File

@@ -41,13 +41,14 @@ use Dict;
use Exception;
use FieldExpression;
use iPopupMenuExtension;
use IssueLog;
use JSButtonItem;
use LogChannels;
use MetaModel;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use UnaryExpression;
use URLButtonItem;
use VariableExpression;
/**
* Class ManageBrickController
@@ -89,10 +90,10 @@ class ManageBrickController extends BrickController
/** @var \Combodo\iTop\Portal\Brick\ManageBrick $oBrick */
$oBrick = $oBrickCollection->GetBrickById($sBrickId);
if (is_null($sDisplayMode))
{
if (is_null($sDisplayMode)) {
$sDisplayMode = $oBrick->GetDefaultDisplayMode();
}
$aData = $this->GetData($oRequest, $sBrickId, $sGroupingTab, $oBrick::AreDetailsNeededForDisplayMode($sDisplayMode));
$aExportFields = $oBrick->GetExportFields();
@@ -102,8 +103,7 @@ class ManageBrickController extends BrickController
'iDefaultListLength' => $oBrick->GetDefaultListLength(),
);
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
if ($oRequest->isXmlHttpRequest()) {
$oResponse = new JsonResponse($aData);
}
else
@@ -252,6 +252,7 @@ class ManageBrickController extends BrickController
$oExporter->SetObjectList($oSearch);
$oExporter->SetFormat($sFormat);
$oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE);
$oExporter->SetLocalizeOutput(true);
$oExporter->SetFields($sFields);
$aData = array(
@@ -812,31 +813,32 @@ class ManageBrickController extends BrickController
'iItemsCount' => $oSet->Count(),
'aColumnsDefinition' => $aColumnsDefinition,
);
IssueLog::Debug('Portal ManageBrick query', LogChannels::PORTAL, array(
'sPortalId' => $sPortalId,
'sBrickId' => $sBrickId,
'sGroupingTab' => $sGroupingTab,
'oql' => $oSet->GetFilter()->ToOQL(),
'aGroupingTabs' => $aGroupingTabs,
));
}
}
else
{
} else {
$aGroupingAreasData = array();
$sGroupingArea = null;
}
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
if ($oRequest->isXmlHttpRequest()) {
$aData = $aData + array(
'data' => $aGroupingAreasData[$sGroupingArea]['aItems'],
);
}
else
{
} else {
$aDisplayValues = array();
$aUrls = array();
$aColumns = array();
$aNames = array();
if ($bHasScope)
{
foreach ($aGroupingTabsValues as $aValues)
{
if ($bHasScope) {
foreach ($aGroupingTabsValues as $aValues) {
$aDisplayValues[] = array(
'value' => $aValues['count'],
'label' => $aValues['label'],

View File

@@ -639,7 +639,7 @@ class ObjectController extends BrickController
$oSet = new DBObjectSet($oSearch, array(), array('this' => $oHostObject, 'ac_query' => '%'.$sQuery.'%'));
$oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => array('friendlyname')));
// Note : This limit is also used in the field renderer by typeahead to determine how many suggestions to display
$oSet->SetLimit(MetaModel::GetConfig()->Get('max_display_limit'));
$oSet->SetLimit(MetaModel::GetConfig()->GetMaxDisplayLimit());
// - Retrieving objects
while ($oItem = $oSet->Fetch())

View File

@@ -211,6 +211,7 @@ class UserProfileBrickController extends BrickController
{
$aFormData['validation']['redirection'] = array(
'url' => $oUrlGenerator->generate('p_user_profile_brick'),
'timeout_duration' => 1000, //since there are several ajax request, we use a longer timeout in hope that they will all be finished in time. A promise would have been more reliable, but since this change is made in a minor version, this approach is less error prone.
);
}
}

View File

@@ -25,14 +25,12 @@ use AttributeDateTime;
use AttributeTagSet;
use CMDBChangeOpAttachmentAdded;
use CMDBChangeOpAttachmentRemoved;
use CMDBSource;
use Combodo\iTop\Form\Field\Field;
use Combodo\iTop\Form\Field\FileUploadField;
use Combodo\iTop\Form\Field\LabelField;
use Combodo\iTop\Form\Form;
use Combodo\iTop\Form\FormManager;
use Combodo\iTop\Portal\Helper\ApplicationHelper;
use CoreCannotSaveObjectException;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
@@ -80,6 +78,12 @@ class ObjectFormManager extends FormManager
protected $aFormProperties;
/** @var array $aCallbackUrls */
protected $aCallbackUrls = array();
/**
* List of hidden fields, used for form update (eg. remove them from the form regarding they dependencies)
* @var array $aHiddenFieldsId
* @since 2.7.5
*/
protected $aHiddenFieldsId = array();
/**
* Creates an instance of \Combodo\iTop\Portal\Form\ObjectFormManager from JSON data that must contain at least :
@@ -943,6 +947,8 @@ class ObjectFormManager extends FormManager
if($oField->GetHidden() === false)
{
$oForm->AddField($oField);
} else {
$this->aHiddenFieldsId[]=$oField->GetId();
}
}
@@ -1048,6 +1054,26 @@ class ObjectFormManager extends FormManager
$this->CancelAttachments();
}
/**
* @inheritDoc
*/
public function CheckTransaction(&$aData)
{
$isTransactionValid = \utils::IsTransactionValid($this->oForm->GetTransactionId(), false); //The transaction token is kept in order to preserve BC with ajax forms (the second call would fail if the token is deleted). (The GC will take care of cleaning the token for us later on)
if (!$isTransactionValid) {
if ($this->oObject->IsNew()) {
$sError = Dict::S('UI:Error:ObjectAlreadyCreated');
} else {
$sError = Dict::S('UI:Error:ObjectAlreadyUpdated');
}
$aData['messages']['error'] += [
'_main' => [$sError]
];
$aData['valid'] = false;
}
}
/**
* Validates the form and returns an array with the validation status and the messages.
* If the form is valid, creates/updates the object.
@@ -1070,122 +1096,106 @@ class ObjectFormManager extends FormManager
*/
public function OnSubmit($aArgs = null)
{
$aData = array(
'valid' => true,
'messages' => array(
'success' => array(),
'warnings' => array(), // Not used as of today, just to show that the structure is ready for change like this.
'error' => array(),
),
);
$aData = parent::OnSubmit($aArgs);
if (! $aData['valid']) {
return $aData;
}
// Update object and form
$this->OnUpdate($aArgs);
// Check if form valid
if ($this->oForm->Validate())
{
// The try catch is essentially to start a MySQL transaction in order to ensure that all or none objects are persisted when creating an object with links
try
{
$sObjectClass = get_class($this->oObject);
// Starting transaction
CMDBSource::Query('START TRANSACTION');
// Forcing allowed writing on the object if necessary. This is used in some particular cases.
$bAllowWrite = ($sObjectClass === 'Person' && $this->oObject->GetKey() == UserRights::GetContactId());
if ($bAllowWrite)
{
$this->oObject->AllowWrite(true);
}
// Writing object to DB
$bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified());
$bWasModified = $this->oObject->IsModified();
try
{
$this->oObject->DBWrite();
}
catch (CoreCannotSaveObjectException $e)
{
throw new Exception($e->getHtmlMessage());
}
// Finalizing images link to object, otherwise it will be cleaned by the GC
InlineImage::FinalizeInlineImages($this->oObject);
// Finalizing attachments link to object
// TODO : This has to be refactored when the function from itop-attachments has been migrated into the core
if (isset($aArgs['attachmentIds']))
{
$this->FinalizeAttachments($aArgs['attachmentIds']);
}
// Ending transaction with a commit as everything was fine
CMDBSource::Query('COMMIT');
// Checking if we have to apply a stimulus
if (isset($aArgs['applyStimulus']))
{
$this->oObject->ApplyStimulus($aArgs['applyStimulus']['code']);
}
// Activating triggers only on update
if ($bActivateTriggers)
{
$sTriggersQuery = $this->oContainer->getParameter('combodo.portal.instance.conf')['properties']['triggers_query'];
if ($sTriggersQuery !== null)
{
$aParentClasses = MetaModel::EnumParentClasses($sObjectClass, ENUM_PARENT_CLASSES_ALL);
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL($sTriggersQuery), array(),
array('parent_classes' => $aParentClasses));
/** @var \Trigger $oTrigger */
while ($oTrigger = $oTriggerSet->Fetch())
{
try
{
$oTrigger->DoActivate($this->oObject->ToArgs('this'));
}
catch(Exception $e)
{
utils::EnrichRaisedException($oTrigger, $e);
}
}
}
}
// Resetting caselog fields value, otherwise the value will stay in it after submit.
$this->oForm->ResetCaseLogFields();
if ($bWasModified)
{
//=if (isNew) because $bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified())
if(!$bActivateTriggers)
{
$aData['messages']['success'] += array( '_main' => array(Dict::Format('UI:Title:Object_Of_Class_Created', $this->oObject->GetName(),MetaModel::GetName(get_class($this->oObject)))));
}
else
{
$aData['messages']['success'] += array('_main' => array(Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($this->oObject)), $this->oObject->GetName())));
}
}
}
catch (Exception $e)
{
// End transaction with a rollback as something failed
CMDBSource::Query('ROLLBACK');
$aData['valid'] = false;
$aData['messages']['error'] += array('_main' => array($e->getMessage()));
IssueLog::Error(__METHOD__.' at line '.__LINE__.' : Rollback during submit ('.$e->getMessage().')');
}
finally
{
// Removing transaction id
utils::RemoveTransaction($this->oForm->GetTransactionId());
}
}
else
if (! $this->oForm->Validate())
{
// Handle errors
$aData['valid'] = false;
$aData['messages']['error'] += $this->oForm->GetErrorMessages();
return $aData;
}
$sObjectClass = get_class($this->oObject);
try {
// Forcing allowed writing on the object if necessary. This is used in some particular cases.
$bAllowWrite = ($sObjectClass === 'Person' && $this->oObject->GetKey() == UserRights::GetContactId());
if ($bAllowWrite) {
$this->oObject->AllowWrite(true);
}
// Writing object to DB
$bIsNew = $this->oObject->IsNew();
$bWasModified = $this->oObject->IsModified();
$bActivateTriggers = (!$bIsNew && $bWasModified);
try
{
$this->oObject->DBWrite();
}
catch (Exception $e) {
if ($bIsNew) {
throw new Exception(Dict::S('Portal:Error:ObjectCannotBeCreated'));
}
throw new Exception(Dict::S('Portal:Error:ObjectCannotBeUpdated'));
}
// Finalizing images link to object, otherwise it will be cleaned by the GC
InlineImage::FinalizeInlineImages($this->oObject);
// Finalizing attachments link to object
// TODO : This has to be refactored when the function from itop-attachments has been migrated into the core
if (isset($aArgs['attachmentIds']))
{
$this->FinalizeAttachments($aArgs['attachmentIds']);
}
// Checking if we have to apply a stimulus
if (isset($aArgs['applyStimulus']))
{
$this->oObject->ApplyStimulus($aArgs['applyStimulus']['code']);
}
// Activating triggers only on update
if ($bActivateTriggers)
{
$sTriggersQuery = $this->oContainer->getParameter('combodo.portal.instance.conf')['properties']['triggers_query'];
if ($sTriggersQuery !== null)
{
$aParentClasses = MetaModel::EnumParentClasses($sObjectClass, ENUM_PARENT_CLASSES_ALL);
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL($sTriggersQuery), array(),
array('parent_classes' => $aParentClasses));
/** @var \Trigger $oTrigger */
while ($oTrigger = $oTriggerSet->Fetch())
{
try
{
$oTrigger->DoActivate($this->oObject->ToArgs('this'));
}
catch(Exception $e)
{
utils::EnrichRaisedException($oTrigger, $e);
}
}
}
}
// Resetting caselog fields value, otherwise the value will stay in it after submit.
$this->oForm->ResetCaseLogFields();
if ($bWasModified)
{
//=if (isNew) because $bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified())
if(!$bActivateTriggers)
{
$aData['messages']['success'] += array( '_main' => array(Dict::Format('UI:Title:Object_Of_Class_Created', $this->oObject->GetName(),MetaModel::GetName(get_class($this->oObject)))));
}
else
{
$aData['messages']['success'] += array('_main' => array(Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($this->oObject)), $this->oObject->GetName())));
}
}
}
catch (Exception $e)
{
$aData['valid'] = false;
$aData['messages']['error'] += array('_main' => array($e->getMessage()));
IssueLog::Error(__METHOD__.' at line '.__LINE__.' : '.$e->getMessage());
}
return $aData;
@@ -1470,4 +1480,22 @@ class ObjectFormManager extends FormManager
}
return $oChangeOp;
}
/**
* @return array
* @since 2.7.5
*/
public function GetHiddenFieldsId()
{
return $this->aHiddenFieldsId;
}
/**
* @param array $aHiddenFieldsId
* @since 2.7.5
*/
public function SetHiddenFieldsId($aHiddenFieldsId)
{
$this->aHiddenFieldsId = $aHiddenFieldsId;
}
}

View File

@@ -48,6 +48,8 @@ class PasswordFormManager extends FormManager
// Building the form
$oForm = new Form('change_password');
$oForm->SetTransactionId(\utils::GetNewTransactionId());
// Adding hidden field with form type
$oField = new HiddenField('form_type');
$oField->SetCurrentValue('change_password');
@@ -93,14 +95,11 @@ class PasswordFormManager extends FormManager
*/
public function OnSubmit($aArgs = null)
{
$aData = array(
'valid' => true,
'messages' => array(
'success' => array(),
'warnings' => array(), // Not used as of today, just to show that the structure is ready for change like this.
'error' => array(),
),
);
$aData = parent::OnSubmit($aArgs);
if (! $aData['valid']) {
return $aData;
}
// Update object and form
$this->OnUpdate($aArgs);

View File

@@ -49,6 +49,8 @@ class PreferencesFormManager extends FormManager
// Building the form
$oForm = new Form('preferences');
$oForm->SetTransactionId(\utils::GetNewTransactionId());
// Adding hidden field with form type
$oField = new HiddenField('form_type');
$oField->SetCurrentValue('preferences');
@@ -97,14 +99,11 @@ class PreferencesFormManager extends FormManager
*/
public function OnSubmit($aArgs = null)
{
$aData = array(
'valid' => true,
'messages' => array(
'success' => array(),
'warnings' => array(), // Not used as of today, just to show that the structure is ready for change like this.
'error' => array(),
),
);
$aData = parent::OnSubmit($aArgs);
if (! $aData['valid']) {
return $aData;
}
// Update object and form
$this->OnUpdate($aArgs);

View File

@@ -280,8 +280,8 @@ class ObjectFormHandlerHelper
->SetFormProperties($aFormProperties);
$oFormManager->Build();
// Check the number of editable fields
$aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId();
// Check the number of editable fields
$aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount();
}
else
@@ -388,6 +388,7 @@ class ObjectFormHandlerHelper
$aFormData['object_state'] = $oFormManager->GetObject()->GetState();
$aFormData['fieldset'] = $aFieldSetData;
$aFormData['display_mode'] = (isset($aFormProperties['properties'])) ? $aFormProperties['properties']['display_mode'] : ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE;
$aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId();
// - Set a text to be copied on title if the object is not in creation
if($sMode !== static::ENUM_MODE_CREATE && !empty($sObjectId))
{

View File

@@ -397,15 +397,16 @@
}
}
};
// N°3995: Loader is shown immediately, otherwise when we have a huge amount of items, we can have a bottleneck on the buildMosaic() function, blocking the display of the loader
showMosaicLoader();
$(document).ready(function(){
// Auto collapse item actions popup
$('body').click(function(){
$('#brick_content_mosaic .item-action-wrapper.collapse.in').collapse('hide');
});
// Build the tree (collapsed)
showMosaicLoader();
// Build the mosaic (collapsed)
buildMosaic(oRawDatas);
hideMosaicLoader();
registerFilterListeners();

View File

@@ -321,6 +321,8 @@
}
};
// N°3995: Loader is shown immediately, otherwise when we have a huge amount of items, we can have a bottleneck on the buildTree() function, blocking the display of the loader
showTreeLoader();
$(document).ready(function(){
// Init expand/collapse all buttons
$('#btn-collapse-all').on('click', function (oEvent) {
@@ -405,7 +407,6 @@
});
// Build the tree (collapsed)
showTreeLoader();
buildTree(oRawDatas);
hideTreeLoader();
registerFilterListeners();

View File

@@ -227,16 +227,10 @@
$('#user-profile-wrapper .form_field .help-block > p').remove();
// Submiting contact form through AJAX
//if($('#{{ oContactForm.id }} .field_set').field_set('hasTouchedFields'))
//{
$('#{{ oContactForm.id }}').portal_form_handler('submit', oEvent);
//}
$('#{{ oContactForm.id }}').portal_form_handler('submit', oEvent);
// Submiting preferences form through AJAX
//if($('#{{ oPreferencesForm.id }} .field_set').field_set('hasTouchedFields'))
//{
$('#{{ oPreferencesForm.id }}').portal_form_handler('submit', oEvent);
//}
$('#{{ oPreferencesForm.id }}').portal_form_handler('submit', oEvent);
{% if oPasswordForm is not null %}
// Submiting password form through AJAX

View File

@@ -480,6 +480,10 @@
{
ShowErrorDialog('{{ 'UI:ObjectDoesNotExist'|dict_s|escape('js') }}', '{{ 'Error:HTTP:404'|dict_s|escape('js') }}');
}
else if(oXHR.status === 0 && oXHR.readyState === 0)
{
//DO NOTHING the ajax call has been aborted
}
else if(oXHR.responseText !== undefined && IsJSONString(oXHR.responseText) === true)
{
var oData = JSON.parse(oXHR.responseText);

View File

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

View File

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

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