mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-14 07:54:10 +01:00
Compare commits
1103 Commits
3.0.0-beta
...
feature/fa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b87845c844 | ||
|
|
1f65ab5a92 | ||
|
|
7cfac7300b | ||
|
|
ede720c9b9 | ||
|
|
684efee5d2 | ||
|
|
e347989dcb | ||
|
|
37dd887cf1 | ||
|
|
b6cfb40f8f | ||
|
|
e1051e7a47 | ||
|
|
7e8eb26866 | ||
|
|
6775a3e928 | ||
|
|
faa38155e5 | ||
|
|
558bbc3357 | ||
|
|
cd7f9e478f | ||
|
|
e3586cff65 | ||
|
|
106127e6b7 | ||
|
|
2299983db3 | ||
|
|
2fab7de34b | ||
|
|
c0ab79971c | ||
|
|
03ed9c9deb | ||
|
|
7152277760 | ||
|
|
14d05da9f6 | ||
|
|
69a0bd0c34 | ||
|
|
c09cee994a | ||
|
|
50ee0b3eed | ||
|
|
84169a864c | ||
|
|
9f27cf2b84 | ||
|
|
f78986009f | ||
|
|
4fdbec72d4 | ||
|
|
94d31827e7 | ||
|
|
45a8eb93e7 | ||
|
|
6ac3539373 | ||
|
|
a5d8425fe7 | ||
|
|
3608c6b8ab | ||
|
|
2b1e583dc7 | ||
|
|
3081aa473a | ||
|
|
809ea2eb49 | ||
|
|
2f98a3e7ac | ||
|
|
c737fc46ff | ||
|
|
619c0d34e8 | ||
|
|
bdc7ed6f8e | ||
|
|
d9819d9c2a | ||
|
|
f24f8a2f34 | ||
|
|
3890b1a020 | ||
|
|
2e08cff9ee | ||
|
|
968a0e5f3a | ||
|
|
4694e8152e | ||
|
|
4b731dd336 | ||
|
|
5e0c7c211b | ||
|
|
815870fe6b | ||
|
|
33ceab86fb | ||
|
|
fe1bf3bfc1 | ||
|
|
3727223db3 | ||
|
|
5b96d9f778 | ||
|
|
162b15236d | ||
|
|
83e98ef2b8 | ||
|
|
d783954d13 | ||
|
|
7207dc657c | ||
|
|
f1cce8e430 | ||
|
|
3da49e6603 | ||
|
|
5048421bfa | ||
|
|
b9c5f2c523 | ||
|
|
ec75195a2f | ||
|
|
e971d628dd | ||
|
|
bbd50ba73b | ||
|
|
ba71c1bcd5 | ||
|
|
16be8fd3d3 | ||
|
|
cacae0a2b3 | ||
|
|
b1ed15f31c | ||
|
|
788caf9c50 | ||
|
|
1728dcc40c | ||
|
|
35165568af | ||
|
|
cb5554ddb1 | ||
|
|
6a5840ed82 | ||
|
|
4a67819f87 | ||
|
|
81c39c35cd | ||
|
|
4caf52f1ae | ||
|
|
0c5b845c8a | ||
|
|
cdfdb1f2ca | ||
|
|
f29a8792af | ||
|
|
b494ff2ce6 | ||
|
|
a65c55fc48 | ||
|
|
01b94f42fe | ||
|
|
df1e19dc43 | ||
|
|
cbef9bb267 | ||
|
|
9ad341f73a | ||
|
|
03e9bcd47a | ||
|
|
55effea0a3 | ||
|
|
dfaa973359 | ||
|
|
2e45b20fc4 | ||
|
|
e89090f0ec | ||
|
|
c9d02826a0 | ||
|
|
47db04bcb7 | ||
|
|
a49c451ae4 | ||
|
|
25c3704990 | ||
|
|
3000659e86 | ||
|
|
ce36c00b83 | ||
|
|
2a3e6384d9 | ||
|
|
dd7e73e413 | ||
|
|
1709082e39 | ||
|
|
41f6e85673 | ||
|
|
3ef3166bd5 | ||
|
|
0d26211dbe | ||
|
|
3b99006159 | ||
|
|
299ad7e753 | ||
|
|
94a2421186 | ||
|
|
923c3a27a3 | ||
|
|
c1a1186bf7 | ||
|
|
b360514646 | ||
|
|
541794ca9c | ||
|
|
5fbcae1f55 | ||
|
|
0f6e0e5085 | ||
|
|
3541a9e1db | ||
|
|
1f95a4ee21 | ||
|
|
af5a0d5006 | ||
|
|
84280a3b5f | ||
|
|
e3bce622e5 | ||
|
|
ef9392a8d5 | ||
|
|
15087ab848 | ||
|
|
7bb7445c91 | ||
|
|
6a4ce3c3b1 | ||
|
|
7df27de5c1 | ||
|
|
b4fc647845 | ||
|
|
de053eed72 | ||
|
|
17612f88d3 | ||
|
|
e14845728c | ||
|
|
4e80fc0f76 | ||
|
|
fcfcf85e0d | ||
|
|
f0715baf7d | ||
|
|
2733e7966f | ||
|
|
52327d1086 | ||
|
|
81cf3df5e2 | ||
|
|
45b5c39af7 | ||
|
|
4463e91d85 | ||
|
|
ce87bf9e23 | ||
|
|
dbc3da7bc3 | ||
|
|
ebc9fa684a | ||
|
|
606bdc1909 | ||
|
|
7495fb9af4 | ||
|
|
75dbad7406 | ||
|
|
c95064b76d | ||
|
|
f0933eaf1e | ||
|
|
b9ea7d4913 | ||
|
|
9c75a705f3 | ||
|
|
3381c085f4 | ||
|
|
b89d181125 | ||
|
|
c3a2713bba | ||
|
|
99b73fe1ee | ||
|
|
151f42ceef | ||
|
|
627c0070c1 | ||
|
|
3a56824cde | ||
|
|
9b6f7d94f4 | ||
|
|
9a6d40e9db | ||
|
|
64e8aa5fee | ||
|
|
a839b1c4ae | ||
|
|
b58df7150f | ||
|
|
1cd230a543 | ||
|
|
fc47e031b6 | ||
|
|
477128ad53 | ||
|
|
3b4ba2aafd | ||
|
|
70f67068f4 | ||
|
|
4fec344799 | ||
|
|
cc5f7608e3 | ||
|
|
468de06fe1 | ||
|
|
aa20289dfe | ||
|
|
4449fdbbc5 | ||
|
|
04a690caff | ||
|
|
0156eb0dda | ||
|
|
e8448332f4 | ||
|
|
e8e6ceb29e | ||
|
|
1f42f897d8 | ||
|
|
aa66bec783 | ||
|
|
1da52a8517 | ||
|
|
cbd2181862 | ||
|
|
102528195b | ||
|
|
7def094291 | ||
|
|
ffb3edced5 | ||
|
|
17e8c53236 | ||
|
|
910638d93f | ||
|
|
d005ff0099 | ||
|
|
4fdf8803a5 | ||
|
|
06af788480 | ||
|
|
7aa1719514 | ||
|
|
81c0951c2a | ||
|
|
89ecdeb13b | ||
|
|
c6211cde09 | ||
|
|
739001eca4 | ||
|
|
7243da3576 | ||
|
|
c9d547030f | ||
|
|
4923ac7015 | ||
|
|
c1c264d6d8 | ||
|
|
0940741568 | ||
|
|
041a938fc0 | ||
|
|
839048fab2 | ||
|
|
4180a41f27 | ||
|
|
8ada56fc53 | ||
|
|
6c5ca614e0 | ||
|
|
4ddee0b624 | ||
|
|
135ddbf37d | ||
|
|
a43adcd202 | ||
|
|
e8e170fb06 | ||
|
|
5ac5d649aa | ||
|
|
ec0c98bb0f | ||
|
|
decb802df4 | ||
|
|
cacc3a3085 | ||
|
|
0fd2ea6a47 | ||
|
|
ab7e73ef9b | ||
|
|
31e7a5383c | ||
|
|
64afa07ff5 | ||
|
|
656bbfe46d | ||
|
|
426f275c03 | ||
|
|
b55ba2ac7f | ||
|
|
693a861e7d | ||
|
|
0ee6c60e94 | ||
|
|
a663e9fded | ||
|
|
b3bf516b20 | ||
|
|
c2408b74cd | ||
|
|
6855c2f83a | ||
|
|
b11bf30881 | ||
|
|
64736f1463 | ||
|
|
930b224ca2 | ||
|
|
c87de1024d | ||
|
|
24e6840a8b | ||
|
|
06ed048983 | ||
|
|
937313495d | ||
|
|
9493e31a5d | ||
|
|
92b61c7491 | ||
|
|
3a4c4cd33d | ||
|
|
9aa399894c | ||
|
|
bde5dc825d | ||
|
|
8578d18e7f | ||
|
|
9b0e55210d | ||
|
|
e530cbb4f2 | ||
|
|
ddb8378fe6 | ||
|
|
47db23d91c | ||
|
|
fc1f701bf6 | ||
|
|
ece31855af | ||
|
|
d57ef77758 | ||
|
|
365c7bb89e | ||
|
|
bf2c4dee1b | ||
|
|
11b812b5fc | ||
|
|
b073e4385c | ||
|
|
c37c46a4a8 | ||
|
|
f592d94af3 | ||
|
|
f9359431fe | ||
|
|
25e560fdaa | ||
|
|
0ce805b192 | ||
|
|
6bf25f90bc | ||
|
|
30d36fca81 | ||
|
|
ffd0bbb1a4 | ||
|
|
3db20e8028 | ||
|
|
ed12f98075 | ||
|
|
19eef5bd72 | ||
|
|
7b2bcd1055 | ||
|
|
108c1fcb2b | ||
|
|
ced29dea8e | ||
|
|
20a07d61c6 | ||
|
|
682c821d0e | ||
|
|
5f382ee053 | ||
|
|
dc81630b16 | ||
|
|
f84c095b22 | ||
|
|
9bfa60d272 | ||
|
|
3e51d010d2 | ||
|
|
b190d0e385 | ||
|
|
0bd64ef700 | ||
|
|
9665c7c947 | ||
|
|
dcf872bcd5 | ||
|
|
6254617814 | ||
|
|
fc4d19be5e | ||
|
|
88d6b63e12 | ||
|
|
60b24bad64 | ||
|
|
cde2f333b4 | ||
|
|
427ec288ef | ||
|
|
18041527c5 | ||
|
|
f58e2ce6c0 | ||
|
|
6312063cd3 | ||
|
|
d0c1f650d1 | ||
|
|
4511883baf | ||
|
|
1e6a4621ea | ||
|
|
2a0fc227de | ||
|
|
05e98be6c7 | ||
|
|
fe7fa2035d | ||
|
|
a00f4156ea | ||
|
|
4d37e2267f | ||
|
|
5055397024 | ||
|
|
2bb142e8ee | ||
|
|
93f273a287 | ||
|
|
04e7616c84 | ||
|
|
219b970703 | ||
|
|
408ca6d427 | ||
|
|
ae323d393d | ||
|
|
76c139253e | ||
|
|
2a50a543bf | ||
|
|
ee54d6869b | ||
|
|
bd753d65a4 | ||
|
|
2fe8f0e43b | ||
|
|
94cb5d3439 | ||
|
|
0ed2388cf8 | ||
|
|
b91dee6c0c | ||
|
|
02b09c2535 | ||
|
|
10cfb373f2 | ||
|
|
69578d5d07 | ||
|
|
97d6d413bb | ||
|
|
4f27f3ac37 | ||
|
|
4771908f42 | ||
|
|
d1751a042e | ||
|
|
99a0e0c58e | ||
|
|
7e0d5d64ce | ||
|
|
3f8f57fa9a | ||
|
|
434ce57b7e | ||
|
|
c1eb928893 | ||
|
|
97cf0a2534 | ||
|
|
ec02657113 | ||
|
|
600a629734 | ||
|
|
9acdb45482 | ||
|
|
e51bd4c95f | ||
|
|
71b3eb0ec7 | ||
|
|
c14c851e94 | ||
|
|
011ac0222c | ||
|
|
00580856ae | ||
|
|
9b6dabf5fb | ||
|
|
eb2a615bd2 | ||
|
|
cfc3a82c44 | ||
|
|
b23dac591e | ||
|
|
de004af288 | ||
|
|
f48cdd51f4 | ||
|
|
10d303f43f | ||
|
|
6a7c953344 | ||
|
|
0432727ace | ||
|
|
d0a4e541c3 | ||
|
|
d515816488 | ||
|
|
25a5ec4bcf | ||
|
|
4fbe391243 | ||
|
|
c9ef543e2e | ||
|
|
cd5286d03b | ||
|
|
e6d61d1ebd | ||
|
|
f916f9cde8 | ||
|
|
be06adc794 | ||
|
|
271c51f1f4 | ||
|
|
28e9f95f7c | ||
|
|
d4da1b99d8 | ||
|
|
28ecb77bf1 | ||
|
|
85effd0fb0 | ||
|
|
c89c47d671 | ||
|
|
6de4511601 | ||
|
|
00acf271e2 | ||
|
|
0e03e78490 | ||
|
|
99cbb6c996 | ||
|
|
0e36070f0a | ||
|
|
bc349253cf | ||
|
|
f3ed286ee0 | ||
|
|
efcd0654c7 | ||
|
|
d413ebff2d | ||
|
|
33f94b7886 | ||
|
|
817aded5aa | ||
|
|
5e61388b65 | ||
|
|
9e9cdd3d56 | ||
|
|
d7d430e2a3 | ||
|
|
09c4ba4044 | ||
|
|
3f795c7555 | ||
|
|
c451e61972 | ||
|
|
afc0a02058 | ||
|
|
a5bf059e23 | ||
|
|
9f0e2c0a3a | ||
|
|
827a108a38 | ||
|
|
4b50f5e1db | ||
|
|
910bbe1160 | ||
|
|
5b742c97c9 | ||
|
|
16a2137777 | ||
|
|
e8316782aa | ||
|
|
edcf9e6a1e | ||
|
|
9addc4a7ca | ||
|
|
5ed71ecb96 | ||
|
|
7db97c6804 | ||
|
|
f039d54a51 | ||
|
|
dab0e372d0 | ||
|
|
dc8c6ed7a9 | ||
|
|
18e341b0b7 | ||
|
|
2722f305e0 | ||
|
|
469e2e6e0e | ||
|
|
05301d4191 | ||
|
|
3df5febd3e | ||
|
|
dfd1d5fe35 | ||
|
|
d289457c0c | ||
|
|
f52b3bff0d | ||
|
|
b6b17733bf | ||
|
|
fc2b00699e | ||
|
|
b00b08d08b | ||
|
|
c02ea05883 | ||
|
|
61a0028d02 | ||
|
|
c1c2d027c3 | ||
|
|
5269096ecd | ||
|
|
e6511e049a | ||
|
|
e4c68936a0 | ||
|
|
74fbd12709 | ||
|
|
4db5d4c08d | ||
|
|
3511867ba3 | ||
|
|
c40394638a | ||
|
|
cc2862837a | ||
|
|
e27c6b1cd7 | ||
|
|
ae00686e58 | ||
|
|
7934f9b9dc | ||
|
|
7f2eef4a24 | ||
|
|
8a65a592f3 | ||
|
|
7d6b019cfa | ||
|
|
5e48400cb1 | ||
|
|
252562ace9 | ||
|
|
c9c32b0de1 | ||
|
|
770ac8ffe5 | ||
|
|
db137d3816 | ||
|
|
dcfdb2d0a9 | ||
|
|
0cbf34ba5a | ||
|
|
77768e8400 | ||
|
|
5621eb2191 | ||
|
|
f1037147a9 | ||
|
|
bea52d5fb9 | ||
|
|
2bf970932e | ||
|
|
b6fac4b411 | ||
|
|
d8a77c22a3 | ||
|
|
b75a495336 | ||
|
|
ed3c387712 | ||
|
|
bbadc1f0be | ||
|
|
a141db4737 | ||
|
|
cb67dd1f1c | ||
|
|
af653608ef | ||
|
|
9dac061b04 | ||
|
|
ccf1bba235 | ||
|
|
312a5b246b | ||
|
|
ddd6bf22af | ||
|
|
903de43589 | ||
|
|
2d67594ccf | ||
|
|
0c7eee7f9c | ||
|
|
65f9f86bcc | ||
|
|
efaf53e568 | ||
|
|
81a2a9278c | ||
|
|
e15d4bfab6 | ||
|
|
e23c41232d | ||
|
|
e25d070a38 | ||
|
|
499c97b914 | ||
|
|
c472b8ad3b | ||
|
|
94ceb98b9e | ||
|
|
b1de8683f0 | ||
|
|
cff2105908 | ||
|
|
e11d5c142c | ||
|
|
f77b3bedaf | ||
|
|
3654df1cae | ||
|
|
f3d2e89c68 | ||
|
|
dd46536f7c | ||
|
|
e249907a9c | ||
|
|
4ded943a43 | ||
|
|
bd52f4fefb | ||
|
|
f57785e422 | ||
|
|
77880c3675 | ||
|
|
bb369e68b5 | ||
|
|
2a2d68e204 | ||
|
|
b49fbf1a97 | ||
|
|
308bff7875 | ||
|
|
9444e61856 | ||
|
|
2f114701a1 | ||
|
|
5128db05cf | ||
|
|
9675202bb2 | ||
|
|
cbe42cd727 | ||
|
|
3559425fc1 | ||
|
|
a246528eec | ||
|
|
7fbbf1088a | ||
|
|
fa4a5e2990 | ||
|
|
b662a7e3c6 | ||
|
|
cdc452282e | ||
|
|
d8c4251c03 | ||
|
|
72d1ab5cbc | ||
|
|
18b8e7093a | ||
|
|
aedb9ccbba | ||
|
|
67fa156c0e | ||
|
|
9437e689fd | ||
|
|
a79459bc53 | ||
|
|
500bd15843 | ||
|
|
3e8dd2f4a5 | ||
|
|
d0fade9ce1 | ||
|
|
51a49dfce8 | ||
|
|
db8462ccc9 | ||
|
|
066b71686d | ||
|
|
be633001a5 | ||
|
|
b0904cabfd | ||
|
|
84426c6634 | ||
|
|
9d19c189e6 | ||
|
|
91d030933b | ||
|
|
d0f9868a19 | ||
|
|
2a913cd484 | ||
|
|
dbaf924171 | ||
|
|
1b2d75efd6 | ||
|
|
d66a1e8c10 | ||
|
|
a5ad981d70 | ||
|
|
8adf743cc7 | ||
|
|
4e544a8eab | ||
|
|
75450ded1d | ||
|
|
2b97fed5db | ||
|
|
62d8a2ba1f | ||
|
|
fe9a442500 | ||
|
|
11d2991286 | ||
|
|
bcca6ac720 | ||
|
|
abb63182e3 | ||
|
|
4409162eb7 | ||
|
|
0dc3d249da | ||
|
|
3598da8808 | ||
|
|
a9b30e160f | ||
|
|
249fa6ca93 | ||
|
|
50d73e7fca | ||
|
|
1b9e67dc6e | ||
|
|
ca2124130f | ||
|
|
fffd4cf962 | ||
|
|
4a6b351c37 | ||
|
|
206143824b | ||
|
|
90c866f696 | ||
|
|
0bbdbdb1cd | ||
|
|
865f9f4f67 | ||
|
|
a7e54d4bad | ||
|
|
395c9c288b | ||
|
|
60a17c9a65 | ||
|
|
2de6ba2827 | ||
|
|
2beb795f9a | ||
|
|
6847d8a5c7 | ||
|
|
f6885567eb | ||
|
|
4b888a3805 | ||
|
|
07f470024a | ||
|
|
4c69865480 | ||
|
|
06985d3cf2 | ||
|
|
ab40c67678 | ||
|
|
a286564345 | ||
|
|
c9e592c07d | ||
|
|
5bcdcb52b2 | ||
|
|
456283866c | ||
|
|
821c14ee86 | ||
|
|
30f1ad8da8 | ||
|
|
6d0b979bcd | ||
|
|
e16425ab8a | ||
|
|
2e426d373d | ||
|
|
a1a38ca224 | ||
|
|
05830de4e7 | ||
|
|
2e8920f3d1 | ||
|
|
4450c28295 | ||
|
|
aa43201cbe | ||
|
|
3d7df7600f | ||
|
|
aba0d4144c | ||
|
|
fe82f54066 | ||
|
|
2d44d9d1c6 | ||
|
|
8c03525fbb | ||
|
|
79c17970f8 | ||
|
|
04f7660267 | ||
|
|
8c7f7abaab | ||
|
|
e963a6741f | ||
|
|
e8d314e1f6 | ||
|
|
ebe493f7f7 | ||
|
|
e29f1825be | ||
|
|
2df8bb1707 | ||
|
|
27995c14bf | ||
|
|
890d0f64ce | ||
|
|
a29bb83a20 | ||
|
|
413820a22b | ||
|
|
860472beea | ||
|
|
dd4283199e | ||
|
|
e147aa31aa | ||
|
|
b8aa3dfb0c | ||
|
|
83e2dbc30a | ||
|
|
f63dad7a3a | ||
|
|
c75bb6b892 | ||
|
|
15ffb82257 | ||
|
|
5e2ae18570 | ||
|
|
070a23e61b | ||
|
|
0ab6655ae7 | ||
|
|
322371dd9f | ||
|
|
b8e974cfd1 | ||
|
|
e03eb1e279 | ||
|
|
6d2b75df9e | ||
|
|
ab1b6837a2 | ||
|
|
5c02b5c38c | ||
|
|
dd486dd015 | ||
|
|
b7e8c0de8a | ||
|
|
32dae59b9b | ||
|
|
5540988001 | ||
|
|
f8c59e6171 | ||
|
|
ab23ea1da0 | ||
|
|
3d49cd199a | ||
|
|
ee848129b7 | ||
|
|
63448276cc | ||
|
|
d5af6e9061 | ||
|
|
2fc3e7ad38 | ||
|
|
465ed58471 | ||
|
|
fac454595a | ||
|
|
89ea4adbce | ||
|
|
427f107ddf | ||
|
|
59d674d744 | ||
|
|
54db7243bf | ||
|
|
e6d2b0bc18 | ||
|
|
9e1f5a1b63 | ||
|
|
5b42b8983a | ||
|
|
5bae964064 | ||
|
|
ef9c18e393 | ||
|
|
ed43d00afe | ||
|
|
908a48e0a1 | ||
|
|
9b854dbcc7 | ||
|
|
7757f1f2d2 | ||
|
|
5e2f8a4ea6 | ||
|
|
62f7eca0a8 | ||
|
|
820f2cbed6 | ||
|
|
d03718681f | ||
|
|
a353317746 | ||
|
|
723eb90160 | ||
|
|
0e14be8b15 | ||
|
|
ef6d7925fc | ||
|
|
ec8c2ca122 | ||
|
|
ebe50b319a | ||
|
|
48b1a15bf7 | ||
|
|
5102400be4 | ||
|
|
4bde828dbb | ||
|
|
e0929f4d0d | ||
|
|
ef1903dabe | ||
|
|
e53a45ec5d | ||
|
|
0811fd4aa7 | ||
|
|
cdf5789d62 | ||
|
|
8f2b5ad8e2 | ||
|
|
df49e9c3b5 | ||
|
|
6915b0b824 | ||
|
|
0b0c99c935 | ||
|
|
b9a68f4aeb | ||
|
|
c611b11981 | ||
|
|
25763c2d2e | ||
|
|
c8f3d23d30 | ||
|
|
88fda1466e | ||
|
|
8a7f0d346d | ||
|
|
3e5440aa3b | ||
|
|
6f08038f34 | ||
|
|
216e62c448 | ||
|
|
f4856150ed | ||
|
|
d73d39e71a | ||
|
|
6abdabd197 | ||
|
|
020460d048 | ||
|
|
347cbca5cf | ||
|
|
8ea5be4ead | ||
|
|
b3f827ed5e | ||
|
|
eaf8a187aa | ||
|
|
34f64c61f6 | ||
|
|
1d28bbe3f4 | ||
|
|
54c89dcbb5 | ||
|
|
06dac2417a | ||
|
|
83125d9ae1 | ||
|
|
20f4419062 | ||
|
|
0f4ca4237d | ||
|
|
bc2c12a8c3 | ||
|
|
8154e718a1 | ||
|
|
9ec6c2098b | ||
|
|
4305e40ae5 | ||
|
|
0f149ed852 | ||
|
|
d5f9ca9c4b | ||
|
|
198c63dd79 | ||
|
|
96ae91494b | ||
|
|
ceaa98f4ef | ||
|
|
af4b9aaa52 | ||
|
|
d2662b608b | ||
|
|
591d189a33 | ||
|
|
36bea54bac | ||
|
|
b5369a0c03 | ||
|
|
1c11cef2f8 | ||
|
|
d8bd5528d3 | ||
|
|
9ed8cf7970 | ||
|
|
be24d409ed | ||
|
|
64d91b194f | ||
|
|
2bc61caab1 | ||
|
|
8f0a5fcaf9 | ||
|
|
b6df73bdcc | ||
|
|
836a35d0dd | ||
|
|
c7a7bfcd68 | ||
|
|
e7e09b5023 | ||
|
|
3f24b80043 | ||
|
|
fe3512cb5f | ||
|
|
2a32c5691b | ||
|
|
dce244f5aa | ||
|
|
63bd1643ce | ||
|
|
3c84a74ea4 | ||
|
|
d9870c2513 | ||
|
|
7f0493c91d | ||
|
|
26e32b1759 | ||
|
|
1b1a6321d7 | ||
|
|
0eba00259a | ||
|
|
a7a9e5f0eb | ||
|
|
bf4835eec0 | ||
|
|
9fbc631b07 | ||
|
|
c6cb7c41cd | ||
|
|
0cb1583688 | ||
|
|
370b42d1fd | ||
|
|
0da8c761c7 | ||
|
|
8c39374abb | ||
|
|
eb239843aa | ||
|
|
e38ca54691 | ||
|
|
f9fc85e763 | ||
|
|
94d99a4109 | ||
|
|
f39e801719 | ||
|
|
645f20742c | ||
|
|
d86065b4a6 | ||
|
|
8af54efd44 | ||
|
|
c96ee4fafc | ||
|
|
ced4d1c5f1 | ||
|
|
6d3e8df3e4 | ||
|
|
963fae243c | ||
|
|
5a09365221 | ||
|
|
49c5f75c6c | ||
|
|
bd67bc8838 | ||
|
|
c9aa693872 | ||
|
|
776c03ef6a | ||
|
|
ae6f8fba5c | ||
|
|
a139dc7e6e | ||
|
|
7d46626d9c | ||
|
|
9ea25188ba | ||
|
|
fadafa8267 | ||
|
|
e0d6bc18be | ||
|
|
2ae01c19e1 | ||
|
|
a5e2831e31 | ||
|
|
e86454ff74 | ||
|
|
62be3f9f58 | ||
|
|
cddbe27182 | ||
|
|
1f56e39577 | ||
|
|
a35c80de57 | ||
|
|
4b8ef4f919 | ||
|
|
c3d23981fb | ||
|
|
9c6d8253f4 | ||
|
|
4d6a8a76aa | ||
|
|
8fa6ae6703 | ||
|
|
fdc987f367 | ||
|
|
be9bb10606 | ||
|
|
11d3d5af49 | ||
|
|
b630492d7a | ||
|
|
1a1439946b | ||
|
|
1db32c6dee | ||
|
|
dade45308c | ||
|
|
b1597f7d90 | ||
|
|
e2904fb0ee | ||
|
|
8dbbc9a124 | ||
|
|
4297b854e1 | ||
|
|
c53c1d5b9c | ||
|
|
a3db7f0e83 | ||
|
|
157c031236 | ||
|
|
78b1ee04e8 | ||
|
|
30da7d2ea4 | ||
|
|
d467cb5c79 | ||
|
|
142699c3b1 | ||
|
|
9dcb789cfd | ||
|
|
aac504ec0b | ||
|
|
a7a7ce77fb | ||
|
|
6caf78fdcd | ||
|
|
5680d9579c | ||
|
|
ec47645ef7 | ||
|
|
4c6a7ca30b | ||
|
|
a70acf29ae | ||
|
|
57b08b5b24 | ||
|
|
90906912cc | ||
|
|
3064d868b7 | ||
|
|
548d08b1ec | ||
|
|
f226916bb8 | ||
|
|
47fcddc9a6 | ||
|
|
86b03b9e92 | ||
|
|
9811d6f8ea | ||
|
|
f67f3eaf74 | ||
|
|
0b4d4764bd | ||
|
|
580deb655d | ||
|
|
5e2bfdf660 | ||
|
|
0d51dc61f9 | ||
|
|
9949b1b11a | ||
|
|
8be141f692 | ||
|
|
a11bc9ad33 | ||
|
|
3214ae91c7 | ||
|
|
21545da062 | ||
|
|
7476b6d059 | ||
|
|
b15c8e30bc | ||
|
|
39d71c9c43 | ||
|
|
75d913a003 | ||
|
|
2b38c98183 | ||
|
|
ca7f6362bf | ||
|
|
4d8ac5fee5 | ||
|
|
938de4c71c | ||
|
|
3ff117596d | ||
|
|
194459e0de | ||
|
|
95179b0c18 | ||
|
|
d76c9cee6f | ||
|
|
6e45b74665 | ||
|
|
3d8259a083 | ||
|
|
6d8a36e061 | ||
|
|
814038f5fd | ||
|
|
f237b4dd30 | ||
|
|
4cf4c0e4c3 | ||
|
|
86538cf88e | ||
|
|
722cae240e | ||
|
|
f0d3149a1c | ||
|
|
92e315e2c7 | ||
|
|
6150881154 | ||
|
|
cf223b583e | ||
|
|
eb5e5591d7 | ||
|
|
dcf4053c30 | ||
|
|
fedc3d4d76 | ||
|
|
e93c0123aa | ||
|
|
0004586779 | ||
|
|
7ae4fe06ed | ||
|
|
ec1dcc8df6 | ||
|
|
47ed863da9 | ||
|
|
7404599721 | ||
|
|
88290f9e91 | ||
|
|
eb8aed19c2 | ||
|
|
3a05e9159d | ||
|
|
cfdbc8ae62 | ||
|
|
99026cec1f | ||
|
|
029d2ad526 | ||
|
|
1cb100b010 | ||
|
|
197864ff83 | ||
|
|
1e73ee8ccd | ||
|
|
e2b73995e1 | ||
|
|
24cedbdebd | ||
|
|
0dc95f93a9 | ||
|
|
b6bd7fe400 | ||
|
|
03a19ab3f4 | ||
|
|
50849ae4ea | ||
|
|
70d7f576f3 | ||
|
|
bf491b7298 | ||
|
|
40ec7e35fd | ||
|
|
ffe5541361 | ||
|
|
73c55748d7 | ||
|
|
0754dda1d9 | ||
|
|
057bea196e | ||
|
|
e97a266c44 | ||
|
|
b4278a6987 | ||
|
|
aaa8f6d311 | ||
|
|
50f860a0e8 | ||
|
|
198c9ed479 | ||
|
|
6e076ae1fa | ||
|
|
9d44f0982c | ||
|
|
fd933ce49a | ||
|
|
ae0c43a099 | ||
|
|
7c7386afc7 | ||
|
|
c306c6e30d | ||
|
|
d9ccac3aea | ||
|
|
d8316734e2 | ||
|
|
7ac5c1bbbb | ||
|
|
37585614ba | ||
|
|
03b728b394 | ||
|
|
ae2072f4d5 | ||
|
|
2f6ed8f8af | ||
|
|
1f5dabf8f6 | ||
|
|
eb5cdb053e | ||
|
|
25ee577eba | ||
|
|
f0aaf21a79 | ||
|
|
a906086751 | ||
|
|
ab17eaad27 | ||
|
|
469c1553bf | ||
|
|
4f72f8be0c | ||
|
|
5f2323f10b | ||
|
|
98013a68a4 | ||
|
|
f55193604c | ||
|
|
586b8c5c71 | ||
|
|
a771d35197 | ||
|
|
96e4ef68df | ||
|
|
df40b53b6b | ||
|
|
6d4f919519 | ||
|
|
8af116275c | ||
|
|
e930d34963 | ||
|
|
4de79afe56 | ||
|
|
daf24d8cb3 | ||
|
|
6e0d570d41 | ||
|
|
ce4379920d | ||
|
|
291bbdf3da | ||
|
|
61f6c1fe33 | ||
|
|
a1d6a705ca | ||
|
|
b861d45b08 | ||
|
|
fc7d2551cd | ||
|
|
b15267d8db | ||
|
|
94ce62e424 | ||
|
|
6271a33fdb | ||
|
|
4b6db53e84 | ||
|
|
540b9f8164 | ||
|
|
5839bab042 | ||
|
|
adc7666dd0 | ||
|
|
e3687b3cbb | ||
|
|
e81c02b25a | ||
|
|
28a8a4457e | ||
|
|
eee0f453da | ||
|
|
164a5501c0 | ||
|
|
ab3c1ad4af | ||
|
|
83a3530880 | ||
|
|
0ad23e272b | ||
|
|
b28ef803a3 | ||
|
|
b2155c042e | ||
|
|
b1b1d25186 | ||
|
|
61ee4d6807 | ||
|
|
443292e1f6 | ||
|
|
c025a7aa3b | ||
|
|
86ebb93149 | ||
|
|
120670240c | ||
|
|
5bd8a25440 | ||
|
|
754f755141 | ||
|
|
74b7223d44 | ||
|
|
1fb2ff355d | ||
|
|
61f3d3aeda | ||
|
|
99860e9e20 | ||
|
|
eb164b47e2 | ||
|
|
dbd197d9bc | ||
|
|
e638c65685 | ||
|
|
42b779d301 | ||
|
|
da3bab9647 | ||
|
|
94d53575ae | ||
|
|
c26cfad062 | ||
|
|
9cd9087cc7 | ||
|
|
df7991adeb | ||
|
|
2dcfe0e281 | ||
|
|
bac584a6b4 | ||
|
|
e4d7bf7dbb | ||
|
|
2d907f31dd | ||
|
|
e2001f4585 | ||
|
|
9810f666fd | ||
|
|
de5f47d43e | ||
|
|
d2662e27e1 | ||
|
|
9ac0100d3a | ||
|
|
1ea5983464 | ||
|
|
15081ba82c | ||
|
|
df189bd1f2 | ||
|
|
06f469faf2 | ||
|
|
b27aa70a1e | ||
|
|
0e35b8e4fa | ||
|
|
c0fc62bcb8 | ||
|
|
d1721b0834 | ||
|
|
66e4f369f7 | ||
|
|
c5e1dbce88 | ||
|
|
5d23a250ae | ||
|
|
b46357a1bd | ||
|
|
4433cdb21b | ||
|
|
45dc79f5af | ||
|
|
2ab0fab051 | ||
|
|
be81d1f6c0 | ||
|
|
ddedce1589 | ||
|
|
b557f16cfa | ||
|
|
30bf2015cb | ||
|
|
14b6e903cb | ||
|
|
4f6e040346 | ||
|
|
3e12799d1d | ||
|
|
7d0550879f | ||
|
|
6a765fad50 | ||
|
|
cbb70c94e5 | ||
|
|
b120488085 | ||
|
|
191891ac35 | ||
|
|
f171380396 | ||
|
|
f9e54e1dde | ||
|
|
15e99a898e | ||
|
|
27c397cc1a | ||
|
|
616229140e | ||
|
|
04e016c919 | ||
|
|
a4e43d3f17 | ||
|
|
ed6969be2c | ||
|
|
8105c4d6cd | ||
|
|
3b7073ad4e | ||
|
|
dbb84fa4e6 | ||
|
|
91c7ed9f4d | ||
|
|
e4c818cacb | ||
|
|
5b6b07af48 | ||
|
|
1331bc2139 | ||
|
|
08946066fb | ||
|
|
0de4e62fcd | ||
|
|
8edd155351 | ||
|
|
3980ba81e7 | ||
|
|
0193868581 | ||
|
|
d2b3de9734 | ||
|
|
d1c39c5e10 | ||
|
|
e29ae488a1 | ||
|
|
ad3e3195a8 | ||
|
|
738411005b | ||
|
|
40b095e407 | ||
|
|
3b7cb9a554 | ||
|
|
f474b3de06 | ||
|
|
7890cbd701 | ||
|
|
eef3e9b7ae | ||
|
|
3c081461b0 | ||
|
|
a916df64c9 | ||
|
|
1d0b96e10f | ||
|
|
28070f8682 | ||
|
|
3e428000bd | ||
|
|
165b11f948 | ||
|
|
c6495e7212 | ||
|
|
0fb0b9deab | ||
|
|
ecb1acf5af | ||
|
|
b239e822ef | ||
|
|
615aa594f0 | ||
|
|
2e346a7e80 | ||
|
|
f5b557b0bc | ||
|
|
2703075d39 | ||
|
|
f6fbd5a7a5 | ||
|
|
d1a05f41e5 | ||
|
|
6b67ad93b9 | ||
|
|
884d3c9477 | ||
|
|
26426123b8 | ||
|
|
6725a9f1ef | ||
|
|
8e0ae67803 | ||
|
|
979a43edc9 | ||
|
|
2aaac00015 | ||
|
|
c46cd1c514 | ||
|
|
74d2a6e46c | ||
|
|
1d57b4330b | ||
|
|
034516d0ef | ||
|
|
72d7758259 | ||
|
|
3e9a19b0ea | ||
|
|
6118ee6876 | ||
|
|
fea3c719af | ||
|
|
78eda6a2fc | ||
|
|
65db576654 | ||
|
|
69bca189fd | ||
|
|
5b78820b32 | ||
|
|
79954d3cee | ||
|
|
8a5c144e3b | ||
|
|
af28a2e01d | ||
|
|
55513b2f4b | ||
|
|
e4bf15feb3 | ||
|
|
0080caf55f | ||
|
|
aed8135d56 | ||
|
|
d0ba84068d | ||
|
|
d6d1a5cc23 | ||
|
|
6700de097a | ||
|
|
cf556de76e | ||
|
|
72cddf30fb | ||
|
|
5ec6a1cc0c | ||
|
|
fe0db8f357 | ||
|
|
1ab2b9c5d4 | ||
|
|
67cd5e321e | ||
|
|
81d9ea389d | ||
|
|
3ee9757a85 | ||
|
|
97e0150974 | ||
|
|
7f37c5912a | ||
|
|
2f5a1c99a5 | ||
|
|
5ce9b6ca99 | ||
|
|
bd9286f903 | ||
|
|
4ae7090a51 | ||
|
|
335074701f | ||
|
|
76f70d45dd | ||
|
|
6f147acd76 | ||
|
|
a84d2ce6bb | ||
|
|
ea9c1ab0b3 | ||
|
|
e1dc269171 | ||
|
|
16dc7de8e2 | ||
|
|
88e210d84d | ||
|
|
4f6bd5444b | ||
|
|
0f204f94eb | ||
|
|
f5ae76360e | ||
|
|
8d59024d8e | ||
|
|
32e0031242 | ||
|
|
3fdebdc217 | ||
|
|
7083807319 | ||
|
|
0f01dbc3e5 | ||
|
|
8289b028cf | ||
|
|
f72a6ee963 | ||
|
|
08359cdd05 | ||
|
|
de17439f55 | ||
|
|
6e555e29d1 | ||
|
|
9dae34461b | ||
|
|
06b1e581fe | ||
|
|
d4b515c7b7 | ||
|
|
20759eca23 | ||
|
|
4e420cbcd6 | ||
|
|
0e60c67910 | ||
|
|
808c1fa571 | ||
|
|
dbf8475883 | ||
|
|
2c2155a8e0 | ||
|
|
ffbd94d671 | ||
|
|
3abcdc9c58 | ||
|
|
242f499101 | ||
|
|
6b0106ff73 | ||
|
|
bf991ffb8a | ||
|
|
280afb35a9 | ||
|
|
e095749c90 | ||
|
|
ced0e7cc8f | ||
|
|
6df98c3d41 | ||
|
|
21e16fd2e8 | ||
|
|
bc2e25be99 | ||
|
|
0c5ebc3a84 | ||
|
|
43412b78e3 | ||
|
|
9126635cf2 | ||
|
|
d0986c048a | ||
|
|
fe0c52bedd | ||
|
|
8926c6783a | ||
|
|
95b6dd0cc3 | ||
|
|
121e39b738 | ||
|
|
48dee9c136 | ||
|
|
befde44215 | ||
|
|
a827eed59d | ||
|
|
1613c1bbdc | ||
|
|
909af7f59c | ||
|
|
495b39a7ab | ||
|
|
f9ff66941d | ||
|
|
29871bc6da | ||
|
|
1c983e8093 | ||
|
|
92a9a8c65f | ||
|
|
a3e1b95c8e | ||
|
|
bf2d98a1bf | ||
|
|
abe103eade | ||
|
|
034052cf4b | ||
|
|
4458f26379 | ||
|
|
27217815d1 | ||
|
|
2fe4265223 | ||
|
|
2b71ea108a | ||
|
|
ed719e13c7 | ||
|
|
2d705c6697 | ||
|
|
c98ad106c4 |
BIN
.doc/contributing-guide/contributing-stickers-side-by-side.png
Normal file
BIN
.doc/contributing-guide/contributing-stickers-side-by-side.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
@@ -11,7 +11,7 @@ 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_formatter_tags_enabled = true
|
||||
ij_smart_tabs = false
|
||||
ij_visual_guides = 300
|
||||
ij_wrap_on_typing = true
|
||||
@@ -78,7 +78,7 @@ 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
|
||||
@@ -280,16 +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_class_constants = false
|
||||
ij_php_align_class_constants = true
|
||||
ij_php_align_group_field_declarations = false
|
||||
ij_php_align_inline_comments = false
|
||||
ij_php_align_key_value_pairs = true
|
||||
ij_php_align_match_arm_bodies = false
|
||||
ij_php_align_multiline_array_initializer_expression = true
|
||||
ij_php_align_multiline_binary_operation = false
|
||||
ij_php_align_multiline_chained_methods = false
|
||||
@@ -298,6 +299,7 @@ ij_php_align_multiline_for = true
|
||||
ij_php_align_multiline_parameters = false
|
||||
ij_php_align_multiline_parameters_in_calls = false
|
||||
ij_php_align_multiline_ternary_operation = false
|
||||
ij_php_align_named_arguments = false
|
||||
ij_php_align_phpdoc_comments = false
|
||||
ij_php_align_phpdoc_param_names = false
|
||||
ij_php_anonymous_brace_style = end_of_line
|
||||
@@ -417,6 +419,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_enum_backed_type = 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
|
||||
@@ -431,6 +434,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_enum_backed_type = false
|
||||
ij_php_space_before_colon_in_named_argument = false
|
||||
ij_php_space_before_colon_in_return_type = false
|
||||
ij_php_space_before_comma = false
|
||||
@@ -466,6 +470,7 @@ ij_php_spaces_around_equality_operators = true
|
||||
ij_php_spaces_around_logical_operators = true
|
||||
ij_php_spaces_around_multiplicative_operators = true
|
||||
ij_php_spaces_around_null_coalesce_operator = true
|
||||
ij_php_spaces_around_pipe_in_union_type = false
|
||||
ij_php_spaces_around_relational_operators = true
|
||||
ij_php_spaces_around_shift_operators = true
|
||||
ij_php_spaces_around_unary_operator = false
|
||||
@@ -540,7 +545,6 @@ ij_html_space_after_tag_name = false
|
||||
ij_html_space_around_equality_in_attribute = false
|
||||
ij_html_space_inside_empty_tag = false
|
||||
ij_html_text_wrap = normal
|
||||
ij_html_uniform_ident = false
|
||||
|
||||
[{*.markdown,*.md}]
|
||||
ij_visual_guides = none
|
||||
|
||||
@@ -36,18 +36,24 @@ clearstatcache();
|
||||
$oiTopComposer = new iTopComposer();
|
||||
$aDeniedButStillPresent = $oiTopComposer->ListDeniedButStillPresent();
|
||||
|
||||
echo "\n";
|
||||
foreach ($aDeniedButStillPresent as $sDir)
|
||||
{
|
||||
if (! preg_match('#[tT]ests?/?$#', $sDir))
|
||||
if (false === iTopComposer::IsTestDir($sDir))
|
||||
{
|
||||
echo "\nfound INVALID denied test dir: '$sDir'\n";
|
||||
echo "ERROR found INVALID denied test dir: '$sDir'\n";
|
||||
throw new \Exception("$sDir must end with /Test/ or /test/");
|
||||
}
|
||||
|
||||
if (false === file_exists($sDir)) {
|
||||
echo "INFO $sDir is in denied list, but not existing on disk => skipping !\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SetupUtils::rrmdir($sDir);
|
||||
echo "Remove denied test dir: '$sDir'\n";
|
||||
echo "OK Remove denied test dir: '$sDir'\n";
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
|
||||
13
.make/git-hooks/README.md
Normal file
13
.make/git-hooks/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Git hooks for iTop
|
||||
|
||||
## ❓ Goal
|
||||
|
||||
Those [git hooks](https://git-scm.com/docs/githooks) aims to ease developing on [iTop](https://github.com/Combodo/iTop).
|
||||
|
||||
## ☑ Available hooks
|
||||
|
||||
* pre-commit : rejects commit if you have at least one SCSS file staged, and no CSS file
|
||||
|
||||
## ⚙ Install
|
||||
|
||||
Just run install.php !
|
||||
26
.make/git-hooks/install.php
Normal file
26
.make/git-hooks/install.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
$aHooks = [
|
||||
'pre-commit.php',
|
||||
];
|
||||
|
||||
$sAppRoot = dirname(__DIR__, 2);
|
||||
|
||||
|
||||
foreach ($aHooks as $sSourceHookFileName) {
|
||||
echo "Processing for `{$sSourceHookFileName}`...\n";
|
||||
$sSourceHookPath = __DIR__.DIRECTORY_SEPARATOR.$sSourceHookFileName;
|
||||
|
||||
$aPathParts = pathinfo($sSourceHookFileName);
|
||||
$sTargetHookPath = $sAppRoot.DIRECTORY_SEPARATOR.'.git'.DIRECTORY_SEPARATOR.'hooks'.DIRECTORY_SEPARATOR.$aPathParts['filename'];
|
||||
|
||||
if (file_exists($sTargetHookPath) || is_link($sTargetHookPath)) {
|
||||
echo "Existing $sTargetHookPath ! Removing...";
|
||||
unlink($sTargetHookPath);
|
||||
echo "OK !\n";
|
||||
}
|
||||
|
||||
echo "Creating symlink for hook in $sTargetHookPath...";
|
||||
symlink($sSourceHookPath, $sTargetHookPath);
|
||||
echo "OK !\n";
|
||||
}
|
||||
|
||||
49
.make/git-hooks/pre-commit.php
Normal file
49
.make/git-hooks/pre-commit.php
Normal file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
/**
|
||||
* Reject any commit containing .scss files, but no .css file !
|
||||
*/
|
||||
|
||||
echo "Checking files staged...\n";
|
||||
$sFilesToCommit = shell_exec('git diff --cached --name-only --diff-filter=ACMRT');
|
||||
$aFilesToCommit = explode("\n", $sFilesToCommit);
|
||||
|
||||
$aScssFiles = GetFilesWithExtension('scss', $aFilesToCommit);
|
||||
if (count($aScssFiles) === 0) {
|
||||
echo "No scss file : OK to go !\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$aCssFiles = GetFilesWithExtension('css', $aFilesToCommit);
|
||||
if (count($aCssFiles) === 0) {
|
||||
echo "There are SCSS files staged but no CSS file : REJECTING commit.\n";
|
||||
echo "You must add the compiled SCSS files by running the setup !\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "We have SCSS but also CSS => OK to commit !\n";
|
||||
exit(0);
|
||||
|
||||
|
||||
|
||||
function GetFilesWithExtension($sExtension, $aFiles) {
|
||||
return array_filter(
|
||||
$aFiles,
|
||||
function($item) use ($sExtension) {
|
||||
return (endsWith($item, '.'.$sExtension));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function endsWith( $haystack, $needle ) {
|
||||
$length = strlen( $needle );
|
||||
if( !$length ) {
|
||||
return true;
|
||||
}
|
||||
return substr( $haystack, -$length ) === $needle;
|
||||
}
|
||||
|
||||
function exitWithMessage($sMessage, $iCode) {
|
||||
echo $sMessage;
|
||||
exit($iCode);
|
||||
}
|
||||
@@ -25,8 +25,8 @@ require_once (__DIR__.DIRECTORY_SEPARATOR.'update.classes.inc.php');
|
||||
/** @var \FileVersionUpdater[] $aFilesUpdaters */
|
||||
$aFilesUpdaters = array(
|
||||
new iTopVersionFileUpdater(),
|
||||
new CssVariablesFileUpdater(),
|
||||
new DatamodelsModulesFiles(),
|
||||
new ConstantFileUpdater('ITOP_CORE_VERSION', 'approot.inc.php'),
|
||||
);
|
||||
|
||||
if (count($argv) === 1)
|
||||
|
||||
@@ -69,6 +69,40 @@ abstract class AbstractSingleFileVersionUpdater extends FileVersionUpdater
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°4714
|
||||
*/
|
||||
class ConstantFileUpdater extends AbstractSingleFileVersionUpdater {
|
||||
/** @var string */
|
||||
private $sConstantName;
|
||||
|
||||
/**
|
||||
* @param $sConstantName constant to search, for example `ITOP_CORE_VERSION`
|
||||
* @param $sFileToUpdate file containing constant definition
|
||||
*/
|
||||
public function __construct($sConstantName, $sFileToUpdate)
|
||||
{
|
||||
$this->sConstantName = $sConstantName;
|
||||
parent::__construct($sFileToUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath)
|
||||
{
|
||||
$sConstantSearchPattern = <<<REGEXP
|
||||
/define\('{$this->sConstantName}', ?'[^']+'\);/
|
||||
REGEXP;
|
||||
|
||||
return preg_replace(
|
||||
$sConstantSearchPattern,
|
||||
"define('{$this->sConstantName}', '{$sVersionLabel}');",
|
||||
$sFileContent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class iTopVersionFileUpdater extends AbstractSingleFileVersionUpdater
|
||||
{
|
||||
public function __construct()
|
||||
@@ -89,26 +123,6 @@ class iTopVersionFileUpdater extends AbstractSingleFileVersionUpdater
|
||||
}
|
||||
}
|
||||
|
||||
class CssVariablesFileUpdater extends AbstractSingleFileVersionUpdater
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('css/css-variables.scss');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath)
|
||||
{
|
||||
return preg_replace(
|
||||
'/(\$version: "v)[^"]*(";)/',
|
||||
'${1}'.$sVersionLabel.'${2}',
|
||||
$sFileContent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractGlobFileVersionUpdater extends FileVersionUpdater
|
||||
{
|
||||
protected $sGlobPattern;
|
||||
|
||||
@@ -152,5 +152,9 @@ Stickers' design might change from one year to another. For the first year we wa
|
||||
* White hat: Find and/or fix a vulnerability
|
||||
* Contributor: Contribute by finding a bug, making an extension or any other way
|
||||
* Partner: For Combodo's official partners
|
||||
* Graduated: Follow a Combodo's iTop training
|
||||
* Ambassador: Outstanding community contributors
|
||||
* Beta tester: Test and give feedback on beta releases
|
||||
* Extension developer: Develop and publish an extension
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -99,9 +99,11 @@ We would like to give a special thank you 🤗 to the people from the community
|
||||
- Lucas, Jonathan
|
||||
- Malik, Remie
|
||||
- Mindêllo de Andrade, Lucas (a.k.a @rokam)
|
||||
- Raenker, Martin
|
||||
- Rosenke, Stephan
|
||||
- Seki, Shoji
|
||||
- Shilov, Vladimir
|
||||
- Stukalov, Ilya (a.k.a @ilya-stukalov)
|
||||
- Tulio, Marco
|
||||
- Turrubiates, Miguel
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "addon/userrights,grant_by_profile",
|
||||
"category" => "addon/userrights,grant_by_profile,filter",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
@@ -219,7 +219,7 @@ class URP_UserProfile extends UserRightsBaseClassGUI
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "addon/userrights,grant_by_profile",
|
||||
"category" => "addon/userrights,grant_by_profile,filter",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => array("userlogin", "profile"),
|
||||
"state_attcode" => "",
|
||||
@@ -610,30 +610,115 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
{
|
||||
$this->LoadCache();
|
||||
|
||||
$aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ);
|
||||
if ($aObjectPermissions['permission'] == UR_ALLOWED_NO)
|
||||
// Let us pass an administrator for bypassing the grant matrix check in order to test this method without the need to set up a complex profile
|
||||
// In the nominal case Administrators never end up here (since they completely bypass GetSelectFilter)
|
||||
if (!static::IsAdministrator($oUser) && (MetaModel::HasCategory($sClass, 'silo') || MetaModel::HasCategory($sClass, 'bizmodel')))
|
||||
{
|
||||
return false;
|
||||
// N°4354 - Categories 'silo' and 'bizmodel' do check the grant matrix. Whereas 'filter' always allows to read (but the result can be filtered)
|
||||
$aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ);
|
||||
if ($aObjectPermissions['permission'] == UR_ALLOWED_NO)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine how to position the objects of this class
|
||||
//
|
||||
$oFilter = true;
|
||||
$aConditions = array();
|
||||
|
||||
// Determine if this class is part of a silo and build the filter for it
|
||||
$sAttCode = self::GetOwnerOrganizationAttCode($sClass);
|
||||
if (is_null($sAttCode))
|
||||
if (!is_null($sAttCode))
|
||||
{
|
||||
// No filtering for this object
|
||||
return true;
|
||||
$aUserOrgs = $this->GetUserOrgs($oUser, $sClass);
|
||||
if (count($aUserOrgs) > 0)
|
||||
{
|
||||
$oFilter = $this->MakeSelectFilter($sClass, $aUserOrgs, $aSettings, $sAttCode);
|
||||
}
|
||||
// else: No org means 'any org'
|
||||
}
|
||||
// Position the user
|
||||
//
|
||||
$aUserOrgs = $this->GetUserOrgs($oUser, $sClass);
|
||||
if (count($aUserOrgs) == 0)
|
||||
// else: No silo for this class
|
||||
|
||||
// Specific conditions to hide, for non-administrators, the Administrator Users, the Administrator Profile and related links
|
||||
// Note: when logged as an administrator, GetSelectFilter is completely bypassed.
|
||||
if ($this->AdministratorsAreHidden())
|
||||
{
|
||||
// No org means 'any org'
|
||||
return true;
|
||||
if ($sClass == 'URP_Profiles')
|
||||
{
|
||||
$oExpression = new FieldExpression('id', $sClass);
|
||||
$oScalarExpr = new ScalarExpression(1);
|
||||
|
||||
$aConditions[] = new BinaryExpression($oExpression, '!=', $oScalarExpr);
|
||||
}
|
||||
else if (($sClass == 'URP_UserProfile') || ($sClass == 'User') || (is_subclass_of($sClass, 'User')))
|
||||
{
|
||||
$aAdministrators = $this->GetAdministrators();
|
||||
if (count($aAdministrators) > 0)
|
||||
{
|
||||
$sAttCode = ($sClass == 'URP_UserProfile') ? 'userid' : 'id';
|
||||
$oExpression = new FieldExpression($sAttCode, $sClass);
|
||||
$oListExpr = ListExpression::FromScalars($aAdministrators);
|
||||
$aConditions[] = new BinaryExpression($oExpression, 'NOT IN', $oListExpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->MakeSelectFilter($sClass, $aUserOrgs, $aSettings, $sAttCode);
|
||||
// Handling of the added conditions
|
||||
if (count($aConditions) > 0)
|
||||
{
|
||||
if($oFilter === true)
|
||||
{
|
||||
// No 'silo' filter, let's build a clean one
|
||||
$oFilter = new DBObjectSearch($sClass);
|
||||
}
|
||||
|
||||
// Add the conditions to the filter
|
||||
foreach($aConditions as $oCondition)
|
||||
{
|
||||
$oFilter->AddConditionExpression($oCondition);
|
||||
}
|
||||
}
|
||||
|
||||
return $oFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve (and memoize) the list of administrator accounts.
|
||||
* Note that there should always be at least one administrator account
|
||||
* @return number[]
|
||||
*/
|
||||
private function GetAdministrators()
|
||||
{
|
||||
static $aAdministrators = null;
|
||||
|
||||
if ($aAdministrators === null)
|
||||
{
|
||||
// Find all administrators
|
||||
$aAdministrators = array();
|
||||
$oAdministratorsFilter = new DBObjectSearch('User');
|
||||
$oLnkFilter = new DBObjectSearch('URP_UserProfile');
|
||||
$oExpression = new FieldExpression('profileid', 'URP_UserProfile');
|
||||
$oScalarExpr = new ScalarExpression(1);
|
||||
$oCondition = new BinaryExpression($oExpression, '=', $oScalarExpr);
|
||||
$oLnkFilter->AddConditionExpression($oCondition);
|
||||
$oAdministratorsFilter->AddCondition_ReferencedBy($oLnkFilter, 'userid');
|
||||
$oAdministratorsFilter->AllowAllData(true); // Mandatory to prevent infinite recursion !!
|
||||
$oSet = new DBObjectSet($oAdministratorsFilter);
|
||||
$oSet->OptimizeColumnLoad(array('User' => array('login')));
|
||||
while($oUser = $oSet->Fetch())
|
||||
{
|
||||
$aAdministrators[] = $oUser->GetKey();
|
||||
}
|
||||
}
|
||||
return $aAdministrators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not to hide the 'Administrator' profile and the administrator accounts
|
||||
* @return boolean
|
||||
*/
|
||||
private function AdministratorsAreHidden()
|
||||
{
|
||||
return ((bool)MetaModel::GetConfig()->Get('security.hide_administrators'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
||||
use Combodo\iTop\Application\UI\Base\UIBlock;
|
||||
@@ -227,6 +228,20 @@ class ApplicationContext
|
||||
}
|
||||
return $sContext;
|
||||
}
|
||||
/**
|
||||
* Returns the context an array of input blocks
|
||||
*
|
||||
* @return array The context as a sequence of <input type="hidden" /> tags
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function GetForUIForm()
|
||||
{
|
||||
$aContextInputBlocks = [];
|
||||
foreach ($this->aValues as $sName => $sValue) {
|
||||
$aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", htmlentities($sValue, ENT_QUOTES, 'UTF-8'));
|
||||
}
|
||||
return $aContextInputBlocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the context as sequence of input tags to be inserted inside a <form> tag
|
||||
@@ -321,7 +336,7 @@ class ApplicationContext
|
||||
$sPrevious = self::GetUrlMakerClass();
|
||||
|
||||
self::$m_sUrlMakerClass = $sClass;
|
||||
$_SESSION['UrlMakerClass'] = $sClass;
|
||||
Session::Set('UrlMakerClass', $sClass);
|
||||
|
||||
return $sPrevious;
|
||||
}
|
||||
@@ -334,9 +349,9 @@ class ApplicationContext
|
||||
{
|
||||
if (is_null(self::$m_sUrlMakerClass))
|
||||
{
|
||||
if (isset($_SESSION['UrlMakerClass']))
|
||||
if (Session::IsSet('UrlMakerClass'))
|
||||
{
|
||||
self::$m_sUrlMakerClass = $_SESSION['UrlMakerClass'];
|
||||
self::$m_sUrlMakerClass = Session::Get('UrlMakerClass');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -389,9 +404,9 @@ class ApplicationContext
|
||||
*/
|
||||
protected static function LoadPluginProperties()
|
||||
{
|
||||
if (isset($_SESSION['PluginProperties']))
|
||||
if (Session::IsSet('PluginProperties'))
|
||||
{
|
||||
self::$m_aPluginProperties = $_SESSION['PluginProperties'];
|
||||
self::$m_aPluginProperties = Session::Get('PluginProperties');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -411,7 +426,7 @@ class ApplicationContext
|
||||
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
|
||||
|
||||
self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value;
|
||||
$_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value;
|
||||
Session::Set(['PluginProperties', $sPluginClass, $sProperty], $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -909,6 +909,8 @@ abstract class ApplicationPopupMenuItem
|
||||
/**
|
||||
* Class for adding an item into a popup menu that browses to the given URL
|
||||
*
|
||||
* Note: This works only in the backoffice, {@see \URLButtonItem} for the end-user portal
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @since 2.0
|
||||
@@ -963,6 +965,8 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
/**
|
||||
* Class for adding an item into a popup menu that triggers some Javascript code
|
||||
*
|
||||
* Note: This works only in the backoffice, {@see \JSButtonItem} for the end-user portal
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @since 2.0
|
||||
@@ -1097,7 +1101,9 @@ class JSButtonItem extends JSPopupMenuItem
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @since 2.0
|
||||
* @deprecated since 3.0.0 use iPageUIBlockExtension instead
|
||||
* @deprecated 3.0.0 If you need to include:
|
||||
* * JS/CSS files/snippets, use {@see \iBackofficeLinkedScriptsExtension}, {@see \iBackofficeLinkedStylesheetsExtension}, etc instead
|
||||
* * HTML (and optionally JS/CSS), use {@see \iPageUIBlockExtension} to manipulate {@see \Combodo\iTop\Application\UI\Base\UIBlock} instead
|
||||
*/
|
||||
interface iPageUIExtension
|
||||
{
|
||||
@@ -1248,6 +1254,151 @@ abstract class AbstractPageUIBlockExtension implements iPageUIBlockExtension
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add script (JS) files to the backoffice pages
|
||||
*
|
||||
* @see \iTopWebPage::$a_linked_scripts
|
||||
* @api
|
||||
* @since 3.0.0
|
||||
*/
|
||||
interface iBackofficeLinkedScriptsExtension
|
||||
{
|
||||
/**
|
||||
* @see \iTopWebPage::$a_linked_scripts Each script will be included using this property
|
||||
* @return array An array of absolute URLs to the files to include
|
||||
*/
|
||||
public function GetLinkedScriptsAbsUrls(): array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add inline script (JS) to the backoffice pages' head.
|
||||
* Will be executed first, BEFORE the DOM interpretation.
|
||||
*
|
||||
* @see \iTopWebPage::$a_early_scripts
|
||||
* @api
|
||||
* @since 3.0.0
|
||||
*/
|
||||
interface iBackofficeEarlyScriptExtension
|
||||
{
|
||||
/**
|
||||
* @see \iTopWebPage::$a_early_scripts
|
||||
* @return string
|
||||
*/
|
||||
public function GetEarlyScript(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add inline script (JS) to the backoffice pages that will be executed immediately, without waiting for the DOM to be ready.
|
||||
*
|
||||
* @see \iTopWebPage::$a_scripts
|
||||
* @api
|
||||
* @since 3.0.0
|
||||
*/
|
||||
interface iBackofficeScriptExtension
|
||||
{
|
||||
/**
|
||||
* @see \iTopWebPage::$a_scripts
|
||||
* @return string
|
||||
*/
|
||||
public function GetScript(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add inline script (JS) to the backoffice pages that will be executed right when the DOM is ready.
|
||||
*
|
||||
* @see \iTopWebPage::$a_init_scripts
|
||||
* @api
|
||||
* @since 3.0.0
|
||||
*/
|
||||
interface iBackofficeInitScriptExtension
|
||||
{
|
||||
/**
|
||||
* @see \iTopWebPage::$a_init_scripts
|
||||
* @return string
|
||||
*/
|
||||
public function GetInitScript(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add inline script (JS) to the backoffice pages that will be executed slightly AFTER the DOM is ready (just after the init. scripts).
|
||||
*
|
||||
* @see \iTopWebPage::$a_ready_scripts
|
||||
* @api
|
||||
* @since 3.0.0
|
||||
*/
|
||||
interface iBackofficeReadyScriptExtension
|
||||
{
|
||||
/**
|
||||
* @see \iTopWebPage::$a_ready_scripts
|
||||
* @return string
|
||||
*/
|
||||
public function GetReadyScript(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add stylesheets (CSS) to the backoffice pages
|
||||
*
|
||||
* @see \iTopWebPage::$a_linked_stylesheets
|
||||
* @api
|
||||
* @since 3.0.0
|
||||
*/
|
||||
interface iBackofficeLinkedStylesheetsExtension
|
||||
{
|
||||
/**
|
||||
* @see \iTopWebPage::$a_linked_stylesheets
|
||||
* @return array An array of absolute URLs to the files to include
|
||||
*/
|
||||
public function GetLinkedStylesheetsAbsUrls(): array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add inline style (CSS) to the backoffice pages' head.
|
||||
*
|
||||
* @see \iTopWebPage::$a_styles
|
||||
* @api
|
||||
* @since 3.0.0
|
||||
*/
|
||||
interface iBackofficeStyleExtension
|
||||
{
|
||||
/**
|
||||
* @see \iTopWebPage::$a_styles
|
||||
* @return string
|
||||
*/
|
||||
public function GetStyle(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add Dict entries
|
||||
*
|
||||
* @see \iTopWebPage::$a_dict_entries
|
||||
* @api
|
||||
* @since 3.0.0
|
||||
*/
|
||||
interface iBackofficeDictEntriesExtension
|
||||
{
|
||||
/**
|
||||
* @see \iTopWebPage::a_dict_entries
|
||||
* @return array
|
||||
*/
|
||||
public function GetDictEntries(): array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add Dict entries prefixes
|
||||
*
|
||||
* @see \iTopWebPage::$a_dict_entries_prefixes
|
||||
* @api
|
||||
* @since 3.0.0
|
||||
*/
|
||||
interface iBackofficeDictEntriesPrefixesExtension
|
||||
{
|
||||
/**
|
||||
* @see \iTopWebPage::a_dict_entries_prefixes
|
||||
* @return array
|
||||
*/
|
||||
public function GetDictEntriesPrefixes(): array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add content to any enhanced portal page
|
||||
*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -543,7 +543,7 @@ EOF
|
||||
|
||||
if ($bFromDasboardPage) {
|
||||
$sTitleForHTML = utils::HtmlEntities(Dict::S($this->sTitle));
|
||||
$sHtml = "<div class=\"ibo-top-bar--toolbar-dashboard-title\">{$sTitleForHTML}</div>";
|
||||
$sHtml = "<div class=\"ibo-top-bar--toolbar-dashboard-title\" title=\"{$sTitleForHTML}\">{$sTitleForHTML}</div>";
|
||||
if ($oPage instanceof iTopWebPage) {
|
||||
$oTopBar = $oPage->GetTopBarLayout();
|
||||
$oToolbar = ToolbarUIBlockFactory::MakeStandard();
|
||||
@@ -552,7 +552,7 @@ EOF
|
||||
$oToolbar->AddHtml($sHtml);
|
||||
} else {
|
||||
$oPage->add_script(<<<JS
|
||||
$(".ibo-top-bar--toolbar-dashboard-title").html("$sTitleForHTML");
|
||||
$(".ibo-top-bar--toolbar-dashboard-title").html("$sTitleForHTML").attr("title", $('<div>').html("$sTitleForHTML").text());
|
||||
JS
|
||||
);
|
||||
}
|
||||
@@ -583,7 +583,7 @@ JS
|
||||
$oPage->add('<div id="select_dashlet" class="ibo-dashboard--available-dashlets--list" data-role="ibo-dashboard--available-dashlets--list">');
|
||||
$aAvailableDashlets = $this->GetAvailableDashlets();
|
||||
foreach ($aAvailableDashlets as $sDashletClass => $aInfo) {
|
||||
$oPage->add('<span dashlet_class="'.$sDashletClass.'" class="ibo-dashboard-editor--available-dashlet-icon dashlet_icon ui-widget-content ui-corner-all" data-role="ibo-dashboard-editor--available-dashlet-icon" id="dashlet_'.$sDashletClass.'" title="'.$aInfo['label'].'"><img src="'.$sUrl.$aInfo['icon'].'" /></span>');
|
||||
$oPage->add('<span dashlet_class="'.$sDashletClass.'" class="ibo-dashboard-editor--available-dashlet-icon dashlet_icon ui-widget-content ui-corner-all" data-role="ibo-dashboard-editor--available-dashlet-icon" id="dashlet_'.$sDashletClass.'" data-tooltip-content="'.$aInfo['label'].'" title="'.$aInfo['label'].'"><img src="'.$sUrl.$aInfo['icon'].'" /></span>');
|
||||
}
|
||||
$oPage->add('</div>');
|
||||
|
||||
@@ -860,28 +860,29 @@ class RuntimeDashboard extends Dashboard
|
||||
{
|
||||
$bCustomized = false;
|
||||
|
||||
if (!appUserPreferences::GetPref('display_original_dashboard_'.$sDashBoardId, false))
|
||||
{
|
||||
$sDashboardFileSanitized = utils::RealPath($sDashboardFile, APPROOT);
|
||||
if (false === $sDashboardFileSanitized) {
|
||||
throw new SecurityException('Invalid dashboard file !');
|
||||
}
|
||||
|
||||
if (!appUserPreferences::GetPref('display_original_dashboard_'.$sDashBoardId, false)) {
|
||||
// Search for an eventual user defined dashboard
|
||||
$oUDSearch = new DBObjectSearch('UserDashboard');
|
||||
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
|
||||
$oUDSearch->AddCondition('menu_code', $sDashBoardId, '=');
|
||||
$oUDSet = new DBObjectSet($oUDSearch);
|
||||
if ($oUDSet->Count() > 0)
|
||||
{
|
||||
if ($oUDSet->Count() > 0) {
|
||||
// Assuming there is at most one couple {user, menu}!
|
||||
$oUserDashboard = $oUDSet->Fetch();
|
||||
$sDashboardDefinition = $oUserDashboard->Get('contents');
|
||||
$bCustomized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDashboardDefinition = @file_get_contents($sDashboardFile);
|
||||
} else {
|
||||
$sDashboardDefinition = @file_get_contents($sDashboardFileSanitized);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDashboardDefinition = @file_get_contents($sDashboardFile);
|
||||
$sDashboardDefinition = @file_get_contents($sDashboardFileSanitized);
|
||||
}
|
||||
|
||||
if ($sDashboardDefinition !== false)
|
||||
@@ -889,7 +890,7 @@ class RuntimeDashboard extends Dashboard
|
||||
$oDashboard = new RuntimeDashboard($sDashBoardId);
|
||||
$oDashboard->FromXml($sDashboardDefinition);
|
||||
$oDashboard->SetCustomFlag($bCustomized);
|
||||
$oDashboard->SetDefinitionFile($sDashboardFile);
|
||||
$oDashboard->SetDefinitionFile($sDashboardFileSanitized);
|
||||
} else {
|
||||
$oDashboard = null;
|
||||
}
|
||||
@@ -1065,11 +1066,11 @@ EOF
|
||||
dashboard.html(data);
|
||||
dashboard.unblock();
|
||||
if ($('#ibo-dashboard-selector$sDivId input').prop("checked")) {
|
||||
$('#ibo-dashboard-selector$sDivId').data('tooltip-content', '$sSwitchToStandard');
|
||||
$('#ibo-dashboard-selector$sDivId').attr('data-tooltip-content', '$sSwitchToStandard');
|
||||
} else {
|
||||
$('#ibo-dashboard-selector$sDivId').data('tooltip-content', '$sSwitchToCustom');
|
||||
$('#ibo-dashboard-selector$sDivId').attr('data-tooltip-content', '$sSwitchToCustom');
|
||||
}
|
||||
CombodoTooltip.InitAllNonInstantiatedTooltips($('#ibo-dashboard-selector$sDivId').parent());
|
||||
CombodoTooltip.InitAllNonInstantiatedTooltips($('#ibo-dashboard-selector$sDivId').parent(), true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
use Combodo\iTop\Application\Helper\WebResourcesHelper;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletContainer;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
@@ -202,6 +203,24 @@ abstract class Dashlet
|
||||
$this->OnUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array Rel. path to the app. root of the JS files required by the dashlet
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function GetJSFilesRelPaths(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array Rel. path to the app. root of the CSS files required by the dashlet
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function GetCSSFilesRelPaths(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WebPage $oPage
|
||||
* @param bool $bEditMode
|
||||
@@ -224,6 +243,9 @@ abstract class Dashlet
|
||||
$oDashletContainer->AddCSSClasses($this->aCSSClasses);
|
||||
}
|
||||
|
||||
$oDashletContainer->AddMultipleJsFilesRelPaths($this->GetJSFilesRelPaths());
|
||||
$oDashletContainer->AddMultipleCssFilesRelPaths($this->GetCSSFilesRelPaths());
|
||||
|
||||
try {
|
||||
if (get_class($this->oModelReflection) == 'ModelReflectionRuntime') {
|
||||
$oBlock = $this->Render($oPage, $bEditMode, $aExtraParams);
|
||||
@@ -604,8 +626,7 @@ class DashletUnknown extends Dashlet
|
||||
|
||||
$oDashletContainer = new DashletContainer(null, ['dashlet-content']);
|
||||
|
||||
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
|
||||
$oDashletContainer->AddHtml('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
|
||||
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div><div class="dashlet-ukn-text">'.$sExplainText.'</div>');
|
||||
|
||||
return $oDashletContainer;
|
||||
}
|
||||
@@ -624,8 +645,7 @@ class DashletUnknown extends Dashlet
|
||||
|
||||
$oDashletContainer = new DashletContainer(null, ['dashlet-content']);
|
||||
|
||||
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
|
||||
$oDashletContainer->AddHtml('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
|
||||
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div><div class="dashlet-ukn-text">'.$sExplainText.'</div>');
|
||||
|
||||
return $oDashletContainer;
|
||||
}
|
||||
@@ -988,7 +1008,8 @@ HTML;
|
||||
|
||||
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $this->aProperties['query']);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-queryoql");
|
||||
$oField->AddCSSClass("ibo-query-oql");
|
||||
$oField->AddCSSClass("ibo-is-code");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
$oField = new DesignerBooleanField('menu', Dict::S('UI:DashletObjectList:Prop-Menu'), $this->aProperties['menu']);
|
||||
@@ -1025,7 +1046,8 @@ HTML;
|
||||
|
||||
$oField = new DesignerHiddenField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $sOQL);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-queryoql");
|
||||
$oField->AddCSSClass("ibo-query-oql");
|
||||
$oField->AddCSSClass("ibo-is-code");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
$oField = new DesignerBooleanField('menu', Dict::S('UI:DashletObjectList:Prop-Menu'), $this->aProperties['menu']);
|
||||
@@ -1375,7 +1397,8 @@ abstract class DashletGroupBy extends Dashlet
|
||||
|
||||
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $this->aProperties['query']);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-queryoql");
|
||||
$oField->AddCSSClass("ibo-query-oql");
|
||||
$oField->AddCSSClass("ibo-is-code");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
try {
|
||||
@@ -1632,7 +1655,8 @@ abstract class DashletGroupBy extends Dashlet
|
||||
|
||||
$oField = new DesignerHiddenField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $sOQL);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-queryoql");
|
||||
$oField->AddCSSClass("ibo-query-oql");
|
||||
$oField->AddCSSClass("ibo-is-code");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
if (!is_null($sOQL)) {
|
||||
@@ -1676,6 +1700,28 @@ class DashletGroupByPie extends DashletGroupBy
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetJSFilesRelPaths(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::GetJSFilesRelPaths(),
|
||||
WebResourcesHelper::GetJSFilesRelPathsForC3JS()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetCSSFilesRelPaths(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::GetCSSFilesRelPaths(),
|
||||
WebResourcesHelper::GetCSSFilesRelPathsForC3JS()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
@@ -2183,7 +2229,8 @@ class DashletHeaderDynamic extends Dashlet
|
||||
|
||||
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletHeaderDynamic:Prop-Query'), $this->aProperties['query']);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-queryoql");
|
||||
$oField->AddCSSClass("ibo-query-oql");
|
||||
$oField->AddCSSClass("ibo-is-code");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
try
|
||||
|
||||
@@ -258,6 +258,66 @@
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
<method id="SetComputedDate">
|
||||
<arguments>
|
||||
<argument id="1">
|
||||
<type>attcode</type>
|
||||
<mandatory>true</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>allow</operation>
|
||||
<types>
|
||||
<type id="AttributeDate"/>
|
||||
<type id="AttributeDateTime"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
<argument id="2">
|
||||
<type>string</type>
|
||||
<mandatory>false</mandatory>
|
||||
</argument>
|
||||
<argument id="3">
|
||||
<type>attcode</type>
|
||||
<mandatory>false</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>allow</operation>
|
||||
<types>
|
||||
<type id="AttributeDate"/>
|
||||
<type id="AttributeDateTime"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
<method id="SetComputedDateIfNull">
|
||||
<arguments>
|
||||
<argument id="1">
|
||||
<type>attcode</type>
|
||||
<mandatory>true</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>allow</operation>
|
||||
<types>
|
||||
<type id="AttributeDate"/>
|
||||
<type id="AttributeDateTime"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
<argument id="2">
|
||||
<type>string</type>
|
||||
<mandatory>false</mandatory>
|
||||
</argument>
|
||||
<argument id="3">
|
||||
<type>attcode</type>
|
||||
<mandatory>false</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>allow</operation>
|
||||
<types>
|
||||
<type id="AttributeDate"/>
|
||||
<type id="AttributeDateTime"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
<method id="SetCurrentDate">
|
||||
<arguments>
|
||||
<argument id="1">
|
||||
@@ -409,30 +469,6 @@
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
<method id="PrefillCreationForm">
|
||||
<arguments>
|
||||
<argument id="1">
|
||||
<type>reference</type>
|
||||
<mandatory>true</mandatory>
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
<method id="PrefillTransitionForm">
|
||||
<arguments>
|
||||
<argument id="1">
|
||||
<type>reference</type>
|
||||
<mandatory>true</mandatory>
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
<method id="PrefillSearchForm">
|
||||
<arguments>
|
||||
<argument id="1">
|
||||
<type>reference</type>
|
||||
<mandatory>true</mandatory>
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
</methods>
|
||||
</class>
|
||||
</classes>
|
||||
|
||||
@@ -174,6 +174,8 @@ class DisplayBlock
|
||||
/** string Max. height of the list, if not specified will occupy all the available height no matter the pagination */
|
||||
'localize_values',
|
||||
/** param for export.php */
|
||||
'refresh_action',
|
||||
/**to add refresh button in datatable*/
|
||||
], DataTableUIBlockFactory::GetAllowedParams()),
|
||||
'list_search' => array_merge([
|
||||
'update_history',
|
||||
@@ -268,8 +270,12 @@ class DisplayBlock
|
||||
'surround_with_panel',
|
||||
/** string title of panel block */
|
||||
'panel_title',
|
||||
/** string true if panel title should be displayed as html */
|
||||
'panel_title_is_html',
|
||||
/** string class for panel block style */
|
||||
'panel_class',
|
||||
/** string class for panel block style */
|
||||
'panel_icon',
|
||||
];
|
||||
|
||||
if (isset($aAllowedParams[$sStyle])) {
|
||||
@@ -465,10 +471,8 @@ class DisplayBlock
|
||||
$oHtml->AddSubBlock($this->GetRenderContent($oPage, $aExtraParams, $sId));
|
||||
} catch (Exception $e) {
|
||||
if (UserRights::IsAdministrator()) {
|
||||
$sExceptionContent = <<<HTML
|
||||
Exception thrown:<br>
|
||||
<code>{$e->getMessage()}</code>
|
||||
HTML;
|
||||
$sExceptionContent = 'Exception thrown:<br><code>'.utils::Sanitize($e->getMessage(), '', utils::ENUM_SANITIZATION_FILTER_STRING).'</code>';
|
||||
|
||||
$oExceptionAlert = AlertUIBlockFactory::MakeForFailure('Cannot display results', $sExceptionContent);
|
||||
$oHtml->AddSubBlock($oExceptionAlert);
|
||||
}
|
||||
@@ -535,8 +539,10 @@ HTML;
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws MySQLException
|
||||
* @throws Exception
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 add type hinting to $aExtraParams
|
||||
*/
|
||||
public function GetRenderContent(WebPage $oPage, array $aExtraParams = [], string $sId = null)
|
||||
public function GetRenderContent(WebPage $oPage, array $aExtraParams, string $sId)
|
||||
{
|
||||
$sHtml = '';
|
||||
$oBlock = null;
|
||||
@@ -849,7 +855,7 @@ JS
|
||||
{
|
||||
$oField = new FieldExpression($sFilterCode, $oFilter->GetClassAlias());
|
||||
$sListExpr = '('.implode(', ', CMDBSource::Quote($condition)).')';
|
||||
$sOQLCondition = $oField->Render()." IN $sListExpr";
|
||||
$sOQLCondition = $oField->RenderExpression()." IN $sListExpr";
|
||||
$oNewCondition = Expression::FromOQL($sOQLCondition);
|
||||
return $oNewCondition;
|
||||
}
|
||||
@@ -1010,9 +1016,10 @@ JS
|
||||
$aCountsQueryResults[$aCountGroupBySingleResult[0]] = $aCountGroupBySingleResult[1];
|
||||
}
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
|
||||
$aValues = $oAttDef->GetAllowedValues();
|
||||
foreach ($aStates as $sStateValue) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
|
||||
$aStateLabels[$sStateValue] = $oAttDef->GetAsPlainText($sStateValue);
|
||||
$aStateLabels[$sStateValue] = $aValues[$sStateValue] ?? '';
|
||||
$aCounts[$sStateValue] = (array_key_exists($sStateValue, $aCountsQueryResults))
|
||||
? $aCountsQueryResults[$sStateValue]
|
||||
: 0;
|
||||
@@ -1040,8 +1047,7 @@ JS
|
||||
$sCountLabel = $aCount['label'];
|
||||
$oPill = PillFactory::MakeForState($sClass, $sStateValue)
|
||||
->SetTooltip($sStateLabel)
|
||||
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--count\">$sCountLabel</span>")
|
||||
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--label ibo-text-truncated-with-ellipsis\">$sStateLabel</span>");
|
||||
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--count\">$sCountLabel</span><span class=\"ibo-dashlet-header-dynamic--label ibo-text-truncated-with-ellipsis\">".utils::HtmlEntities($sStateLabel)."</span>");
|
||||
if ($sHyperlink != '-') {
|
||||
$oPill->SetUrl($sHyperlink);
|
||||
}
|
||||
@@ -1196,6 +1202,9 @@ JS
|
||||
$sTitle = Dict::Format($sFormat, $iTotalCount);
|
||||
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
$oBlock->AddSubTitleBlock(new Html($sTitle));
|
||||
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
|
||||
$oBlock->SetIcon($aExtraParams["panel_icon"]);
|
||||
}
|
||||
$oDataTable = DataTableUIBlockFactory::MakeForStaticData("", $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
|
||||
$oBlock->AddSubBlock($oDataTable);
|
||||
} else {
|
||||
@@ -1212,6 +1221,9 @@ JS
|
||||
}
|
||||
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
|
||||
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
|
||||
$oBlock->SetIcon($aExtraParams["panel_icon"]);
|
||||
}
|
||||
$oBlock->AddSubBlock(new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>'));
|
||||
} else {
|
||||
$oBlock = new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>');
|
||||
@@ -1347,6 +1359,9 @@ JS
|
||||
|
||||
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
|
||||
$oPanel->SetIcon($aExtraParams["panel_icon"]);
|
||||
}
|
||||
$oPanel->AddSubBlock($oBlock);
|
||||
|
||||
return $oPanel;
|
||||
@@ -1544,6 +1559,9 @@ JS
|
||||
|
||||
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
|
||||
$oPanel->SetIcon($aExtraParams["panel_icon"]);
|
||||
}
|
||||
$oPanel->AddSubBlock($oBlock);
|
||||
|
||||
return $oPanel;
|
||||
@@ -1632,6 +1650,9 @@ JS
|
||||
}
|
||||
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
|
||||
$oPanel->SetIcon($aExtraParams["panel_icon"]);
|
||||
}
|
||||
$oPanel->AddSubBlock($oBlock);
|
||||
|
||||
return $oPanel;
|
||||
@@ -1711,7 +1732,24 @@ class HistoryBlock extends DisplayBlock
|
||||
$this->iLimitCount = $iCount;
|
||||
}
|
||||
|
||||
public function GetRenderContent(WebPage $oPage, array $aExtraParams = [], string $sId = null)
|
||||
/**
|
||||
* @param \WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
* @param string $sId
|
||||
*
|
||||
* @return string
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aExtraParams and add type hinting for PHP 8.0 compatibility
|
||||
* (var is unused, and all calls were already made using a default value)
|
||||
*/
|
||||
public function GetRenderContent(WebPage $oPage, array $aExtraParams, string $sId)
|
||||
{
|
||||
$sHtml = '';
|
||||
$bTruncated = false;
|
||||
@@ -1852,11 +1890,10 @@ class MenuBlock extends DisplayBlock
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \OQLException
|
||||
* @throws \ReflectionException
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value and add type hinting on $aExtraParams for PHP 8.0 compatibility
|
||||
*/
|
||||
public function GetRenderContent(WebPage $oPage, array $aExtraParams = [], string $sId = null)
|
||||
public function GetRenderContent(WebPage $oPage, array $aExtraParams, string $sId)
|
||||
{
|
||||
$oRenderBlock = new UIContentBlock();
|
||||
|
||||
@@ -1867,7 +1904,7 @@ class MenuBlock extends DisplayBlock
|
||||
|
||||
$sClass = $this->m_oFilter->GetClass();
|
||||
$oSet = new CMDBObjectSet($this->m_oFilter);
|
||||
$sRefreshAction = $aExtraParams['sRefreshAction'] ?? '';
|
||||
$sRefreshAction = $aExtraParams['refresh_action'] ?? '';
|
||||
|
||||
/** @var array $aRegularActions Any action other than a transition */
|
||||
$aRegularActions = [];
|
||||
@@ -2203,8 +2240,14 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
if ($oPopupMenuItemsBlock->HasSubBlocks()) {
|
||||
$oRenderBlock->AddSubBlock($oPopupMenuItemsBlock);
|
||||
} else {
|
||||
foreach ($oPopupMenuItemsBlock->GetJsFilesRelPaths() as $sJsPath) {
|
||||
$oRenderBlock->AddJsFileRelPath($sJsPath);
|
||||
}
|
||||
foreach ($oPopupMenuItemsBlock->GetCssFilesRelPaths() as $sCssPath) {
|
||||
$oRenderBlock->AddCssFileRelPath($sCssPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract favorite actions from their menus
|
||||
$aFavoriteRegularActions = [];
|
||||
$aFavoriteTransitionActions = [];
|
||||
@@ -2305,13 +2348,25 @@ class MenuBlock extends DisplayBlock
|
||||
$sIconClass = 'fas fa-share-alt';
|
||||
$sLabel = '';
|
||||
break;
|
||||
|
||||
default:
|
||||
if (isset($aAction['icon_class']) && (strlen($aAction['icon_class']) > 0)) {
|
||||
$sIconClass = $aAction['icon_class'];
|
||||
$sLabel = '';
|
||||
}
|
||||
}
|
||||
|
||||
$sTarget = isset($aAction['target']) ? $aAction['target'] : '';
|
||||
$oActionButton = ButtonUIBlockFactory::MakeLinkNeutral($sUrl, $sLabel, $sIconClass, $sTarget, $sActionId);
|
||||
$oActionButton = ButtonUIBlockFactory::MakeLinkNeutral($sUrl, $sLabel, $sIconClass, $sTarget, utils::Sanitize($sActionId, '', utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER));
|
||||
// ResourceId should not be sanitized
|
||||
$oActionButton->AddDataAttribute('resource-id', $sActionId);
|
||||
$oActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
|
||||
if (empty($sLabel)) {
|
||||
$oActionButton->SetTooltip(Dict::S($sActionId));
|
||||
if (empty($aAction['tooltip'])) {
|
||||
$oActionButton->SetTooltip(Dict::S($sActionId));
|
||||
} else {
|
||||
$oActionButton->SetTooltip($aAction['tooltip']);
|
||||
}
|
||||
}
|
||||
$oActionsToolbar->AddSubBlock($oActionButton);
|
||||
}
|
||||
@@ -2319,7 +2374,7 @@ class MenuBlock extends DisplayBlock
|
||||
// - Refresh
|
||||
if ($sRefreshAction != '') {
|
||||
$oActionButton = ButtonUIBlockFactory::MakeAlternativeNeutral('', 'UI:Button:Refresh');
|
||||
$oActionButton->SetIconClass('fas fa-sync')
|
||||
$oActionButton->SetIconClass('fas fa-sync-alt')
|
||||
->SetOnClickJsCode($sRefreshAction)
|
||||
->SetTooltip(Dict::S('UI:Button:Refresh'))
|
||||
->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
|
||||
|
||||
@@ -229,14 +229,14 @@ class DesignerForm
|
||||
$aRow = $oField->Render($oP, $sFormId, 'property');
|
||||
if ($oField->IsVisible()) {
|
||||
$sFieldId = $this->GetFieldId($oField->GetCode());
|
||||
$sValidation = $this->GetValidationArea($sFieldId, '<span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></span>');
|
||||
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply ibo-button ibo-is-alternative" >'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel ibo-button ibo-is-alternative"><span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-times"></i></span></td>'
|
||||
$sValidation = $this->GetValidationArea($sFieldId, '<div class="ibo-button ibo-is-alternative ibo-is-success" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></div>');
|
||||
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply" >'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel"><span><div class="ibo-button ibo-is-alternative ibo-is-neutral" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-undo"></i></div></span></td>'
|
||||
.$this->EndRow();
|
||||
|
||||
if (is_null($aRow['label'])) {
|
||||
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_value ibo-field--value" colspan="2">'.$aRow['value'];
|
||||
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_value" colspan="2">'.$aRow['value'];
|
||||
} else {
|
||||
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_label ibo-field--label">'.$aRow['label'].'</td><td class="prop_value ibo-field--value">'.$aRow['value'];
|
||||
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_label">'.$aRow['label'].'</td><td class="prop_value">'.$aRow['value'];
|
||||
}
|
||||
if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField)) {
|
||||
$sReturn .= $sValidationFields;
|
||||
@@ -354,7 +354,7 @@ EOF
|
||||
<<<EOF
|
||||
$('#$sDialogId').dialog({
|
||||
height: 'auto',
|
||||
maxHeight: $(window).height() - 8,
|
||||
maxHeight: $(window).height() * 0.9,
|
||||
width: $iDialogWidth,
|
||||
modal: true,
|
||||
autoOpen: $sAutoOpen,
|
||||
@@ -710,7 +710,10 @@ class DesignerFormField
|
||||
$this->bMandatory = false;
|
||||
$this->bReadOnly = false;
|
||||
$this->bAutoApply = false;
|
||||
$this->aCSSClasses = array('ibo-input');
|
||||
$this->aCSSClasses = [];
|
||||
if (ContextTag::Check(ContextTag::TAG_CONSOLE)) {
|
||||
$this->aCSSClasses[] = 'ibo-input';
|
||||
}
|
||||
$this->bDisplayed = true;
|
||||
$this->aWidgetExtraParams = array();
|
||||
}
|
||||
@@ -1065,7 +1068,10 @@ class DesignerLongTextField extends DesignerTextField
|
||||
public function __construct($sCode, $sLabel = '', $defaultValue = '')
|
||||
{
|
||||
parent::__construct($sCode, $sLabel, $defaultValue);
|
||||
$this->aCSSClasses[] = 'ibo-input-text';
|
||||
|
||||
if (ContextTag::Check(ContextTag::TAG_CONSOLE)) {
|
||||
$this->aCSSClasses[] = 'ibo-input-text';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1199,7 +1205,10 @@ class DesignerComboField extends DesignerFormField
|
||||
$this->bMultipleSelection = false;
|
||||
$this->bOtherChoices = false;
|
||||
$this->sNullLabel = Dict::S('UI:SelectOne');
|
||||
$this->aCSSClasses[] = 'ibo-input-select';
|
||||
|
||||
if (ContextTag::Check(ContextTag::TAG_CONSOLE)) {
|
||||
$this->aCSSClasses[] = 'ibo-input-select';
|
||||
}
|
||||
|
||||
$this->bAutoApply = true;
|
||||
$this->bSorted = true; // Sorted by default
|
||||
@@ -1217,9 +1226,14 @@ class DesignerComboField extends DesignerFormField
|
||||
return cmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_RAW;
|
||||
}
|
||||
}
|
||||
|
||||
public function SetAllowedValues($aAllowedValues)
|
||||
|
||||
public function SetAllowedValues(?array $aAllowedValues)
|
||||
{
|
||||
// Make sure to have an actual array for values
|
||||
if (is_null($aAllowedValues)) {
|
||||
$aAllowedValues = [];
|
||||
}
|
||||
|
||||
$this->aAllowedValues = $aAllowedValues;
|
||||
}
|
||||
|
||||
@@ -1296,11 +1310,11 @@ class DesignerComboField extends DesignerFormField
|
||||
{
|
||||
if ($this->bMultipleSelection)
|
||||
{
|
||||
$sHtml = "<select $sCSSClasses multiple size=\"8\"id=\"$sId\" name=\"$sName\">";
|
||||
$sHtml = "<span><select $sCSSClasses multiple size=\"8\"id=\"$sId\" name=\"$sName\">";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\">";
|
||||
$sHtml = "<span class=\"ibo-input-select-wrapper\"><select $sCSSClasses id=\"$sId\" name=\"$sName\">";
|
||||
if ($this->sNullLabel != '')
|
||||
{
|
||||
$sHtml .= "<option value=\"\">".$this->sNullLabel."</option>";
|
||||
@@ -1320,7 +1334,7 @@ class DesignerComboField extends DesignerFormField
|
||||
$sHtmlValue = str_replace(' ', ' ', htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8'));
|
||||
$sHtml .= "<option value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\" $sSelected>$sHtmlValue</option>";
|
||||
}
|
||||
$sHtml .= "</select>";
|
||||
$sHtml .= "</select></span>";
|
||||
if ($this->bOtherChoices)
|
||||
{
|
||||
$sHtml .= '<br/><input type="checkbox" id="other_chk_'.$sId.'"><label for="other_chk_'.$sId.'"> Other:</label> <input type="text" id="other_'.$sId.'" name="other_'.$sName.'" size="30"/>';
|
||||
@@ -1351,7 +1365,9 @@ class DesignerBooleanField extends DesignerFormField
|
||||
{
|
||||
parent::__construct($sCode, $sLabel, $defaultValue);
|
||||
$this->bAutoApply = true;
|
||||
$this->aCSSClasses[] = 'ibo-input-checkbox';
|
||||
if (ContextTag::Check(ContextTag::TAG_CONSOLE)) {
|
||||
$this->aCSSClasses[] = 'ibo-input-checkbox';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1498,7 +1514,8 @@ class DesignerIconSelectionField extends DesignerFormField
|
||||
$sPostUploadTo = ($this->sUploadUrl == null) ? 'null' : "'{$this->sUploadUrl}'";
|
||||
if (!$this->IsReadOnly()) {
|
||||
$sDefaultValue = ($this->defaultValue !== '') ? $this->defaultValue : $this->aAllowedValues[$idx]['value'];
|
||||
$sValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$sDefaultValue}\"/>";
|
||||
$sCSSClasses = ContextTag::Check(ContextTag::TAG_CONSOLE) ? 'class="ibo-input-select-wrapper"' : '';
|
||||
$sValue = "<span $sCSSClasses><input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$sDefaultValue}\"/></span>";
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$sId').icon_select({current_idx: $idx, items: $sJSItems, post_upload_to: $sPostUploadTo});
|
||||
@@ -1676,7 +1693,9 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
$this->defaultRealValue = $defaultValue;
|
||||
$this->aSubForms = array();
|
||||
$this->bSorted = true;
|
||||
$this->aCSSClasses[] = 'ibo-input-select';
|
||||
if (ContextTag::Check(ContextTag::TAG_CONSOLE)) {
|
||||
$this->aCSSClasses[] = 'ibo-input-select';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1753,18 +1772,18 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
}
|
||||
$sHtml = "<span $sCSSClasses>".$sDisplayValue.$sHiddenValue."</span>";
|
||||
} else {
|
||||
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
|
||||
$sHtml = "<span class=\"ibo-input-select-wrapper\"><select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
|
||||
foreach ($this->aSubForms as $iKey => $aFormData) {
|
||||
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
|
||||
$sValue = htmlentities($aFormData['value'], ENT_QUOTES, 'UTF-8');
|
||||
$sSelected = ($iKey == $this->defaultValue) ? 'selected' : '';
|
||||
$sHtml .= "<option data-value=\"$sValue\" value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
|
||||
}
|
||||
$sHtml .= "</select>";
|
||||
$sHtml .= "</select></span>";
|
||||
}
|
||||
|
||||
if ($sRenderMode == 'property') {
|
||||
$sHtml .= '</td><td class="prop_icon prop_apply ibo-prop--apply ibo-button ibo-is-alternative"><span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></span></td><td class="prop_icon prop_cancel ibo-prop--cancel ibo-button ibo-is-alternative"><span data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-times"></i></span></td></tr>';
|
||||
$sHtml .= '</td><td class="prop_icon prop_apply ibo-prop--apply"><span><button class="ibo-button ibo-is-alternative ibo-is-success" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></button></span></td><td class="prop_icon prop_cancel ibo-prop--cancel"><span><button class="ibo-button ibo-is-alternative ibo-is-neutral" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-times"></i></button></span></td></tr>';
|
||||
}
|
||||
foreach ($this->aSubForms as $sKey => $aFormData) {
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
|
||||
/**
|
||||
* Class LoginBasic
|
||||
*
|
||||
@@ -20,19 +23,19 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnModeDetection(&$iErrorCode)
|
||||
{
|
||||
if (!isset($_SESSION['login_mode']))
|
||||
if (!Session::IsSet('login_mode'))
|
||||
{
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
|
||||
{
|
||||
$_SESSION['login_mode'] = 'basic';
|
||||
Session::Set('login_mode', 'basic');
|
||||
}
|
||||
elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
|
||||
{
|
||||
$_SESSION['login_mode'] = 'basic';
|
||||
Session::Set('login_mode', 'basic');
|
||||
}
|
||||
elseif (isset($_SERVER['PHP_AUTH_USER']))
|
||||
{
|
||||
$_SESSION['login_mode'] = 'basic';
|
||||
Session::Set('login_mode', 'basic');
|
||||
}
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
@@ -40,10 +43,10 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnReadCredentials(&$iErrorCode)
|
||||
{
|
||||
if (!isset($_SESSION['login_mode']) || $_SESSION['login_mode'] == 'basic')
|
||||
if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'basic')
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
|
||||
$_SESSION['login_temp_auth_user'] = $sAuthUser;
|
||||
list($sAuthUser) = $this->GetAuthUserAndPassword();
|
||||
Session::Set('login_temp_auth_user', $sAuthUser);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -51,10 +54,10 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (Session::Get('login_mode') == 'basic')
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
|
||||
{
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
@@ -65,17 +68,17 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (Session::Get('login_mode') == 'basic')
|
||||
{
|
||||
list($sAuthUser) = $this->GetAuthUserAndPassword();
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (Session::Get('login_mode') == 'basic')
|
||||
{
|
||||
LoginWebPage::HTTP401Error();
|
||||
}
|
||||
@@ -84,9 +87,9 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (Session::Get('login_mode') == 'basic')
|
||||
{
|
||||
$_SESSION['can_logoff'] = true;
|
||||
Session::Set('can_logoff', true);
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
|
||||
/**
|
||||
* Class LoginDefaultBefore
|
||||
*/
|
||||
@@ -23,7 +25,7 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
|
||||
{
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_OK;
|
||||
|
||||
unset($_SESSION['login_temp_auth_user']);
|
||||
Session::Unset('login_temp_auth_user');
|
||||
|
||||
// Check if proposed login mode is present and allowed
|
||||
$aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes();
|
||||
@@ -32,11 +34,11 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
|
||||
if ($index !== false)
|
||||
{
|
||||
// Force login mode
|
||||
$_SESSION['login_mode'] = $sProposedLoginMode;
|
||||
Session::Set('login_mode', $sProposedLoginMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($_SESSION['login_mode']);
|
||||
Session::Unset('login_mode');
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -91,7 +93,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
|
||||
protected function OnCredentialsOk(&$iErrorCode)
|
||||
{
|
||||
if (!isset($_SESSION['login_mode']))
|
||||
if (!Session::IsSet('login_mode'))
|
||||
{
|
||||
// If no plugin validated the user, exit
|
||||
self::ResetLoginSession();
|
||||
@@ -110,7 +112,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
unset($_SESSION['login_temp_auth_user']);
|
||||
Session::Unset('login_temp_auth_user');
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
@@ -118,11 +120,11 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
private static function ResetLoginSession()
|
||||
{
|
||||
LoginWebPage::ResetSession();
|
||||
foreach (array_keys($_SESSION) as $sKey)
|
||||
foreach (Session::ListVariables() as $sKey)
|
||||
{
|
||||
if (utils::StartsWith($sKey, 'login_'))
|
||||
{
|
||||
unset($_SESSION[$sKey]);
|
||||
Session::Unset($sKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
|
||||
/**
|
||||
* Class LoginExternal
|
||||
*
|
||||
@@ -22,12 +24,12 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnModeDetection(&$iErrorCode)
|
||||
{
|
||||
if (!isset($_SESSION['login_mode']))
|
||||
if (!Session::IsSet('login_mode'))
|
||||
{
|
||||
$sAuthUser = $this->GetAuthUser();
|
||||
if ($sAuthUser && (strlen($sAuthUser) > 0))
|
||||
{
|
||||
$_SESSION['login_mode'] = 'external';
|
||||
Session::Set('login_mode', 'external');
|
||||
}
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
@@ -35,10 +37,10 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (Session::Get('login_mode') == 'external')
|
||||
{
|
||||
$sAuthUser = $this->GetAuthUser();
|
||||
if (!UserRights::CheckCredentials($sAuthUser, '', $_SESSION['login_mode'], 'external'))
|
||||
if (!UserRights::CheckCredentials($sAuthUser, '', Session::Get('login_mode'), 'external'))
|
||||
{
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
@@ -49,19 +51,19 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (Session::Get('login_mode') == 'external')
|
||||
{
|
||||
$sAuthUser = $this->GetAuthUser();
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', $_SESSION['login_mode']);
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (Session::Get('login_mode') == 'external')
|
||||
{
|
||||
$_SESSION['can_logoff'] = false;
|
||||
Session::Set('can_logoff', false);
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
@@ -69,7 +71,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (Session::Get('login_mode') == 'external')
|
||||
{
|
||||
LoginWebPage::HTTP401Error();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
|
||||
/**
|
||||
* Class LoginForm
|
||||
*
|
||||
@@ -29,8 +31,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnReadCredentials(&$iErrorCode)
|
||||
{
|
||||
if (!isset($_SESSION['login_mode']) || ($_SESSION['login_mode'] == 'form'))
|
||||
{
|
||||
if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'form') {
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
|
||||
if ($this->bForceFormOnError || empty($sAuthUser) || empty($sAuthPwd))
|
||||
@@ -50,9 +51,8 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
$this->bForceFormOnError = false;
|
||||
exit;
|
||||
}
|
||||
|
||||
$_SESSION['login_temp_auth_user'] = $sAuthUser;
|
||||
$_SESSION['login_mode'] = 'form';
|
||||
Session::Set('login_temp_auth_user', $sAuthUser);
|
||||
Session::Set('login_mode', 'form');
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -62,11 +62,11 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (Session::Get('login_mode') == 'form')
|
||||
{
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
|
||||
{
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
@@ -80,19 +80,19 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (Session::Get('login_mode') == 'form')
|
||||
{
|
||||
if (isset($_SESSION['auth_user']))
|
||||
if (Session::IsSet('auth_user'))
|
||||
{
|
||||
// If FSM reenter this state (example 2FA) then the auth_user is not resubmitted
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
$sAuthUser = Session::Get('auth_user');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
|
||||
}
|
||||
// Store 'auth_user' in session for further use
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (Session::Get('login_mode') == 'form')
|
||||
{
|
||||
$this->bForceFormOnError = true;
|
||||
}
|
||||
@@ -114,9 +114,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (Session::Get('login_mode') == 'form')
|
||||
{
|
||||
$_SESSION['can_logoff'] = true;
|
||||
Session::Set('can_logoff', true);
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
|
||||
use Combodo\iTop\Application\Branding;
|
||||
use Combodo\iTop\TwigExtension;
|
||||
|
||||
/**
|
||||
@@ -238,16 +239,9 @@ class LoginTwigRenderer
|
||||
|
||||
public function GetDefaultVars()
|
||||
{
|
||||
$sLogo = 'itop-logo-external.png';
|
||||
$sBrandingLogo = 'login-logo.png';
|
||||
|
||||
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
|
||||
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?t='.utils::GetCacheBusterTimestamp();
|
||||
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
|
||||
{
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?t='.utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
$sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl();
|
||||
|
||||
$aVars = array(
|
||||
'sAppRootUrl' => utils::GetAbsoluteUrlAppRoot(),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
|
||||
/**
|
||||
* Class LoginURL
|
||||
*
|
||||
@@ -26,13 +28,13 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnModeDetection(&$iErrorCode)
|
||||
{
|
||||
if (!isset($_SESSION['login_mode']) && !$this->bErrorOccurred)
|
||||
if (!Session::IsSet('login_mode') && !$this->bErrorOccurred)
|
||||
{
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
if (!empty($sAuthUser) && !empty($sAuthPwd))
|
||||
{
|
||||
$_SESSION['login_mode'] = 'url';
|
||||
Session::Set('login_mode', 'url');
|
||||
}
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
@@ -40,20 +42,20 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnReadCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (Session::Get('login_mode') == 'url')
|
||||
{
|
||||
$_SESSION['login_temp_auth_user'] = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
Session::Set('login_temp_auth_user', utils::ReadParam('auth_user', '', false, 'raw_data'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (Session::Get('login_mode') == 'url')
|
||||
{
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
|
||||
{
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
@@ -64,17 +66,17 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (Session::Get('login_mode') == 'url')
|
||||
{
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (Session::Get('login_mode') == 'url')
|
||||
{
|
||||
$this->bErrorOccurred = true;
|
||||
}
|
||||
@@ -83,9 +85,9 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (Session::Get('login_mode') == 'url')
|
||||
{
|
||||
$_SESSION['can_logoff'] = true;
|
||||
Session::Set('can_logoff', true);
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Branding;
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
|
||||
/**
|
||||
* Web page used for displaying the login form
|
||||
*/
|
||||
@@ -139,16 +142,9 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
public function DisplayLoginHeader($bMainAppLogo = false)
|
||||
{
|
||||
$sLogo = 'itop-logo-external.png';
|
||||
$sBrandingLogo = 'login-logo.png';
|
||||
|
||||
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
|
||||
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?t='.utils::GetCacheBusterTimestamp();
|
||||
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
|
||||
{
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?t='.utils::GetCacheBusterTimestamp();
|
||||
}
|
||||
$sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl();
|
||||
$this->add("<div id=\"login-logo\"><a href=\"".htmlentities($sIconUrl, ENT_QUOTES,
|
||||
self::PAGES_CHARSET)."\"><img title=\"$sVersionShort\" src=\"$sDisplayIcon\"></a></div>\n");
|
||||
}
|
||||
@@ -392,11 +388,11 @@ class LoginWebPage extends NiceWebPage
|
||||
public static function ResetSession()
|
||||
{
|
||||
// Unset all of the session variables.
|
||||
unset($_SESSION['auth_user']);
|
||||
unset($_SESSION['login_state']);
|
||||
unset($_SESSION['can_logoff']);
|
||||
unset($_SESSION['archive_mode']);
|
||||
unset($_SESSION['impersonate_user']);
|
||||
Session::Unset('auth_user');
|
||||
Session::Unset('login_state');
|
||||
Session::Unset('can_logoff');
|
||||
Session::Unset('archive_mode');
|
||||
Session::Unset('impersonate_user');
|
||||
UserRights::_ResetSessionCache();
|
||||
// If it's desired to kill the session, also delete the session cookie.
|
||||
// Note: This will destroy the session, and not just the session data!
|
||||
@@ -442,11 +438,11 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
$bLoginDebug = MetaModel::GetConfig()->Get('login_debug');
|
||||
|
||||
if (!isset($_SESSION['login_state']) || ($_SESSION['login_state'] == self::LOGIN_STATE_ERROR))
|
||||
if (Session::Get('login_state') == self::LOGIN_STATE_ERROR)
|
||||
{
|
||||
$_SESSION['login_state'] = self::LOGIN_STATE_START;
|
||||
Session::Set('login_state', self::LOGIN_STATE_START);
|
||||
}
|
||||
$sLoginState = $_SESSION['login_state'];
|
||||
$sLoginState = Session::Get('login_state');
|
||||
|
||||
$sSessionLog = '';
|
||||
if ($bLoginDebug)
|
||||
@@ -487,6 +483,7 @@ class LoginWebPage extends NiceWebPage
|
||||
$iResponse = $oLoginFSMExtensionInstance->LoginAction($sLoginState, $iErrorCode);
|
||||
if ($iResponse == self::LOGIN_FSM_RETURN)
|
||||
{
|
||||
Session::WriteClose();
|
||||
return $iErrorCode; // Asked to exit FSM, generally login OK
|
||||
}
|
||||
if ($iResponse == self::LOGIN_FSM_ERROR)
|
||||
@@ -500,7 +497,7 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
// Every plugin has nothing else to do in this state, go forward
|
||||
$sLoginState = self::AdvanceLoginFSMState($sLoginState);
|
||||
$_SESSION['login_state'] = $sLoginState;
|
||||
Session::Set('login_state', $sLoginState);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
@@ -526,7 +523,7 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
if ($bFilterWithMode)
|
||||
{
|
||||
$sCurrentLoginMode = isset($_SESSION['login_mode']) ? $_SESSION['login_mode'] : '';
|
||||
$sCurrentLoginMode = Session::Get('login_mode', '');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -665,8 +662,8 @@ class LoginWebPage extends NiceWebPage
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$_SESSION['login_mode'] = $sLoginMode;
|
||||
Session::Set('auth_user', $sAuthUser);
|
||||
Session::Set('login_mode', $sLoginMode);
|
||||
UserRights::_InitSessionCache();
|
||||
}
|
||||
|
||||
@@ -681,10 +678,10 @@ class LoginWebPage extends NiceWebPage
|
||||
*/
|
||||
public static function CheckLoggedUser(&$iErrorCode)
|
||||
{
|
||||
if (isset($_SESSION['auth_user']))
|
||||
if (Session::IsSet('auth_user'))
|
||||
{
|
||||
// Already authenticated
|
||||
$bRet = UserRights::Login($_SESSION['auth_user']); // Login & set the user's language
|
||||
$bRet = UserRights::Login(Session::Get('auth_user')); // Login & set the user's language
|
||||
if ($bRet)
|
||||
{
|
||||
$iErrorCode = self::EXIT_CODE_OK;
|
||||
@@ -712,11 +709,11 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
public static function SetLoginModeAndReload($sNewLoginMode)
|
||||
{
|
||||
if (isset($_SESSION['login_mode']) && ($_SESSION['login_mode'] == $sNewLoginMode))
|
||||
if (Session::Get('login_mode') == $sNewLoginMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
$_SESSION['login_mode'] = $sNewLoginMode;
|
||||
Session::Set('login_mode', $sNewLoginMode);
|
||||
self::HTTPReload();
|
||||
}
|
||||
|
||||
@@ -829,9 +826,9 @@ class LoginWebPage extends NiceWebPage
|
||||
{
|
||||
CMDBObject::SetTrackOrigin('custom-extension');
|
||||
$sInfo = 'External User provisioning';
|
||||
if (isset($_SESSION['login_mode']))
|
||||
if (Session::IsSet('login_mode'))
|
||||
{
|
||||
$sInfo .= " ({$_SESSION['login_mode']})";
|
||||
$sInfo .= " (".Session::Get('login_mode').")";
|
||||
}
|
||||
CMDBObject::SetTrackInfo($sInfo);
|
||||
|
||||
@@ -883,9 +880,9 @@ class LoginWebPage extends NiceWebPage
|
||||
{
|
||||
CMDBObject::SetTrackOrigin('custom-extension');
|
||||
$sInfo = 'External User provisioning';
|
||||
if (isset($_SESSION['login_mode']))
|
||||
if (Session::IsSet('login_mode'))
|
||||
{
|
||||
$sInfo .= " ({$_SESSION['login_mode']})";
|
||||
$sInfo .= " (".Session::Get('login_mode').")";
|
||||
}
|
||||
CMDBObject::SetTrackInfo($sInfo);
|
||||
|
||||
@@ -924,9 +921,9 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
// Now synchronize the profiles
|
||||
$sOrigin = 'External User provisioning';
|
||||
if (isset($_SESSION['login_mode']))
|
||||
if (Session::IsSet('login_mode'))
|
||||
{
|
||||
$sOrigin .= " ({$_SESSION['login_mode']})";
|
||||
$sOrigin .= " (".Session::Get('login_mode').")";
|
||||
}
|
||||
$aExistingProfiles = self::SynchronizeProfiles($oUser, $aProfiles, $sOrigin);
|
||||
if ($oUser->IsModified())
|
||||
@@ -1011,7 +1008,6 @@ class LoginWebPage extends NiceWebPage
|
||||
$sMessage = self::HandleOperations($operation); // May exit directly
|
||||
|
||||
$iRet = self::Login($iOnExit);
|
||||
|
||||
if ($iRet == self::EXIT_CODE_OK)
|
||||
{
|
||||
if ($bMustBeAdmin && !UserRights::IsAdministrator())
|
||||
@@ -1091,11 +1087,11 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
else if ($operation == 'change_pwd')
|
||||
{
|
||||
if (isset($_SESSION['auth_user']))
|
||||
if (Session::IsSet('auth_user'))
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
$sIssue = $_SESSION['pwd_issue'] ?? null;
|
||||
unset($_SESSION['pwd_issue']);
|
||||
$sAuthUser = Session::Get('auth_user');
|
||||
$sIssue = Session::Get('pwd_issue');
|
||||
Session::Unset('pwd_issue');
|
||||
$bFailedLogin = ($sIssue != null); // Force the "failed login" flag to display the "issue" message
|
||||
|
||||
UserRights::Login($sAuthUser); // Set the user's language
|
||||
@@ -1107,7 +1103,7 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
else if ($operation == 'check_pwd_policy')
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
$sAuthUser = Session::Get('auth_user');
|
||||
UserRights::Login($sAuthUser); // Set the user's language
|
||||
|
||||
$aPwdMap = array();
|
||||
@@ -1125,9 +1121,9 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
if ($operation == 'do_change_pwd')
|
||||
{
|
||||
if (isset($_SESSION['auth_user']))
|
||||
if (Session::IsSet('auth_user'))
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
$sAuthUser = Session::Get('auth_user');
|
||||
UserRights::Login($sAuthUser); // Set the user's language
|
||||
$sOldPwd = utils::ReadPostedParam('old_pwd', '', 'raw_data');
|
||||
$sNewPwd = utils::ReadPostedParam('new_pwd', '', 'raw_data');
|
||||
|
||||
@@ -77,17 +77,28 @@ function _MaintenanceHtmlMessage($sMessage)
|
||||
*/
|
||||
function _MaintenanceJsonMessage($sTitle, $sMessage)
|
||||
{
|
||||
@include_once(APPROOT."/application/ajaxwebpage.class.inc.php");
|
||||
if (class_exists('ajax_page'))
|
||||
if (class_exists('JsonPage'))
|
||||
{
|
||||
$oP = new ajax_page($sTitle);
|
||||
$oP = new JsonPage($sTitle);
|
||||
$oP->add_header('Access-Control-Allow-Origin: *');
|
||||
$oP->SetContentType('application/json');
|
||||
$oP->add('{"code":100, "message":"'.$sMessage.'"}');
|
||||
|
||||
$aMessage = [
|
||||
'code' => 100,
|
||||
'message' =>$sMessage
|
||||
];
|
||||
|
||||
$oP->AddData($aMessage);
|
||||
$oP->Output();
|
||||
}
|
||||
else
|
||||
{
|
||||
_MaintenanceTextMessage($sMessage);
|
||||
} else {
|
||||
@include_once(APPROOT."/application/ajaxwebpage.class.inc.php");
|
||||
if (class_exists('ajax_page')) {
|
||||
$oP = new ajax_page($sTitle);
|
||||
$oP->add_header('Access-Control-Allow-Origin: *');
|
||||
$oP->SetContentType('application/json');
|
||||
$oP->add('{"code":100, "message":"'.$sMessage.'"}');
|
||||
$oP->Output();
|
||||
} else {
|
||||
_MaintenanceTextMessage($sMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Helper\WebResourcesHelper;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\Title;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
@@ -58,6 +60,10 @@ class ApplicationMenu
|
||||
* @var array
|
||||
*/
|
||||
static $aMenusIndex = array();
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
static $aMenusById = [];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@@ -166,6 +172,7 @@ class ApplicationMenu
|
||||
$aBacktrace = debug_backtrace();
|
||||
$sFile = isset($aBacktrace[2]["file"]) ? $aBacktrace[2]["file"] : $aBacktrace[1]["file"];
|
||||
self::$aMenusIndex[$index] = array('node' => $oMenuNode, 'children' => array(), 'parent' => $sParentId, 'rank' => $fRank, 'source_file' => $sFile);
|
||||
self::$aMenusById[$oMenuNode->GetMenuId()] = $index;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -506,17 +513,11 @@ EOF
|
||||
*/
|
||||
public static function GetMenuIndexById($sTitle)
|
||||
{
|
||||
$index = -1;
|
||||
/** @var MenuNode[] $aMenu */
|
||||
foreach(self::$aMenusIndex as $aMenu)
|
||||
{
|
||||
if ($aMenu['node']->GetMenuId() == $sTitle)
|
||||
{
|
||||
$index = $aMenu['node']->GetIndex();
|
||||
break;
|
||||
}
|
||||
if (isset(self::$aMenusById[$sTitle])) {
|
||||
return self::$aMenusById[$sTitle];
|
||||
}
|
||||
return $index;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -715,9 +716,10 @@ abstract class MenuNode
|
||||
{
|
||||
// Count the entries up to 99
|
||||
$oSearch = DBSearch::FromOQL($sOQL);
|
||||
|
||||
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
DBSearchHelper::AddContextFilter($oSearch);
|
||||
|
||||
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$iCount = $oSet->CountWithLimit(99);
|
||||
if ($iCount > 99) {
|
||||
@@ -1125,16 +1127,20 @@ class OQLMenuNode extends MenuNode
|
||||
{
|
||||
$sUsageId = utils::GetSafeId($sUsageId);
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql);
|
||||
|
||||
$sClass= $oSearch->GetClass();
|
||||
$sIcon = MetaModel::GetClassIcon($sClass, false);
|
||||
if ($bSearchPane) {
|
||||
$aParams = array_merge(['open' => $bSearchOpen, 'table_id' => $sUsageId, 'submit_on_load' => true], $aExtraParams);
|
||||
$aParams = array_merge(['open' => $bSearchOpen, 'table_id' => $sUsageId, 'submit_on_load' => false], $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
|
||||
$oBlock->Display($oPage, 0);
|
||||
$oPage->add("<div class='sf_results_area ibo-add-margin-top-250' data-target='search_results'>");
|
||||
}
|
||||
|
||||
$oPage->add("<div class='sf_results_area' data-target='search_results'>");
|
||||
$oTitle = TitleUIBlockFactory::MakeForPage($sTitle);
|
||||
$oPage->AddUiBlock($oTitle);
|
||||
else {
|
||||
$oPage->add("<div class='sf_results_area' data-target='search_results'>");
|
||||
}
|
||||
$aExtraParams['panel_class'] =$sClass;
|
||||
$aExtraParams['panel_title'] = $sTitle;
|
||||
$aExtraParams['panel_icon'] = $sIcon;
|
||||
|
||||
$aParams = array_merge(array('table_id' => $sUsageId), $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams);
|
||||
@@ -1414,6 +1420,8 @@ class DashboardMenuNode extends MenuNode
|
||||
$oDashboard = $this->GetDashboard();
|
||||
if ($oDashboard != null)
|
||||
{
|
||||
WebResourcesHelper::EnableC3JSToWebPage($oPage);
|
||||
|
||||
$sDivId = utils::Sanitize($this->sMenuId, '', 'element_identifier');
|
||||
$oPage->add('<div id="'.$sDivId.'" class="ibo-dashboard" data-role="ibo-dashboard">');
|
||||
$aExtraParams['dashboard_div_id'] = $sDivId;
|
||||
|
||||
@@ -126,7 +126,7 @@ class QueryOQL extends Query
|
||||
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
|
||||
{
|
||||
$aFieldsMap = parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams);
|
||||
$oPage->add_script("$('[name=\"attr_oql\"]').addClass('ibo-queryoql'); $('[data-attribute-code=\"oql\"]').addClass('ibo-queryoql');");
|
||||
$oPage->add_script("$('[name=\"attr_oql\"]').addClass('ibo-query-oql ibo-is-code'); $('[data-attribute-code=\"oql\"]').addClass('ibo-query-oql ibo-is-code');");
|
||||
|
||||
if (!$bEditMode) {
|
||||
$sFields = trim($this->Get('fields'));
|
||||
|
||||
@@ -288,7 +288,8 @@ class ShortcutOQL extends Shortcut
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
// Note: the title gets deleted by the validation mechanism
|
||||
$("#attr_auto_reload_sec").tooltip({items: 'input', content: '$sRateTitle'});
|
||||
$("#attr_auto_reload_sec").attr('data-tooltip-content', '$sRateTitle');
|
||||
CombodoTooltip.InitTooltipFromMarkup($("#attr_auto_reload_sec"));
|
||||
$("#attr_auto_reload_sec").prop('disabled', !$('#attr_auto_reload').is(':checked'));
|
||||
|
||||
$('#attr_auto_reload').change( function(ev) {
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
|
||||
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
require_once(APPROOT.'/core/contexttag.class.inc.php');
|
||||
require_once(APPROOT.'/core/kpi.class.inc.php');
|
||||
|
||||
|
||||
/**
|
||||
@@ -27,6 +30,9 @@ require_once(APPROOT.'/core/contexttag.class.inc.php');
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
ExecutionKPI::EnableDuration(1);
|
||||
ExecutionKPI::EnableMemory(1);
|
||||
|
||||
// This storage is freed on error (case of allowed memory exhausted)
|
||||
$sReservedMemory = str_repeat('*', 1024 * 1024);
|
||||
register_shutdown_function(function()
|
||||
@@ -35,41 +41,38 @@ register_shutdown_function(function()
|
||||
$sReservedMemory = null;
|
||||
if (!is_null($err = error_get_last()) && ($err['type'] == E_ERROR))
|
||||
{
|
||||
// Remove stack trace from MySQLException
|
||||
// Remove stack trace from MySQLException (since 2.7.2 see N°3174)
|
||||
$sMessage = $err['message'];
|
||||
if (strpos($sMessage, 'MySQLException') !== false)
|
||||
{
|
||||
if (strpos($sMessage, 'MySQLException') !== false) {
|
||||
$iStackTracePos = strpos($sMessage, 'Stack trace:');
|
||||
if ($iStackTracePos !== false)
|
||||
{
|
||||
if ($iStackTracePos !== false) {
|
||||
$sMessage = substr($sMessage, 0, $iStackTracePos);
|
||||
}
|
||||
}
|
||||
IssueLog::error($sMessage, null, $err);
|
||||
if (strpos($err['message'], 'Allowed memory size of') !== false)
|
||||
{
|
||||
// Log additional info but message from $err (since 2.7.6 N°4174)
|
||||
$aErrToLog = $err;
|
||||
unset($aErrToLog['message']);
|
||||
IssueLog::error($sMessage, null, $aErrToLog);
|
||||
if (strpos($err['message'], 'Allowed memory size of') !== false) {
|
||||
$sLimit = ini_get('memory_limit');
|
||||
echo "<p>iTop: Allowed memory size of $sLimit exhausted, contact your administrator to increase 'memory_limit' in php.ini</p>\n";
|
||||
}
|
||||
elseif (strpos($err['message'], 'Maximum execution time') !== false)
|
||||
{
|
||||
} elseif (strpos($err['message'], 'Maximum execution time') !== false) {
|
||||
$sLimit = ini_get('max_execution_time');
|
||||
echo "<p>iTop: Maximum execution time of $sLimit exceeded, contact your administrator to increase 'max_execution_time' in php.ini</p>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
echo "<p>iTop: An error occurred, check server error log for more information.</p>\n";
|
||||
}
|
||||
}
|
||||
});
|
||||
$oKPI = new ExecutionKPI();
|
||||
Session::Start();
|
||||
$oKPI->ComputeAndReport("Session Start");
|
||||
|
||||
session_name('itop-'.md5(APPROOT));
|
||||
session_start();
|
||||
$sSwitchEnv = utils::ReadParam('switch_env', null);
|
||||
$bAllowCache = true;
|
||||
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)) && isset($_SESSION['itop_env']) && ($_SESSION['itop_env'] !== $sSwitchEnv))
|
||||
if (($sSwitchEnv != null) && file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE) &&( Session::Get('itop_env') !== $sSwitchEnv))
|
||||
{
|
||||
$_SESSION['itop_env'] = $sSwitchEnv;
|
||||
Session::Set('itop_env', $sSwitchEnv);
|
||||
$sEnv = $sSwitchEnv;
|
||||
$bAllowCache = false;
|
||||
// Reset the opcache since otherwise the PHP "model" files may still be cached !!
|
||||
@@ -85,14 +88,14 @@ if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FI
|
||||
}
|
||||
// TODO: reset the credentials as well ??
|
||||
}
|
||||
else if (isset($_SESSION['itop_env']))
|
||||
else if (Session::IsSet('itop_env'))
|
||||
{
|
||||
$sEnv = $_SESSION['itop_env'];
|
||||
$sEnv = Session::Get('itop_env');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sEnv = ITOP_DEFAULT_ENV;
|
||||
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
|
||||
Session::Set('itop_env', ITOP_DEFAULT_ENV);
|
||||
}
|
||||
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
|
||||
|
||||
@@ -906,7 +906,8 @@ CSS;
|
||||
|
||||
foreach($aImportsPaths as $sPath)
|
||||
{
|
||||
$sFilePath = $sPath.'/'.$sFileURI;
|
||||
$sAlterableFileURI = $sFileURI;
|
||||
$sFilePath = $sPath.'/'.$sAlterableFileURI;
|
||||
$sImportedFile = realpath($sFilePath);
|
||||
if ($sImportedFile === false){
|
||||
// Handle shortcut syntax : @import "typo" ;
|
||||
@@ -914,7 +915,7 @@ CSS;
|
||||
$sFilePath2 = "$sFilePath.scss";
|
||||
$sImportedFile = realpath($sFilePath2);
|
||||
if ($sImportedFile){
|
||||
self::FindStylesheetFile("$sFileURI.scss", [ $sPath ], $oFindStylesheetObject, $bImports);
|
||||
self::FindStylesheetFile("$sAlterableFileURI.scss", [ $sPath ], $oFindStylesheetObject, $bImports);
|
||||
$sImportedFile = false;
|
||||
}
|
||||
}
|
||||
@@ -924,7 +925,7 @@ CSS;
|
||||
// file matched: _typo.scss
|
||||
$sShortCut = substr($sFilePath, strrpos($sFilePath, '/') + 1);
|
||||
$sFilePath = static::ReplaceLastOccurrence($sShortCut, "_$sShortCut.scss", $sFilePath);
|
||||
$sFileURI = static::ReplaceLastOccurrence($sShortCut, "_$sShortCut.scss", $sFileURI);
|
||||
$sAlterableFileURI = static::ReplaceLastOccurrence($sShortCut, "_$sShortCut.scss", $sAlterableFileURI);
|
||||
$sImportedFile = realpath($sFilePath);
|
||||
}
|
||||
|
||||
@@ -932,14 +933,14 @@ CSS;
|
||||
&& (!$oFindStylesheetObject->AlreadyFetched($sImportedFile)))
|
||||
{
|
||||
if ($bImports){
|
||||
$oFindStylesheetObject->AddImport($sFileURI, $sImportedFile);
|
||||
$oFindStylesheetObject->AddImport($sAlterableFileURI, $sImportedFile);
|
||||
}else{
|
||||
$oFindStylesheetObject->AddStylesheet($sFileURI, $sImportedFile);
|
||||
$oFindStylesheetObject->AddStylesheet($sAlterableFileURI, $sImportedFile);
|
||||
}
|
||||
$oFindStylesheetObject->UpdateLastModified($sImportedFile);
|
||||
|
||||
//Regexp matching on all included scss files : @import 'XXX.scss';
|
||||
$sDirUri = dirname($sFileURI);
|
||||
$sDirUri = dirname($sAlterableFileURI);
|
||||
preg_match_all('/@import \s*[\"\']([^\"\']*)\s*[\"\']\s*;/', file_get_contents($sImportedFile), $aMatches);
|
||||
if ( (is_array($aMatches)) && (count($aMatches)!==0) ){
|
||||
foreach ($aMatches[1] as $sImportedFile){
|
||||
|
||||
@@ -29,7 +29,7 @@ class ThemeHandlerService
|
||||
{
|
||||
}
|
||||
|
||||
public function CompileTheme($sThemeId, $bSetup = false, $sSetupCompilationTimestamp="", $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null){
|
||||
return ThemeHandler::CompileTheme($sThemeId, $bSetup, $sSetupCompilationTimestamp="", $aThemeParameters, $aImportsPaths, $sWorkingPath);
|
||||
public function CompileTheme($sThemeId, $bSetup = false, $sSetupCompilationTimestamp = "", $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null){
|
||||
return ThemeHandler::CompileTheme($sThemeId, $bSetup, $sSetupCompilationTimestamp, $aThemeParameters, $aImportsPaths, $sWorkingPath);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
|
||||
/**
|
||||
* This class records the pending "transactions" corresponding to forms that have not been
|
||||
@@ -26,8 +27,6 @@
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
class privUITransaction
|
||||
{
|
||||
/**
|
||||
@@ -99,9 +98,12 @@ class privUITransaction
|
||||
}
|
||||
|
||||
/**
|
||||
* The original (and by default) mechanism for storing transaction information
|
||||
* as an array in the $_SESSION variable
|
||||
* The original mechanism for storing transaction information as an array in the $_SESSION variable
|
||||
*
|
||||
* Warning, since 2.6.0 the session is regenerated on each login (see PR #20) !
|
||||
* Also, we saw some problems when using memcached as the PHP session implementation (see N°1835)
|
||||
*
|
||||
* @see \Combodo\iTop\Application\Helper\Session
|
||||
*/
|
||||
class privUITransactionSession
|
||||
{
|
||||
@@ -112,15 +114,15 @@ class privUITransactionSession
|
||||
*/
|
||||
public static function GetNewTransactionId()
|
||||
{
|
||||
if (!isset($_SESSION['transactions']))
|
||||
if (!Session::IsSet('transactions'))
|
||||
{
|
||||
$_SESSION['transactions'] = array();
|
||||
Session::Set('transactions', []);
|
||||
}
|
||||
// Strictly speaking, the two lines below should be grouped together
|
||||
// by a critical section
|
||||
// sem_acquire($rSemIdentified);
|
||||
$id = static::GetUserPrefix() . str_replace(array('.', ' '), '', microtime()); //1 + count($_SESSION['transactions']);
|
||||
$_SESSION['transactions'][$id] = true;
|
||||
$id = static::GetUserPrefix() . str_replace(array('.', ' '), '', microtime());
|
||||
Session::Set(['transactions', $id], true);
|
||||
// sem_release($rSemIdentified);
|
||||
|
||||
return (string)$id;
|
||||
@@ -137,17 +139,17 @@ class privUITransactionSession
|
||||
public static function IsTransactionValid($id, $bRemoveTransaction = true)
|
||||
{
|
||||
$bResult = false;
|
||||
if (isset($_SESSION['transactions']))
|
||||
if (Session::IsSet('transactions'))
|
||||
{
|
||||
// Strictly speaking, the eight lines below should be grouped together
|
||||
// inside the same critical section as above
|
||||
// sem_acquire($rSemIdentified);
|
||||
if (isset($_SESSION['transactions'][$id]))
|
||||
if (Session::IsSet(['transactions', $id]))
|
||||
{
|
||||
$bResult = true;
|
||||
if ($bRemoveTransaction)
|
||||
{
|
||||
unset($_SESSION['transactions'][$id]);
|
||||
Session::Unset(['transactions', $id]);
|
||||
}
|
||||
}
|
||||
// sem_release($rSemIdentified);
|
||||
@@ -162,14 +164,14 @@ class privUITransactionSession
|
||||
*/
|
||||
public static function RemoveTransaction($id)
|
||||
{
|
||||
if (isset($_SESSION['transactions']))
|
||||
if (Session::IsSet('transactions'))
|
||||
{
|
||||
// Strictly speaking, the three lines below should be grouped together
|
||||
// inside the same critical section as above
|
||||
// sem_acquire($rSemIdentified);
|
||||
if (isset($_SESSION['transactions'][$id]))
|
||||
if (Session::IsSet(['transactions', $id]))
|
||||
{
|
||||
unset($_SESSION['transactions'][$id]);
|
||||
Session::Unset(['transactions', $id]);
|
||||
}
|
||||
// sem_release($rSemIdentified);
|
||||
}
|
||||
@@ -194,9 +196,35 @@ class privUITransactionSession
|
||||
*/
|
||||
class privUITransactionFile
|
||||
{
|
||||
/** @var int Value to use when no user logged */
|
||||
const UNAUTHENTICATED_USER_ID = -666;
|
||||
|
||||
/**
|
||||
* @return int current user id, or {@see self::UNAUTHENTICATED_USER_ID} if no user logged
|
||||
*
|
||||
* @since 2.6.5 2.7.6 3.0.0 N°4289 method creation
|
||||
*/
|
||||
private static function GetCurrentUserId()
|
||||
{
|
||||
$iCurrentUserId = UserRights::GetConnectedUserId();
|
||||
if ('' === $iCurrentUserId) {
|
||||
$iCurrentUserId = static::UNAUTHENTICATED_USER_ID;
|
||||
}
|
||||
|
||||
return $iCurrentUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new transaction id, store it in the session and return its id
|
||||
*
|
||||
* @param void
|
||||
*
|
||||
* @return int The new transaction identifier
|
||||
*
|
||||
* @throws \SecurityException
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 2.6.5 2.7.6 3.0.0 security hardening + throws SecurityException if no user logged
|
||||
*/
|
||||
public static function GetNewTransactionId()
|
||||
{
|
||||
@@ -213,24 +241,36 @@ class privUITransactionFile
|
||||
throw new Exception('Failed to create the directory "'.APPROOT.'data/transactions". Ajust the rights on the parent directory or let an administrator create the transactions directory and give the web sever enough rights to write into it.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_writable(APPROOT.'data/transactions'))
|
||||
{
|
||||
throw new Exception('The directory "'.APPROOT.'data/transactions" must be writable to the application.');
|
||||
}
|
||||
self::CleanupOldTransactions();
|
||||
$id = basename(tempnam(APPROOT.'data/transactions', static::GetUserPrefix()));
|
||||
self::Info('GetNewTransactionId: Created transaction: '.$id);
|
||||
|
||||
return (string)$id;
|
||||
$iCurrentUserId = static::GetCurrentUserId();
|
||||
|
||||
self::CleanupOldTransactions();
|
||||
|
||||
$sTransactionIdFullPath = tempnam(APPROOT.'data/transactions', static::GetUserPrefix());
|
||||
file_put_contents($sTransactionIdFullPath, $iCurrentUserId, LOCK_EX);
|
||||
|
||||
$sTransactionIdFileName = basename($sTransactionIdFullPath);
|
||||
self::Info('GetNewTransactionId: Created transaction: '.$sTransactionIdFileName);
|
||||
|
||||
return $sTransactionIdFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a transaction is valid or not and (optionally) remove the valid transaction from
|
||||
* the session so that another call to IsTransactionValid for the same transaction id
|
||||
* will return false
|
||||
*
|
||||
* @param int $id Identifier of the transaction, as returned by GetNewTransactionId
|
||||
* @param bool $bRemoveTransaction True if the transaction must be removed
|
||||
*
|
||||
* @return bool True if the transaction is valid, false otherwise
|
||||
*
|
||||
* @since 2.6.5 2.7.6 3.0.0 N°4289 security hardening
|
||||
*/
|
||||
public static function IsTransactionValid($id, $bRemoveTransaction = true)
|
||||
{
|
||||
@@ -244,53 +284,53 @@ class privUITransactionFile
|
||||
|
||||
clearstatcache(true, $sFilepath);
|
||||
$bResult = file_exists($sFilepath);
|
||||
if ($bResult)
|
||||
|
||||
if (false === $bResult) {
|
||||
self::Info("IsTransactionValid: Transaction '$id' not found. Pending transactions:\n".implode("\n", self::GetPendingTransactions()));
|
||||
return false;
|
||||
}
|
||||
|
||||
$iCurrentUserId = static::GetCurrentUserId();
|
||||
$sTransactionIdUserId = file_get_contents($sFilepath);
|
||||
if ($iCurrentUserId != $sTransactionIdUserId) {
|
||||
self::Info("IsTransactionValid: Transaction '$id' not existing for current user. Pending transactions:\n".implode("\n", self::GetPendingTransactions()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($bRemoveTransaction)
|
||||
{
|
||||
if ($bRemoveTransaction)
|
||||
$bResult = @unlink($sFilepath);
|
||||
if (!$bResult)
|
||||
{
|
||||
$bResult = @unlink($sFilepath);
|
||||
if (!$bResult)
|
||||
{
|
||||
self::Error('IsTransactionValid: FAILED to remove transaction '.$id);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::Info('IsTransactionValid: OK. Removed transaction: '.$id);
|
||||
}
|
||||
self::Error('IsTransactionValid: FAILED to remove transaction '.$id);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::Info('IsTransactionValid: OK. Removed transaction: '.$id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self::Info("IsTransactionValid: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions()));
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the transaction specified by its id
|
||||
* @param int $id The Identifier (as returned by GetNewTransactionId) of the transaction to be removed.
|
||||
* @return void
|
||||
* @return bool true if the token can be removed
|
||||
*
|
||||
* @since 2.6.5 2.7.6 3.0.0 N°4289 security hardening
|
||||
*/
|
||||
public static function RemoveTransaction($id)
|
||||
{
|
||||
$bSuccess = true;
|
||||
$sFilepath = APPROOT.'data/transactions/'.$id;
|
||||
clearstatcache(true, $sFilepath);
|
||||
if(!file_exists($sFilepath))
|
||||
{
|
||||
$bSuccess = false;
|
||||
self::Error("RemoveTransaction: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions()));
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
$bResult = static::IsTransactionValid($id, true);
|
||||
if (false === $bResult) {
|
||||
self::Error("RemoveTransaction: Transaction '$id' is invalid. Pending transactions:\n"
|
||||
.implode("\n", self::GetPendingTransactions()));
|
||||
return false;
|
||||
}
|
||||
$bSuccess = @unlink($sFilepath);
|
||||
if (!$bSuccess)
|
||||
{
|
||||
self::Error('RemoveTransaction: FAILED to remove transaction '.$id);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::Info('RemoveTransaction: OK '.$id);
|
||||
}
|
||||
return $bSuccess;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,22 +403,35 @@ class privUITransactionFile
|
||||
{
|
||||
self::Write('Error | '.$sText);
|
||||
}
|
||||
|
||||
|
||||
protected static function IsLogEnabled() {
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
if (is_null($oConfig)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$bLogTransactions = $oConfig->Get('log_transactions');
|
||||
if (true === $bLogTransactions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static function Write($sText)
|
||||
{
|
||||
$bLogEnabled = MetaModel::GetConfig()->Get('log_transactions');
|
||||
if ($bLogEnabled)
|
||||
{
|
||||
if (false === static::IsLogEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hLogFile = @fopen(APPROOT.'log/transactions.log', 'a');
|
||||
if ($hLogFile !== false)
|
||||
{
|
||||
if ($hLogFile !== false) {
|
||||
flock($hLogFile, LOCK_EX);
|
||||
$sDate = date('Y-m-d H:i:s');
|
||||
fwrite($hLogFile, "$sDate | $sText\n");
|
||||
fflush($hLogFile);
|
||||
flock($hLogFile, LOCK_UN);
|
||||
fclose($hLogFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Combodo\iTop;
|
||||
|
||||
use AttributeDate;
|
||||
use AttributeDateTime;
|
||||
use Dict;
|
||||
use Exception;
|
||||
@@ -11,6 +12,13 @@ use Twig_SimpleFilter;
|
||||
use Twig_SimpleFunction;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* Class TwigExtension
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
* @package Combodo\iTop
|
||||
* @deprecated 3.1.0 N°4034
|
||||
*/
|
||||
class TwigExtension
|
||||
{
|
||||
/**
|
||||
@@ -48,6 +56,10 @@ class TwigExtension
|
||||
{
|
||||
return AttributeDateTime::GetFormat()->Format($sDate);
|
||||
}
|
||||
if (preg_match('@^\d\d\d\d-\d\d-\d\d$@', trim($sDate)))
|
||||
{
|
||||
return AttributeDate::GetFormat()->Format($sDate);
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
@@ -80,30 +92,14 @@ class TwigExtension
|
||||
|
||||
// Filter to add itopversion to an url
|
||||
$oTwigEnv->addFilter(new Twig_SimpleFilter('add_itop_version', function ($sUrl) {
|
||||
if (strpos($sUrl, '?') === false)
|
||||
{
|
||||
$sUrl = $sUrl."?itopversion=".ITOP_VERSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = $sUrl."&itopversion=".ITOP_VERSION;
|
||||
}
|
||||
|
||||
$sUrl = utils::AddParameterToUrl($sUrl, 'itopversion', ITOP_VERSION);
|
||||
return $sUrl;
|
||||
}));
|
||||
|
||||
// Filter to add a module's version to an url
|
||||
$oTwigEnv->addFilter(new Twig_SimpleFilter('add_module_version', function ($sUrl, $sModuleName) {
|
||||
$sModuleVersion = utils::GetCompiledModuleVersion($sModuleName);
|
||||
|
||||
if (strpos($sUrl, '?') === false)
|
||||
{
|
||||
$sUrl = $sUrl."?moduleversion=".$sModuleVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = $sUrl."&moduleversion=".$sModuleVersion;
|
||||
}
|
||||
$sUrl = utils::AddParameterToUrl($sUrl, 'moduleversion', $sModuleVersion);
|
||||
|
||||
return $sUrl;
|
||||
}));
|
||||
@@ -123,6 +119,14 @@ class TwigExtension
|
||||
return $oConfig->Get($sParamName);
|
||||
}));
|
||||
|
||||
// Function to get a module setting
|
||||
// Usage in twig: {{ get_module_setting(<MODULE_CODE>, <PROPERTY_CODE> [, <DEFAULT_VALUE>]) }}
|
||||
// since 3.0.0, but see N°4034 for upcoming evolutions in the 3.1
|
||||
$oTwigEnv->addFunction(new Twig_SimpleFunction('get_module_setting', function (string $sModuleCode, string $sPropertyCode, $defaultValue = null) {
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
return $oConfig->GetModuleSetting($sModuleCode, $sPropertyCode, $defaultValue);
|
||||
}));
|
||||
|
||||
// Function to get the URL of a static page in a module
|
||||
// Usage in twig: {{ get_static_page_module_url('itop-my-module', 'path-to-my-page') }}
|
||||
$oTwigEnv->addFunction(new Twig_SimpleFunction('get_static_page_module_url', function($sModuleName, $sPage)
|
||||
@@ -137,4 +141,5 @@ class TwigExtension
|
||||
return utils::GetAbsoluteUrlModulePage($sModuleName, $sPage);
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
|
||||
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
|
||||
/**
|
||||
@@ -57,6 +61,8 @@ class UIExtKeyWidget
|
||||
protected $sAttCode;
|
||||
protected $bSearchMode;
|
||||
|
||||
//public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
|
||||
|
||||
/**
|
||||
* @param \WebPage $oPage
|
||||
* @param string $sAttCode
|
||||
@@ -76,18 +82,13 @@ class UIExtKeyWidget
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 3.0.0 N°3750 new $sInputType parameter
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Add default value for $aArgs for PHP 8.0 compat
|
||||
*/
|
||||
public static function DisplayFromAttCode(
|
||||
$oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '',
|
||||
$aArgs = [], $bSearchMode = false, &$sInputType = ''
|
||||
)
|
||||
{
|
||||
// we will only use key & name, so let's reduce fields loaded !
|
||||
$aAttToLoad = [
|
||||
$sClass => [], // nothing, id and friendlyname are automatically added by the API
|
||||
];
|
||||
$oAllowedValues->OptimizeColumnLoad($aAttToLoad);
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
|
||||
@@ -192,7 +193,7 @@ class UIExtKeyWidget
|
||||
|
||||
$bIsAutocomplete = $oAllowedValues->CountExceeds($iMaxComboLength);
|
||||
$sWrapperCssClass = $bIsAutocomplete ? 'ibo-input-select-autocomplete-wrapper' : 'ibo-input-select-wrapper';
|
||||
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey ibo-input-wrapper ibo-input-select-wrapper--with-buttons $sWrapperCssClass\" data-attcode=\"".$this->sAttCode."\" data-validation=\"untouched\">";
|
||||
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey ibo-input-wrapper ibo-input-select-wrapper--with-buttons $sWrapperCssClass\" data-attcode=\"".$this->sAttCode."\" data-validation=\"untouched\" data-accessibility-selectize-label=\"$sTitle\">";
|
||||
|
||||
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
|
||||
if (!$bIsAutocomplete) {
|
||||
@@ -200,42 +201,36 @@ class UIExtKeyWidget
|
||||
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
|
||||
//$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
|
||||
$aOptions = [];
|
||||
$sDisplayValue = "";
|
||||
|
||||
$aOption = [];
|
||||
$aOption['value'] = "";
|
||||
$aOption['label'] = Dict::S('UI:SelectOne');
|
||||
array_push($aOptions,$aOption);
|
||||
array_push($aOptions, $aOption);
|
||||
|
||||
$oAllowedValues->Rewind();
|
||||
$bAddingValue=false;
|
||||
$sClassAllowed = $oAllowedValues->GetClass();
|
||||
$bAddingValue = false;
|
||||
|
||||
$aComplementAttributeSpec = MetaModel::GetComplementAttributeSpec($oAllowedValues->GetClass());
|
||||
$aComplementAttributeSpec = MetaModel::GetNameSpec($oAllowedValues->GetClass(), FriendlyNameType::COMPLEMENTARY);
|
||||
$sFormatAdditionalField = $aComplementAttributeSpec[0];
|
||||
$aAdditionalField = $aComplementAttributeSpec[1];
|
||||
|
||||
if (count($aAdditionalField)>0)
|
||||
{
|
||||
$bAddingValue=true;
|
||||
if (count($aAdditionalField) > 0) {
|
||||
$bAddingValue = true;
|
||||
}
|
||||
while($oObj = $oAllowedValues->Fetch())
|
||||
{
|
||||
$aOption=[];
|
||||
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sClassAllowed);
|
||||
$bInitValue = false;
|
||||
while ($oObj = $oAllowedValues->Fetch()) {
|
||||
$aOption = [];
|
||||
$aOption['value'] = $oObj->GetKey();
|
||||
$aOption['label'] = $oObj->GetName();//.'<span class=\"object-ref-icon fas fa-eye-slash object-obsolete fa-1x fa-fw\"></span>';
|
||||
$aOption['label'] = $oObj->GetName();
|
||||
$aOption['search_label'] = utils::HtmlEntityDecode($oObj->GetName());
|
||||
|
||||
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true') )
|
||||
{
|
||||
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true')) {
|
||||
// When there is only once choice, select it by default
|
||||
$sDisplayValue=$oObj->GetName();
|
||||
if($value != $oObj->GetKey())
|
||||
{
|
||||
$value=$oObj->GetKey();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((is_array($value) && in_array($oObj->GetKey(), $value)) || ($value == $oObj->GetKey())) {
|
||||
$sDisplayValue = $oObj->GetName();
|
||||
if ($value != $oObj->GetKey()) {
|
||||
$value = $oObj->GetKey();
|
||||
$bInitValue = true;
|
||||
}
|
||||
}
|
||||
if ($oObj->IsObsolete()) {
|
||||
@@ -246,23 +241,36 @@ class UIExtKeyWidget
|
||||
foreach ($aAdditionalField as $sAdditionalField) {
|
||||
array_push($aArguments, $oObj->Get($sAdditionalField));
|
||||
}
|
||||
$aOption['additional_field'] = vsprintf($sFormatAdditionalField, $aArguments);
|
||||
$aOption['additional_field'] = utils::HtmlEntities(vsprintf($sFormatAdditionalField, $aArguments));
|
||||
}
|
||||
if (!empty($sObjectImageAttCode)) {
|
||||
// Try to retrieve image for contact
|
||||
/** @var \ormDocument $oImage */
|
||||
$oImage = $oObj->Get($sObjectImageAttCode);
|
||||
if (!$oImage->IsEmpty()) {
|
||||
$aOption['picture_url'] = $oImage->GetDisplayURL($sClassAllowed, $oObj->GetKey(), $sObjectImageAttCode);
|
||||
$aOption['initials'] = '';
|
||||
} else {
|
||||
$aOption['initials'] = utils::FormatInitialsForMedallion(utils::ToAcronym($oObj->Get('friendlyname')));
|
||||
}
|
||||
}
|
||||
array_push($aOptions, $aOption);
|
||||
}
|
||||
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_DECORATED;
|
||||
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\" tabindex=\"0\"></select>";
|
||||
$sJsonOptions = str_replace('\\', '\\\\', json_encode($aOptions));
|
||||
$sHTMLValue .= "<select class=\"ibo-input-select-placeholder\" title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\" tabindex=\"0\"></select>";
|
||||
$sJsonOptions = str_replace("'", "\'", str_replace('\\', '\\\\', json_encode($aOptions)));
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
|
||||
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
|
||||
oACWidget_{$this->iId}.AddSelectize('$sJsonOptions','$value');
|
||||
$('#$this->iId').on('update', function() { oACWidget_{$this->iId}.Update(); } );
|
||||
$('#$this->iId').on('change', function() { $(this).trigger('extkeychange') } );
|
||||
|
||||
$('#$this->iId').on('change', function() { $(this).trigger('extkeychange'); } );
|
||||
EOF
|
||||
);
|
||||
if ($bInitValue) {
|
||||
$oPage->add_ready_script("$('#$this->iId').one('validate', function() { $(this).trigger('change'); } );");
|
||||
}
|
||||
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\">";
|
||||
}
|
||||
else
|
||||
@@ -527,7 +535,7 @@ JS
|
||||
$sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";
|
||||
}
|
||||
} else {
|
||||
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
||||
$sHTMLValue .= "<select class=\"ibo-input-select-placeholder\" title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
||||
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
|
||||
}
|
||||
|
||||
@@ -599,7 +607,7 @@ EOF
|
||||
|
||||
// the input for the auto-complete
|
||||
$sHTMLValue .= "<input class=\"field_autocomplete ibo-input-select\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
|
||||
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\"><i class=\"fas fa-search\"></i></div></span>";
|
||||
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\"><span class=\"field_input_btn\"><div class=\"mini_button ibo-input-select--action-button\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\"><i class=\"fas fa-search\"></i></div></span></div>";
|
||||
|
||||
// another hidden input to store & pass the object's Id
|
||||
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
|
||||
@@ -693,14 +701,14 @@ JS
|
||||
HTML
|
||||
);
|
||||
|
||||
$sDialogTitle = addslashes($sTitle);
|
||||
$sDialogTitleSanitized = addslashes(utils::HtmlToText($sTitle));
|
||||
$oPage->add_ready_script(<<<JS
|
||||
$('#ac_dlg_{$this->iId}').dialog({
|
||||
width: $(window).width()*0.8,
|
||||
height: $(window).height()*0.8,
|
||||
autoOpen: false,
|
||||
modal: true,
|
||||
title: '$sDialogTitle',
|
||||
title: '$sDialogTitleSanitized',
|
||||
resizeStop: oACWidget_{$this->iId}.UpdateSizes,
|
||||
close: oACWidget_{$this->iId}.OnClose,
|
||||
buttons: [
|
||||
@@ -765,9 +773,11 @@ JS
|
||||
*
|
||||
* @throws CoreException
|
||||
* @throws OQLException
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $oObj for PHP 8.0 compatibility
|
||||
*/
|
||||
public function AutoComplete(
|
||||
WebPage $oP, $sFilter, $oObj = null, $sContains = '', $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null
|
||||
WebPage $oP, $sFilter, $oObj, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null
|
||||
)
|
||||
{
|
||||
if (is_null($sFilter)) {
|
||||
@@ -811,21 +821,24 @@ JS
|
||||
{
|
||||
case static::ENUM_OUTPUT_FORMAT_JSON:
|
||||
|
||||
$aJsonMap = array();
|
||||
foreach ($aValues as $sKey => $aValue)
|
||||
{
|
||||
if ($aValue['additional_field'] != '')
|
||||
{
|
||||
$aJsonMap[] = array('value' => $sKey, 'label' => $aValue['label'], 'obsolescence_flag' => $aValue['obsolescence_flag'], 'additional_field' => $aValue['additional_field']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aJsonMap[] = array('value' => $sKey, 'label' => $aValue['label'], 'obsolescence_flag' => $aValue['obsolescence_flag']);
|
||||
}
|
||||
}
|
||||
$aJsonMap = array();
|
||||
foreach ($aValues as $sKey => $aValue) {
|
||||
$aElt = ['value' => $sKey, 'label' => utils::EscapeHtml($aValue['label']), 'obsolescence_flag' => $aValue['obsolescence_flag']];
|
||||
if ($aValue['additional_field'] != '') {
|
||||
$aElt['additional_field'] = utils::EscapeHtml($aValue['additional_field']);
|
||||
}
|
||||
|
||||
$oP->SetContentType('application/json');
|
||||
$oP->add(json_encode($aJsonMap));
|
||||
if (array_key_exists('initials', $aValue)) {
|
||||
$aElt['initials'] = utils::FormatInitialsForMedallion($aValue['initials']);
|
||||
if (array_key_exists('picture_url', $aValue)) {
|
||||
$aElt['picture_url'] = $aValue['picture_url'];
|
||||
}
|
||||
}
|
||||
$aJsonMap[] = $aElt;
|
||||
}
|
||||
|
||||
$oP->SetContentType('application/json');
|
||||
$oP->add(json_encode($aJsonMap));
|
||||
break;
|
||||
|
||||
case static::ENUM_OUTPUT_FORMAT_CSV:
|
||||
@@ -892,26 +905,17 @@ JS
|
||||
}
|
||||
}
|
||||
|
||||
$sDialogTitle = '';
|
||||
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
|
||||
$oPage->add('<form>');
|
||||
|
||||
$sClassLabel = MetaModel::GetName($this->sTargetClass);
|
||||
$oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel));
|
||||
$oPage->add('<nobr><select name="class">');
|
||||
asort($aPossibleClasses);
|
||||
foreach($aPossibleClasses as $sClassName => $sClassLabel)
|
||||
{
|
||||
$oPage->add("<option value=\"$sClassName\">$sClassLabel</option>");
|
||||
}
|
||||
$oPage->add('</select>');
|
||||
$oPage->add(' <button type="submit" class="action" style="margin-top:15px;"><span>' . Dict::S('UI:Button:Ok') . '</span></button></nobr></p>');
|
||||
|
||||
$oPage->add('</form>');
|
||||
$oPage->add('</div></div></div>');
|
||||
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
|
||||
$oPage->add_ready_script("$('#dcr_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
|
||||
$sDialogTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);;
|
||||
$oBlock = UIContentBlockUIBlockFactory::MakeStandard('ac_create_'.$this->iId,['ibo-is-visible']);
|
||||
$oPage->AddSubBlock($oBlock);
|
||||
$oClassForm = FormUIBlockFactory::MakeStandard();
|
||||
$oBlock->AddSubBlock($oClassForm);
|
||||
$oClassForm->AddSubBlock(cmdbAbstractObject::DisplayBlockSelectClassToCreate( $sClassLabel, $this->sTargetClass, $aPossibleClasses));
|
||||
$sDialogTitleEscaped = addslashes($sDialogTitle);
|
||||
$oPage->add_ready_script("$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitleEscaped'});\n");
|
||||
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').removeAttr('onsubmit');");
|
||||
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1003,16 +1007,46 @@ JS
|
||||
|
||||
if ($bHasChildLeafs)
|
||||
{
|
||||
$oPage->add('<div class="treecontrol" id="treecontrolid"><a href="?#">'.Dict::S("UI:Treeview:CollapseAll").'</a> | <a href="?#">'.Dict::S("UI:Treeview:ExpandAll").'</a></div>');
|
||||
$oPage->add('<span class="treecontrol ibo-button-group" id="treecontrolid"><a class="ibo-button ibo-is-regular ibo-is-neutral" href="?#">'.Dict::S("UI:Treeview:CollapseAll").'</a><a class="ibo-button ibo-is-regular ibo-is-neutral" href="?#">'.Dict::S("UI:Treeview:ExpandAll").'</a></span>');
|
||||
}
|
||||
|
||||
$oPage->add("<input type=\"button\" class=\"ibo-button ibo-is-regular ibo-is-neutral\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_tree_{$this->iId}').dialog('close');\"> ");
|
||||
$oPage->add("<input type=\"button\" class=\"ibo-button ibo-is-regular ibo-is-primary\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoHKOk();\">");
|
||||
|
||||
|
||||
$oPage->add('</div></div>');
|
||||
|
||||
|
||||
$sOkButtonLabel = Dict::S('UI:Button:Ok');
|
||||
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
|
||||
$oPage->add_ready_script("\$('#tree_$this->iId ul').treeview({ control: '#treecontrolid', persist: 'false'});\n");
|
||||
$oPage->add_ready_script("\$('#dlg_tree_$this->iId').dialog({ width: 'auto', height: 'auto', autoOpen: true, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.OnHKResize, close: oACWidget_{$this->iId}.OnHKClose });\n");
|
||||
$oPage->add_ready_script(<<<JS
|
||||
$('#dlg_tree_$this->iId').dialog({
|
||||
width: 'auto',
|
||||
height: 'auto',
|
||||
autoOpen: true,
|
||||
modal: true,
|
||||
title: '$sDialogTitle',
|
||||
open: function() {
|
||||
$(this).css("max-height", parseInt($(window).height()*0.7)+'px');
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: "$sCancelButtonLabel",
|
||||
click: function() { $(this).dialog( "close" ); }
|
||||
},
|
||||
{
|
||||
text: "$sOkButtonLabel",
|
||||
class: "ibo-is-primary",
|
||||
click: function() {
|
||||
oACWidget_{$this->iId}.DoHKOk();
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
resizeStop: oACWidget_{$this->iId}.OnHKResize,
|
||||
close: oACWidget_{$this->iId}.OnHKClose
|
||||
});
|
||||
|
||||
$('#dlg_tree_$this->iId + .ui-dialog-buttonpane .ui-dialog-buttonset').prepend($('#treecontrolid'));
|
||||
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
use Combodo\iTop\Application\Helper\WebResourcesHelper;
|
||||
|
||||
/**
|
||||
* Class UIHTMLEditorWidget
|
||||
@@ -64,7 +65,7 @@ class UIHTMLEditorWidget
|
||||
$sHelpText = $this->m_sHelpText;
|
||||
$sValidationField = $this->m_sValidationField;
|
||||
|
||||
$sHtmlValue = "<div class=\"field_input_zone field_input_html\"><textarea class=\"htmlEditor\" title=\"$sHelpText\" name=\"attr_{$this->m_sFieldPrefix}{$sCode}\" rows=\"10\" cols=\"10\" id=\"$iId\">$sValue</textarea></div>$sValidationField";
|
||||
$sHtmlValue = "<div class=\"field_input_zone field_input_html ibo-input-wrapper\"><textarea class=\"htmlEditor ibo-input-richtext-placeholder\" title=\"$sHelpText\" name=\"attr_{$this->m_sFieldPrefix}{$sCode}\" id=\"$iId\">$sValue</textarea></div>$sValidationField";
|
||||
|
||||
// Replace the text area with CKEditor
|
||||
// To change the default settings of the editor,
|
||||
@@ -83,6 +84,7 @@ class UIHTMLEditorWidget
|
||||
}
|
||||
$sConfigJS = json_encode($aConfig);
|
||||
|
||||
WebResourcesHelper::EnableCKEditorToWebPage($oPage);
|
||||
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
|
||||
|
||||
// Please read...
|
||||
|
||||
@@ -75,9 +75,15 @@ class UILinksWidgetDirect
|
||||
* @param array $aArgs
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (handling wrong values at method start)
|
||||
*/
|
||||
public function Display(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
|
||||
public function Display(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj)
|
||||
{
|
||||
if (empty($aArgs)) {
|
||||
$aArgs = [];
|
||||
}
|
||||
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
switch($oLinksetDef->GetEditMode())
|
||||
{
|
||||
@@ -127,8 +133,10 @@ class UILinksWidgetDirect
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param bool $bDisplayMenu
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (protected method, always called with default value)
|
||||
*/
|
||||
protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
{
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$sTargetClass = $oLinksetDef->GetLinkedClass();
|
||||
@@ -197,7 +205,6 @@ class UILinksWidgetDirect
|
||||
|
||||
if ($sRealClass != '')
|
||||
{
|
||||
$oPage->add("<h1>".MetaModel::GetClassIcon($sRealClass)." ".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($sRealClass))."</h1>\n");
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
|
||||
$aFieldFlags = array( $sExtKeyToMe => OPT_ATT_HIDDEN);
|
||||
@@ -229,8 +236,10 @@ class UILinksWidgetDirect
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param array $aButtons
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (protected method, caller already handles it)
|
||||
*/
|
||||
protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
{
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
$oValue->Rewind();
|
||||
@@ -297,7 +306,7 @@ class UILinksWidgetDirect
|
||||
}
|
||||
$oHiddenCriteria = $oHiddenFilter->GetCriteria();
|
||||
$aArgs = $oHiddenFilter->GetInternalParams();
|
||||
$sHiddenCriteria = $oHiddenCriteria->Render($aArgs);
|
||||
$sHiddenCriteria = $oHiddenCriteria->RenderExpression(false, $aArgs);
|
||||
|
||||
$oLinkSetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$valuesDef = $oLinkSetDef->GetValuesDef();
|
||||
@@ -474,6 +483,33 @@ HTML
|
||||
}
|
||||
return $oPage->GetTableRow($aRow, $aAttribs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param $sRealClass
|
||||
* @param $aValues
|
||||
* @param int $iTempId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetFormRow($oPage, $sRealClass, $aValues, $iTempId)
|
||||
{
|
||||
if ($sRealClass == '')
|
||||
{
|
||||
$sRealClass = $this->sLinkedClass;
|
||||
}
|
||||
$oLinkObj = new $sRealClass();
|
||||
$oLinkObj->UpdateObjectFromPostedForm($this->sInputid);
|
||||
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
$aRow = array();
|
||||
$aRow[] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.($iTempId).'"/>';
|
||||
foreach($this->aZlist as $sLinkedAttCode)
|
||||
{
|
||||
$aRow[] = $oLinkObj->GetAsHTML($sLinkedAttCode);
|
||||
}
|
||||
return $aRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context
|
||||
|
||||
@@ -375,6 +375,7 @@ JS
|
||||
|
||||
$oValue->Rewind();
|
||||
$aForm = array();
|
||||
$iMaxAddedId = 0;
|
||||
$iAddedId = -1; // Unique id for new links
|
||||
while ($oCurrentLink = $oValue->Fetch())
|
||||
{
|
||||
@@ -395,8 +396,12 @@ JS
|
||||
} else {
|
||||
$key = $oCurrentLink->GetKey();
|
||||
}
|
||||
|
||||
$iMaxAddedId = max($iMaxAddedId, $key);
|
||||
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly);
|
||||
}
|
||||
$oBlock->iMaxAddedId = (int) $iMaxAddedId;
|
||||
|
||||
$oDataTable = DataTableUIBlockFactory::MakeForForm("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aForm);
|
||||
$oDataTable->SetOptions(['select_mode' => 'custom']);
|
||||
$oBlock->AddSubBlock($oDataTable);
|
||||
|
||||
@@ -49,6 +49,8 @@ class UIPasswordWidget
|
||||
*/
|
||||
public function Display(WebPage $oPage, $aArgs = array())
|
||||
{
|
||||
$oPage->add_dict_entry('UI:Component:Input:Password:DoesNotMatch');
|
||||
|
||||
$sCode = $this->sAttCode.$this->sNameSuffix;
|
||||
$iWidgetIndex = self::$iWidgetIndex;
|
||||
|
||||
@@ -57,11 +59,12 @@ class UIPasswordWidget
|
||||
$sConfirmPasswordValue = $aPasswordValues ? $aPasswordValues['confirm'] : '*****';
|
||||
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
|
||||
$sHtmlValue = '';
|
||||
$sHtmlValue .= '<div class="field_input_zone field_input_onewaypassword">';
|
||||
$sHtmlValue .= '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/>';
|
||||
$sHtmlValue .= '<span>'.Dict::S('UI:PasswordConfirm').'</span>';
|
||||
$sHtmlValue .= '<input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
|
||||
$sHtmlValue .= '<div class="field_input_zone field_input_onewaypassword ibo-input-wrapper ibo-input-one-way-password-wrapper">';
|
||||
$sHtmlValue .= '<input class="ibo-input" type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$sHtmlValue .= '<div class="ibo-input-wrapper ibo-input-wrapper--with-buttons"><input class="ibo-input" type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/>';
|
||||
$sHtmlValue .= '<div class="ibo-input-select--action-buttons"><div class="ibo-input-select--action-button ibo-input-select--action-button--create" data-tooltip-content="'.Dict::S('UI:PasswordConfirm').'"><i class="fas fa-question-circle"></i></div></div></div>';
|
||||
$sHtmlValue .= '<button id="'.$this->iId.'_reset" class="ibo-button ibo-is-regular ibo-is-neutral" onClick="ResetPwd(\''.$this->iId.'\');">';
|
||||
$sHtmlValue .= '<span class="ibo-button--icon fas fa-undo"></span><span class="ibo-button--label">'.Dict::S('UI:Button:ResetPassword').'</span></button>';
|
||||
$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'[changed]" value="'.$sChangedValue.'"/>';
|
||||
$sHtmlValue .= '</div>';
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\Application\UI\Base\iUIBlock;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
||||
use ScssPhp\ScssPhp\Compiler;
|
||||
@@ -239,9 +240,9 @@ class utils
|
||||
|
||||
public static function InitArchiveMode()
|
||||
{
|
||||
if (isset($_SESSION['archive_mode']))
|
||||
if (Session::IsSet('archive_mode'))
|
||||
{
|
||||
$iDefault = $_SESSION['archive_mode'];
|
||||
$iDefault = Session::Get('archive_mode');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -249,9 +250,9 @@ class utils
|
||||
}
|
||||
// Read and record the value for switching the archive mode
|
||||
$iCurrent = self::ReadParam('with-archive', $iDefault);
|
||||
if (isset($_SESSION))
|
||||
if (Session::IsInitialized())
|
||||
{
|
||||
$_SESSION['archive_mode'] = $iCurrent;
|
||||
Session::Set('archive_mode', $iCurrent);
|
||||
}
|
||||
// Read and use the value for the current page (web services)
|
||||
$iCurrent = self::ReadParam('with_archive', $iCurrent, true);
|
||||
@@ -860,6 +861,8 @@ class utils
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $bForceGetFromDisk if true then will always read from disk without using instances in memory
|
||||
*
|
||||
* @return \Config Get object in the following order :
|
||||
* <ol>
|
||||
* <li>from {@link MetaModel::GetConfig} if loaded
|
||||
@@ -872,25 +875,26 @@ class utils
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @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
|
||||
* @since 3.0.0 N°4158 new $bReadFromDisk parameter
|
||||
*/
|
||||
public static function GetConfig()
|
||||
public static function GetConfig($bForceGetFromDisk = false)
|
||||
{
|
||||
$oMetaModelConfig = MetaModel::GetConfig();
|
||||
if ($oMetaModelConfig !== null)
|
||||
{
|
||||
return $oMetaModelConfig;
|
||||
}
|
||||
if (!$bForceGetFromDisk) {
|
||||
$oMetaModelConfig = MetaModel::GetConfig();
|
||||
if ($oMetaModelConfig !== null) {
|
||||
return $oMetaModelConfig;
|
||||
}
|
||||
|
||||
if (self::$oConfig !== null)
|
||||
{
|
||||
return self::$oConfig;
|
||||
if (self::$oConfig !== null) {
|
||||
return self::$oConfig;
|
||||
}
|
||||
}
|
||||
|
||||
$sCurrentEnvConfigPath = self::GetConfigFilePath();
|
||||
if (file_exists($sCurrentEnvConfigPath))
|
||||
{
|
||||
if (file_exists($sCurrentEnvConfigPath)) {
|
||||
$oCurrentEnvDiskConfig = new Config($sCurrentEnvConfigPath);
|
||||
self::SetConfig($oCurrentEnvDiskConfig);
|
||||
|
||||
return self::$oConfig;
|
||||
}
|
||||
|
||||
@@ -1210,7 +1214,7 @@ class utils
|
||||
*/
|
||||
static function CanLogOff()
|
||||
{
|
||||
return (isset($_SESSION['can_logoff']) ? $_SESSION['can_logoff'] : false);
|
||||
return Session::Get('can_logoff', false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1219,7 +1223,7 @@ class utils
|
||||
*/
|
||||
public static function GetSessionLog()
|
||||
{
|
||||
return print_r($_SESSION, true);
|
||||
return Session::GetLog();
|
||||
}
|
||||
|
||||
static function DebugBacktrace($iLimit = 5)
|
||||
@@ -1312,14 +1316,17 @@ class utils
|
||||
*/
|
||||
public static function GetCurrentEnvironment()
|
||||
{
|
||||
if (isset($_SESSION['itop_env']))
|
||||
{
|
||||
return $_SESSION['itop_env'];
|
||||
}
|
||||
else
|
||||
{
|
||||
return ITOP_DEFAULT_ENV;
|
||||
}
|
||||
return Session::Get('itop_env', ITOP_DEFAULT_ENV);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Absolute path to the folder into which the current environment has been compiled.
|
||||
* The corresponding folder is created or cleaned upon code compilation
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetCompiledEnvironmentPath(): string
|
||||
{
|
||||
return APPROOT . 'env-' . MetaModel::GetEnvironment() . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1330,6 +1337,7 @@ class utils
|
||||
{
|
||||
return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string A path to a folder into which any module can store log
|
||||
* @since 2.7.0
|
||||
@@ -1922,7 +1930,7 @@ class utils
|
||||
public static function CompileCSSFromSASS($sSassContent, $aImportPaths = array(), $aVariables = array())
|
||||
{
|
||||
$oSass = new Compiler();
|
||||
$oSass->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Expanded');
|
||||
$oSass->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Compressed');
|
||||
// Setting our variables
|
||||
$oSass->setVariables($aVariables);
|
||||
// Setting our imports paths
|
||||
@@ -2110,11 +2118,21 @@ class utils
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative (to MODULESROOT) path of the root directory of the module containing the file where the call to
|
||||
* this function is made
|
||||
* or an empty string if no such module is found (or not called within a module file)
|
||||
* @param number $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
|
||||
* @return string
|
||||
* **Warning** : returned result can be invalid as we're using backtrace to find the module dir name
|
||||
*
|
||||
* @param int $iCallDepth The depth of the module in the callstack. Zero when called directly from within the module
|
||||
*
|
||||
* @return string the relative (to MODULESROOT) path of the root directory of the module containing the file where the call to
|
||||
* this function is made
|
||||
* or an empty string if no such module is found (or not called within a module file)
|
||||
*
|
||||
* @uses \debug_backtrace()
|
||||
*
|
||||
* @since 3.0.0 Before writing model.*.php file, compiler will now always delete it.
|
||||
* If you have symlinks enabled, base dir will be original module dir, but since this behavior change this won't be true anymore for model.*.php
|
||||
* In consequence the backtrace analysis won't be possible for this file
|
||||
* See N°4854
|
||||
* @link https://www.itophub.io/wiki/page?id=3_0_0%3Arelease%3A3_0_whats_new#compiler_always_generate_new_model_php compiler behavior change documentation
|
||||
*/
|
||||
public static function GetCurrentModuleDir($iCallDepth)
|
||||
{
|
||||
@@ -2139,9 +2157,14 @@ class utils
|
||||
}
|
||||
|
||||
/**
|
||||
* **Warning** : as this method uses {@see GetCurrentModuleDir} it produces hazardous results.
|
||||
* You should better uses directly {@see GetAbsoluteUrlModulesRoot} and add the module dir name yourself ! See N°4573
|
||||
*
|
||||
* @return string the base URL for all files in the current module from which this method is called
|
||||
* or an empty string if no such module is found (or not called within a module file)
|
||||
* @throws \Exception
|
||||
*
|
||||
* @uses GetCurrentModuleDir
|
||||
*/
|
||||
public static function GetCurrentModuleUrl()
|
||||
{
|
||||
@@ -2396,43 +2419,19 @@ class utils
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string eg : '2_7_0' ITOP_VERSION is '2.7.1-dev'
|
||||
* @return string eg : '2_7_0' if iTop core version is '2.7.5-2'
|
||||
* @throws \ApplicationException if constant value is invalid
|
||||
* @uses ITOP_CORE_VERSION
|
||||
*/
|
||||
public static function GetItopVersionWikiSyntax() {
|
||||
$sMinorVersion = self::GetItopMinorVersion();
|
||||
public static function GetItopVersionWikiSyntax($sItopVersion = ITOP_CORE_VERSION)
|
||||
{
|
||||
$aExplodedVersion = explode('.', $sItopVersion);
|
||||
|
||||
return str_replace('.', '_', $sMinorVersion).'_0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sPatchVersion if non provided, will call GetItopPatchVersion
|
||||
*
|
||||
* @return string eg 2.7 if ITOP_VERSION is '2.7.0-dev'
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function GetItopMinorVersion($sPatchVersion = null) {
|
||||
if (is_null($sPatchVersion)) {
|
||||
$sPatchVersion = self::GetItopPatchVersion();
|
||||
}
|
||||
$aExplodedVersion = explode('.', $sPatchVersion);
|
||||
|
||||
if (count($aExplodedVersion) < 2) {
|
||||
throw new Exception('iTop version is wrongfully configured!');
|
||||
}
|
||||
if (($aExplodedVersion[0] == '') || ($aExplodedVersion[1] == '')) {
|
||||
throw new Exception('iTop version is wrongfully configured!');
|
||||
if ((false === isset($aExplodedVersion[0])) || (false === isset($aExplodedVersion[1]))) {
|
||||
throw new ApplicationException('iTop version is wrongfully configured!');
|
||||
}
|
||||
|
||||
return sprintf('%d.%d', $aExplodedVersion[0], $aExplodedVersion[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string eg '2.7.0' if ITOP_VERSION is '2.7.0-dev'
|
||||
*/
|
||||
public static function GetItopPatchVersion() {
|
||||
$aExplodedVersion = explode('-', ITOP_VERSION);
|
||||
|
||||
return $aExplodedVersion[0];
|
||||
return "{$aExplodedVersion[0]}_{$aExplodedVersion[1]}_0";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2630,7 +2629,9 @@ class utils
|
||||
$aDefaultConf = array(
|
||||
'language'=> $sLanguage,
|
||||
'contentsLanguage' => $sLanguage,
|
||||
'extraPlugins' => 'disabler,codesnippet,mentions,objectshortcut',
|
||||
'extraPlugins' => 'disabler,codesnippet,mentions,objectshortcut,font,uploadimage',
|
||||
'uploadUrl' => utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php',
|
||||
'contentsCss' => array(utils::GetAbsoluteUrlAppRoot().'js/ckeditor/contents.css', utils::GetAbsoluteUrlAppRoot().'css/ckeditor/contents.css'),
|
||||
);
|
||||
|
||||
// Mentions
|
||||
@@ -2652,11 +2653,11 @@ class utils
|
||||
}
|
||||
|
||||
// Note: Endpoints are defaults only and should be overloaded by other GUIs such as the end-users portal
|
||||
$sMentionEndpoint = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=cke_mentions&marker='.$sMentionMarker.'&needle={encodedQuery}';
|
||||
$sMentionEndpoint = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=cke_mentions&marker='.urlencode($sMentionMarker).'&needle={encodedQuery}';
|
||||
$sMentionItemUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class='.$sMentionClass.'&id={id}';
|
||||
|
||||
$sMentionItemPictureTemplate = (empty(MetaModel::GetImageAttributeCode($sMentionClass))) ? '' : <<<HTML
|
||||
<span class="ibo-vendors-ckeditor--autocomplete-item-image" style="background-image: url('{picture_url}');">{initials}</span>
|
||||
<span class="ibo-vendors-ckeditor--autocomplete-item-image" style="{picture_style}">{initials}</span>
|
||||
HTML;
|
||||
$sMentionItemTemplate = <<<HTML
|
||||
<li class="ibo-vendors-ckeditor--autocomplete-item" data-id="{id}">{$sMentionItemPictureTemplate}<span class="ibo-vendors-ckeditor--autocomplete-item-title">{friendlyname}</span></li>
|
||||
@@ -2823,20 +2824,19 @@ HTML;
|
||||
//----------------------------------------------
|
||||
|
||||
/**
|
||||
* Check if iTop is in a development environment (VCS vs build number)
|
||||
* Check if iTop is in a development environment
|
||||
*
|
||||
* @return bool
|
||||
* @return bool true if development environment
|
||||
*
|
||||
* @since 2.6.0 method creation
|
||||
* @since 3.0.0 add the `developer_mode.enabled` config parameter
|
||||
*
|
||||
* @use `developer_mode.enabled` config parameter
|
||||
* @use ITOP_REVISION
|
||||
* @uses GetDeveloperModeParam
|
||||
* @uses ITOP_REVISION constant (check 'svn' value)
|
||||
*/
|
||||
public static function IsDevelopmentEnvironment()
|
||||
{
|
||||
$oConfig = utils::GetConfig();
|
||||
$bIsDevEnvInConfig = $oConfig->Get('developer_mode.enabled');
|
||||
$bIsDevEnvInConfig = static::GetDeveloperModeParam();
|
||||
if ($bIsDevEnvInConfig === true) {
|
||||
return true;
|
||||
}
|
||||
@@ -2854,7 +2854,36 @@ HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool : indicate whether we run under a windows environnement or not
|
||||
* In the setup there are times when the MetaModel config attribute is loaded but partially (only setup parameters are set, others have the default value)
|
||||
* So we need to load from disk then !
|
||||
*
|
||||
* But in other scenario we want to read from memory : for example when changing the option in a PHPUnit setUp method
|
||||
*
|
||||
* This method will first try to get the `developer_mode.enabled` config parameter the standard way (call to GetConfig without modification).
|
||||
* If we are getting null (not defined parameter), then we will load config from disk only (GetConfig(true))
|
||||
*
|
||||
* @return bool|null
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @uses developer_mode.enabled config parameter
|
||||
*/
|
||||
private static function GetDeveloperModeParam(): ?bool
|
||||
{
|
||||
$oConfig = static::GetConfig(false);
|
||||
$bIsDevEnvInConfig = $oConfig->Get('developer_mode.enabled');
|
||||
|
||||
if (!is_null($bIsDevEnvInConfig)) {
|
||||
return $bIsDevEnvInConfig;
|
||||
}
|
||||
|
||||
$oConfigFromDisk = static::GetConfig(true);
|
||||
|
||||
return $oConfigFromDisk->Get('developer_mode.enabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool true if we are running under a Windows environment
|
||||
* @since 2.7.4 : N°3412
|
||||
*/
|
||||
public static function IsWindowsEnvironment()
|
||||
@@ -3020,4 +3049,40 @@ HTML;
|
||||
|
||||
return $aMentionedObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: This method is not ideal, but other solutions seemed even less ideal:
|
||||
* * Add a "$sMaxLength" param. to utils::ToAcronym(): Does not work for every use cases (see corresponding ticket) as in some parts utils::ToAcronym isn't necessarly meant to be used in a medallion.
|
||||
*
|
||||
* @param string $sInitials
|
||||
*
|
||||
* @return string Truncates $sInitials so it can fit in medallions
|
||||
* @since 3.0.1 N°4913
|
||||
*/
|
||||
public static function FormatInitialsForMedallion(string $sInitials): string
|
||||
{
|
||||
return mb_substr($sInitials, 0, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sUrl
|
||||
* @param string $sParamName
|
||||
* @param string $sParamValue
|
||||
*
|
||||
* @return string
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function AddParameterToUrl(string $sUrl, string $sParamName, string $sParamValue): string
|
||||
{
|
||||
if (strpos($sUrl, '?') === false)
|
||||
{
|
||||
$sUrl = $sUrl.'?'.urlencode($sParamName).'='.urlencode($sParamValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = $sUrl.'&'.urlencode($sParamName).'='.urlencode($sParamValue);
|
||||
}
|
||||
|
||||
return $sUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ class WizardHelper
|
||||
*/
|
||||
public function GetJsForUpdateFields()
|
||||
{
|
||||
$sWizardHelperJsVar = ($this->m_aData['m_sWizHelperJsVarName']) ?? 'oWizardHelper'.$this->GetFormPrefix();
|
||||
$sWizardHelperJsVar = (!is_null($this->m_aData['m_sWizHelperJsVarName'])) ? utils::Sanitize($this->m_aData['m_sWizHelperJsVarName'], '', utils::ENUM_SANITIZATION_FILTER_PARAMETER) : 'oWizardHelper'.$this->GetFormPrefix();
|
||||
$sWizardHelperJson = $this->ToJSON();
|
||||
|
||||
return <<<JS
|
||||
|
||||
@@ -3,4 +3,26 @@
|
||||
define('APPROOT', dirname(__FILE__).'/');
|
||||
define('APPCONF', APPROOT.'conf/');
|
||||
|
||||
/**
|
||||
* iTop Datamodel XML format version
|
||||
*
|
||||
* It was also used in iTop 3.0.0 to get iTop core version (instead of {@see ITOP_VERSION} which gives the application version).
|
||||
* To address this need you should now use {@see ITOP_CORE_VERSION}
|
||||
*
|
||||
* @see ITOP_CORE_VERSION to get full iTop core version
|
||||
*/
|
||||
define('ITOP_DESIGN_LATEST_VERSION', '3.0');
|
||||
|
||||
/**
|
||||
* Constant containing the iTop core version, whatever application was built
|
||||
*
|
||||
* Note that in iTop 3.0.0 we used {@see ITOP_DESIGN_LATEST_VERSION} to get core version.
|
||||
* When releasing, both constants should be updated : see `.make/release/update-versions.php` for that !
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°4714 constant creation
|
||||
* @used-by utils::GetItopVersionWikiSyntax()
|
||||
* @used-by iTopModulesPhpVersionIntegrationTest
|
||||
*/
|
||||
define('ITOP_CORE_VERSION', '3.0.1');
|
||||
|
||||
require_once APPROOT.'bootstrap.inc.php';
|
||||
|
||||
@@ -44,11 +44,7 @@ define('ITOP_DEFAULT_ENV', 'production');
|
||||
define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
|
||||
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
|
||||
|
||||
if (function_exists('microtime')) {
|
||||
$fItopStarted = microtime(true);
|
||||
} else {
|
||||
$fItopStarted = 1000 * time();
|
||||
}
|
||||
$fItopStarted = microtime(true);
|
||||
|
||||
if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) {
|
||||
require_once APPROOT.'/lib/autoload.php';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"type": "project",
|
||||
"license": "AGPLv3",
|
||||
"require": {
|
||||
"php": ">=7.1.3",
|
||||
"php": ">=7.1.3 <8.0.0",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
@@ -12,20 +12,20 @@
|
||||
"ext-soap": "*",
|
||||
"combodo/tcpdf": "6.3.5",
|
||||
"nikic/php-parser": "^4.12.0",
|
||||
"pear/archive_tar": "1.4.13",
|
||||
"pelago/emogrifier": "2.1.0",
|
||||
"pear/archive_tar": "1.4.14",
|
||||
"pelago/emogrifier": "3.1.0",
|
||||
"scssphp/scssphp": "1.0.6",
|
||||
"swiftmailer/swiftmailer": "5.4.12",
|
||||
"symfony/console": "3.4.*",
|
||||
"symfony/dotenv": "3.4.*",
|
||||
"symfony/framework-bundle": "3.4.*",
|
||||
"symfony/console": "~3.4.47",
|
||||
"symfony/dotenv": "~3.4.47",
|
||||
"symfony/framework-bundle": "~3.4.47",
|
||||
"symfony/polyfill-php70": "1.*",
|
||||
"symfony/twig-bundle": "3.4.*",
|
||||
"symfony/yaml": "3.4.*"
|
||||
"symfony/twig-bundle": "~3.4.47",
|
||||
"symfony/yaml": "~3.4.47"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/stopwatch": "3.4.*",
|
||||
"symfony/web-profiler-bundle": "3.4.*"
|
||||
"symfony/stopwatch": "~3.4.47",
|
||||
"symfony/web-profiler-bundle": "~3.4.47"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Required to use the AttributeEncryptedString.",
|
||||
@@ -37,14 +37,15 @@
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.2.0"
|
||||
"php": "7.1.3"
|
||||
},
|
||||
"vendor-dir": "lib",
|
||||
"preferred-install": {
|
||||
"*": "dist"
|
||||
},
|
||||
"sort-packages": true,
|
||||
"classmap-authoritative": true
|
||||
"classmap-authoritative": true,
|
||||
"platform-check": true
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
|
||||
971
composer.lock
generated
971
composer.lock
generated
File diff suppressed because it is too large
Load Diff
67
core/DbConnectionWrapper.php
Normal file
67
core/DbConnectionWrapper.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Core;
|
||||
|
||||
use mysqli;
|
||||
|
||||
/**
|
||||
* mysqli object is really hard to mock as it contains lots of attributes & methods ! Thought we need to mock it to test transactions !
|
||||
*
|
||||
* To solve this, a new attribute exists and is only used in specific use cases, so there are just few things to mock.
|
||||
*
|
||||
* This object adds more readability than previous model with 2 attributes in {@see CMDBSource}.
|
||||
*
|
||||
* @used-by \CMDBSource
|
||||
*
|
||||
* @since 3.0.0 N°4325 Object creation
|
||||
* This wrapper handles the 2 {@mysqli myqsli} attributes that were previously in {@see CMDBSource}
|
||||
* To allow testing we added a second mysqli object (N°3513 in 2.7.5) and code became a bit confusing :/
|
||||
* With this wrapper everything is in the same place, and we can express the intention more clearly !
|
||||
*/
|
||||
class DbConnectionWrapper
|
||||
{
|
||||
/** @var mysqli */
|
||||
protected static $oDbCnxStandard;
|
||||
|
||||
/**
|
||||
* Can contain a genuine mysqli object, or a mock that would emulate {@see mysqli::query()}
|
||||
*
|
||||
* @var mysqli
|
||||
* @used-by \Combodo\iTop\Test\UnitTest\Core\TransactionsTest
|
||||
*/
|
||||
protected static $oDbCnxMockableForQuery;
|
||||
|
||||
/**
|
||||
* @param bool $bIsForQuery set to true if using {@see mysqli::query()}
|
||||
*
|
||||
* @return \mysqli|null
|
||||
*/
|
||||
public static function GetDbConnection(bool $bIsForQuery = false): ?mysqli
|
||||
{
|
||||
if ($bIsForQuery) {
|
||||
return static::$oDbCnxMockableForQuery;
|
||||
}
|
||||
|
||||
return static::$oDbCnxStandard;
|
||||
}
|
||||
|
||||
public static function SetDbConnection(?mysqli $oMysqli): void
|
||||
{
|
||||
static::$oDbCnxStandard = $oMysqli;
|
||||
static::SetDbConnectionMockForQuery($oMysqli);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to register a mock that will handle {@see mysqli::query()}
|
||||
*
|
||||
* @param \mysqli|null $oMysqli
|
||||
*/
|
||||
public static function SetDbConnectionMockForQuery(?mysqli $oMysqli): void
|
||||
{
|
||||
static::$oDbCnxMockableForQuery = $oMysqli;
|
||||
}
|
||||
}
|
||||
@@ -66,9 +66,8 @@ class MyHelpers
|
||||
// getmicrotime()
|
||||
// format sss.mmmuuupppnnn
|
||||
public static function getmicrotime()
|
||||
{
|
||||
list($usec, $sec) = explode(" ",microtime());
|
||||
return ((float)$usec + (float)$sec);
|
||||
{
|
||||
return microtime(true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -51,6 +51,7 @@ abstract class Action extends cmdbAbstractObject
|
||||
"db_table" => "priv_action",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "realclass",
|
||||
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-in-transit.svg'),
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
//MetaModel::Init_InheritAttributes();
|
||||
@@ -117,6 +118,39 @@ abstract class Action extends cmdbAbstractObject
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function AfterInsert()
|
||||
{
|
||||
parent::AfterInsert();
|
||||
$this->DoCheckIfHasTrigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function AfterUpdate()
|
||||
{
|
||||
parent::AfterUpdate();
|
||||
$this->DoCheckIfHasTrigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Action has at least 1 trigger linked. Otherwise, it adds a warning.
|
||||
* @return void
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected function DoCheckIfHasTrigger()
|
||||
{
|
||||
$oTriggersSet = $this->Get('trigger_list');
|
||||
if ($oTriggersSet->Count() === 0) {
|
||||
$this->m_aCheckWarnings[] = Dict::S('Action:WarningNoTriggerLinked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,8 +400,7 @@ class ActionEmail extends ActionNotification
|
||||
{
|
||||
$this->m_iRecipients = 0;
|
||||
$this->m_aMailErrors = array();
|
||||
$bRes = false; // until we do succeed in sending the email
|
||||
|
||||
|
||||
// Determine recipients
|
||||
//
|
||||
$sTo = $this->FindRecipients('to', $aContextArgs);
|
||||
@@ -381,29 +414,42 @@ class ActionEmail extends ActionNotification
|
||||
|
||||
$sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
|
||||
$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
|
||||
|
||||
|
||||
$oObj = $aContextArgs['this->object()'];
|
||||
$sMessageId = sprintf('iTop_%s_%d_%f@%s.openitop.org', get_class($oObj), $oObj->GetKey(), microtime(true /* get as float*/), MetaModel::GetEnvironmentId());
|
||||
$sMessageId = sprintf('iTop_%s_%d_%f@%s.openitop.org', get_class($oObj), $oObj->GetKey(), microtime(true /* get as float*/),
|
||||
MetaModel::GetEnvironmentId());
|
||||
$sReference = '<'.$sMessageId.'>';
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
|
||||
throw $e;
|
||||
}
|
||||
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
|
||||
|
||||
if (!is_null($oLog))
|
||||
{
|
||||
catch (Exception $e) {
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
throw $e;
|
||||
}
|
||||
finally {
|
||||
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
|
||||
}
|
||||
|
||||
if (!is_null($oLog)) {
|
||||
// Note: we have to secure this because those values are calculated
|
||||
// inside the try statement, and we would like to keep track of as
|
||||
// many data as we could while some variables may still be undefined
|
||||
if (isset($sTo)) $oLog->Set('to', $sTo);
|
||||
if (isset($sCC)) $oLog->Set('cc', $sCC);
|
||||
if (isset($sBCC)) $oLog->Set('bcc', $sBCC);
|
||||
if (isset($sFrom)) $oLog->Set('from', empty($sFromLabel) ? $sFrom : "$sFromLabel <$sFrom>");
|
||||
if (isset($sSubject)) $oLog->Set('subject', $sSubject);
|
||||
if (isset($sBody)) $oLog->Set('body', $sBody);
|
||||
if (isset($sTo)) {
|
||||
$oLog->Set('to', $sTo);
|
||||
}
|
||||
if (isset($sCC)) {
|
||||
$oLog->Set('cc', $sCC);
|
||||
}
|
||||
if (isset($sBCC)) {
|
||||
$oLog->Set('bcc', $sBCC);
|
||||
}
|
||||
if (isset($sFrom)) {
|
||||
$oLog->Set('from', $sFrom);
|
||||
}
|
||||
if (isset($sSubject)) {
|
||||
$oLog->Set('subject', $sSubject);
|
||||
}
|
||||
if (isset($sBody)) {
|
||||
$oLog->Set('body', $sBody);
|
||||
}
|
||||
}
|
||||
$sStyles = file_get_contents(APPROOT.'css/email.css');
|
||||
$sStyles .= MetaModel::GetConfig()->Get('email_css');
|
||||
|
||||
42
core/apc-service.class.inc.php
Normal file
42
core/apc-service.class.inc.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ApcService
|
||||
* @since 2.7.6 N°4125
|
||||
*/
|
||||
class ApcService {
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function_name
|
||||
* @return bool
|
||||
* @see function_exists()
|
||||
*/
|
||||
public function function_exists($function_name) {
|
||||
return function_exists($function_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $key
|
||||
* @return mixed
|
||||
* @see apc_fetch()
|
||||
*/
|
||||
function apc_fetch($key)
|
||||
{
|
||||
return apc_fetch($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $key
|
||||
* @param $var
|
||||
* @param int $ttl
|
||||
* @return array|bool
|
||||
* @see apc_store()
|
||||
*/
|
||||
function apc_store($key, $var = NULL, $ttl = 0)
|
||||
{
|
||||
return apc_store($key, $var, $ttl);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -157,7 +157,7 @@ abstract class AsyncTask extends DBObject
|
||||
if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
|
||||
{
|
||||
$aConfig = $aRetries[get_class($this)];
|
||||
$iRetryDelay = $aConfig['retry_delay'];
|
||||
$iRetryDelay = $aConfig['retry_delay'] ?? $iRetryDelay;
|
||||
}
|
||||
return $iRetryDelay;
|
||||
}
|
||||
@@ -169,11 +169,71 @@ abstract class AsyncTask extends DBObject
|
||||
if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
|
||||
{
|
||||
$aConfig = $aRetries[get_class($this)];
|
||||
$iMaxRetries = $aConfig['max_retries'];
|
||||
$iMaxRetries = $aConfig['max_retries'] ?? $iMaxRetries;
|
||||
}
|
||||
return $iMaxRetries;
|
||||
}
|
||||
|
||||
public function IsRetryDelayExponential()
|
||||
{
|
||||
$bExponential = false;
|
||||
$aRetries = MetaModel::GetConfig()->Get('async_task_retries');
|
||||
if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
|
||||
{
|
||||
$aConfig = $aRetries[get_class($this)];
|
||||
$bExponential = (bool)$aConfig['exponential_delay'] ?? $bExponential;
|
||||
}
|
||||
return $bExponential;
|
||||
}
|
||||
|
||||
public static function CheckRetryConfig(Config $oConfig, $sAsyncTaskClass)
|
||||
{
|
||||
$aMessages = [];
|
||||
$aRetries = $oConfig->Get('async_task_retries');
|
||||
if (is_array($aRetries) && array_key_exists($sAsyncTaskClass, $aRetries))
|
||||
{
|
||||
$aValidKeys = array("retry_delay", "max_retries", "exponential_delay");
|
||||
$aConfig = $aRetries[$sAsyncTaskClass];
|
||||
if (!is_array($aConfig))
|
||||
{
|
||||
$aMessages[] = Dict::Format('Class:AsyncTask:InvalidConfig_Class_Keys', $sAsyncTaskClass, implode(', ', $aValidKeys));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($aConfig as $key => $value)
|
||||
{
|
||||
if (!in_array($key, $aValidKeys))
|
||||
{
|
||||
$aMessages[] = Dict::Format('Class:AsyncTask:InvalidConfig_Class_InvalidKey_Keys', $sAsyncTaskClass, $key, implode(', ', $aValidKeys));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $aMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the delay to wait for the "next retry", based on the given parameters
|
||||
* @param bool $bIsExponential
|
||||
* @param int $iRetryDelay
|
||||
* @param int $iMaxRetries
|
||||
* @param int $iRemainingRetries
|
||||
* @return int
|
||||
*/
|
||||
public static function GetNextRetryDelay($bIsExponential, $iRetryDelay, $iMaxRetries, $iRemainingRetries)
|
||||
{
|
||||
if ($bIsExponential)
|
||||
{
|
||||
$iExponent = $iMaxRetries - $iRemainingRetries;
|
||||
if ($iExponent < 0) $iExponent = 0; // Safety net in case on configuration change in the middle of retries
|
||||
return $iRetryDelay * (2 ** $iExponent);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $iRetryDelay;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to notify people that a task cannot be performed
|
||||
*/
|
||||
@@ -241,25 +301,26 @@ abstract class AsyncTask extends DBObject
|
||||
if ($iRemaining > 0)
|
||||
{
|
||||
$iRetryDelay = $this->GetRetryDelay($iErrorCode);
|
||||
IssueLog::Info('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage.' - remaining retries: '.$iRemaining.' - next retry in '.$iRetryDelay.'s');
|
||||
$iNextRetryDelay = static::GetNextRetryDelay($this->IsRetryDelayExponential(), $iRetryDelay, $this->GetMaxRetries($iErrorCode), $iRemaining);
|
||||
IssueLog::Info('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage.' - remaining retries: '.$iRemaining.' - next retry in '.$iNextRetryDelay.'s');
|
||||
if ($this->Get('event_id') != 0)
|
||||
{
|
||||
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
|
||||
$oEventLog->Set('message', "$sErrorMessage\nFailed to process async task. Remaining retries: '.$iRemaining.'. Next retry in '.$iRetryDelay.'s'");
|
||||
$oEventLog->Set('message', "$sErrorMessage\nFailed to process async task. Remaining retries: $iRemaining. Next retry in {$iNextRetryDelay}s");
|
||||
try
|
||||
{
|
||||
$oEventLog->DBUpdate();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$oEventLog->Set('message', "Failed to process async task. Remaining retries: '.$iRemaining.'. Next retry in '.$iRetryDelay.'s', more details in the log");
|
||||
$oEventLog->Set('message', "Failed to process async task. Remaining retries: $iRemaining. Next retry in {$iNextRetryDelay}s, more details in the log");
|
||||
$oEventLog->DBUpdate();
|
||||
}
|
||||
}
|
||||
$this->Set('remaining_retries', $iRemaining - 1);
|
||||
$this->Set('status', 'planned');
|
||||
$this->Set('started', null);
|
||||
$this->Set('planned', time() + $iRetryDelay);
|
||||
$this->Set('planned', time() + $iNextRetryDelay);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1793,7 +1793,7 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
public function GetImportColumns()
|
||||
{
|
||||
$aColumns = array();
|
||||
$aColumns[$this->GetCode()] = 'TEXT';
|
||||
$aColumns[$this->GetCode()] = 'TEXT'.CMDBSource::GetSqlStringColumnDefinition();
|
||||
|
||||
return $aColumns;
|
||||
}
|
||||
@@ -4123,7 +4123,7 @@ class AttributeText extends AttributeString
|
||||
{
|
||||
// Propose a std link to the object
|
||||
$sClassLabel = MetaModel::GetName($sClass);
|
||||
$sToolTipForHtml = utils::EscapeHtml(Dict::S('Core:UnknownObjectTip'));
|
||||
$sToolTipForHtml = utils::EscapeHtml(Dict::Format('Core:UnknownObjectLabel', $sClass, $sName));
|
||||
$sReplacement = "<span class=\"wiki_broken_link ibo-is-broken-hyperlink\" data-tooltip-content=\"$sToolTipForHtml\">$sClassLabel:$sName" . (!empty($sLabel) ? " ($sLabel)" : "") . "</span>";
|
||||
$sText = str_replace($aMatches[0], $sReplacement, $sText);
|
||||
// Later: propose a link to create a new object
|
||||
@@ -4159,6 +4159,7 @@ class AttributeText extends AttributeString
|
||||
{
|
||||
$sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize);
|
||||
$sValue = self::RenderWikiHtml($sValue);
|
||||
$sValue = nl2br($sValue);
|
||||
|
||||
return "<div $sStyle>$sValue</div>";
|
||||
}
|
||||
@@ -5716,7 +5717,7 @@ class AttributeMetaEnum extends AttributeEnum
|
||||
$aLocalizedValues = array();
|
||||
foreach($aRawValues as $sKey => $sValue)
|
||||
{
|
||||
$aLocalizedValues[$sKey] = Str::pure2html($this->GetValueLabel($sKey));
|
||||
$aLocalizedValues[$sKey] = $this->GetValueLabel($sKey);
|
||||
}
|
||||
|
||||
return $aLocalizedValues;
|
||||
@@ -5994,7 +5995,7 @@ class AttributeDateTime extends AttributeDBField
|
||||
{
|
||||
// Allow an empty string to be a valid value (synonym for "reset")
|
||||
$aColumns = array();
|
||||
$aColumns[$this->GetCode()] = 'VARCHAR(19)';
|
||||
$aColumns[$this->GetCode()] = 'VARCHAR(19)'.CMDBSource::GetSqlStringColumnDefinition();
|
||||
|
||||
return $aColumns;
|
||||
}
|
||||
@@ -6482,7 +6483,7 @@ class AttributeDate extends AttributeDateTime
|
||||
{
|
||||
// Allow an empty string to be a valid value (synonym for "reset")
|
||||
$aColumns = array();
|
||||
$aColumns[$this->GetCode()] = 'VARCHAR(10)';
|
||||
$aColumns[$this->GetCode()] = 'VARCHAR(10)'.CMDBSource::GetSqlStringColumnDefinition();
|
||||
|
||||
return $aColumns;
|
||||
}
|
||||
@@ -8129,6 +8130,25 @@ class AttributeImage extends AttributeBlob
|
||||
return "Image";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see AttributeBlob::MakeRealValue()
|
||||
*/
|
||||
public function MakeRealValue($proposedValue, $oHostObj)
|
||||
{
|
||||
$oDoc = parent::MakeRealValue($proposedValue, $oHostObj);
|
||||
|
||||
if (($oDoc instanceof ormDocument)
|
||||
&& (false === $oDoc->IsEmpty())
|
||||
&& ($oDoc->GetMimeType() === 'image/svg+xml')) {
|
||||
$sCleanSvg = HTMLSanitizer::Sanitize($oDoc->GetData(), 'svg_sanitizer');
|
||||
$oDoc = new ormDocument($sCleanSvg, $oDoc->GetMimeType(), $oDoc->GetFileName());
|
||||
}
|
||||
|
||||
// The validation of the MIME Type is done by CheckFormat below
|
||||
return $oDoc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the supplied ormDocument actually contains an image
|
||||
* {@inheritDoc}
|
||||
@@ -8159,24 +8179,27 @@ class AttributeImage extends AttributeBlob
|
||||
$sRet = '';
|
||||
$bIsCustomImage = false;
|
||||
|
||||
$iMaxWidthPx = $this->Get('display_max_width').'px';
|
||||
$iMaxHeightPx = $this->Get('display_max_height').'px';
|
||||
$iMaxWidth = $this->Get('display_max_width');
|
||||
$sMaxWidthPx = $iMaxWidth.'px';
|
||||
$iMaxHeight = $this->Get('display_max_height');
|
||||
$sMaxHeightPx = $iMaxHeight.'px';
|
||||
|
||||
$sDefaultImageUrl = $this->Get('default_image');
|
||||
if ($sDefaultImageUrl !== null) {
|
||||
$sRet = $this->GetHtmlForImageUrl($sDefaultImageUrl, $iMaxWidthPx, $iMaxHeightPx);
|
||||
$sRet = $this->GetHtmlForImageUrl($sDefaultImageUrl, $sMaxWidthPx, $sMaxHeightPx);
|
||||
}
|
||||
|
||||
$sCustomImageUrl = $this->GetAttributeImageFileUrl($value, $oHostObject);
|
||||
if ($sCustomImageUrl !== null) {
|
||||
$bIsCustomImage = true;
|
||||
$sRet = $this->GetHtmlForImageUrl($sCustomImageUrl, $iMaxWidthPx, $iMaxHeightPx);
|
||||
$sRet = $this->GetHtmlForImageUrl($sCustomImageUrl, $sMaxWidthPx, $sMaxHeightPx);
|
||||
}
|
||||
|
||||
$sCssClasses = 'ibo-input-image--image-view attribute-image';
|
||||
$sCssClasses .= ' '.(($bIsCustomImage) ? 'attribute-image-custom' : 'attribute-image-default');
|
||||
|
||||
return '<div class="'.$sCssClasses.'" style="width: '.$iMaxWidthPx.'; height: '.$iMaxHeightPx.';">'.$sRet.'</div>';
|
||||
// Important: If you change this, mind updating edit_image.js as well
|
||||
return '<div class="'.$sCssClasses.'" style="max-width: '.$sMaxWidthPx.'; max-height: '.$sMaxHeightPx.'; aspect-ratio: '.$iMaxWidth.' / '.$iMaxHeight.'">'.$sRet.'</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -10283,7 +10306,6 @@ abstract class AttributeSet extends AttributeDBFieldVoid
|
||||
} else {
|
||||
$sTooltipContent = <<<HTML
|
||||
<h4>$sLabel</h4>
|
||||
<br>
|
||||
<div>$sDescription</div>
|
||||
HTML;
|
||||
$sTooltipHtmlEnabled = 'true';
|
||||
@@ -10830,7 +10852,7 @@ class AttributeClassAttCodeSet extends AttributeSet
|
||||
}
|
||||
}
|
||||
|
||||
$sLabelForHtmlAttribute = MetaModel::GetLabel($sAttClass, $sAttCode)." ($sAttCode)";
|
||||
$sLabelForHtmlAttribute = utils::HtmlEntities(MetaModel::GetLabel($sAttClass, $sAttCode)." ($sAttCode)");
|
||||
$aLocalizedValues[] = '<span class="attribute-set-item" data-code="'.$sAttCode.'" data-label="'.$sLabelForHtmlAttribute.'" data-description="" data-tooltip-content="'.$sLabelForHtmlAttribute.'">'.$sAttCode.'</span>';
|
||||
} catch (Exception $e)
|
||||
{
|
||||
@@ -11023,7 +11045,7 @@ class AttributeQueryAttCodeSet extends AttributeSet
|
||||
$aLocalizedValues = array();
|
||||
foreach ($value as $sAttCode) {
|
||||
if (isset($aAllowedAttributes[$sAttCode])) {
|
||||
$sLabelForHtmlAttribute = $aAllowedAttributes[$sAttCode];
|
||||
$sLabelForHtmlAttribute = utils::HtmlEntities($aAllowedAttributes[$sAttCode]);
|
||||
$aLocalizedValues[] = '<span class="attribute-set-item" data-code="'.$sAttCode.'" data-label="'.$sLabelForHtmlAttribute.'" data-description="" data-tooltip-content="'.$sLabelForHtmlAttribute.'">'.$sAttCode.'</span>';
|
||||
}
|
||||
}
|
||||
@@ -11572,20 +11594,20 @@ class AttributeTagSet extends AttributeSet
|
||||
$sTooltipContent = $sTagLabel;
|
||||
$sTooltipHtmlEnabled = 'false';
|
||||
} else {
|
||||
$sTagLabelEscaped = utils::EscapeHtml($sTagLabel);
|
||||
$sTooltipContent = <<<HTML
|
||||
<h4>$sTagLabel</h4>
|
||||
<br>
|
||||
<h4>$sTagLabelEscaped</h4>
|
||||
<div>$sTagDescription</div>
|
||||
HTML;
|
||||
$sTooltipHtmlEnabled = 'true';
|
||||
}
|
||||
$sTooltipContent = utils::EscapeHtml($sTooltipContent);
|
||||
$sTooltipContent = utils::HtmlEntities($sTooltipContent);
|
||||
|
||||
$sHtml .= '<a'.$sLink.' class="attribute-set-item attribute-set-item-'.$sTagCode.'" data-code="'.$sTagCode.'" data-label="'.$sLabelForHtml.'" data-description="'.$sDescriptionForHtml.'" data-tooltip-content="'.$sTooltipContent.'" data-tooltip-html-enabled="'.$sTooltipHtmlEnabled.'">'.$sLabelForHtml.'</a>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHtml .= '<span class="attribute-set-item">'.$oTag.'</span>';
|
||||
$sHtml .= '<span class="attribute-set-item">'.utils::EscapeHtml($oTag).'</span>';
|
||||
}
|
||||
}
|
||||
$sHtml .= '</span>';
|
||||
@@ -12545,10 +12567,12 @@ class AttributeCustomFields extends AttributeDefinition
|
||||
*/
|
||||
public function ReadValueFromPostedForm($oHostObject, $sFormPrefix)
|
||||
{
|
||||
$aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'),
|
||||
true);
|
||||
|
||||
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData);
|
||||
$aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true);
|
||||
if ($aRawData != null) {
|
||||
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData);
|
||||
} else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function MakeRealValue($proposedValue, $oHostObject)
|
||||
|
||||
@@ -1199,9 +1199,6 @@ EOF
|
||||
$.get(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?{$sAppContext}', {operation: 'displayCSVHistory', showall: bShowAll}, function(data)
|
||||
{
|
||||
$('#$sAjaxDivId').html(data);
|
||||
var table = $('#$sAjaxDivId .listResults');
|
||||
table.tableHover(); // hover tables
|
||||
table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1077,7 +1077,7 @@ class CMDBChangeOpSetAttributeLinksTune extends CMDBChangeOpSetAttributeLinks
|
||||
{
|
||||
$oField = new FieldExpression('objclass', $oSearch->GetClassAlias());
|
||||
$sListExpr = '('.implode(', ', CMDBSource::Quote($aLinkClasses)).')';
|
||||
$sOQLCondition = $oField->Render()." IN $sListExpr";
|
||||
$sOQLCondition = $oField->RenderExpression()." IN $sListExpr";
|
||||
$oNewCondition = Expression::FromOQL($sOQLCondition);
|
||||
$oSearch->AddConditionExpression($oNewCondition);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ abstract class CMDBObject extends DBObject
|
||||
*
|
||||
* @param string $sId ID of the user doing the change, null if not done by a user (eg. background task)
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @since 3.0.0 N°2847 following the addition of CMDBChange.user_id
|
||||
*/
|
||||
public static function SetTrackUserId($sId)
|
||||
{
|
||||
|
||||
@@ -24,13 +24,15 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Core\DbConnectionWrapper;
|
||||
|
||||
require_once('MyHelpers.class.inc.php');
|
||||
require_once(APPROOT.'core/kpi.class.inc.php');
|
||||
|
||||
|
||||
/**
|
||||
* CMDBSource
|
||||
* database access wrapper
|
||||
* database access wrapper
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
@@ -66,11 +68,6 @@ class CMDBSource
|
||||
*/
|
||||
protected static $m_sDBTlsCA;
|
||||
|
||||
/** @var mysqli $m_oMysqli */
|
||||
protected static $m_oMysqli;
|
||||
/** @var mysqli or mock used for test purpose, only used in query() method */
|
||||
protected static $oMySQLiForQuery;
|
||||
|
||||
/**
|
||||
* @var int number of level for nested transactions : 0 if no transaction was ever opened, +1 for each 'START TRANSACTION' sent
|
||||
* @since 2.7.0 N°679
|
||||
@@ -135,8 +132,8 @@ class CMDBSource
|
||||
self::$m_bDBTlsEnabled = empty($bTlsEnabled) ? false : $bTlsEnabled;
|
||||
self::$m_sDBTlsCA = empty($sTlsCA) ? null : $sTlsCA;
|
||||
|
||||
self::$m_oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
|
||||
self::SetMySQLiForQuery(self::$m_oMysqli);
|
||||
$oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
|
||||
DbConnectionWrapper::SetDbConnection($oMysqli);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +147,8 @@ 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
|
||||
@@ -252,41 +251,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)
|
||||
{
|
||||
@@ -345,11 +344,18 @@ class CMDBSource
|
||||
{
|
||||
// In case we don't have rights to enumerate the databases
|
||||
// Let's try to connect directly
|
||||
return @((bool)self::$m_oMysqli->query("USE `$sSource`"));
|
||||
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
|
||||
return @((bool)DbConnectionWrapper::GetDbConnection(true)->query("USE `$sSource`"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @uses \CMDBSource::QueryToCol() so needs a connection opened !
|
||||
*/
|
||||
public static function GetDBVersion()
|
||||
{
|
||||
$aVersions = self::QueryToCol('SELECT Version() as version', 'version');
|
||||
@@ -361,14 +367,16 @@ class CMDBSource
|
||||
*/
|
||||
public static function GetServerInfo()
|
||||
{
|
||||
return mysqli_get_server_info ( self::$m_oMysqli );
|
||||
return mysqli_get_server_info(DbConnectionWrapper::GetDbConnection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DB vendor between MySQL and its main forks
|
||||
* @return string
|
||||
*
|
||||
* @uses \CMDBSource::GetServerVariable() so needs a connection opened !
|
||||
*/
|
||||
static public function GetDBVendor()
|
||||
public static function GetDBVendor()
|
||||
{
|
||||
$sDBVendor = static::ENUM_DB_VENDOR_MYSQL;
|
||||
|
||||
@@ -392,9 +400,9 @@ class CMDBSource
|
||||
*/
|
||||
public static function SelectDB($sSource)
|
||||
{
|
||||
if (!((bool)self::$m_oMysqli->query("USE `$sSource`")))
|
||||
{
|
||||
throw new MySQLException('Could not select DB', array('db_name'=>$sSource));
|
||||
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
|
||||
if (!((bool)DbConnectionWrapper::GetDbConnection(true)->query("USE `$sSource`"))) {
|
||||
throw new MySQLException('Could not select DB', array('db_name' => $sSource));
|
||||
}
|
||||
self::$m_sDBName = $sSource;
|
||||
}
|
||||
@@ -445,51 +453,29 @@ class CMDBSource
|
||||
|
||||
/**
|
||||
* @return \mysqli
|
||||
*
|
||||
* @since 2.5.0 N°1260
|
||||
*/
|
||||
public static function GetMysqli()
|
||||
{
|
||||
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;
|
||||
return DbConnectionWrapper::GetDbConnection(false);
|
||||
}
|
||||
|
||||
public static function GetErrNo()
|
||||
{
|
||||
if (self::$m_oMysqli->errno != 0)
|
||||
{
|
||||
return self::$m_oMysqli->errno;
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::$m_oMysqli->connect_errno;
|
||||
if (DbConnectionWrapper::GetDbConnection()->errno != 0) {
|
||||
return DbConnectionWrapper::GetDbConnection()->errno;
|
||||
} else {
|
||||
return DbConnectionWrapper::GetDbConnection()->connect_errno;
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetError()
|
||||
{
|
||||
if (self::$m_oMysqli->error != '')
|
||||
{
|
||||
return self::$m_oMysqli->error;
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::$m_oMysqli->connect_error;
|
||||
if (DbConnectionWrapper::GetDbConnection()->error != '') {
|
||||
return DbConnectionWrapper::GetDbConnection()->error;
|
||||
} else {
|
||||
return DbConnectionWrapper::GetDbConnection()->connect_error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,7 +515,8 @@ class CMDBSource
|
||||
// Quote if not a number or a numeric string
|
||||
if ($bAlways || is_string($value))
|
||||
{
|
||||
$value = $cQuoteStyle . self::$m_oMysqli->real_escape_string($value) . $cQuoteStyle;
|
||||
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
|
||||
$value = $cQuoteStyle.DbConnectionWrapper::GetDbConnection()->real_escape_string($value).$cQuoteStyle;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
@@ -590,7 +577,7 @@ class CMDBSource
|
||||
/**
|
||||
* Send the query directly to the DB. **Be extra cautious with this !**
|
||||
*
|
||||
* Use {@link Query} if you're not sure.
|
||||
* Use {@see Query} if you're not sure.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
@@ -612,26 +599,25 @@ class CMDBSource
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
|
||||
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
|
||||
}
|
||||
catch (mysqli_sql_exception $e)
|
||||
{
|
||||
self::LogDeadLock($e);
|
||||
self::LogDeadLock($e, true);
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
}
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
if ($oResult === false)
|
||||
{
|
||||
if ($oResult === false) {
|
||||
$aContext = array('query' => $sSql);
|
||||
|
||||
$iMySqlErrorNo = self::$m_oMysqli->errno;
|
||||
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection(true)->errno;
|
||||
$aMySqlHasGoneAwayErrorCodes = MySQLHasGoneAwayException::getErrorCodes();
|
||||
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes))
|
||||
{
|
||||
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes)) {
|
||||
throw new MySQLHasGoneAwayException(self::GetError(), $aContext);
|
||||
}
|
||||
$e = new MySQLException('Failed to issue SQL query', $aContext);
|
||||
self::LogDeadLock($e);
|
||||
self::LogDeadLock($e, true);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -640,23 +626,24 @@ class CMDBSource
|
||||
|
||||
/**
|
||||
* @param \Exception $e
|
||||
* @param bool $bForQuery to get the proper DB connection
|
||||
*
|
||||
* @since 2.7.1
|
||||
* @since 3.0.0 N°4325 add new optional parameter to use the correct DB connection
|
||||
*/
|
||||
private static function LogDeadLock(Exception $e)
|
||||
private static function LogDeadLock(Exception $e, $bForQuery = false)
|
||||
{
|
||||
// checks MySQL error code
|
||||
$iMySqlErrorNo = self::$m_oMysqli->errno;
|
||||
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK)))
|
||||
{
|
||||
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno;
|
||||
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get error info
|
||||
$sUser = UserRights::GetUser();
|
||||
$oError = self::$m_oMysqli->query('SHOW ENGINE INNODB STATUS');
|
||||
if ($oError !== false)
|
||||
{
|
||||
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
|
||||
$oError = DbConnectionWrapper::GetDbConnection(true)->query('SHOW ENGINE INNODB STATUS');
|
||||
if ($oError !== false) {
|
||||
$aData = $oError->fetch_all(MYSQLI_ASSOC);
|
||||
$sInnodbStatus = $aData[0];
|
||||
}
|
||||
@@ -693,13 +680,13 @@ 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", LogChannels::CMDB_SOURCE);
|
||||
IssueLog::Trace("START TRANSACTION was sent to the DB", LogChannels::CMDB_SOURCE, ['stacktrace' => $aStackTrace]);
|
||||
self::DBQuery('START TRANSACTION');
|
||||
} else {
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") START TRANSACTION $sCaller", LogChannels::CMDB_SOURCE);
|
||||
IssueLog::Trace("START TRANSACTION ignored as a transaction is already opened", LogChannels::CMDB_SOURCE, ['stacktrace' => $aStackTrace]);
|
||||
}
|
||||
|
||||
self::AddTransactionLevel();
|
||||
@@ -829,7 +816,7 @@ class CMDBSource
|
||||
|
||||
public static function GetInsertId()
|
||||
{
|
||||
$iRes = self::$m_oMysqli->insert_id;
|
||||
$iRes = DbConnectionWrapper::GetDbConnection()->insert_id;
|
||||
if (is_null($iRes))
|
||||
{
|
||||
return 0;
|
||||
@@ -861,26 +848,28 @@ 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)
|
||||
{
|
||||
$oMysqliForQuery = $oMysqli ?: DbConnectionWrapper::GetDbConnection(true);
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
try {
|
||||
/** @noinspection NullPointerExceptionInspection this shouldn't happen : either cnx is passed or the DB was init */
|
||||
$oResult = $oMysqliForQuery->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
catch (mysqli_sql_exception $e) {
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
}
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
if ($oResult === false)
|
||||
{
|
||||
if ($oResult === false) {
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
|
||||
}
|
||||
|
||||
@@ -912,7 +901,8 @@ class CMDBSource
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
|
||||
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -962,7 +952,8 @@ class CMDBSource
|
||||
$aData = array();
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
|
||||
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -994,7 +985,8 @@ class CMDBSource
|
||||
{
|
||||
try
|
||||
{
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
|
||||
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -1019,7 +1011,7 @@ class CMDBSource
|
||||
|
||||
public static function AffectedRows()
|
||||
{
|
||||
return self::$m_oMysqli->affected_rows;
|
||||
return DbConnectionWrapper::GetDbConnection()->affected_rows;
|
||||
}
|
||||
|
||||
public static function FetchArray($oResult)
|
||||
@@ -1137,7 +1129,7 @@ class CMDBSource
|
||||
*
|
||||
* 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
|
||||
* A better solution would be to generate SQL field definitions ({@see GetFieldSpec} method) based on the DB used... But for
|
||||
* now (N°2490 / SF #1756 / PR #91) we did implement this simpler solution
|
||||
*
|
||||
* @see GetFieldDataTypeAndOptions extracts all info from the SQL field definition
|
||||
@@ -1482,7 +1474,8 @@ class CMDBSource
|
||||
$sSql = "SELECT * FROM `$sTable`";
|
||||
try
|
||||
{
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
|
||||
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,15 @@
|
||||
|
||||
define('ITOP_APPLICATION', 'iTop');
|
||||
define('ITOP_APPLICATION_SHORT', 'iTop');
|
||||
define('ITOP_VERSION', '3.0.0-dev');
|
||||
|
||||
/**
|
||||
* Constant containing the application version
|
||||
* Warning: this might be different from iTop core version!
|
||||
*
|
||||
* @see ITOP_CORE_VERSION to get iTop core version
|
||||
*/
|
||||
define('ITOP_VERSION', '3.0.1-dev');
|
||||
|
||||
define('ITOP_VERSION_NAME', 'Fullmoon');
|
||||
define('ITOP_REVISION', 'svn');
|
||||
define('ITOP_BUILD_DATE', '$WCNOW$');
|
||||
@@ -107,7 +115,15 @@ class Config
|
||||
protected $m_aSettings = [
|
||||
'log_level_min' => [
|
||||
'type' => 'array',
|
||||
'description' => 'Optional min log level per channel',
|
||||
'description' => 'Optional min log level, per channel.',
|
||||
'default' => '',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'log_level_min.write_in_db' => [
|
||||
'type' => 'array',
|
||||
'description' => 'Optional min log level IN DB, per channel.',
|
||||
'default' => '',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
@@ -600,6 +616,13 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
/**
|
||||
* The timezone is automatically set using this parameter in \utils::InitTimeZone
|
||||
* This method is called almost everywhere, cause it's called in \MetaModel::LoadConfig and exec.php... but you might
|
||||
* need to get it yourself !
|
||||
*
|
||||
* @used-by utils::InitTimeZone()
|
||||
*/
|
||||
'timezone' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitly configured in PHP',
|
||||
@@ -843,13 +866,23 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'impact_analysis_lazy_loading' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'In the impact analysis view: display the analysis or filter before display',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'url_validation_pattern' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)',
|
||||
'default' => '(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9%+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\$_.-]*)?',
|
||||
// SHEME.......... USER....................... PASSWORD.......................... HOST/IP........... PORT.......... PATH........................ GET............................................ ANCHOR............................
|
||||
'default' => /** @lang RegExp */
|
||||
'(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9:%+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\$_.-]*)?',
|
||||
// SCHEME....... USER....................... PASSWORD.......................... HOST/IP........... PORT.......... PATH......................... GET............................................ ANCHOR..........................
|
||||
// Example: http://User:passWord@127.0.0.1:8888/patH/Page.php?arrayArgument[2]=something:blah20#myAnchor
|
||||
// Origin of this regexp: http://www.php.net/manual/fr/function.preg-match.php#93824
|
||||
// RegExp source: http://www.php.net/manual/fr/function.preg-match.php#93824
|
||||
// Update with N°4515
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
@@ -1062,72 +1095,88 @@ class Config
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'transactions_gc_threshold' => [
|
||||
'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' => '',
|
||||
'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' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to enable the debug log for the transactions.',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to enable the debug log for the transactions.',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'concurrent_lock_enabled' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to activate the locking mechanism in order to prevent concurrent edition of the same object.',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to activate the locking mechanism in order to prevent concurrent edition of the same object.',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'concurrent_lock_expiration_delay' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Delay (in seconds) for a concurrent lock to expire',
|
||||
'default' => 120,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'type' => 'integer',
|
||||
'description' => 'Delay (in seconds) for a concurrent lock to expire',
|
||||
'default' => 120,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'concurrent_lock_override_profiles' => [
|
||||
'type' => 'array',
|
||||
'description' => 'The list of profiles allowed to "kill" a lock',
|
||||
'default' => ['Administrator'],
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'type' => 'array',
|
||||
'description' => 'The list of profiles allowed to "kill" a lock',
|
||||
'default' => ['Administrator'],
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'force_transition_confirmation' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If set to true force confirmation in all transition even if there is no field to complete',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'html_sanitizer' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The class to use for HTML sanitization: HTMLDOMSanitizer, HTMLPurifierSanitizer or HTMLNullSanitizer',
|
||||
'default' => 'HTMLDOMSanitizer',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'svg_sanitizer' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The class to use for HTML sanitization: HTMLDOMSanitizer, HTMLPurifierSanitizer or HTMLNullSanitizer',
|
||||
'default' => 'HTMLDOMSanitizer',
|
||||
'description' => 'The class to use for SVG sanitization : allow to provide a custom made sanitizer',
|
||||
'default' => 'SVGDOMSanitizer',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'inline_image_max_display_width' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.',
|
||||
'default' => '250',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'type' => 'integer',
|
||||
'description' => 'The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.',
|
||||
'default' => '250',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'inline_image_max_storage_width' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.',
|
||||
'default' => '1600',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'type' => 'integer',
|
||||
'description' => 'The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.',
|
||||
'default' => '1600',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'draft_attachments_lifetime' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Lifetime (in seconds) of drafts\' attachments and inline images: after this duration, the garbage collector will delete them.',
|
||||
'type' => 'integer',
|
||||
'description' => 'Lifetime (in seconds) of drafts\' attachments and inline images: after this duration, the garbage collector will delete them.',
|
||||
'default' => 86400,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
@@ -1141,6 +1190,38 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'compatibility.include_moved_js_files' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Include back JS files which are now only included when necessary to ease usage of not migrated extensions',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'compatibility.include_deprecated_js_files' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Include the deprecated JS files (in iTop previous version) to ease usage of not migrated extensions',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'compatibility.include_moved_css_files' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Include back CSS files which are now only included when necessary to ease usage of not migrated extensions',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'compatibility.include_deprecated_css_files' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Include the deprecated CSS files (in iTop previous version) to ease usage of not migrated extensions',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'navigation_menu.show_menus_count' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Display count badges for OQL menu entries',
|
||||
@@ -1287,9 +1368,9 @@ class Config
|
||||
],
|
||||
'mentions.allowed_classes' => [
|
||||
'type' => 'array',
|
||||
'description' => 'Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete, value can be either a DM class or a valid OQL (eg. "@" => "Person", "?" => "SELECT FAQ WHERE status = \'published\'")',
|
||||
'description' => 'Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete, value must be a DM class (eg. "@" => "Person", "?" => "FAQ")',
|
||||
'default' => [
|
||||
'@' => 'SELECT Person WHERE status = \'active\'',
|
||||
'@' => 'Person',
|
||||
],
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
@@ -1415,6 +1496,22 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.disable_inline_documents_sandbox' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true then the sandbox for documents displayed in a browser tab will be disabled; enabling scripts and other interactive content. Note that setting this to true will open the application to potential XSS attacks!',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.hide_administrators' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, non-administrator users will not be able to see the administrator accounts, the Administrator profile and the links between the administrator accounts and their profiles.',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'behind_reverse_proxy' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',
|
||||
@@ -1528,6 +1625,16 @@ class Config
|
||||
return $this->m_aSettings[$sPropCode]['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*
|
||||
* @since 3.0.1 N°4515
|
||||
*/
|
||||
public function GetDefault(string $sPropCode)
|
||||
{
|
||||
return $this->m_aSettings[$sPropCode]['default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the $sPropCode parameter has a custom value or the default one.
|
||||
*
|
||||
|
||||
@@ -220,16 +220,15 @@ class CSVBulkExport extends TabularBulkExport
|
||||
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="csv_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oRadioCustom = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput), "csv_date_format_radio", "custom", "csv_date_time_format_custom", "radio");
|
||||
$oRadioCustom->SetDescription(Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip'));
|
||||
$oRadioCustom->GetInput()->SetIsChecked($sDateTimeFormat !== (string)AttributeDateTime::GetFormat());
|
||||
$oRadioCustom->SetBeforeInput(false);
|
||||
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
$oFieldSetDate->AddSubBlock($oRadioCustom);
|
||||
|
||||
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#csv_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_csv_options').on('preview_updated', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
$('#csv_date_time_format_default').on('click', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
$('#csv_date_time_format_custom').on('click', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
|
||||
/**
|
||||
* All objects to be displayed in the application (either as a list or as details)
|
||||
* must implement this interface.
|
||||
@@ -946,10 +948,9 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
protected function ComputeHighlightCode()
|
||||
{
|
||||
// First if the state defines a HiglightCode, apply it
|
||||
$sState = $this->GetState();
|
||||
if ($sState != '')
|
||||
if (MetaModel::HasLifecycle(get_class($this)))
|
||||
{
|
||||
$sState = $this->GetState();
|
||||
$sCode = MetaModel::GetHighlightCode(get_class($this), $sState);
|
||||
$this->SetHighlightCode($sCode);
|
||||
}
|
||||
@@ -1469,24 +1470,78 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function GetIcon($bImgTag = true)
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
|
||||
if($this->HasHighlightIcon()) {
|
||||
$sIconUrl = MetaModel::GetHighlightScale($sClass)[$this->ComputeHighlightCode()]['icon'];
|
||||
if($bImgTag) {
|
||||
return "<img src=\"$sIconUrl\" style=\"vertical-align:middle\"/>";
|
||||
}
|
||||
else {
|
||||
return $sIconUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Get object instance own image if it exists
|
||||
$sImageAttCode = MetaModel::GetImageAttributeCode($sClass);
|
||||
$sIconUrl = $this->HasInstanceIcon() ? $this->Get($sImageAttCode)->GetDisplayURL($sClass, $this->GetKey(), $sImageAttCode) : '';
|
||||
if (strlen($sIconUrl) > 0) {
|
||||
if($bImgTag) {
|
||||
return "<img src=\"$sIconUrl\"/>";
|
||||
}
|
||||
else {
|
||||
return $sIconUrl;
|
||||
}
|
||||
}
|
||||
return MetaModel::GetClassIcon(get_class($this), $bImgTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if the object has an image attribute as semantic attribute and its value is not empty
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function HasInstanceIcon(): bool
|
||||
{
|
||||
$bHasInstanceIcon = false;
|
||||
$sClass = get_class($this);
|
||||
|
||||
if (!$this->IsNew() && MetaModel::HasImageAttributeCode($sClass)) {
|
||||
$sImageAttCode = MetaModel::GetImageAttributeCode($sClass);
|
||||
if (!empty($sImageAttCode)) {
|
||||
/** @var \ormDocument $oImage */
|
||||
$oImage = $this->Get($sImageAttCode);
|
||||
$bHasInstanceIcon = !$oImage->IsEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
return $bHasInstanceIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if the class has an highlight icon declared for the current object state
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function HasHighlightIcon(): bool
|
||||
{
|
||||
$bHasHighlightIcon = false;
|
||||
|
||||
$sCode = $this->ComputeHighlightCode();
|
||||
$sClass = get_class($this);
|
||||
|
||||
if($sCode != '')
|
||||
{
|
||||
$aHighlightScale = MetaModel::GetHighlightScale(get_class($this));
|
||||
$aHighlightScale = MetaModel::GetHighlightScale($sClass);
|
||||
if (array_key_exists($sCode, $aHighlightScale))
|
||||
{
|
||||
$sIconUrl = $aHighlightScale[$sCode]['icon'];
|
||||
if($bImgTag)
|
||||
{
|
||||
return "<img src=\"$sIconUrl\" style=\"vertical-align:middle\"/>";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sIconUrl;
|
||||
}
|
||||
$bHasHighlightIcon = true;
|
||||
}
|
||||
}
|
||||
return MetaModel::GetClassIcon(get_class($this), $bImgTag);
|
||||
}
|
||||
|
||||
return $bHasHighlightIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1525,14 +1580,15 @@ abstract class DBObject implements iDisplay
|
||||
* Helper to get the friendly name in a safe manner for displaying inside a web page
|
||||
*
|
||||
* @internal
|
||||
* @since 3.0.0 N°4106 This method is now internal. It will be set final in 3.1.0 (N°4107)
|
||||
*
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
* @since 3.0.0 N°4106 This method is now internal. It will be set final in 3.1.0 (N°4107)
|
||||
* @since 3.0.0 N°580 New $sType parameter
|
||||
*
|
||||
*/
|
||||
public function GetName()
|
||||
public function GetName($sType = FriendlyNameType::SHORT)
|
||||
{
|
||||
return htmlentities($this->GetRawName(), ENT_QUOTES, 'UTF-8');
|
||||
return htmlentities($this->GetRawName($sType), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1546,11 +1602,17 @@ abstract class DBObject implements iDisplay
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
* @since 3.0.0 N°4106 This method is now internal. It will be set final in 3.1.0 (N°4107)
|
||||
* @since 3.0.0 N°580 New $sType parameter
|
||||
*
|
||||
*/
|
||||
public function GetRawName()
|
||||
public function GetRawName($sType = FriendlyNameType::SHORT)
|
||||
{
|
||||
return $this->Get('friendlyname');
|
||||
if ($sType == FriendlyNameType::SHORT) {
|
||||
return $this->Get('friendlyname');
|
||||
} else {
|
||||
$oExpression = MetaModel::GetNameExpression(get_class($this), $sType);
|
||||
$this->EvaluateExpression($oExpression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2134,28 +2196,23 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
if ($oAttDef->DuplicatesAllowed()) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
// To control duplicates go through all the entries and check if the remote has been seen
|
||||
/** @var \ormLinkSet $value */
|
||||
$aModifiedLnk = $value->ListModifiedLinks();
|
||||
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
|
||||
$aExistingRemotesId = $value->GetColumnAsArray($sExtKeyToRemote, true);
|
||||
$aExistingRemotesFriendlyName = $value->GetColumnAsArray($sExtKeyToRemote.'_friendlyname', true);
|
||||
$aCurrentRemoteIds = [];
|
||||
$aDuplicatesFields = [];
|
||||
foreach ($aModifiedLnk as $oModifiedLnk) {
|
||||
$iModifiedLnkId = $oModifiedLnk->GetKey();
|
||||
$iModifiedLnkRemoteId = $oModifiedLnk->Get($sExtKeyToRemote);
|
||||
$aExistingRemotesIdToCheck = array_filter($aExistingRemotesId, function ($iLnkKey) use ($iModifiedLnkId) {
|
||||
return ($iLnkKey != $iModifiedLnkId);
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
|
||||
if (!in_array($iModifiedLnkRemoteId, $aExistingRemotesIdToCheck, true)) {
|
||||
continue;
|
||||
$value->rewind();
|
||||
while ($oCurrentLnk = $value->current()) {
|
||||
$iExtKeyToRemote = $oCurrentLnk->Get($sExtKeyToRemote);
|
||||
if (isset($aCurrentRemoteIds[$iExtKeyToRemote])) {
|
||||
$aDuplicatesFields[] = $oCurrentLnk->Get($sExtKeyToRemote.'_friendlyname');
|
||||
} else {
|
||||
$aCurrentRemoteIds[$iExtKeyToRemote] = true;
|
||||
}
|
||||
|
||||
$iLnkId = $oModifiedLnk->GetKey();
|
||||
$aDuplicatesFields[] = $aExistingRemotesFriendlyName[$iLnkId];
|
||||
$value->next();
|
||||
}
|
||||
|
||||
if (!empty($aDuplicatesFields)) {
|
||||
@@ -2877,6 +2934,9 @@ abstract class DBObject implements iDisplay
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// - TriggerOnObjectMention
|
||||
$this->ActivateOnMentionTriggers(true);
|
||||
|
||||
return $this->m_iKey;
|
||||
}
|
||||
@@ -3140,48 +3200,7 @@ abstract class DBObject implements iDisplay
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
// - TriggerOnObjectMention
|
||||
// 1 - Check if any caselog updated
|
||||
$aUpdatedLogAttCodes = array();
|
||||
foreach($aChanges as $sAttCode => $value)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if($oAttDef instanceof AttributeCaseLog)
|
||||
{
|
||||
$aUpdatedLogAttCodes[] = $sAttCode;
|
||||
}
|
||||
}
|
||||
// 2 - Find mentioned objects
|
||||
$aMentionedObjects = array();
|
||||
foreach ($aUpdatedLogAttCodes as $sAttCode) {
|
||||
/** @var \ormCaseLog $oUpdatedCaseLog */
|
||||
$oUpdatedCaseLog = $this->Get($sAttCode);
|
||||
$aMentionedObjects = array_merge_recursive($aMentionedObjects, utils::GetMentionedObjectsFromText($oUpdatedCaseLog->GetModifiedEntry()));
|
||||
}
|
||||
// 3 - Trigger for those objects
|
||||
// TODO: This should be refactored and moved into the caselogs loop, otherwise, we won't be able to know which case log triggered the action.
|
||||
foreach ($aMentionedObjects as $sMentionedClass => $aMentionedIds) {
|
||||
foreach ($aMentionedIds as $sMentionedId) {
|
||||
/** @var \DBObject $oMentionedObject */
|
||||
$oMentionedObject = MetaModel::GetObject($sMentionedClass, $sMentionedId);
|
||||
// Important: Here the "$this->object()$" placeholder is actually the mentioned object and not the current object. The current object can be used through the $source->object()$ placeholder.
|
||||
// This is due to the current implementation of triggers, the events will only be visible on the object the trigger's OQL is based on... 😕
|
||||
$aTriggerArgs = $this->ToArgs('source') + array('this->object()' => $oMentionedObject);
|
||||
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses($sMentionedClass, ENUM_PARENT_CLASSES_ALL));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"),
|
||||
array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \TriggerOnObjectMention $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($aTriggerArgs);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->ActivateOnMentionTriggers(false);
|
||||
|
||||
$bHasANewExternalKeyValue = false;
|
||||
$aHierarchicalKeys = array();
|
||||
@@ -3394,6 +3413,74 @@ abstract class DBObject implements iDisplay
|
||||
return $this->m_iKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate TriggerOnObjectMention triggers for the current object
|
||||
*
|
||||
* @param bool $bNewlyCreatedObject
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
* @since 3.0.1 N°4741
|
||||
*/
|
||||
private function ActivateOnMentionTriggers(bool $bNewlyCreatedObject): void
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
$aChanges = $bNewlyCreatedObject ? $this->m_aOrigValues : $this->ListChanges();
|
||||
|
||||
// 1 - Check if any caselog updated
|
||||
$aUpdatedLogAttCodes = [];
|
||||
foreach ($aChanges as $sAttCode => $value) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeCaseLog) {
|
||||
// Skip empty log on creation
|
||||
if ($bNewlyCreatedObject && $value->GetModifiedEntry() === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aUpdatedLogAttCodes[] = $sAttCode;
|
||||
}
|
||||
}
|
||||
|
||||
// 2 - Find mentioned objects
|
||||
$aMentionedObjects = [];
|
||||
foreach ($aUpdatedLogAttCodes as $sAttCode) {
|
||||
/** @var \ormCaseLog $oUpdatedCaseLog */
|
||||
$oUpdatedCaseLog = $this->Get($sAttCode);
|
||||
$aMentionedObjects = array_merge_recursive($aMentionedObjects, utils::GetMentionedObjectsFromText($oUpdatedCaseLog->GetModifiedEntry()));
|
||||
}
|
||||
|
||||
// 3 - Trigger for those objects
|
||||
// TODO: This should be refactored and moved into the caselogs loop, otherwise, we won't be able to know which case log triggered the action.
|
||||
foreach ($aMentionedObjects as $sMentionedClass => $aMentionedIds) {
|
||||
foreach ($aMentionedIds as $sMentionedId) {
|
||||
/** @var \DBObject $oMentionedObject */
|
||||
$oMentionedObject = MetaModel::GetObject($sMentionedClass, $sMentionedId);
|
||||
$aTriggerArgs = $this->ToArgs('this') + ['mentioned->object()' => $oMentionedObject];
|
||||
|
||||
$aParams = ['class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)];
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"), [], $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \TriggerOnObjectMention $oTrigger */
|
||||
try {
|
||||
// Ensure to handle only mentioned object in the trigger's scope
|
||||
if ($oTrigger->IsMentionedObjectInScope($oMentionedObject) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oTrigger->DoActivate($aTriggerArgs);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Save updated fields previous values for {@see DBObject::DBUpdate()} callbacks
|
||||
@@ -4002,6 +4089,58 @@ abstract class DBObject implements iDisplay
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to set a date computed from another date with extra logic
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $sAttCode attribute code of a date or date-time which will be set
|
||||
* @param string $sModifier string specifying how to modify the date time
|
||||
* @param string $sAttCodeSource attribute code of a date or date-time used as source
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function SetComputedDate($sAttCode, $sModifier = '', $sAttCodeSource = '')
|
||||
{
|
||||
$oDate = new DateTime(); // Use now if no Source provided
|
||||
if ($sAttCodeSource != "") {
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCodeSource);
|
||||
$oSourceValue = $this->Get($sAttCodeSource);
|
||||
if (!$oAttDef->IsNull($oSourceValue)) {
|
||||
// Use the existing value as the Source date
|
||||
$oDate = new DateTime($oSourceValue);
|
||||
}
|
||||
}
|
||||
$oDate->modify($sModifier);
|
||||
$this->Set($sAttCode, $oDate->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to set a date computed from another date with extra logic
|
||||
* Call SetComputedDate() only of the internal representation of the attribute is null.
|
||||
*
|
||||
* @api
|
||||
* @see SetComputedDate()
|
||||
*
|
||||
* @param string $sAttCode attribute code which will be set
|
||||
* @param string $sModifier string specifying how to modify the date time
|
||||
* @param string $sAttCodeSource attribute code used as source
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function SetComputedDateIfNull($sAttCode, $sModifier = '', $sAttCodeSource = '')
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$oCurrentValue = $this->Get($sAttCode);
|
||||
if ($oAttDef->IsNull($oCurrentValue)) {
|
||||
$this->SetComputedDate($sAttCode, $sModifier, $sAttCodeSource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to set a value only if it is currently undefined
|
||||
*
|
||||
@@ -4020,40 +4159,35 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$oCurrentValue = $this->Get($sAttCode);
|
||||
if ($oAttDef->IsNull($oCurrentValue))
|
||||
{
|
||||
if ($oAttDef->IsNull($oCurrentValue)) {
|
||||
$this->SetCurrentDate($sAttCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to set the current logged in user for the given attribute
|
||||
* Suitable for use as a lifecycle action
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $sAttCode
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper to set the current logged in user for the given attribute
|
||||
* Suitable for use as a lifecycle action
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $sAttCode
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
*/
|
||||
public function SetCurrentUser($sAttCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeString)
|
||||
{
|
||||
if ($oAttDef instanceof AttributeString) {
|
||||
// Note: the user friendly name is the contact friendly name if a contact is attached to the logged in user
|
||||
$this->Set($sAttCode, UserRights::GetUserFriendlyName());
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($oAttDef->IsExternalKey())
|
||||
{
|
||||
} else {
|
||||
if ($oAttDef->IsExternalKey()) {
|
||||
/** @var \AttributeExternalKey $oAttDef */
|
||||
if ($oAttDef->GetTargetClass() != 'User')
|
||||
{
|
||||
if ($oAttDef->GetTargetClass() != 'User') {
|
||||
throw new Exception("SetCurrentUser: the attribute $sAttCode must be an external key to 'User', found '".$oAttDef->GetTargetClass()."'");
|
||||
}
|
||||
}
|
||||
@@ -4572,8 +4706,9 @@ abstract class DBObject implements iDisplay
|
||||
public function GetRelatedObjectsUp($sRelCode, $iMaxDepth = 99, $bEnableRedundancy = true)
|
||||
{
|
||||
$oGraph = new RelationGraph();
|
||||
$oGraph->AddSourceObject($this);
|
||||
$oGraph->AddSinkObject($this);
|
||||
$oGraph->ComputeRelatedObjectsUp($sRelCode, $iMaxDepth, $bEnableRedundancy);
|
||||
|
||||
return $oGraph;
|
||||
}
|
||||
|
||||
@@ -5271,7 +5406,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
$oLnk->Set($sRoleAttCode, $sRoleValue);
|
||||
}
|
||||
$oLinkSet->AddObject($oLnk);
|
||||
$oLinkSet->AddItem($oLnk);
|
||||
$this->Set($sTargetListAttCode, $oLinkSet);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -416,6 +416,10 @@ class DBObjectSearch extends DBSearch
|
||||
* @param string $sFilterCode
|
||||
* @param mixed $value
|
||||
* @param string $sOpCode operator to use : 'IN', 'NOT IN', 'Contains',' Begins with', 'Finishes with', ...
|
||||
* If no operator is specified then :
|
||||
* * for id field we will use "="
|
||||
* * for other fields we will call the corresponding {@link AttributeDefinition::GetSmartConditionExpression} method impl
|
||||
* to generate the expression
|
||||
* @param bool $bParseSearchString
|
||||
*
|
||||
* @throws \CoreException
|
||||
@@ -465,14 +469,14 @@ class DBObjectSearch extends DBSearch
|
||||
if (!is_array($value)) $value = array($value);
|
||||
if (count($value) === 0) throw new Exception('AddCondition '.$sOpCode.': Value cannot be an empty array.');
|
||||
$sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')';
|
||||
$sOQLCondition = $oField->Render()." IN $sListExpr";
|
||||
$sOQLCondition = $oField->RenderExpression()." IN $sListExpr";
|
||||
break;
|
||||
|
||||
case 'NOTIN':
|
||||
if (!is_array($value)) $value = array($value);
|
||||
if (count($value) === 0) throw new Exception('AddCondition '.$sOpCode.': Value cannot be an empty array.');
|
||||
$sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')';
|
||||
$sOQLCondition = $oField->Render()." NOT IN $sListExpr";
|
||||
$sOQLCondition = $oField->RenderExpression()." NOT IN $sListExpr";
|
||||
break;
|
||||
|
||||
case 'Contains':
|
||||
@@ -1232,7 +1236,7 @@ class DBObjectSearch extends DBSearch
|
||||
elseif (MetaModel::IsParentClass($oRightFilter->GetFirstJoinedClass(), $oLeftFilter->GetClass()))
|
||||
{
|
||||
// Specialize $oRightFilter
|
||||
$oRightFilter->ChangeClass($oLeftFilter->GetClass());
|
||||
$oRightFilter->ChangeClass($oLeftFilter->GetFirstJoinedClass());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1368,7 +1372,7 @@ class DBObjectSearch extends DBSearch
|
||||
public function GetQueryParams($bExcludeMagicParams = true)
|
||||
{
|
||||
$aParams = array();
|
||||
$this->m_oSearchCondition->Render($aParams, true);
|
||||
$this->m_oSearchCondition->RenderExpression(false, $aParams, true);
|
||||
|
||||
if ($bExcludeMagicParams)
|
||||
{
|
||||
@@ -1457,7 +1461,7 @@ class DBObjectSearch extends DBSearch
|
||||
|
||||
$sRes .= ' ' . $this->GetFirstJoinedClass() . ' AS `' . $this->GetFirstJoinedClassAlias() . '`';
|
||||
$sRes .= $this->ToOQL_Joins();
|
||||
$sRes .= " WHERE ".$this->m_oSearchCondition->Render($aParams, $bRetrofitParams);
|
||||
$sRes .= " WHERE ".$this->m_oSearchCondition->RenderExpression(false, $aParams, $bRetrofitParams);
|
||||
|
||||
if ($bWithAllowAllFlag && $this->m_bAllowAllData)
|
||||
{
|
||||
|
||||
@@ -416,7 +416,11 @@ class DBUnionSearch extends DBSearch
|
||||
$aSearches = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aSearches[] = $oSearch->Filter($sClassAlias, $oFilter);
|
||||
if (!$oSearch->IsAllDataAllowed()) {
|
||||
$aSearches[] = $oSearch->Filter($sClassAlias, $oFilter);
|
||||
} else {
|
||||
$aSearches[] = $oSearch;
|
||||
}
|
||||
}
|
||||
return new DBUnionSearch($aSearches);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ class Dict
|
||||
protected static $m_aLanguages = array(); // array( code => array( 'description' => '...', 'localized_description' => '...') ...)
|
||||
protected static $m_aData = array();
|
||||
protected static $m_sApplicationPrefix = null;
|
||||
/** @var \ApcService $m_oApcService */
|
||||
protected static $m_oApcService = null;
|
||||
|
||||
/**
|
||||
* @param $sLanguageCode
|
||||
@@ -116,15 +118,17 @@ class Dict
|
||||
{
|
||||
// Attempt to find the string in the user language
|
||||
//
|
||||
self::InitLangIfNeeded(self::GetUserLanguage());
|
||||
$sLangCode = self::GetUserLanguage();
|
||||
self::InitLangIfNeeded($sLangCode);
|
||||
|
||||
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
|
||||
if (!array_key_exists($sLangCode, self::$m_aData))
|
||||
{
|
||||
IssueLog::Warning("Cannot find $sLangCode in dictionnaries. default labels displayed");
|
||||
// It may happen, when something happens before the dictionaries get loaded
|
||||
return $sStringCode;
|
||||
}
|
||||
$aCurrentDictionary = self::$m_aData[self::GetUserLanguage()];
|
||||
if (array_key_exists($sStringCode, $aCurrentDictionary))
|
||||
$aCurrentDictionary = self::$m_aData[$sLangCode];
|
||||
if (is_array($aCurrentDictionary) && array_key_exists($sStringCode, $aCurrentDictionary))
|
||||
{
|
||||
return $aCurrentDictionary[$sStringCode];
|
||||
}
|
||||
@@ -135,7 +139,7 @@ class Dict
|
||||
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
||||
|
||||
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
|
||||
if (array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
{
|
||||
return $aDefaultDictionary[$sStringCode];
|
||||
}
|
||||
@@ -144,7 +148,7 @@ class Dict
|
||||
self::InitLangIfNeeded('EN US');
|
||||
|
||||
$aDefaultDictionary = self::$m_aData['EN US'];
|
||||
if (array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
{
|
||||
return $aDefaultDictionary[$sStringCode];
|
||||
}
|
||||
@@ -203,7 +207,26 @@ class Dict
|
||||
{
|
||||
self::$m_aLanguages = $aLanguagesList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 2.7.6 N°4125
|
||||
* @return \ApcService
|
||||
*/
|
||||
public static function GetApcService() {
|
||||
if (self::$m_oApcService === null){
|
||||
self::$m_oApcService = new ApcService();
|
||||
}
|
||||
return self::$m_oApcService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7.6 N°4125
|
||||
* @param \ApcService $m_oApcService
|
||||
*/
|
||||
public static function SetApcService($oApcService) {
|
||||
self::$m_oApcService = $oApcService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a language from the language dictionary, if not already loaded
|
||||
* @param string $sLangCode Language code
|
||||
@@ -212,20 +235,23 @@ class Dict
|
||||
public static function InitLangIfNeeded($sLangCode)
|
||||
{
|
||||
if (array_key_exists($sLangCode, self::$m_aData)) return true;
|
||||
|
||||
|
||||
$bResult = false;
|
||||
|
||||
if (function_exists('apc_fetch') && (self::$m_sApplicationPrefix !== null))
|
||||
|
||||
if (self::GetApcService()->function_exists('apc_fetch')
|
||||
&& (self::$m_sApplicationPrefix !== null))
|
||||
{
|
||||
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
|
||||
//
|
||||
self::$m_aData[$sLangCode] = apc_fetch(self::$m_sApplicationPrefix.'-dict-'.$sLangCode);
|
||||
if (self::$m_aData[$sLangCode] === false)
|
||||
{
|
||||
self::$m_aData[$sLangCode] = self::GetApcService()->apc_fetch(self::$m_sApplicationPrefix.'-dict-'.$sLangCode);
|
||||
if (self::$m_aData[$sLangCode] === false) {
|
||||
unset(self::$m_aData[$sLangCode]);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else if (! is_array(self::$m_aData[$sLangCode])) {
|
||||
// N°4125: we dont fix dictionnary corrupted cache (on iTop side).
|
||||
// but we log an error in a dedicated channel to let itop administrator be aware of a potential APCu issue to fix.
|
||||
IssueLog::Error("APCu corrupted data (with $sLangCode dictionnary). APCu configuration and running version should be troubleshooted...", LogChannels::APC);
|
||||
$bResult = true;
|
||||
} else {
|
||||
$bResult = true;
|
||||
}
|
||||
}
|
||||
@@ -234,9 +260,10 @@ class Dict
|
||||
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
|
||||
require_once($sDictFile);
|
||||
|
||||
if (function_exists('apc_store') && (self::$m_sApplicationPrefix !== null))
|
||||
if (self::GetApcService()->function_exists('apc_store')
|
||||
&& (self::$m_sApplicationPrefix !== null))
|
||||
{
|
||||
apc_store(self::$m_sApplicationPrefix.'-dict-'.$sLangCode, self::$m_aData[$sLangCode]);
|
||||
self::GetApcService()->apc_store(self::$m_sApplicationPrefix.'-dict-'.$sLangCode, self::$m_aData[$sLangCode]);
|
||||
}
|
||||
$bResult = true;
|
||||
}
|
||||
@@ -275,13 +302,12 @@ class Dict
|
||||
*
|
||||
* @param $sSourceCode
|
||||
* @param $sDestCode
|
||||
* @since 3.0.1 Not clone sSourceCode entry if sDestCode entry already exist
|
||||
*/
|
||||
public static function CloneString($sSourceCode, $sDestCode)
|
||||
{
|
||||
foreach(self::$m_aLanguages as $sLanguageCode => $foo)
|
||||
{
|
||||
if (isset(self::$m_aData[$sLanguageCode][$sSourceCode]))
|
||||
{
|
||||
foreach(self::$m_aLanguages as $sLanguageCode => $foo) {
|
||||
if (isset(self::$m_aData[$sLanguageCode][$sSourceCode]) && !isset(self::$m_aData[$sLanguageCode][$sDestCode] )) {
|
||||
self::$m_aData[$sLanguageCode][$sDestCode] = self::$m_aData[$sLanguageCode][$sSourceCode];
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,10 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Pelago\Emogrifier\CssInliner;
|
||||
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
|
||||
use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
|
||||
|
||||
Swift_Preferences::getInstance()->setCharset('UTF-8');
|
||||
|
||||
|
||||
@@ -335,8 +339,9 @@ class EMail
|
||||
{
|
||||
if (($sMimeType === 'text/html') && ($sCustomStyles !== null))
|
||||
{
|
||||
$emogrifier = new \Pelago\Emogrifier($sBody, $sCustomStyles);
|
||||
$sBody = $emogrifier->emogrify(); // Adds html/body tags if not already present
|
||||
$oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument();
|
||||
HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone();
|
||||
$sBody = CssToAttributeConverter::fromDomDocument($oDomDocument)->convertCssToVisualAttributes()->render(); // Adds html/body tags if not already present
|
||||
}
|
||||
$this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType);
|
||||
$this->m_oMessage->setBody($sBody, $sMimeType);
|
||||
|
||||
@@ -222,6 +222,10 @@ class EventIssue extends Event
|
||||
//
|
||||
$this->Set('page', @$GLOBALS['_SERVER']['SCRIPT_NAME']);
|
||||
|
||||
if (strlen($this->Get('userinfo')) == 0) {
|
||||
$this->Set('userinfo', UserRights::GetUserId());
|
||||
}
|
||||
|
||||
if (array_key_exists('_GET', $GLOBALS) && is_array($GLOBALS['_GET']))
|
||||
{
|
||||
$this->Set('arguments_get', $GLOBALS['_GET']);
|
||||
|
||||
@@ -111,16 +111,15 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="excel_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oRadioCustom = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput), "excel_date_format_radio", "custom", "excel_date_time_format_custom", "radio");
|
||||
$oRadioCustom->SetDescription(Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip'));
|
||||
$oRadioCustom->GetInput()->SetIsChecked($sDateTimeFormat !== (string)AttributeDateTime::GetFormat());
|
||||
$oRadioCustom->SetBeforeInput(false);
|
||||
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
$oFieldSetDate->AddSubBlock($oRadioCustom);
|
||||
|
||||
|
||||
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#excel_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_xlsx_options').on('preview_updated', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
$('#excel_date_time_format_default').on('click', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
$('#excel_date_time_format_custom').on('click', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
|
||||
@@ -97,64 +97,163 @@ class HTMLNullSanitizer extends HTMLSanitizer
|
||||
{
|
||||
return $sHTML;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A standard-compliant HTMLSanitizer based on the HTMLPurifier library by Edward Z. Yang
|
||||
* Complete but quite slow
|
||||
* http://htmlpurifier.org
|
||||
* Common implementation for sanitizer using DOM parsing
|
||||
*/
|
||||
/*
|
||||
class HTMLPurifierSanitizer extends HTMLSanitizer
|
||||
abstract class DOMSanitizer extends HTMLSanitizer
|
||||
{
|
||||
protected static $oPurifier = null;
|
||||
/** @var DOMDocument */
|
||||
protected $oDoc;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (self::$oPurifier == null)
|
||||
{
|
||||
$sLibPath = APPROOT.'lib/htmlpurifier/HTMLPurifier.auto.php';
|
||||
if (!file_exists($sLibPath))
|
||||
{
|
||||
throw new Exception("Missing library '$sLibPath', cannot use HTMLPurifierSanitizer.");
|
||||
}
|
||||
require_once($sLibPath);
|
||||
abstract public function GetTagsWhiteList();
|
||||
|
||||
$oPurifierConfig = HTMLPurifier_Config::createDefault();
|
||||
$oPurifierConfig->set('Core.Encoding', 'UTF-8'); // defaults to 'UTF-8'
|
||||
$oPurifierConfig->set('HTML.Doctype', 'XHTML 1.0 Strict'); // defaults to 'XHTML 1.0 Transitional'
|
||||
$oPurifierConfig->set('URI.AllowedSchemes', array (
|
||||
'http' => true,
|
||||
'https' => true,
|
||||
'data' => true, // This one is not present by default
|
||||
));
|
||||
$sPurifierCache = APPROOT.'data/HTMLPurifier';
|
||||
if (!is_dir($sPurifierCache))
|
||||
{
|
||||
mkdir($sPurifierCache);
|
||||
}
|
||||
if (!is_dir($sPurifierCache))
|
||||
{
|
||||
throw new Exception("Could not create the cache directory '$sPurifierCache'");
|
||||
}
|
||||
$oPurifierConfig->set('Cache.SerializerPath', $sPurifierCache); // no trailing slash
|
||||
self::$oPurifier = new HTMLPurifier($oPurifierConfig);
|
||||
}
|
||||
}
|
||||
abstract public function GetTagsBlackList();
|
||||
|
||||
abstract public function GetAttrsWhiteList();
|
||||
|
||||
abstract public function GetAttrsBlackList();
|
||||
|
||||
abstract public function GetStylesWhiteList();
|
||||
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
$sCleanHtml = self::$oPurifier->purify($sHTML);
|
||||
$this->oDoc = new DOMDocument();
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
|
||||
// MS outlook implements empty lines by the mean of <p><o:p></o:p></p>
|
||||
// We have to transform that into <p><br></p> (which is how Thunderbird implements empty lines)
|
||||
// Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace)
|
||||
// therefore we have to do the transformation upfront
|
||||
$sHTML = preg_replace('@<o:p>(\s| )*</o:p>@', '<br>', $sHTML);
|
||||
|
||||
$this->LoadDoc($sHTML);
|
||||
|
||||
$this->CleanNode($this->oDoc);
|
||||
|
||||
$sCleanHtml = $this->PrintDoc();
|
||||
|
||||
return $sCleanHtml;
|
||||
}
|
||||
|
||||
abstract public function LoadDoc($sHTML);
|
||||
|
||||
/**
|
||||
* @return string cleaned source
|
||||
* @uses \DOMSanitizer::oDoc
|
||||
*/
|
||||
abstract public function PrintDoc();
|
||||
|
||||
protected function CleanNode(DOMNode $oElement)
|
||||
{
|
||||
$aAttrToRemove = array();
|
||||
// Gather the attributes to remove
|
||||
if ($oElement->hasAttributes()) {
|
||||
foreach ($oElement->attributes as $oAttr) {
|
||||
$sAttr = strtolower($oAttr->name);
|
||||
if ((false === empty($this->GetAttrsBlackList()))
|
||||
&& (in_array($sAttr, $this->GetAttrsBlackList(), true))) {
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
} else if ((false === empty($this->GetTagsWhiteList()))
|
||||
&& (false === in_array($sAttr, $this->GetTagsWhiteList()[strtolower($oElement->tagName)]))) {
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
} else if (!$this->IsValidAttributeContent($sAttr, $oAttr->value)) {
|
||||
// Invalid content
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
} else if ($sAttr == 'style') {
|
||||
// Special processing for style tags
|
||||
$sCleanStyle = $this->CleanStyle($oAttr->value);
|
||||
if ($sCleanStyle == '') {
|
||||
// Invalid content
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
} else {
|
||||
$oElement->setAttribute($oAttr->name, $sCleanStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aAttrToRemove as $sName)
|
||||
{
|
||||
$oElement->removeAttribute($sName);
|
||||
}
|
||||
}
|
||||
|
||||
if ($oElement->hasChildNodes())
|
||||
{
|
||||
$aChildElementsToRemove = array();
|
||||
// Gather the child noes to remove
|
||||
foreach($oElement->childNodes as $oNode) {
|
||||
if ($oNode instanceof DOMElement) {
|
||||
$sNodeTagName = strtolower($oNode->tagName);
|
||||
}
|
||||
if (($oNode instanceof DOMElement)
|
||||
&& (false === empty($this->GetTagsBlackList()))
|
||||
&& (in_array($sNodeTagName, $this->GetTagsBlackList(), true))) {
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
} else if (($oNode instanceof DOMElement)
|
||||
&& (false === empty($this->GetTagsWhiteList()))
|
||||
&& (false === array_key_exists($sNodeTagName, $this->GetTagsWhiteList()))) {
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
} else if ($oNode instanceof DOMComment) {
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
} else {
|
||||
// Recurse
|
||||
$this->CleanNode($oNode);
|
||||
if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img')) {
|
||||
InlineImage::ProcessImageTag($oNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aChildElementsToRemove as $oDomElement)
|
||||
{
|
||||
$oElement->removeChild($oDomElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function IsValidAttributeContent($sAttributeName, $sValue)
|
||||
{
|
||||
if ((false === empty($this->GetAttrsBlackList()))
|
||||
&& (in_array($sAttributeName, $this->GetAttrsBlackList(), true))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (array_key_exists($sAttributeName, $this->GetAttrsWhiteList())) {
|
||||
return preg_match($this->GetAttrsWhiteList()[$sAttributeName], $sValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function CleanStyle($sStyle)
|
||||
{
|
||||
if (empty($this->GetStylesWhiteList())) {
|
||||
return $sStyle;
|
||||
}
|
||||
|
||||
$aAllowedStyles = array();
|
||||
$aItems = explode(';', $sStyle);
|
||||
{
|
||||
foreach ($aItems as $sItem) {
|
||||
$aElements = explode(':', trim($sItem));
|
||||
if (in_array(trim(strtolower($aElements[0])), $this->GetStylesWhiteList())) {
|
||||
$aAllowedStyles[] = trim($sItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode(';', $aAllowedStyles);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
|
||||
|
||||
class HTMLDOMSanitizer extends DOMSanitizer
|
||||
{
|
||||
protected $oDoc;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @see https://www.itophub.io/wiki/page?id=2_6_0%3Aadmin%3Arich_text_limitations
|
||||
@@ -239,6 +338,31 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
'white-space',
|
||||
);
|
||||
|
||||
public function GetTagsWhiteList()
|
||||
{
|
||||
return static::$aTagsWhiteList;
|
||||
}
|
||||
|
||||
public function GetTagsBlackList()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function GetAttrsWhiteList()
|
||||
{
|
||||
return static::$aAttrsWhiteList;
|
||||
}
|
||||
|
||||
public function GetAttrsBlackList()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function GetStylesWhiteList()
|
||||
{
|
||||
return static::$aStylesWhiteList;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
@@ -264,139 +388,152 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
}
|
||||
}
|
||||
|
||||
public function DoSanitize($sHTML)
|
||||
public function LoadDoc($sHTML)
|
||||
{
|
||||
$this->oDoc = new DOMDocument();
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
|
||||
// MS outlook implements empty lines by the mean of <p><o:p></o:p></p>
|
||||
// We have to transform that into <p><br></p> (which is how Thunderbird implements empty lines)
|
||||
// Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace)
|
||||
// therefore we have to do the transformation upfront
|
||||
$sHTML = preg_replace('@<o:p>(\s| )*</o:p>@', '<br>', $sHTML);
|
||||
// Replace badly encoded non breaking space
|
||||
$sHTML = preg_replace('~\xc2\xa0~', ' ', $sHTML);
|
||||
|
||||
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
}
|
||||
|
||||
$this->CleanNode($this->oDoc);
|
||||
|
||||
public function PrintDoc()
|
||||
{
|
||||
$oXPath = new DOMXPath($this->oDoc);
|
||||
$sXPath = "//body";
|
||||
$oNodesList = $oXPath->query($sXPath);
|
||||
|
||||
if ($oNodesList->length == 0)
|
||||
{
|
||||
if ($oNodesList->length == 0) {
|
||||
// No body, save the whole document
|
||||
$sCleanHtml = $this->oDoc->saveHTML();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Export only the content of the body tag
|
||||
$sCleanHtml = $this->oDoc->saveHTML($oNodesList->item(0));
|
||||
// remove the body tag itself
|
||||
$sCleanHtml = str_replace( array('<body>', '</body>'), '', $sCleanHtml);
|
||||
$sCleanHtml = str_replace(array('<body>', '</body>'), '', $sCleanHtml);
|
||||
}
|
||||
|
||||
return $sCleanHtml;
|
||||
}
|
||||
}
|
||||
|
||||
protected function CleanNode(DOMNode $oElement)
|
||||
|
||||
|
||||
/**
|
||||
* @since 2.6.5 2.7.6 3.0.0 N°4360
|
||||
*/
|
||||
class SVGDOMSanitizer extends DOMSanitizer
|
||||
{
|
||||
public function GetTagsWhiteList()
|
||||
{
|
||||
$aAttrToRemove = array();
|
||||
// Gather the attributes to remove
|
||||
if ($oElement->hasAttributes())
|
||||
{
|
||||
foreach($oElement->attributes as $oAttr)
|
||||
{
|
||||
$sAttr = strtolower($oAttr->name);
|
||||
if (!in_array($sAttr, self::$aTagsWhiteList[strtolower($oElement->tagName)]))
|
||||
{
|
||||
// Forbidden (or unknown) attribute
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
}
|
||||
else if (!$this->IsValidAttributeContent($sAttr, $oAttr->value))
|
||||
{
|
||||
// Invalid content
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
}
|
||||
else if ($sAttr == 'style')
|
||||
{
|
||||
// Special processing for style tags
|
||||
$sCleanStyle = $this->CleanStyle($oAttr->value);
|
||||
if ($sCleanStyle == '')
|
||||
{
|
||||
// Invalid content
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oElement->setAttribute($oAttr->name, $sCleanStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aAttrToRemove as $sName)
|
||||
{
|
||||
$oElement->removeAttribute($sName);
|
||||
}
|
||||
}
|
||||
|
||||
if ($oElement->hasChildNodes())
|
||||
{
|
||||
$aChildElementsToRemove = array();
|
||||
// Gather the child noes to remove
|
||||
foreach($oElement->childNodes as $oNode)
|
||||
{
|
||||
if (($oNode instanceof DOMElement) && (!array_key_exists(strtolower($oNode->tagName), self::$aTagsWhiteList)))
|
||||
{
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
}
|
||||
else if ($oNode instanceof DOMComment)
|
||||
{
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Recurse
|
||||
$this->CleanNode($oNode);
|
||||
if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img'))
|
||||
{
|
||||
InlineImage::ProcessImageTag($oNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aChildElementsToRemove as $oDomElement)
|
||||
{
|
||||
$oElement->removeChild($oDomElement);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function CleanStyle($sStyle)
|
||||
/**
|
||||
* @return string[]
|
||||
* @link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script
|
||||
*/
|
||||
public function GetTagsBlackList()
|
||||
{
|
||||
$aAllowedStyles = array();
|
||||
$aItems = explode(';', $sStyle);
|
||||
{
|
||||
foreach($aItems as $sItem)
|
||||
{
|
||||
$aElements = explode(':', trim($sItem));
|
||||
if (in_array(trim(strtolower($aElements[0])), static::$aStylesWhiteList))
|
||||
{
|
||||
$aAllowedStyles[] = trim($sItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return implode(';', $aAllowedStyles);
|
||||
return [
|
||||
'script',
|
||||
];
|
||||
}
|
||||
|
||||
protected function IsValidAttributeContent($sAttributeName, $sValue)
|
||||
public function GetAttrsWhiteList()
|
||||
{
|
||||
if (array_key_exists($sAttributeName, self::$aAttrsWhiteList))
|
||||
{
|
||||
return preg_match(self::$aAttrsWhiteList[$sAttributeName], $sValue);
|
||||
}
|
||||
return true;
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Events#document_event_attributes
|
||||
*/
|
||||
public function GetAttrsBlackList()
|
||||
{
|
||||
return [
|
||||
'onbegin',
|
||||
'onbegin',
|
||||
'onrepeat',
|
||||
'onabort',
|
||||
'onerror',
|
||||
'onerror',
|
||||
'onscroll',
|
||||
'onunload',
|
||||
'oncopy',
|
||||
'oncut',
|
||||
'onpaste',
|
||||
'oncancel',
|
||||
'oncanplay',
|
||||
'oncanplaythrough',
|
||||
'onchange',
|
||||
'onclick',
|
||||
'onclose',
|
||||
'oncuechange',
|
||||
'ondblclick',
|
||||
'ondrag',
|
||||
'ondragend',
|
||||
'ondragenter',
|
||||
'ondragleave',
|
||||
'ondragover',
|
||||
'ondragstart',
|
||||
'ondrop',
|
||||
'ondurationchange',
|
||||
'onemptied',
|
||||
'onended',
|
||||
'onerror',
|
||||
'onfocus',
|
||||
'oninput',
|
||||
'oninvalid',
|
||||
'onkeydown',
|
||||
'onkeypress',
|
||||
'onkeyup',
|
||||
'onload',
|
||||
'onloadeddata',
|
||||
'onloadedmetadata',
|
||||
'onloadstart',
|
||||
'onmousedown',
|
||||
'onmouseenter',
|
||||
'onmouseleave',
|
||||
'onmousemove',
|
||||
'onmouseout',
|
||||
'onmouseover',
|
||||
'onmouseup',
|
||||
'onmousewheel',
|
||||
'onpause',
|
||||
'onplay',
|
||||
'onplaying',
|
||||
'onprogress',
|
||||
'onratechange',
|
||||
'onreset',
|
||||
'onresize',
|
||||
'onscroll',
|
||||
'onseeked',
|
||||
'onseeking',
|
||||
'onselect',
|
||||
'onshow',
|
||||
'onstalled',
|
||||
'onsubmit',
|
||||
'onsuspend',
|
||||
'ontimeupdate',
|
||||
'ontoggle',
|
||||
'onvolumechange',
|
||||
'onwaiting',
|
||||
'onactivate',
|
||||
'onfocusin',
|
||||
'onfocusout',
|
||||
];
|
||||
}
|
||||
|
||||
public function GetStylesWhiteList()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function LoadDoc($sHTML)
|
||||
{
|
||||
@$this->oDoc->loadXml($sHTML, LIBXML_NOBLANKS);
|
||||
}
|
||||
|
||||
public function PrintDoc()
|
||||
{
|
||||
return $this->oDoc->saveXML();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,13 +362,8 @@ class InlineImage extends DBObject
|
||||
{
|
||||
$sJS =
|
||||
<<<JS
|
||||
$('img[data-img-id]').each(function() {
|
||||
if ($(this).width() > {$iMaxWidth})
|
||||
{
|
||||
$(this).css({'max-width': '{$iMaxWidth}px', width: '', height: '', 'max-height': ''});
|
||||
}
|
||||
$(this).addClass('inline-image').attr('href', $(this).attr('src'));
|
||||
}).magnificPopup({type: 'image', closeOnContentClick: true });
|
||||
CombodoInlineImage.SetMaxWidth('{$iMaxWidth}');
|
||||
CombodoInlineImage.FixImagesWidth();
|
||||
JS
|
||||
;
|
||||
}
|
||||
@@ -548,8 +543,6 @@ JS
|
||||
// Hook the file upload of all CKEditor instances
|
||||
$('.htmlEditor').each(function() {
|
||||
var oEditor = $(this).ckeditorGet();
|
||||
oEditor.config.extraPlugins = 'font,uploadimage';
|
||||
oEditor.config.uploadUrl = '$sAbsoluteUrlAppRoot'+'pages/ajax.render.php';
|
||||
oEditor.config.filebrowserBrowseUrl = '$sAbsoluteUrlAppRoot'+'pages/ajax.render.php?operation=cke_browse&temp_id=$sTempId&obj_class=$sObjClass&obj_key=$iObjKey';
|
||||
oEditor.on( 'fileUploadResponse', function( evt ) {
|
||||
var fileLoader = evt.data.fileLoader;
|
||||
|
||||
@@ -31,29 +31,38 @@ class ExecutionKPI
|
||||
static protected $m_bBlameCaller = false;
|
||||
static protected $m_sAllowedUser = '*';
|
||||
|
||||
static protected $m_aStats = array(); // Recurrent operations
|
||||
static protected $m_aExecData = array(); // One shot operations
|
||||
static protected $m_aStats = []; // Recurrent operations
|
||||
static protected $m_aExecData = []; // One shot operations
|
||||
/**
|
||||
* @var array[ExecutionKPI]
|
||||
*/
|
||||
static protected $m_aExecutionStack = []; // embedded execution stats
|
||||
|
||||
protected $m_fStarted = null;
|
||||
protected $m_fChildrenDuration = 0; // Count embedded
|
||||
protected $m_iInitialMemory = null;
|
||||
|
||||
static public function EnableDuration($iLevel)
|
||||
{
|
||||
if ($iLevel > 0)
|
||||
{
|
||||
if ($iLevel > 0) {
|
||||
self::$m_bEnabled_Duration = true;
|
||||
if ($iLevel > 1)
|
||||
{
|
||||
if ($iLevel > 1) {
|
||||
self::$m_bBlameCaller = true;
|
||||
} else {
|
||||
self::$m_bBlameCaller = false;
|
||||
}
|
||||
} else {
|
||||
self::$m_bEnabled_Duration = false;
|
||||
self::$m_bBlameCaller = false;
|
||||
}
|
||||
}
|
||||
|
||||
static public function EnableMemory($iLevel)
|
||||
{
|
||||
if ($iLevel > 0)
|
||||
{
|
||||
if ($iLevel > 0) {
|
||||
self::$m_bEnabled_Memory = true;
|
||||
} else {
|
||||
self::$m_bEnabled_Memory = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,16 +112,17 @@ class ExecutionKPI
|
||||
|
||||
$sTableStyle = 'background-color: #ccc; margin: 10px;';
|
||||
|
||||
self::Report("<hr/>");
|
||||
self::Report("<div style=\"background-color: grey; padding: 10px;\">");
|
||||
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>");
|
||||
self::Report("<p>".date('Y-m-d H:i:s', $fItopStarted)."</p>");
|
||||
self::Report("<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>");
|
||||
self::Report("<div>");
|
||||
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
|
||||
self::Report("<thead>");
|
||||
self::Report(" <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>");
|
||||
self::Report("</thead>");
|
||||
$sHtml = "<hr/>";
|
||||
$sHtml .= "<div style=\"background-color: grey; padding: 10px;\">";
|
||||
$sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>";
|
||||
$oStarted = DateTime::createFromFormat('U.u', $fItopStarted);
|
||||
$sHtml .= "<p>".$oStarted->format('Y-m-d H:i:s.u')."</p>";
|
||||
$sHtml .= "<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>";
|
||||
$sHtml .= "<div>";
|
||||
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
|
||||
$sHtml .= "<thead>";
|
||||
$sHtml .= " <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>";
|
||||
$sHtml .= "</thead>";
|
||||
foreach (self::$m_aExecData as $aOpStats)
|
||||
{
|
||||
$sOperation = $aOpStats['op'];
|
||||
@@ -134,12 +144,12 @@ class ExecutionKPI
|
||||
}
|
||||
}
|
||||
|
||||
self::Report("<tr>");
|
||||
self::Report(" <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>");
|
||||
self::Report("</tr>");
|
||||
$sHtml .= "<tr>";
|
||||
$sHtml .= " <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>";
|
||||
$sHtml .= "</tr>";
|
||||
}
|
||||
self::Report("</table>");
|
||||
self::Report("</div>");
|
||||
$sHtml .= "</table>";
|
||||
$sHtml .= "</div>";
|
||||
|
||||
$aConsolidatedStats = array();
|
||||
foreach (self::$m_aStats as $sOperation => $aOpStats)
|
||||
@@ -175,11 +185,11 @@ class ExecutionKPI
|
||||
);
|
||||
}
|
||||
|
||||
self::Report("<div>");
|
||||
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
|
||||
self::Report("<thead>");
|
||||
self::Report(" <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>");
|
||||
self::Report("</thead>");
|
||||
$sHtml .= "<div>";
|
||||
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
|
||||
$sHtml .= "<thead>";
|
||||
$sHtml .= " <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>";
|
||||
$sHtml .= "</thead>";
|
||||
foreach ($aConsolidatedStats as $sOperation => $aOpStats)
|
||||
{
|
||||
$sOperation = '<a href="#'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
|
||||
@@ -189,22 +199,24 @@ class ExecutionKPI
|
||||
$sMax = '<a href="#'.md5($sExecId.$aOpStats['max_args']).'">'.round($aOpStats['max'], 3).'</a>';
|
||||
$sAvg = round($aOpStats['avg'], 3);
|
||||
|
||||
self::Report("<tr>");
|
||||
self::Report(" <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>");
|
||||
self::Report("</tr>");
|
||||
$sHtml .= "<tr>";
|
||||
$sHtml .= " <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>";
|
||||
$sHtml .= "</tr>";
|
||||
}
|
||||
self::Report("</table>");
|
||||
self::Report("</div>");
|
||||
$sHtml .= "</table>";
|
||||
$sHtml .= "</div>";
|
||||
|
||||
self::Report("</div>");
|
||||
$sHtml .= "</div>";
|
||||
|
||||
self::Report("<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>");
|
||||
$sHtml .= "<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>";
|
||||
|
||||
$fSlowQueries = MetaModel::GetConfig()->Get('log_kpi_slow_queries');
|
||||
self::Report($sHtml);
|
||||
|
||||
// Report operation details
|
||||
foreach (self::$m_aStats as $sOperation => $aOpStats)
|
||||
{
|
||||
$sHtml = '';
|
||||
$bDisplayHeader = true;
|
||||
foreach ($aOpStats as $sArguments => $aEvents)
|
||||
{
|
||||
@@ -250,31 +262,59 @@ class ExecutionKPI
|
||||
if ($bDisplayHeader)
|
||||
{
|
||||
$sOperationHtml = '<a name="'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
|
||||
self::Report("<h4>$sOperationHtml</h4>");
|
||||
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
|
||||
self::Report("<thead>");
|
||||
self::Report(" <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>");
|
||||
self::Report("</thead>");
|
||||
$sHtml .= "<h4>$sOperationHtml</h4>";
|
||||
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
|
||||
$sHtml .= "<thead>";
|
||||
$sHtml .= " <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>";
|
||||
$sHtml .= "</thead>";
|
||||
$bDisplayHeader = false;
|
||||
}
|
||||
self::Report("<tr>");
|
||||
self::Report(" <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>");
|
||||
self::Report("</tr>");
|
||||
$sHtml .= "<tr>";
|
||||
$sHtml .= " <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>";
|
||||
$sHtml .= "</tr>";
|
||||
}
|
||||
}
|
||||
if (!$bDisplayHeader)
|
||||
{
|
||||
self::Report("</table>");
|
||||
self::Report("<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>");
|
||||
$sHtml .= "</table>";
|
||||
$sHtml .= "<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>";
|
||||
}
|
||||
self::Report($sHtml);
|
||||
}
|
||||
self::Report('<a name="end-'.md5($sExecId).'"> </a>');
|
||||
$sHtml = '<a name="end-'.md5($sExecId).'"> </a>';
|
||||
self::Report($sHtml);
|
||||
}
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->ResetCounters();
|
||||
self::Push($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stack executions to remove children duration from stats
|
||||
*
|
||||
* @param \ExecutionKPI $oExecutionKPI
|
||||
*/
|
||||
private static function Push(ExecutionKPI $oExecutionKPI)
|
||||
{
|
||||
array_push(self::$m_aExecutionStack, $oExecutionKPI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop current child and count its duration in its parent
|
||||
*
|
||||
* @param float|int $fChildDuration
|
||||
*/
|
||||
private static function Pop(float $fChildDuration = 0)
|
||||
{
|
||||
array_pop(self::$m_aExecutionStack);
|
||||
// Update the parent's children duration
|
||||
$oPrevExecutionKPI = end(self::$m_aExecutionStack);
|
||||
if ($oPrevExecutionKPI) {
|
||||
$oPrevExecutionKPI->m_fChildrenDuration += $fChildDuration;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the duration since startup, and reset the counter for the next measure
|
||||
@@ -285,13 +325,12 @@ class ExecutionKPI
|
||||
|
||||
$aNewEntry = null;
|
||||
|
||||
if (self::$m_bEnabled_Duration)
|
||||
{
|
||||
if (self::$m_bEnabled_Duration) {
|
||||
$fStopped = MyHelpers::getmicrotime();
|
||||
$aNewEntry = array(
|
||||
'op' => $sOperationDesc,
|
||||
'op' => $sOperationDesc,
|
||||
'time_begin' => $this->m_fStarted - $fItopStarted,
|
||||
'time_end' => $fStopped - $fItopStarted,
|
||||
'time_end' => $fStopped - $fItopStarted,
|
||||
);
|
||||
// Reset for the next operation (if the object is recycled)
|
||||
$this->m_fStarted = $fStopped;
|
||||
@@ -323,31 +362,30 @@ class ExecutionKPI
|
||||
|
||||
public function ComputeStats($sOperation, $sArguments)
|
||||
{
|
||||
if (self::$m_bEnabled_Duration)
|
||||
{
|
||||
$fDuration = 0;
|
||||
if (self::$m_bEnabled_Duration) {
|
||||
$fStopped = MyHelpers::getmicrotime();
|
||||
$fDuration = $fStopped - $this->m_fStarted;
|
||||
if (self::$m_bBlameCaller)
|
||||
{
|
||||
$fSelfDuration = $fDuration - $this->m_fChildrenDuration;
|
||||
if (self::$m_bBlameCaller) {
|
||||
self::$m_aStats[$sOperation][$sArguments][] = array(
|
||||
'time' => $fDuration,
|
||||
'time' => $fSelfDuration,
|
||||
'callers' => MyHelpers::get_callstack(1),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
self::$m_aStats[$sOperation][$sArguments][] = array(
|
||||
'time' => $fDuration
|
||||
'time' => $fSelfDuration,
|
||||
);
|
||||
}
|
||||
}
|
||||
self::Pop($fDuration);
|
||||
}
|
||||
|
||||
protected function ResetCounters()
|
||||
{
|
||||
if (self::$m_bEnabled_Duration)
|
||||
{
|
||||
$this->m_fStarted = MyHelpers::getmicrotime();
|
||||
$this->m_fStarted = microtime(true);
|
||||
}
|
||||
|
||||
if (self::$m_bEnabled_Memory)
|
||||
|
||||
@@ -455,6 +455,7 @@ class LogFileNameBuilderFactory
|
||||
class FileLog
|
||||
{
|
||||
protected $oFileNameBuilder;
|
||||
protected $bHasSQLite;
|
||||
|
||||
/**
|
||||
* FileLog constructor.
|
||||
@@ -467,6 +468,22 @@ class FileLog
|
||||
public function __construct($sFileName = '')
|
||||
{
|
||||
$this->oFileNameBuilder = LogFileNameBuilderFactory::GetInstance($sFileName);
|
||||
$sLogFilePath = $this->oFileNameBuilder->GetLogFilePath();
|
||||
$this->bHasSQLite = class_exists('SQLite3');
|
||||
if ($this->bHasSQLite) {
|
||||
if (empty($sLogFilePath)) {
|
||||
return;
|
||||
}
|
||||
$bCreate = false;
|
||||
$sDBFilePath = "$sLogFilePath.db";
|
||||
if (!is_file($sDBFilePath)) {
|
||||
$bCreate = true;
|
||||
}
|
||||
$this->DB = new SQLite3($sDBFilePath);
|
||||
if ($bCreate) {
|
||||
$this->DB->exec('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT, level TEXT, user TEXT, content LONGTEXT, context LONGTEXT, channel TEXT)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function Error($sText, $sChannel = '', $aContext = array())
|
||||
@@ -502,20 +519,33 @@ 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();
|
||||
if ($this->bHasSQLite) {
|
||||
$stmt = $this->DB->prepare('INSERT INTO log (date, level, user, content, context, channel) VALUES (:date, :level, :user, :content, :context, :channel)');
|
||||
$stmt->bindValue(':date', date('Y-m-d H:i:s'));
|
||||
$stmt->bindValue(':level', $sLevel);
|
||||
$stmt->bindValue(':user', LogAPI::GetUserInfo());
|
||||
$stmt->bindValue(':content', $sText);
|
||||
$stmt->bindValue(':context', empty($aContext) ? '' : var_export($aContext, true));
|
||||
$stmt->bindValue(':channel', $sChannel);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
if (empty($sLogFilePath))
|
||||
{
|
||||
$sTextPrefix = empty($sLevel) ? '' : (str_pad($sLevel, 7));
|
||||
$sTextPrefix .= ' | ';
|
||||
$sTextPrefix .= str_pad(LogAPI::GetUserInfo(), 5)." | ";
|
||||
|
||||
$sTextSuffix = ' | '.(empty($sChannel) ? '' : $sChannel);
|
||||
$sTextSuffix .= ' |||';
|
||||
|
||||
$sText = "{$sTextPrefix}{$sText}{$sTextSuffix}";
|
||||
|
||||
$sLogFilePath = $this->oFileNameBuilder->GetLogFilePath();
|
||||
if (empty($sLogFilePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hLogFile = @fopen($sLogFilePath, 'a');
|
||||
if ($hLogFile !== false)
|
||||
{
|
||||
if ($hLogFile !== false) {
|
||||
flock($hLogFile, LOCK_EX);
|
||||
$sDate = date('Y-m-d H:i:s');
|
||||
if (empty($aContext)) {
|
||||
@@ -540,12 +570,26 @@ class FileLog
|
||||
*/
|
||||
class LogChannels
|
||||
{
|
||||
public const CLI = 'CLI';
|
||||
public const CONSOLE = 'console';
|
||||
public const DEADLOCK = 'DeadLock';
|
||||
public const INLINE_IMAGE = 'InlineImage';
|
||||
public const PORTAL = 'portal';
|
||||
public const APC = 'apc';
|
||||
|
||||
public const CLI = 'CLI';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 2.7.7 N°4558 use this new channel when logging DB transactions
|
||||
* @since 3.0.0 logs info in CMDBSource (see commit a117906f)
|
||||
*/
|
||||
public const CMDB_SOURCE = 'cmdbsource';
|
||||
|
||||
public const CONSOLE = 'console';
|
||||
|
||||
public const CORE = 'core';
|
||||
|
||||
public const DEADLOCK = 'DeadLock';
|
||||
|
||||
public const INLINE_IMAGE = 'InlineImage';
|
||||
|
||||
public const PORTAL = 'portal';
|
||||
}
|
||||
|
||||
|
||||
@@ -559,34 +603,51 @@ abstract class LogAPI
|
||||
public const LEVEL_OK = 'Ok';
|
||||
public const LEVEL_DEBUG = 'Debug';
|
||||
public const LEVEL_TRACE = 'Trace';
|
||||
|
||||
/**
|
||||
* @var string default log level
|
||||
* @see GetMinLogLevel
|
||||
* @see GetMinLogLevel
|
||||
* @used-by GetLevelDefault
|
||||
* @var string default log level.
|
||||
* @since 2.7.1 N°2977
|
||||
*/
|
||||
public const LEVEL_DEFAULT = self::LEVEL_OK;
|
||||
|
||||
/**
|
||||
* @see GetMinLogLevel
|
||||
* @used-by GetLevelDefault
|
||||
* @var string|bool default log level when writing to DB: false by default in order to disable EventIssue creation, and so on, do not change the behavior.
|
||||
* @since 3.0.0 N°4261
|
||||
*/
|
||||
public const LEVEL_DEFAULT_DB = false;
|
||||
|
||||
protected static $aLevelsPriority = array(
|
||||
self::LEVEL_ERROR => 400,
|
||||
self::LEVEL_ERROR => 400,
|
||||
self::LEVEL_WARNING => 300,
|
||||
self::LEVEL_INFO => 200,
|
||||
self::LEVEL_OK => 200,
|
||||
self::LEVEL_DEBUG => 100,
|
||||
self::LEVEL_TRACE => 50,
|
||||
self::LEVEL_INFO => 200,
|
||||
self::LEVEL_OK => 200,
|
||||
self::LEVEL_DEBUG => 100,
|
||||
self::LEVEL_TRACE => 50,
|
||||
);
|
||||
|
||||
public const ENUM_CONFIG_PARAM_FILE = 'log_level_min';
|
||||
public const ENUM_CONFIG_PARAM_DB = 'log_level_min.write_in_db';
|
||||
|
||||
/**
|
||||
* @var \Config attribute allowing to mock config in the tests
|
||||
*/
|
||||
protected static $m_oMockMetaModelConfig = null;
|
||||
|
||||
protected static $oLastEventIssue = null;
|
||||
|
||||
public static function Enable($sTargetFile)
|
||||
{
|
||||
// m_oFileLog is not defined as a class attribute so that each impl will have its own
|
||||
static::$m_oFileLog = new FileLog($sTargetFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal uses only for testing purpose.
|
||||
*/
|
||||
public static function MockStaticObjects($oFileLog, $oMetaModelConfig = null)
|
||||
{
|
||||
static::$m_oFileLog = $oFileLog;
|
||||
@@ -628,10 +689,6 @@ abstract class LogAPI
|
||||
*/
|
||||
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array())
|
||||
{
|
||||
if (!static::$m_oFileLog) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset(self::$aLevelsPriority[$sLevel])) {
|
||||
IssueLog::Error("invalid log level '{$sLevel}'");
|
||||
|
||||
@@ -642,22 +699,46 @@ abstract class LogAPI
|
||||
$sChannel = static::CHANNEL_DEFAULT;
|
||||
}
|
||||
|
||||
if (!static::IsLogLevelEnabled($sLevel, $sChannel)) {
|
||||
return;
|
||||
static::WriteLog($sLevel, $sMessage, $sChannel, $aContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ConfigException
|
||||
*/
|
||||
protected static function WriteLog(string $sLevel, string $sMessage, ?string $sChannel = null, ?array $aContext = array()): void
|
||||
{
|
||||
if (
|
||||
(null !== static::$m_oFileLog)
|
||||
&& static::IsLogLevelEnabled($sLevel, $sChannel, static::ENUM_CONFIG_PARAM_FILE)
|
||||
) {
|
||||
static::$m_oFileLog->$sLevel($sMessage, $sChannel, $aContext);
|
||||
}
|
||||
|
||||
static::$m_oFileLog->$sLevel($sMessage, $sChannel, $aContext);
|
||||
if (static::IsLogLevelEnabled($sLevel, $sChannel, static::ENUM_CONFIG_PARAM_DB)) {
|
||||
self::WriteToDb($sMessage, $sChannel, $aContext);
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetUserInfo(): ?string
|
||||
{
|
||||
$oConnectedUser = UserRights::GetUserObject();
|
||||
if (is_null($oConnectedUser)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $oConnectedUser->GetKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ConfigException if log wrongly configured
|
||||
* @uses GetMinLogLevel
|
||||
*/
|
||||
final public static function IsLogLevelEnabled(string $sLevel, string $sChannel): bool
|
||||
final public static function IsLogLevelEnabled(string $sLevel, string $sChannel, string $sConfigKey = self::ENUM_CONFIG_PARAM_FILE): bool
|
||||
{
|
||||
$sMinLogLevel = self::GetMinLogLevel($sChannel);
|
||||
$sMinLogLevel = self::GetMinLogLevel($sChannel, $sConfigKey);
|
||||
|
||||
if ($sMinLogLevel === false || $sMinLogLevel === 'false') {
|
||||
// the is_bool call is to remove a IDE O:) warning as $sMinLogLevel is typed as string
|
||||
if ((is_bool($sMinLogLevel) && ($sMinLogLevel === false)) || $sMinLogLevel === 'false') {
|
||||
return false;
|
||||
}
|
||||
if (!is_string($sMinLogLevel)) {
|
||||
@@ -675,6 +756,7 @@ abstract class LogAPI
|
||||
|
||||
/**
|
||||
* @param string $sChannel
|
||||
* @param string $sConfigKey
|
||||
*
|
||||
* @return string one of the LEVEL_* const value : the one configured it if exists, otherwise default log level for this channel
|
||||
* Config can be set :
|
||||
@@ -690,21 +772,44 @@ abstract class LogAPI
|
||||
*
|
||||
* @uses \LogAPI::GetConfig()
|
||||
* @uses `log_level_min` config parameter
|
||||
* @uses `log_level_min.write_to_db` config parameter
|
||||
* @uses \LogAPI::GetLevelDefault
|
||||
*
|
||||
* @link https://www.itophub.io/wiki/page?id=3_0_0%3Aadmin%3Alog iTop log reference
|
||||
*/
|
||||
protected static function GetMinLogLevel($sChannel)
|
||||
protected static function GetMinLogLevel($sChannel, $sConfigKey = self::ENUM_CONFIG_PARAM_FILE)
|
||||
{
|
||||
$sLogLevelMin = static::GetLogConfig($sConfigKey);
|
||||
|
||||
$sConfiguredLevelForChannel = static::GetMinLogLevelFromChannel($sLogLevelMin, $sChannel, $sConfigKey);
|
||||
if (!is_null($sConfiguredLevelForChannel)) {
|
||||
return $sConfiguredLevelForChannel;
|
||||
}
|
||||
|
||||
return static::GetMinLogLevelFromDefault($sLogLevelMin, $sChannel, $sConfigKey);
|
||||
}
|
||||
|
||||
final protected static function GetLogConfig($sConfigKey)
|
||||
{
|
||||
$oConfig = static::GetConfig();
|
||||
if (!$oConfig instanceof Config) {
|
||||
return static::GetLevelDefault();
|
||||
return static::GetLevelDefault($sConfigKey);
|
||||
}
|
||||
|
||||
$sLogLevelMin = $oConfig->Get('log_level_min');
|
||||
return $oConfig->Get($sConfigKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $sLogLevelMin log config parameter value
|
||||
* @param string $sChannel
|
||||
* @param string $sConfigKey config option key
|
||||
*
|
||||
* @return string|null null if not defined
|
||||
*/
|
||||
protected static function GetMinLogLevelFromChannel($sLogLevelMin, $sChannel, $sConfigKey)
|
||||
{
|
||||
if (empty($sLogLevelMin)) {
|
||||
return static::GetLevelDefault();
|
||||
return static::GetLevelDefault($sConfigKey);
|
||||
}
|
||||
|
||||
if (!is_array($sLogLevelMin)) {
|
||||
@@ -715,38 +820,113 @@ abstract class LogAPI
|
||||
return $sLogLevelMin[$sChannel];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static function GetMinLogLevelFromDefault($sLogLevelMin, $sChannel, $sConfigKey)
|
||||
{
|
||||
if (isset($sLogLevelMin[static::CHANNEL_DEFAULT])) {
|
||||
return $sLogLevelMin[static::CHANNEL_DEFAULT];
|
||||
return $sLogLevelMin[static::CHANNEL_DEFAULT];
|
||||
}
|
||||
|
||||
// Even though the *self*::CHANNEL_DEFAULT is set to '' in the current class (LogAPI), the test below is necessary as the CHANNEL_DEFAULT constant can be (and is!) overloaded in derivated classes, don't remove this test to factorize it with the previous one.
|
||||
// Even though the *self*::CHANNEL_DEFAULT is set to '' in the current class (LogAPI), the test below is necessary as the CHANNEL_DEFAULT constant can be (and is!) overloaded in children classes, don't remove this test to factorize it with the previous one.
|
||||
if (isset($sLogLevelMin[''])) {
|
||||
return $sLogLevelMin[''];
|
||||
return $sLogLevelMin[''];
|
||||
}
|
||||
|
||||
return static::GetLevelDefault();
|
||||
return static::GetLevelDefault($sConfigKey);
|
||||
}
|
||||
|
||||
protected static function WriteToDb(string $sMessage, string $sChannel, array $aContext): void
|
||||
{
|
||||
if (false === MetaModel::IsLogEnabledIssue()) {
|
||||
return;
|
||||
}
|
||||
if (false === MetaModel::IsValidClass('EventIssue')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Protect against reentrance
|
||||
static $bWriteToDbReentrance;
|
||||
if ($bWriteToDbReentrance === true) {
|
||||
return;
|
||||
}
|
||||
$bWriteToDbReentrance = true;
|
||||
|
||||
try {
|
||||
self::$oLastEventIssue = static::GetEventIssue($sMessage, $sChannel, $aContext);
|
||||
self::$oLastEventIssue->DBInsertNoReload();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// calling low level methods : if we would call Error() for example we would try to write to DB again...
|
||||
static::$m_oFileLog->Error('Failed to log issue into the DB', LogChannels::CORE, [
|
||||
'exception message' => $e->getMessage(),
|
||||
'exception stack' => $e->getTraceAsString(),
|
||||
]);
|
||||
}
|
||||
finally {
|
||||
$bWriteToDbReentrance = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \OQLException
|
||||
*/
|
||||
protected static function GetEventIssue(string $sMessage, string $sChannel, array $aContext): EventIssue
|
||||
{
|
||||
$sDate = date('Y-m-d H:i:s');
|
||||
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
|
||||
$sCurrentCallStack = var_export($aStack, true);
|
||||
|
||||
$oEventIssue = new EventIssue();
|
||||
$oEventIssue->Set('issue', $sMessage);
|
||||
$oEventIssue->Set('message', $sMessage);
|
||||
$oEventIssue->Set('date', $sDate);
|
||||
$oEventIssue->Set('userinfo', static::GetUserInfo());
|
||||
$oEventIssue->Set('callstack', $sCurrentCallStack);
|
||||
$oEventIssue->Set('data', $aContext);
|
||||
|
||||
return $oEventIssue;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Warning** : during \MFCompiler::Compile the config will be partial, so when logging in this method you won't get the proper log config !
|
||||
* See N°4345
|
||||
*
|
||||
* @uses m_oMockMetaModelConfig if defined
|
||||
* @uses \MetaModel::GetConfig()
|
||||
*/
|
||||
protected static function GetConfig(): ?Config
|
||||
{
|
||||
return static::$m_oMockMetaModelConfig ?? \MetaModel::GetConfig();
|
||||
return static::$m_oMockMetaModelConfig ?? \utils::GetConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to override if default log level needs to be computed. Otherwise simply override the {@see LEVEL_DEFAULT} constant
|
||||
* A method to override if default log level needs to be computed. Otherwise, simply override the corresponding constants
|
||||
*
|
||||
* @used-by GetMinLogLevel
|
||||
* @uses \LogAPI::LEVEL_DEFAULT
|
||||
*
|
||||
* @since 3.0.0 N°3731
|
||||
* @param string $sConfigKey config key used for log
|
||||
*
|
||||
* @return string|bool if false, then disable log for any level
|
||||
*
|
||||
* @uses \LogAPI::LEVEL_DEFAULT
|
||||
* @uses \LogAPI::LEVEL_DEFAULT_DB
|
||||
*
|
||||
* @since 3.0.0 N°3731 Method creation
|
||||
* @since 3.0.0 N°4261 add specific default level for DB write
|
||||
*/
|
||||
protected static function GetLevelDefault(): string
|
||||
protected static function GetLevelDefault(string $sConfigKey)
|
||||
{
|
||||
return static::LEVEL_DEFAULT;
|
||||
switch ($sConfigKey) {
|
||||
case static::ENUM_CONFIG_PARAM_DB:
|
||||
return static::LEVEL_DEFAULT_DB;
|
||||
case static::ENUM_CONFIG_PARAM_FILE:
|
||||
default:
|
||||
return static::LEVEL_DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,6 +941,16 @@ class SetupLog extends LogAPI
|
||||
const LEVEL_DEFAULT = self::LEVEL_INFO;
|
||||
|
||||
protected static $m_oFileLog = null;
|
||||
|
||||
/**
|
||||
* In the setup there is no user logged...
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function GetUserInfo(): ?string
|
||||
{
|
||||
return 'SETUP';
|
||||
}
|
||||
}
|
||||
|
||||
class IssueLog extends LogAPI
|
||||
@@ -799,6 +989,7 @@ class DeadLockLog extends LogAPI
|
||||
parent::Enable($sTargetFile);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnreachableStatementInspection we want to keep the break statements to keep clarity and avoid errors */
|
||||
private static function GetChannelFromMysqlErrorNo($iMysqlErrorNo)
|
||||
{
|
||||
switch ($iMysqlErrorNo)
|
||||
@@ -850,10 +1041,32 @@ class DeprecatedCallsLog extends LogAPI
|
||||
/** @var \FileLog we want our own instance ! */
|
||||
protected static $m_oFileLog = null;
|
||||
|
||||
/**
|
||||
* Indirection to {@see \LogAPI::IsLogLevelEnabled()} that is handling possible {@see ConfigException}
|
||||
*
|
||||
* @param string $sLevel
|
||||
* @param string $sChannel
|
||||
*
|
||||
* @return bool if exception occurs, then returns false
|
||||
*
|
||||
* @uses \LogAPI::IsLogLevelEnabled()
|
||||
*/
|
||||
protected static function IsLogLevelEnabledSafe($sLevel, $sChannel): bool
|
||||
{
|
||||
try {
|
||||
$bIsLogLevelEnabled = static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD);
|
||||
}
|
||||
catch (ConfigException $e) {
|
||||
$bIsLogLevelEnabled = false;
|
||||
}
|
||||
|
||||
return $bIsLogLevelEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $sTargetFile
|
||||
*
|
||||
*@uses \set_error_handler() to catch deprecated notices
|
||||
* @uses \set_error_handler() to catch deprecated notices
|
||||
*
|
||||
* @since 3.0.0 N°3002 logs deprecated notices in called code
|
||||
*/
|
||||
@@ -864,13 +1077,7 @@ class DeprecatedCallsLog extends LogAPI
|
||||
}
|
||||
parent::Enable($sTargetFile);
|
||||
|
||||
try {
|
||||
$bIsLogLevelEnabled = static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD);
|
||||
}
|
||||
catch (ConfigException $e) {
|
||||
$bIsLogLevelEnabled = false;
|
||||
}
|
||||
if ($bIsLogLevelEnabled) {
|
||||
if (static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)) {
|
||||
set_error_handler([static::class, 'DeprecatedNoticesErrorHandler']);
|
||||
}
|
||||
}
|
||||
@@ -897,6 +1104,11 @@ class DeprecatedCallsLog extends LogAPI
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false === static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)) {
|
||||
// returns true so that nothing is throwned !
|
||||
return true;
|
||||
}
|
||||
|
||||
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4);
|
||||
$iStackDeprecatedMethodLevel = 2; // level 0 = current method, level 1 = @trigger_error, level 2 = method containing the `trigger_error` call
|
||||
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
|
||||
@@ -911,9 +1123,21 @@ class DeprecatedCallsLog extends LogAPI
|
||||
|
||||
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 3 = caller of the deprecated method
|
||||
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
|
||||
$sCallerObject = $aStack[3]['class'];
|
||||
$sCallerMethod = $aStack[3]['function'];
|
||||
$sMessage .= " ({$sCallerObject}::{$sCallerMethod})";
|
||||
$sCallerObject = $aStack[$iStackCallerMethodLevel]['class'] ?? null;
|
||||
$sCallerMethod = $aStack[$iStackCallerMethodLevel]['function'] ?? null;
|
||||
$sMessage .= ' (';
|
||||
if (!is_null($sCallerObject)) {
|
||||
$sMessage .= "{$sCallerObject}::{$sCallerMethod}";
|
||||
} else {
|
||||
$sCallerMethodFile = $aStack[$iStackCallerMethodLevel]['file'];
|
||||
$sCallerMethodLine = $aStack[$iStackCallerMethodLevel]['line'];
|
||||
if (!is_null($sCallerMethod)) {
|
||||
$sMessage .= "call to {$sCallerMethod}() in {$sCallerMethodFile}#L{$sCallerMethodLine}";
|
||||
} else {
|
||||
$sMessage .= "{$sCallerMethodFile}#L{$sCallerMethodLine}";
|
||||
}
|
||||
}
|
||||
$sMessage .= ')';
|
||||
}
|
||||
|
||||
if (!empty($errstr)) {
|
||||
@@ -925,13 +1149,25 @@ class DeprecatedCallsLog extends LogAPI
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static function GetLevelDefault(): string
|
||||
/**
|
||||
* Override so that :
|
||||
* - if we are in dev mode ({@see \utils::IsDevelopmentEnvironment()}), the level for file will be DEBUG
|
||||
* - else call parent method
|
||||
*
|
||||
* In other words, when in dev mode all deprecated calls will be logged to file
|
||||
*
|
||||
*/
|
||||
protected static function GetLevelDefault(string $sConfigKey)
|
||||
{
|
||||
if ($sConfigKey === self::ENUM_CONFIG_PARAM_DB) {
|
||||
return parent::GetLevelDefault($sConfigKey);
|
||||
}
|
||||
|
||||
if (utils::IsDevelopmentEnvironment()) {
|
||||
return static::LEVEL_DEBUG;
|
||||
}
|
||||
|
||||
return static::LEVEL_DEFAULT;
|
||||
return parent::GetLevelDefault($sConfigKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -941,7 +1177,12 @@ class DeprecatedCallsLog extends LogAPI
|
||||
*/
|
||||
public static function NotifyDeprecatedFile(?string $sAdditionalMessage = null): void
|
||||
{
|
||||
if (!static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_FILE)) {
|
||||
try {
|
||||
if (!static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_FILE)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ConfigException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1087,3 +1328,178 @@ class LogFileRotationProcess implements iScheduledProcess
|
||||
throw new ProcessException(self::class.' : The configured filename builder is invalid (log_filename_builder_impl="'.$sLogFileNameBuilder.'")');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log exceptions using dedicated API and logic.
|
||||
*
|
||||
* Please use {@see ExceptionLog::LogException()} to log exceptions
|
||||
*
|
||||
* @since 3.0.0 N°4261 class creation to ease logging when an exception occurs
|
||||
*/
|
||||
class ExceptionLog extends LogAPI
|
||||
{
|
||||
public const CHANNEL_DEFAULT = 'Exception';
|
||||
public const CONTEXT_EXCEPTION = '__exception';
|
||||
|
||||
protected static $m_oFileLog = null;
|
||||
|
||||
/**
|
||||
* This method should be used to write logs.
|
||||
*
|
||||
* As it encapsulate the operations performed using the Exception, you should prefer it to the standard API inherited from LogApi `ExceptionLog::Error($oException->getMessage(), get_class($oException), ['__exception' => $oException]);`
|
||||
* The parameter order is not standard, but in our use case, the resulting API is way more convenient this way !
|
||||
*/
|
||||
public static function LogException(Throwable $oException, $aContext = array(), $sLevel = self::LEVEL_ERROR): void
|
||||
{
|
||||
if (!isset(self::$aLevelsPriority[$sLevel])) {
|
||||
IssueLog::Error("invalid log level '{$sLevel}'");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sExceptionClass = get_class($oException);
|
||||
|
||||
$aDefaultValues = [
|
||||
self::CONTEXT_EXCEPTION => $oException,
|
||||
'exception class' => $sExceptionClass,
|
||||
'file' => $oException->getFile(),
|
||||
'line' => $oException->getLine(),
|
||||
];
|
||||
$aContext = array_merge($aDefaultValues, $aContext);
|
||||
|
||||
parent::Log($sLevel, $oException->getMessage(), $sExceptionClass, $aContext);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array())
|
||||
{
|
||||
throw new ApplicationException('Do not call this directly, prefer using ExceptionLog::LogException() instead');
|
||||
}
|
||||
|
||||
/** @noinspection PhpParameterNameChangedDuringInheritanceInspection */
|
||||
protected static function WriteLog(string $sLevel, string $sMessage, ?string $sExceptionClass = null, ?array $aContext = array()): void
|
||||
{
|
||||
if (
|
||||
(null !== static::$m_oFileLog)
|
||||
&& static::IsLogLevelEnabled($sLevel, $sExceptionClass, static::ENUM_CONFIG_PARAM_FILE)
|
||||
) {
|
||||
$sExceptionClassConfiguredForFile = static::ExceptionClassFromHierarchy($sExceptionClass, static::ENUM_CONFIG_PARAM_FILE);
|
||||
if (null === $sExceptionClassConfiguredForFile) {
|
||||
$sExceptionClassConfiguredForFile = $sExceptionClass;
|
||||
}
|
||||
|
||||
// clearing the Exception object as it is too verbose to write to a file !
|
||||
$aContextForFile = array_diff_key($aContext, [self::CONTEXT_EXCEPTION => null]);
|
||||
|
||||
static::$m_oFileLog->$sLevel($sMessage, $sExceptionClassConfiguredForFile, $aContextForFile);
|
||||
}
|
||||
|
||||
if (static::IsLogLevelEnabled($sLevel, $sExceptionClass, static::ENUM_CONFIG_PARAM_DB)) {
|
||||
$sExceptionClassConfiguredForDb = static::ExceptionClassFromHierarchy($sExceptionClass, static::ENUM_CONFIG_PARAM_DB);
|
||||
if (null === $sExceptionClassConfiguredForDb) {
|
||||
$sExceptionClassConfiguredForDb = $sExceptionClass;
|
||||
}
|
||||
self::WriteToDb($sMessage, $sExceptionClassConfiguredForDb, $aContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will seek for the configuration based on the exception class, using {@see \ExceptionLog::ExceptionClassFromHierarchy()}
|
||||
*
|
||||
* @param string $sExceptionClass
|
||||
* @param string $sConfigKey
|
||||
*
|
||||
* @return string
|
||||
* @noinspection PhpParameterNameChangedDuringInheritanceInspection
|
||||
*/
|
||||
protected static function GetMinLogLevel($sExceptionClass, $sConfigKey = self::ENUM_CONFIG_PARAM_FILE)
|
||||
{
|
||||
$sLogLevelMin = static::GetLogConfig($sConfigKey);
|
||||
$sExceptionClassInConfig = static::ExceptionClassFromHierarchy($sExceptionClass, $sConfigKey);
|
||||
|
||||
if (null !== $sExceptionClassInConfig) {
|
||||
return $sConfigKey[$sExceptionClassInConfig];
|
||||
}
|
||||
|
||||
return static::GetMinLogLevelFromDefault($sLogLevelMin, $sExceptionClass, $sConfigKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searching config first for the current exception class
|
||||
* If not found we are seeking for config for all the parent classes
|
||||
*
|
||||
* That means if we are logging a UnknownClassOqlException, we will seek log config all the way the class hierarchy :
|
||||
* 1. UnknownClassOqlException
|
||||
* 2. OqlNormalizeException
|
||||
* 3. OQLException
|
||||
* 4. CoreException
|
||||
* 5. Exception
|
||||
*
|
||||
* @param string $sExceptionClass
|
||||
* @param string $sConfigKey
|
||||
*
|
||||
* @return string|null the current or parent class name defined in the config, otherwise null if no class of the hierarchy found in the config
|
||||
*/
|
||||
protected static function ExceptionClassFromHierarchy($sExceptionClass, $sConfigKey = self::ENUM_CONFIG_PARAM_FILE)
|
||||
{
|
||||
$sLogLevelMin = static::GetLogConfig($sConfigKey);
|
||||
|
||||
if (false === is_array($sLogLevelMin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sExceptionClassInHierarchy = $sExceptionClass;
|
||||
while ($sExceptionClassInHierarchy !== false) {
|
||||
$sConfiguredLevelForExceptionClass = static::GetMinLogLevelFromChannel($sLogLevelMin, $sExceptionClassInHierarchy, $sConfigKey);
|
||||
if (!is_null($sConfiguredLevelForExceptionClass)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$sExceptionClassInHierarchy = get_parent_class($sExceptionClassInHierarchy);
|
||||
}
|
||||
|
||||
if ($sExceptionClassInHierarchy === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $sExceptionClassInHierarchy;
|
||||
}
|
||||
|
||||
protected static function GetEventIssue(string $sMessage, string $sChannel, array $aContext): EventIssue
|
||||
{
|
||||
$oEventIssue = parent::GetEventIssue($sMessage, $sChannel, $aContext);
|
||||
|
||||
$oContextException = $aContext[self::CONTEXT_EXCEPTION];
|
||||
unset($aContext[self::CONTEXT_EXCEPTION]);
|
||||
|
||||
$sIssue = ($oContextException instanceof CoreException) ? $oContextException->GetIssue() : 'PHP Exception';
|
||||
$sErrorStackTrace = ($oContextException instanceof CoreException) ? $oContextException->getFullStackTraceAsString() : $oContextException->getTraceAsString();
|
||||
$aContextData = ($oContextException instanceof CoreException) ? $oContextException->getContextData() : [];
|
||||
|
||||
$oEventIssue->Set('issue', $sIssue);
|
||||
$oEventIssue->Set('message', $oContextException->getMessage());
|
||||
$oEventIssue->Set('callstack', $sErrorStackTrace);
|
||||
$oEventIssue->Set('data', array_merge($aContextData, $aContext));
|
||||
|
||||
return $oEventIssue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function Enable($sTargetFile = null)
|
||||
{
|
||||
if (empty($sTargetFile)) {
|
||||
$sTargetFile = APPROOT.'log/error.log';
|
||||
}
|
||||
parent::Enable($sTargetFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Used by the tests
|
||||
*/
|
||||
private static function GetLastEventIssue()
|
||||
{
|
||||
return self::$oLastEventIssue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
//
|
||||
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
|
||||
require_once APPROOT.'core/modulehandler.class.inc.php';
|
||||
require_once APPROOT.'core/querymodifier.class.inc.php';
|
||||
require_once APPROOT.'core/metamodelmodifier.inc.php';
|
||||
@@ -443,10 +445,10 @@ abstract class MetaModel
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param bool $bImgTag
|
||||
* @param string $sMoreStyles
|
||||
* @param bool $bImgTag Whether to surround the icon URL with an HTML IMG tag or not
|
||||
* @param string $sMoreStyles Additional inline CSS style to add to the IMG tag. Only used if $bImgTag is set to true
|
||||
*
|
||||
* @return string
|
||||
* @return string Absolute URL the class icon
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final public static function GetClassIcon($sClass, $bImgTag = true, $sMoreStyles = '')
|
||||
@@ -457,7 +459,7 @@ abstract class MetaModel
|
||||
if (array_key_exists('style', self::$m_aClassParams[$sClass])) {
|
||||
/** @var ormStyle $oStyle */
|
||||
$oStyle = self::$m_aClassParams[$sClass]['style'];
|
||||
$sIcon = $oStyle->GetIcon();
|
||||
$sIcon = $oStyle->GetIconAsAbsUrl();
|
||||
}
|
||||
if (strlen($sIcon) == 0) {
|
||||
$sParentClass = self::GetParentPersistentClass($sClass);
|
||||
@@ -492,7 +494,7 @@ abstract class MetaModel
|
||||
$oStyle = new ormStyle("ibo-class-style--$sClass", "ibo-class-style-alt--$sClass");
|
||||
}
|
||||
|
||||
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIcon()) > 0)) {
|
||||
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIconAsRelPath()) > 0)) {
|
||||
// all the parameters are set, no need to search in the parent classes
|
||||
return $oStyle;
|
||||
}
|
||||
@@ -510,10 +512,10 @@ abstract class MetaModel
|
||||
$oStyle->SetComplementaryColor($oParentStyle->GetComplementaryColor());
|
||||
$oStyle->SetAltStyleClass($oParentStyle->GetAltStyleClass());
|
||||
}
|
||||
if (strlen($oStyle->GetIcon()) == 0) {
|
||||
$oStyle->SetIcon($oParentStyle->GetIcon());
|
||||
if (strlen($oStyle->GetIconAsRelPath()) == 0) {
|
||||
$oStyle->SetIcon($oParentStyle->GetIconAsRelPath());
|
||||
}
|
||||
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIcon()) > 0)) {
|
||||
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIconAsRelPath()) > 0)) {
|
||||
// all the parameters are set, no need to search in the parent classes
|
||||
return $oStyle;
|
||||
}
|
||||
@@ -521,7 +523,7 @@ abstract class MetaModel
|
||||
$sParentClass = self::GetParentPersistentClass($sParentClass);
|
||||
}
|
||||
|
||||
if ((strlen($oStyle->GetMainColor()) == 0) && (strlen($oStyle->GetComplementaryColor()) == 0) && (strlen($oStyle->GetIcon()) == 0)) {
|
||||
if ((strlen($oStyle->GetMainColor()) == 0) && (strlen($oStyle->GetComplementaryColor()) == 0) && (strlen($oStyle->GetIconAsRelPath()) == 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -653,7 +655,7 @@ abstract class MetaModel
|
||||
* @param string $sRuleId
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @since 2.6.1 N°1918 (sous les pavés, la plage) initialize in 'root_class' property the class that has the first
|
||||
* @since 2.6.1 N°1968 (sous les pavés, la plage) initialize in 'root_class' property the class that has the first
|
||||
* definition of the rule in the hierarchy
|
||||
*/
|
||||
private static function SetUniquenessRuleRootClass($sRootClass, $sRuleId)
|
||||
@@ -752,17 +754,56 @@ abstract class MetaModel
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*
|
||||
* @since 3.0.0 N°580 New $sType parameter
|
||||
*/
|
||||
final public static function GetNameSpec($sClass)
|
||||
final public static function GetNameSpec($sClass, $sType = FriendlyNameType::SHORT)
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
$nameRawSpec = self::$m_aClassParams[$sClass]["name_attcode"];
|
||||
|
||||
switch ($sType) {
|
||||
case FriendlyNameType::COMPLEMENTARY:
|
||||
if (!isset(self::$m_aClassParams[$sClass]["complementary_name_attcode"])) {
|
||||
return [$sClass, []];
|
||||
}
|
||||
$nameRawSpec = self::$m_aClassParams[$sClass]["complementary_name_attcode"];
|
||||
$sDictName = 'ComplementaryName';
|
||||
break;
|
||||
case FriendlyNameType::LONG:
|
||||
$nameRawSpec = self::$m_aClassParams[$sClass]["name_attcode"];
|
||||
if (!isset(self::$m_aClassParams[$sClass]["complementary_name_attcode"])) {
|
||||
return self::GetNameSpec($sClass, FriendlyNameType::SHORT);
|
||||
}
|
||||
$complementaryNameRawSpec = self::$m_aClassParams[$sClass]["complementary_name_attcode"];
|
||||
if (is_array($nameRawSpec)) {
|
||||
if (is_array($complementaryNameRawSpec)) {
|
||||
$nameRawSpec = merge($nameRawSpec, $complementaryNameRawSpec);
|
||||
} elseif (!empty($nameRawSpec)) {
|
||||
$nameRawSpec = merge($nameRawSpec, [$complementaryNameRawSpec]);
|
||||
}
|
||||
} elseif (empty($nameRawSpec)) {
|
||||
$nameRawSpec = $complementaryNameRawSpec;
|
||||
} else {
|
||||
if (is_array($complementaryNameRawSpec)) {
|
||||
$nameRawSpec = merge([$nameRawSpec], $complementaryNameRawSpec);
|
||||
} elseif (!empty($nameRawSpec)) {
|
||||
$nameRawSpec = [$nameRawSpec, $complementaryNameRawSpec];
|
||||
}
|
||||
}
|
||||
$sDictName = 'LongName';
|
||||
break;
|
||||
default:
|
||||
$nameRawSpec = self::$m_aClassParams[$sClass]["name_attcode"];
|
||||
$sDictName = 'Name';
|
||||
}
|
||||
|
||||
if (is_array($nameRawSpec)) {
|
||||
$sFormat = Dict::S("Class:$sClass/Name", '');
|
||||
$sFormat = Dict::S("Class:$sClass/$sDictName", '');
|
||||
if (strlen($sFormat) == 0) {
|
||||
// Default to "%1$s %2$s..."
|
||||
for ($i = 1; $i <= count($nameRawSpec); $i++) {
|
||||
@@ -774,12 +815,12 @@ abstract class MetaModel
|
||||
}
|
||||
}
|
||||
|
||||
return array($sFormat, $nameRawSpec);
|
||||
return [$sFormat, $nameRawSpec];
|
||||
} elseif (empty($nameRawSpec)) {
|
||||
return array($sClass, array());
|
||||
return [$sClass, []];
|
||||
} else {
|
||||
// string -> attcode
|
||||
return array('%1$s', array($nameRawSpec));
|
||||
return ['%1$s', [$nameRawSpec]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,24 +828,38 @@ abstract class MetaModel
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param bool $bWithAttributeDefinition
|
||||
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
|
||||
*
|
||||
* @return array of attribute codes used by friendlyname
|
||||
* @throws \CoreException
|
||||
* @since 3.0.0
|
||||
*/
|
||||
final public static function GetNameAttributes(string $sClass, $bWithAttributeDefinition = false): array
|
||||
final public static function GetNameAttributes(string $sClass, $bWithAttributeDefinition = false, $sType = FriendlyNameType::SHORT): array
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
$rawNameAttCodes = self::$m_aClassParams[$sClass]["name_attcode"];
|
||||
$aNameAttCodes = [];
|
||||
if (!is_array($rawNameAttCodes)) {
|
||||
if (self::IsValidAttCode($sClass, $rawNameAttCodes)) {
|
||||
$aNameAttCodes[] = $rawNameAttCodes;
|
||||
if ($sType == FriendlyNameType::SHORT || FriendlyNameType::LONG) {
|
||||
$rawNameAttCodes = self::$m_aClassParams[$sClass]["name_attcode"];
|
||||
if (!is_array($rawNameAttCodes)) {
|
||||
if (self::IsValidAttCode($sClass, $rawNameAttCodes)) {
|
||||
$aNameAttCodes[] = $rawNameAttCodes;
|
||||
}
|
||||
} else {
|
||||
$aNameAttCodes = $rawNameAttCodes;
|
||||
}
|
||||
}
|
||||
if ($sType == FriendlyNameType::COMPLEMENTARY || FriendlyNameType::LONG) {
|
||||
$rawNameAttCodes = self::$m_aClassParams[$sClass]["complementary_name_attcode"];
|
||||
if (!isEmpty($rawNameAttCodes)) {
|
||||
if (!is_array($rawNameAttCodes)) {
|
||||
if (self::IsValidAttCode($sClass, $rawNameAttCodes)) {
|
||||
$aNameAttCodes[] = array_merge($aNameAttCodes, [$rawNameAttCodes]);
|
||||
}
|
||||
} else {
|
||||
$aNameAttCodes = array_merge($rawNameAttCodes, $rawNameAttCodes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$aNameAttCodes = $rawNameAttCodes;
|
||||
}
|
||||
|
||||
if ($bWithAttributeDefinition) {
|
||||
$aResults = [];
|
||||
foreach ($aNameAttCodes as $sAttCode) {
|
||||
@@ -842,68 +897,21 @@ abstract class MetaModel
|
||||
return $aResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*/
|
||||
final static public function GetComplementAttributeSpec($sClass)
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
if (!isset(self::$m_aClassParams[$sClass]["name_complement_for_select"]))
|
||||
{
|
||||
$sParentClass = static::GetParentClass($sClass);
|
||||
if (is_null($sParentClass)) {
|
||||
return array($sClass, array());
|
||||
} else {
|
||||
return static::GetComplementAttributeSpec($sParentClass);
|
||||
}
|
||||
}
|
||||
$nameRawSpec = self::$m_aClassParams[$sClass]["name_complement_for_select"];
|
||||
if (is_array($nameRawSpec))
|
||||
{
|
||||
$sFormat = Dict::S("Class:$sClass/ComplementForSelect", '');
|
||||
if (strlen($sFormat) == 0)
|
||||
{
|
||||
// Default to "%1$s %2$s..."
|
||||
for($i = 1; $i <= count($nameRawSpec); $i++)
|
||||
{
|
||||
if (empty($sFormat))
|
||||
{
|
||||
$sFormat .= '%'.$i.'$s';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sFormat .= ' %'.$i.'$s';
|
||||
}
|
||||
}
|
||||
}
|
||||
return array($sFormat, $nameRawSpec);
|
||||
}
|
||||
elseif (empty($nameRawSpec))
|
||||
{
|
||||
return array($sClass, array());
|
||||
}
|
||||
else
|
||||
{
|
||||
// string -> attcode
|
||||
return array('%1$s', array($nameRawSpec));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the friendly name expression for a given class
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
|
||||
*
|
||||
* @return Expression
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*
|
||||
* @since 3.0.0 N°580 New $sType parameter
|
||||
*/
|
||||
final public static function GetNameExpression($sClass)
|
||||
final public static function GetNameExpression($sClass, $sType = FriendlyNameType::SHORT)
|
||||
{
|
||||
$aNameSpec = self::GetNameSpec($sClass);
|
||||
$aNameSpec = self::GetNameSpec($sClass, $sType);
|
||||
$sFormat = $aNameSpec[0];
|
||||
$aAttributes = $aNameSpec[1];
|
||||
|
||||
@@ -935,14 +943,17 @@ abstract class MetaModel
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
|
||||
*
|
||||
* @return string The friendly name IIF it is equivalent to a single attribute
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*
|
||||
* @since 3.0.0 N°580 New $sType parameter
|
||||
*/
|
||||
final public static function GetFriendlyNameAttributeCode($sClass)
|
||||
final public static function GetFriendlyNameAttributeCode($sClass, $sType = FriendlyNameType::SHORT)
|
||||
{
|
||||
$aNameSpec = self::GetNameSpec($sClass);
|
||||
$aNameSpec = self::GetNameSpec($sClass, $sType);
|
||||
$sFormat = trim($aNameSpec[0]);
|
||||
$aAttributes = $aNameSpec[1];
|
||||
if (($sFormat != '') && ($sFormat != '%1$s')) {
|
||||
@@ -958,13 +969,16 @@ abstract class MetaModel
|
||||
/**
|
||||
* Returns the list of attributes composing the friendlyname
|
||||
*
|
||||
* @param $sClass
|
||||
* @param string $sClass
|
||||
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.0.0 N°580 New $sType parameter
|
||||
*/
|
||||
final public static function GetFriendlyNameAttributeCodeList($sClass)
|
||||
final public static function GetFriendlyNameAttributeCodeList($sClass, $sType = FriendlyNameType::SHORT)
|
||||
{
|
||||
$aNameSpec = self::GetNameSpec($sClass);
|
||||
$aNameSpec = self::GetNameSpec($sClass, $sType);
|
||||
$aAttributes = $aNameSpec[1];
|
||||
|
||||
return $aAttributes;
|
||||
@@ -1133,7 +1147,8 @@ abstract class MetaModel
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
$oAtt = self::GetAttributeDef($sClass, $sAttCode);
|
||||
return $oAtt->GetPrerequisiteAttributes();
|
||||
|
||||
return $oAtt->GetPrerequisiteAttributes($sClass);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2986,7 +3001,32 @@ abstract class MetaModel
|
||||
|
||||
// Build the list of available extensions
|
||||
//
|
||||
$aInterfaces = array('iApplicationUIExtension', 'iPreferencesExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLoginUIExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPageUIBlockExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider', 'iModuleExtension');
|
||||
$aInterfaces = [
|
||||
'iApplicationUIExtension',
|
||||
'iPreferencesExtension',
|
||||
'iApplicationObjectExtension',
|
||||
'iLoginFSMExtension',
|
||||
'iLoginUIExtension',
|
||||
'iLogoutExtension',
|
||||
'iQueryModifier',
|
||||
'iOnClassInitialization',
|
||||
'iPopupMenuExtension',
|
||||
'iPageUIExtension',
|
||||
'iPageUIBlockExtension',
|
||||
'iBackofficeLinkedScriptsExtension',
|
||||
'iBackofficeEarlyScriptExtension',
|
||||
'iBackofficeScriptExtension',
|
||||
'iBackofficeInitScriptExtension',
|
||||
'iBackofficeReadyScriptExtension',
|
||||
'iBackofficeLinkedStylesheetsExtension',
|
||||
'iBackofficeStyleExtension',
|
||||
'iBackofficeDictEntriesExtension',
|
||||
'iBackofficeDictEntriesPrefixesExtension',
|
||||
'iPortalUIExtension',
|
||||
'ModuleHandlerApiInterface',
|
||||
'iNewsroomProvider',
|
||||
'iModuleExtension',
|
||||
];
|
||||
foreach($aInterfaces as $sInterface)
|
||||
{
|
||||
self::$m_aExtensionClassNames[$sInterface] = array();
|
||||
@@ -3068,8 +3108,7 @@ abstract class MetaModel
|
||||
// Set attribute code
|
||||
self::$m_aClassParams[$sPHPClass]['state_attcode'] = self::$m_aClassParams[$sParent]['state_attcode'];
|
||||
|
||||
// Set states
|
||||
self::$m_aStates[$sPHPClass] = self::$m_aStates[$sParent];
|
||||
// Note: Don't set self::$m_aStates[$sPHPClass], it has already been done by self::Init_DefineState()
|
||||
}
|
||||
// - Image attribute
|
||||
$bParentHasImageAttribute = (isset(self::$m_aClassParams[$sParent]['image_attcode']) && !empty(self::$m_aClassParams[$sParent]['image_attcode']));
|
||||
@@ -4117,19 +4156,26 @@ abstract class MetaModel
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param int $iOption one of ENUM_CHILD_CLASSES_EXCLUDETOP, ENUM_CHILD_CLASSES_ALL
|
||||
* @param bool $bRootFirst Only when $iOption NOT set to ENUM_CHILD_CLASSES_EXCLUDETOP. If true, the $sClass will be the first element of the returned array, otherwise it will be the last (legacy behavior)
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
* @since 3.0.0 Added $bRootFirst param.
|
||||
*/
|
||||
public static function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP)
|
||||
public static function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP, $bRootFirst = false)
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
|
||||
$aRes = self::$m_aChildClasses[$sClass];
|
||||
if ($iOption != ENUM_CHILD_CLASSES_EXCLUDETOP)
|
||||
{
|
||||
// Add it to the list
|
||||
$aRes[] = $sClass;
|
||||
if ($bRootFirst) {
|
||||
// Root class on top
|
||||
array_unshift($aRes, $sClass);
|
||||
} else {
|
||||
// Root class at the end, legacy behavior
|
||||
$aRes[] = $sClass;
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
@@ -4516,15 +4562,15 @@ abstract class MetaModel
|
||||
/**
|
||||
* Check (and updates if needed) the hierarchical keys
|
||||
*
|
||||
* @param boolean $bDiagnosticsOnly If true only a diagnostic pass will be run, returning true or false
|
||||
* @param boolean $bVerbose Displays some information about what is done/what needs to be done
|
||||
* @param boolean $bForceComputation If true, the _left and _right parameters will be recomputed even if some
|
||||
* @param bool $bDiagnosticsOnly If true only a diagnostic pass will be run, returning true or false
|
||||
* @param bool $bVerbose Displays some information about what is done/what needs to be done
|
||||
* @param bool $bForceComputation If true, the _left and _right parameters will be recomputed even if some
|
||||
* values already exist in the DB
|
||||
*
|
||||
* @return bool
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function CheckHKeys($bDiagnosticsOnly = false, $bVerbose = false, $bForceComputation = false)
|
||||
public static function CheckHKeys(bool $bDiagnosticsOnly = false, bool $bVerbose = false, bool $bForceComputation = false)
|
||||
{
|
||||
$bChangeNeeded = false;
|
||||
foreach(self::GetClasses() as $sClass)
|
||||
@@ -5949,7 +5995,8 @@ abstract class MetaModel
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated 2.7.0 N°2369 will be removed in 2.8
|
||||
* @deprecated 2.7.0 N°2369 Method will not be removed any time soon as we still need to drop view if the instance is migrating from an iTop 2.x to an iTop 3.0 or newer, even if they skip iTop 3.0.
|
||||
* @since 3.0.0 Does not recreate SQL views, only drops them. Method has not been renamed to avoid regressions
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
@@ -5963,7 +6010,7 @@ abstract class MetaModel
|
||||
|
||||
// Reporting views (must be created after any other table)
|
||||
//
|
||||
foreach(self::GetClasses('bizmodel') as $sClass)
|
||||
foreach(self::GetClasses() as $sClass)
|
||||
{
|
||||
$sView = self::DBGetView($sClass);
|
||||
if (CMDBSource::IsTable($sView))
|
||||
@@ -6509,6 +6556,7 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function LoadConfig($oConfiguration, $bAllowCache = false)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
self::$m_oConfig = $oConfiguration;
|
||||
|
||||
// N°2478 utils has his own private attribute
|
||||
@@ -6529,6 +6577,7 @@ abstract class MetaModel
|
||||
ToolsLog::Enable(APPROOT.'log/tools.log');
|
||||
DeadLockLog::Enable();
|
||||
DeprecatedCallsLog::Enable();
|
||||
ExceptionLog::Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -6585,6 +6634,7 @@ abstract class MetaModel
|
||||
|
||||
$sSource = self::$m_oConfig->Get('db_name');
|
||||
$sTablePrefix = self::$m_oConfig->Get('db_subname');
|
||||
$oKPI->ComputeAndReport('Load config');
|
||||
|
||||
if (self::$m_bUseAPCCache)
|
||||
{
|
||||
@@ -7215,21 +7265,6 @@ abstract class MetaModel
|
||||
return $oRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2.7.0 N°1627, use ItopCounter::incRootClass($sClass) instead
|
||||
*
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return int
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public static function GetNextKey($sClass)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ItopCounter::incRootClass($sClass) instead');
|
||||
|
||||
return ItopCounter::IncClass($sClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletion of records, bypassing {@link DBObject::DBDelete} !!!
|
||||
* It is NOT recommended to use this shortcut
|
||||
@@ -7454,9 +7489,11 @@ abstract class MetaModel
|
||||
* @param string $sInput
|
||||
* @param array $aParams
|
||||
*
|
||||
* @return mixed
|
||||
* @return string
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
static public function ApplyParams($sInput, $aParams)
|
||||
public static function ApplyParams($sInput, $aParams)
|
||||
{
|
||||
$aParams = static::AddMagicPlaceholders($aParams);
|
||||
|
||||
@@ -7466,7 +7503,7 @@ abstract class MetaModel
|
||||
|
||||
$aSearches = array();
|
||||
$aReplacements = array();
|
||||
foreach($aParams as $sSearch => $replace)
|
||||
foreach ($aParams as $sSearch => $replace)
|
||||
{
|
||||
// Some environment parameters are objects, we just need scalars
|
||||
if (is_object($replace))
|
||||
@@ -7715,14 +7752,18 @@ abstract class MetaModel
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param string $sValue
|
||||
* @param string|null $sValue Code of the state value, can be null if allowed by the attribute definition
|
||||
*
|
||||
* @return \ormStyle|null
|
||||
* @throws \Exception
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public static function GetEnumStyle(string $sClass, string $sAttCode, string $sValue = ''): ?ormStyle
|
||||
public static function GetEnumStyle(string $sClass, string $sAttCode, ?string $sValue = ''): ?ormStyle
|
||||
{
|
||||
if (strlen($sAttCode) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
|
||||
if (!$oAttDef instanceof AttributeEnum) {
|
||||
throw new CoreException("MetaModel::GetEnumStyle() Attribute $sAttCode of class $sClass is not an AttributeEnum\n");
|
||||
|
||||
@@ -7,41 +7,50 @@
|
||||
/**
|
||||
* Class ormStyle
|
||||
*
|
||||
* @since 3.0
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class ormStyle
|
||||
{
|
||||
/** @var string */
|
||||
/** @var string|null */
|
||||
protected $sMainColor;
|
||||
/** @var string */
|
||||
/** @var string|null */
|
||||
protected $sComplementaryColor;
|
||||
/** @var string CSS class with color and background-color */
|
||||
/** @var string|null CSS class with color and background-color */
|
||||
protected $sStyleClass;
|
||||
/** @var string CSS class with only color */
|
||||
/** @var string|null CSS class with only color */
|
||||
protected $sAltStyleClass;
|
||||
/** @var string */
|
||||
/** @var string|null */
|
||||
protected $sDecorationClasses;
|
||||
/** @var string */
|
||||
/** @var string|null Relative path (from current environment) to the icon */
|
||||
protected $sIcon;
|
||||
|
||||
/**
|
||||
* ormStyle constructor.
|
||||
*
|
||||
* @param string $sStyleClass
|
||||
* @param string $sAltStyleClass
|
||||
* @param string|null $sStyleClass
|
||||
* @param string|null $sAltStyleClass
|
||||
* @param string|null $sMainColor
|
||||
* @param string|null $sComplementaryColor
|
||||
* @param string|null $sDecorationClasses
|
||||
* @param string|null $sIcon
|
||||
*/
|
||||
public function __construct(string $sStyleClass, string $sAltStyleClass, string $sMainColor = null, string $sComplementaryColor = null, string $sDecorationClasses = null, string $sIcon = null)
|
||||
public function __construct(?string $sStyleClass = null, ?string $sAltStyleClass = null, ?string $sMainColor = null, ?string $sComplementaryColor = null, ?string $sDecorationClasses = null, ?string $sIcon = null)
|
||||
{
|
||||
$this->sMainColor = $sMainColor;
|
||||
$this->sComplementaryColor = $sComplementaryColor;
|
||||
$this->sStyleClass = $sStyleClass;
|
||||
$this->sAltStyleClass = $sAltStyleClass;
|
||||
$this->sDecorationClasses = $sDecorationClasses;
|
||||
$this->sIcon = $sIcon;
|
||||
$this->SetMainColor($sMainColor);
|
||||
$this->SetComplementaryColor($sComplementaryColor);
|
||||
$this->SetStyleClass($sStyleClass);
|
||||
$this->SetAltStyleClass($sAltStyleClass);
|
||||
$this->SetDecorationClasses($sDecorationClasses);
|
||||
$this->SetIcon($sIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see static::$sMainColor
|
||||
* @return bool
|
||||
*/
|
||||
public function HasMainColor(): bool
|
||||
{
|
||||
return strlen($this->sMainColor) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,10 +68,19 @@ class ormStyle
|
||||
*/
|
||||
public function SetMainColor(?string $sMainColor)
|
||||
{
|
||||
$this->sMainColor = $sMainColor;
|
||||
$this->sMainColor = (strlen($sMainColor) === 0) ? null : $sMainColor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see static::$sComplementaryColor
|
||||
* @return bool
|
||||
*/
|
||||
public function HasComplementaryColor(): bool
|
||||
{
|
||||
return strlen($this->sComplementaryColor) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -78,14 +96,33 @@ class ormStyle
|
||||
*/
|
||||
public function SetComplementaryColor(?string $sComplementaryColor)
|
||||
{
|
||||
$this->sComplementaryColor = $sComplementaryColor;
|
||||
$this->sComplementaryColor = (strlen($sComplementaryColor) === 0) ? null : $sComplementaryColor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see static::$sMainColor
|
||||
* @see static::$sComplementaryColor
|
||||
* @return bool
|
||||
*/
|
||||
public function HasAtLeastOneColor(): bool
|
||||
{
|
||||
return $this->HasMainColor() || $this->HasComplementaryColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see static::$sStyleClass
|
||||
* @return bool
|
||||
*/
|
||||
public function HasStyleClass(): bool
|
||||
{
|
||||
return strlen($this->sStyleClass) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetStyleClass(): string
|
||||
public function GetStyleClass(): ?string
|
||||
{
|
||||
return $this->sStyleClass;
|
||||
}
|
||||
@@ -95,16 +132,25 @@ class ormStyle
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function SetStyleClass(string $sStyleClass)
|
||||
public function SetStyleClass(?string $sStyleClass)
|
||||
{
|
||||
$this->sStyleClass = $sStyleClass;
|
||||
$this->sStyleClass = (strlen($sStyleClass) === 0) ? null : $sStyleClass;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see static::$sAltStyleClass
|
||||
* @return bool
|
||||
*/
|
||||
public function HasAltStyleClass(): bool
|
||||
{
|
||||
return strlen($this->sAltStyleClass) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetAltStyleClass(): string
|
||||
public function GetAltStyleClass(): ?string
|
||||
{
|
||||
return $this->sAltStyleClass;
|
||||
}
|
||||
@@ -114,12 +160,21 @@ class ormStyle
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function SetAltStyleClass(string $sAltStyleClass)
|
||||
public function SetAltStyleClass(?string $sAltStyleClass)
|
||||
{
|
||||
$this->sAltStyleClass = $sAltStyleClass;
|
||||
$this->sAltStyleClass = (strlen($sAltStyleClass) === 0) ? null : $sAltStyleClass;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see static::$sDecorationClasses
|
||||
* @return bool
|
||||
*/
|
||||
public function HasDecorationClasses(): bool
|
||||
{
|
||||
return strlen($this->sDecorationClasses) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -135,16 +190,17 @@ class ormStyle
|
||||
*/
|
||||
public function SetDecorationClasses(?string $sDecorationClasses)
|
||||
{
|
||||
$this->sDecorationClasses = $sDecorationClasses;
|
||||
$this->sDecorationClasses = (strlen($sDecorationClasses) === 0) ? null : $sDecorationClasses;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @see static::$sIcon
|
||||
* @return bool
|
||||
*/
|
||||
public function GetIcon(): ?string
|
||||
public function HasIcon(): bool
|
||||
{
|
||||
return $this->sIcon;
|
||||
return strlen($this->sIcon) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,8 +210,30 @@ class ormStyle
|
||||
*/
|
||||
public function SetIcon(?string $sIcon)
|
||||
{
|
||||
$this->sIcon = $sIcon;
|
||||
$this->sIcon = (strlen($sIcon) === 0) ? null : $sIcon;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see static::$sIcon
|
||||
* @return string|null Relative path (from the current environment) of the icon
|
||||
*/
|
||||
public function GetIconAsRelPath(): ?string
|
||||
{
|
||||
return $this->sIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see static::$sIcon
|
||||
* @return string|null Absolute URL of the icon
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function GetIconAsAbsUrl(): ?string
|
||||
{
|
||||
if (is_null($this->sIcon)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return utils::GetAbsoluteUrlModulesRoot().$this->sIcon;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,12 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSectionUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
use Combodo\iTop\Application\UI\Base\iUIBlock;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
||||
use Combodo\iTop\Renderer\BlockRenderer;
|
||||
|
||||
define('CASELOG_VISIBLE_ITEMS', 2);
|
||||
define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\n\n");
|
||||
|
||||
@@ -27,6 +33,17 @@ define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
class ormCaseLog {
|
||||
/**
|
||||
* @var string "plain text" format for the log
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public const ENUM_FORMAT_TEXT = 'text';
|
||||
/**
|
||||
* @var string "HTML" format for the log
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public const ENUM_FORMAT_HTML = 'html';
|
||||
|
||||
protected $m_sLog;
|
||||
protected $m_aIndex;
|
||||
protected $m_bModified;
|
||||
@@ -47,7 +64,7 @@ class ormCaseLog {
|
||||
{
|
||||
if ($bConvertToPlainText)
|
||||
{
|
||||
// Rebuild the log, but filtering any HTML markup for the all 'html' entries in the log
|
||||
// Rebuild the log, but filtering any HTML markup for the all {@see static::ENUM_FORMAT_HTML} entries in the log
|
||||
return $this->GetAsPlainText();
|
||||
}
|
||||
else
|
||||
@@ -130,14 +147,14 @@ class ormCaseLog {
|
||||
$sDate = '';
|
||||
}
|
||||
}
|
||||
$sFormat = array_key_exists('format', $this->m_aIndex[$index]) ? $this->m_aIndex[$index]['format'] : 'text';
|
||||
$sFormat = array_key_exists('format', $this->m_aIndex[$index]) ? $this->m_aIndex[$index]['format'] : static::ENUM_FORMAT_TEXT;
|
||||
switch($sFormat)
|
||||
{
|
||||
case 'text':
|
||||
case static::ENUM_FORMAT_TEXT:
|
||||
$sHtmlEntry = utils::TextToHtml($sTextEntry);
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
case static::ENUM_FORMAT_HTML:
|
||||
$sHtmlEntry = $sTextEntry;
|
||||
$sTextEntry = utils::HtmlToText($sHtmlEntry);
|
||||
break;
|
||||
@@ -168,9 +185,9 @@ class ormCaseLog {
|
||||
return $aEntries;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a "plain text" version of the log (equivalent to $this->m_sLog) where all the HTML markup from the 'html' entries have been removed
|
||||
* Returns a "plain text" version of the log (equivalent to $this->m_sLog) where all the HTML markup from the {@see static::ENUM_FORMAT_HTML} entries have been removed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetAsPlainText()
|
||||
@@ -201,6 +218,15 @@ class ormCaseLog {
|
||||
{
|
||||
return ($this->m_sLog === null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int The number of entries in this log
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function GetEntryCount(): int
|
||||
{
|
||||
return count($this->m_aIndex);
|
||||
}
|
||||
|
||||
public function ClearModifiedFlag()
|
||||
{
|
||||
@@ -223,7 +249,7 @@ class ormCaseLog {
|
||||
$iPos += $aIndex[$index]['separator_length'];
|
||||
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
|
||||
$sCSSClass = 'caselog_entry_html';
|
||||
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
|
||||
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == static::ENUM_FORMAT_TEXT))
|
||||
{
|
||||
$sCSSClass = 'caselog_entry';
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
@@ -306,7 +332,7 @@ class ormCaseLog {
|
||||
$iPos += $aIndex[$index]['separator_length'];
|
||||
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
|
||||
$sCSSClass = 'case_log_simple_html_entry_html';
|
||||
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
|
||||
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == static::ENUM_FORMAT_TEXT))
|
||||
{
|
||||
$sCSSClass = 'case_log_simple_html_entry';
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
@@ -389,7 +415,7 @@ class ormCaseLog {
|
||||
{
|
||||
$bPrintableVersion = (utils::ReadParam('printable', '0') == '1');
|
||||
|
||||
$sHtml = '<table style="width:100%;table-layout:fixed"><tr><td>'; // Use table-layout:fixed to force the with to be independent from the actual content
|
||||
$oBlock = UIContentBlockUIBlockFactory::MakeStandard(null, ['ibo-caselog-list']);
|
||||
$iPos = 0;
|
||||
$aIndex = $this->m_aIndex;
|
||||
if (($bEditMode) && (count($aIndex) > 0) && $this->m_bModified)
|
||||
@@ -403,20 +429,16 @@ class ormCaseLog {
|
||||
{
|
||||
if (!$bPrintableVersion && ($index < count($aIndex) - CASELOG_VISIBLE_ITEMS))
|
||||
{
|
||||
$sOpen = '';
|
||||
$sDisplay = 'style="display:none;"';
|
||||
$bIsOpen = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sOpen = ' open';
|
||||
$sDisplay = '';
|
||||
$bIsOpen = true;
|
||||
}
|
||||
$iPos += $aIndex[$index]['separator_length'];
|
||||
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
|
||||
$sCSSClass= 'caselog_entry_html';
|
||||
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
|
||||
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == static::ENUM_FORMAT_TEXT))
|
||||
{
|
||||
$sCSSClass= 'caselog_entry';
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
if (!is_null($aTransfoHandler))
|
||||
{
|
||||
@@ -433,7 +455,6 @@ class ormCaseLog {
|
||||
}
|
||||
$iPos += $aIndex[$index]['text_length'];
|
||||
|
||||
$sEntry = '<div class="caselog_header'.$sOpen.'">';
|
||||
// Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
|
||||
// therefore we have changed the format. To preserve the compatibility with existing
|
||||
// installations of iTop, both format are allowed:
|
||||
@@ -456,14 +477,11 @@ class ormCaseLog {
|
||||
$sDate = '';
|
||||
}
|
||||
}
|
||||
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']);
|
||||
$sEntry .= '</div>';
|
||||
$sEntry .= '<div class="'.$sCSSClass.'"'.$sDisplay.'>';
|
||||
$sEntry .= $sTextEntry;
|
||||
$sEntry .= '</div>';
|
||||
$sHtml = $sHtml.$sEntry;
|
||||
$oCollapsibleBlock = CollapsibleSectionUIBlockFactory::MakeStandard( sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']));
|
||||
$oCollapsibleBlock->AddSubBlock(new Html($sTextEntry));
|
||||
$oCollapsibleBlock->SetOpenedByDefault($bIsOpen);
|
||||
$oBlock->AddSubBlock($oCollapsibleBlock);
|
||||
}
|
||||
|
||||
// Process the case of an eventual remainder (quick migration of AttributeText fields)
|
||||
if ($iPos < (strlen($this->m_sLog) - 1))
|
||||
{
|
||||
@@ -477,32 +495,51 @@ class ormCaseLog {
|
||||
|
||||
if (count($this->m_aIndex) == 0)
|
||||
{
|
||||
$sHtml .= '<div class="caselog_entry open">';
|
||||
$sHtml .= $sTextEntry;
|
||||
$sHtml .= '</div>';
|
||||
$oCollapsibleBlock = CollapsibleSectionUIBlockFactory::MakeStandard( '');
|
||||
$oCollapsibleBlock->AddSubBlock(new Html($sTextEntry));
|
||||
$oCollapsibleBlock->SetOpenedByDefault(true);
|
||||
$oBlock->AddSubBlock($oCollapsibleBlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!$bPrintableVersion && (count($this->m_aIndex) - CASELOG_VISIBLE_ITEMS > 0))
|
||||
{
|
||||
$sOpen = '';
|
||||
$sDisplay = 'style="display:none;"';
|
||||
$bIsOpen = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sOpen = ' open';
|
||||
$sDisplay = '';
|
||||
$bIsOpen = true;
|
||||
}
|
||||
$sHtml .= '<div class="caselog_header'.$sOpen.'">';
|
||||
$sHtml .= Dict::S('UI:CaseLog:InitialValue');
|
||||
$sHtml .= '</div>';
|
||||
$sHtml .= '<div class="caselog_entry"'.$sDisplay.'>';
|
||||
$sHtml .= $sTextEntry;
|
||||
$sHtml .= '</div>';
|
||||
$oCollapsibleBlock = CollapsibleSectionUIBlockFactory::MakeStandard( Dict::S('UI:CaseLog:InitialValue'));
|
||||
$oCollapsibleBlock->AddSubBlock(new Html($sTextEntry));
|
||||
$oCollapsibleBlock->SetOpenedByDefault($bIsOpen);
|
||||
}
|
||||
}
|
||||
$sHtml .= '</td></tr></table>';
|
||||
return $sHtml;
|
||||
$oBlockRenderer = new BlockRenderer($oBlock);
|
||||
$sHtml = $oBlockRenderer->RenderHtml();
|
||||
$sScript = $oBlockRenderer->RenderJsInlineRecursively($oBlock,iUIBlock::ENUM_JS_TYPE_ON_READY);
|
||||
$aJsFiles = $oBlockRenderer->GetJsFiles();
|
||||
if ($sScript!=''){
|
||||
if ($oP == null) {
|
||||
$sScript = '<script>'.$sScript.'</script>';
|
||||
$sHtml .= $sScript;
|
||||
} else {
|
||||
$oP->add_ready_script($sScript);
|
||||
}
|
||||
}
|
||||
// Ugly hack as we use a block and strip its content above, we'll also need JS files it depends on
|
||||
if(count($aJsFiles) > 0){
|
||||
foreach ($aJsFiles as $sFileAbsUrl) {
|
||||
if ($oP === null) {
|
||||
$sScript = '<script src="'.$sFileAbsUrl.'"></></script>';
|
||||
$sHtml .= $sScript;
|
||||
} else {
|
||||
$oP->add_linked_script($sFileAbsUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,12 +602,11 @@ class ormCaseLog {
|
||||
'date' => time(),
|
||||
'text_length' => $iTextlength,
|
||||
'separator_length' => $iSepLength,
|
||||
'format' => 'html',
|
||||
'format' => static::ENUM_FORMAT_HTML,
|
||||
);
|
||||
$this->m_bModified = true;
|
||||
}
|
||||
|
||||
|
||||
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
|
||||
{
|
||||
if (isset($oJson->user_id))
|
||||
@@ -620,11 +656,11 @@ class ormCaseLog {
|
||||
else
|
||||
{
|
||||
// The default is HTML
|
||||
$sFormat = 'html';
|
||||
$sFormat = static::ENUM_FORMAT_HTML;
|
||||
}
|
||||
|
||||
$sText = isset($oJson->message) ? $oJson->message : '';
|
||||
if ($sFormat == 'html')
|
||||
if ($sFormat == static::ENUM_FORMAT_HTML)
|
||||
{
|
||||
$sText = HTMLSanitizer::Sanitize($sText);
|
||||
}
|
||||
@@ -647,8 +683,7 @@ class ormCaseLog {
|
||||
$this->m_bModified = true;
|
||||
}
|
||||
|
||||
|
||||
public function GetModifiedEntry($sFormat = 'text')
|
||||
public function GetModifiedEntry($sFormat = self::ENUM_FORMAT_TEXT)
|
||||
{
|
||||
$sModifiedEntry = '';
|
||||
if ($this->m_bModified)
|
||||
@@ -663,15 +698,15 @@ class ormCaseLog {
|
||||
* @param string The expected output format text|html
|
||||
* @return string
|
||||
*/
|
||||
public function GetLatestEntry($sFormat = 'text')
|
||||
public function GetLatestEntry($sFormat = self::ENUM_FORMAT_TEXT)
|
||||
{
|
||||
$sRes = '';
|
||||
$aLastEntry = end($this->m_aIndex);
|
||||
$sRaw = substr($this->m_sLog, $aLastEntry['separator_length'], $aLastEntry['text_length']);
|
||||
switch($sFormat)
|
||||
{
|
||||
case 'text':
|
||||
if ($aLastEntry['format'] == 'text')
|
||||
case static::ENUM_FORMAT_TEXT:
|
||||
if ($aLastEntry['format'] == static::ENUM_FORMAT_TEXT)
|
||||
{
|
||||
$sRes = $sRaw;
|
||||
}
|
||||
@@ -681,8 +716,8 @@ class ormCaseLog {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
if ($aLastEntry['format'] == 'text')
|
||||
case static::ENUM_FORMAT_HTML:
|
||||
if ($aLastEntry['format'] == static::ENUM_FORMAT_TEXT)
|
||||
{
|
||||
$sRes = utils::TextToHtml($sRaw);
|
||||
}
|
||||
@@ -726,4 +761,3 @@ class ormCaseLog {
|
||||
return $sText;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -719,70 +719,68 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
{
|
||||
unset($this->aRemoved[$sLinkKey]);
|
||||
}
|
||||
$bIsDuplicate = true;
|
||||
break;
|
||||
$bIsDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($bIsDuplicate)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ($bIsDuplicate) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!array_key_exists($oLink->GetKey(), $aExistingLinks))
|
||||
{
|
||||
} else {
|
||||
if (!array_key_exists($oLink->GetKey(), $aExistingLinks)) {
|
||||
$oLink->DBClone();
|
||||
}
|
||||
}
|
||||
$oLink->DBWrite();
|
||||
|
||||
$this->aPreserved[$oLink->GetKey()] = $oLink;
|
||||
$this->aOriginalObjects[$oLink->GetKey()] = $oLink;
|
||||
}
|
||||
foreach ($this->aRemoved as $iLinkId)
|
||||
{
|
||||
if (array_key_exists($iLinkId, $aExistingLinks))
|
||||
{
|
||||
$this->aAdded = [];
|
||||
|
||||
foreach ($this->aRemoved as $iLinkId) {
|
||||
if (array_key_exists($iLinkId, $aExistingLinks)) {
|
||||
$oLink = $aExistingLinks[$iLinkId];
|
||||
if ($oAttDef->IsIndirect())
|
||||
{
|
||||
if ($oAttDef->IsIndirect()) {
|
||||
$oLink->DBDelete();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$oExtKeyToRemote = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToMe);
|
||||
if ($oExtKeyToRemote->IsNullAllowed())
|
||||
{
|
||||
if ($oLink->Get($sExtKeyToMe) == $oHostObject->GetKey())
|
||||
{
|
||||
if ($oExtKeyToRemote->IsNullAllowed()) {
|
||||
if ($oLink->Get($sExtKeyToMe) == $oHostObject->GetKey()) {
|
||||
// Detach the link object from this
|
||||
$oLink->Set($sExtKeyToMe, 0);
|
||||
$oLink->DBUpdate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$oLink->DBDelete();
|
||||
}
|
||||
}
|
||||
unset($this->aPreserved[$oLink->GetKey()], $this->aOriginalObjects[$oLink->GetKey()]);
|
||||
}
|
||||
}
|
||||
$this->aRemoved = [];
|
||||
|
||||
// Note: process modifications at the end: if a link to remove has also been listed as modified, then it will be gracefully ignored
|
||||
foreach ($this->aModified as $iLinkId => $oLink)
|
||||
{
|
||||
if (array_key_exists($oLink->GetKey(), $aExistingLinks))
|
||||
{
|
||||
foreach ($this->aModified as $iLinkId => $oLink) {
|
||||
if (array_key_exists($oLink->GetKey(), $aExistingLinks)) {
|
||||
$oLink->DBUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$oLink->DBClone();
|
||||
}
|
||||
$this->aPreserved[$oLink->GetKey()] = $oLink;
|
||||
$this->aOriginalObjects[$oLink->GetKey()] = $oLink;
|
||||
}
|
||||
$this->aModified = [];
|
||||
|
||||
// End of the critical section
|
||||
//
|
||||
$oMtx->Unlock();
|
||||
|
||||
// we updated the instance (original/preserved/added/modified/removed arrays) all along the way
|
||||
$this->bHasDelta = false;
|
||||
$this->oOriginalSet->GetFilter()->SetInternalParams(['id', $oHostObject->GetKey()]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -803,6 +801,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
$oLinkSearch = $this->GetFilter();
|
||||
if ($oAttDef->IsIndirect())
|
||||
{
|
||||
$oLinkSearch->RenameAlias($oLinkSearch->GetClassAlias(), self::LINK_ALIAS);
|
||||
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
|
||||
/** @var \AttributeExternalKey $oLinkingAttDef */
|
||||
$oLinkingAttDef = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToRemote);
|
||||
@@ -810,12 +809,12 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
// N°2334 add pointed class (SELECT L,R) to have all fields (lnk + remote) in display
|
||||
// the pointed class is always present in the search, as generated by \AttributeLinkedSet::GetDefaultValue
|
||||
$sTargetClass = $oLinkingAttDef->GetTargetClass();
|
||||
$oRemoteClassSearch = new DBObjectSearch($sTargetClass);
|
||||
$oRemoteClassSearch = new DBObjectSearch($sTargetClass, self::REMOTE_ALIAS);
|
||||
|
||||
if (!$bShowObsolete && MetaModel::IsObsoletable($sTargetClass))
|
||||
{
|
||||
$oNotObsolete = new BinaryExpression(
|
||||
new FieldExpression('obsolescence_flag', $sTargetClass),
|
||||
new FieldExpression('obsolescence_flag', self::REMOTE_ALIAS),
|
||||
'=',
|
||||
new ScalarExpression(0)
|
||||
);
|
||||
@@ -825,7 +824,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
if (!utils::IsArchiveMode() && MetaModel::IsArchivable($sTargetClass))
|
||||
{
|
||||
$oNotArchived = new BinaryExpression(
|
||||
new FieldExpression('archive_flag', $sTargetClass),
|
||||
new FieldExpression('archive_flag', self::REMOTE_ALIAS),
|
||||
'=',
|
||||
new ScalarExpression(0)
|
||||
);
|
||||
@@ -833,9 +832,14 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
$oRemoteClassSearch->AddConditionExpression($oNotArchived);
|
||||
}
|
||||
|
||||
$oLinkSearch->AddCondition_PointingTo($oRemoteClassSearch, $sExtKeyToRemote);
|
||||
$oLinkSearch->RenameAlias($oLinkSearch->GetClassAlias(), self::LINK_ALIAS);
|
||||
$oLinkSearch->RenameAlias($sTargetClass, self::REMOTE_ALIAS);
|
||||
$aReAliasingMap = [];
|
||||
$oLinkSearch->AddCondition_PointingTo($oRemoteClassSearch, $sExtKeyToRemote, TREE_OPERATOR_EQUALS, $aReAliasingMap);
|
||||
if (array_key_exists(self::REMOTE_ALIAS, $aReAliasingMap)) {
|
||||
// If 'Remote' alias has been renamed, change it back.
|
||||
if ($aReAliasingMap[self::REMOTE_ALIAS][0] != self::REMOTE_ALIAS) {
|
||||
$oLinkSearch->RenameAlias($aReAliasingMap[self::REMOTE_ALIAS][0], self::REMOTE_ALIAS);
|
||||
}
|
||||
}
|
||||
$oLinkSearch->SetSelectedClasses([self::LINK_ALIAS, self::REMOTE_ALIAS]);
|
||||
}
|
||||
$oLinkSet = new DBObjectSet($oLinkSearch);
|
||||
|
||||
@@ -138,7 +138,7 @@ final class ormTagSet extends ormSet
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array of tags indexed by code
|
||||
* @return array index: code, value: corresponding {@see \TagSetFieldData}
|
||||
*/
|
||||
public function GetTags()
|
||||
{
|
||||
|
||||
@@ -31,13 +31,13 @@ class iTopOwnershipToken extends DBObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
'category' => 'application',
|
||||
'key_type' => 'autoincrement',
|
||||
'name_attcode' => array('obj_class', 'obj_key'),
|
||||
'state_attcode' => '',
|
||||
'reconc_keys' => array(''),
|
||||
'db_table' => 'priv_ownership_token',
|
||||
'db_key_field' => 'id',
|
||||
'category' => '',
|
||||
'key_type' => 'autoincrement',
|
||||
'name_attcode' => array('obj_class', 'obj_key'),
|
||||
'state_attcode' => '',
|
||||
'reconc_keys' => array(''),
|
||||
'db_table' => 'priv_ownership_token',
|
||||
'db_key_field' => 'id',
|
||||
'db_finalclass_field' => '',
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
|
||||
@@ -95,15 +95,14 @@ class PDFBulkExport extends HTMLBulkExport
|
||||
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="pdf_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oRadioCustom = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput), "pdf_date_format_radio", "custom", "pdf_date_time_format_custom", "radio");
|
||||
$oRadioCustom->SetDescription(Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip'));
|
||||
$oRadioCustom->GetInput()->SetIsChecked($sDateTimeFormat !== (string)AttributeDateTime::GetFormat());
|
||||
$oRadioCustom->SetBeforeInput(false);
|
||||
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
$oFieldSetDate->AddSubBlock($oRadioCustom);
|
||||
|
||||
$sJSTooltip = json_encode('<div id="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#pdf_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_pdf_options').on('preview_updated', function() { FormatDatesInPreview('pdf', 'html'); });
|
||||
$('#pdf_date_time_format_default').on('click', function() { FormatDatesInPreview('pdf', 'html'); });
|
||||
$('#pdf_date_time_format_custom').on('click', function() { FormatDatesInPreview('pdf', 'html'); });
|
||||
|
||||
@@ -84,15 +84,14 @@ class SpreadsheetBulkExport extends TabularBulkExport
|
||||
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="spreadsheet_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oRadioCustom = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput), "spreadsheet_date_format_radio", "custom", "spreadsheet_date_time_format_custom", "radio");
|
||||
$oRadioCustom->SetDescription(Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip'));
|
||||
$oRadioCustom->GetInput()->SetIsChecked($sDateTimeFormat !== (string)AttributeDateTime::GetFormat());
|
||||
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
$oRadioCustom->SetBeforeInput(false);
|
||||
$oFieldSetDate->AddSubBlock($oRadioCustom);
|
||||
|
||||
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#spreadsheet_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_spreadsheet_options').on('preview_updated', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
$('#spreadsheet_date_time_format_default').on('click', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
$('#spreadsheet_date_time_format_custom').on('click', function() { FormatDatesInPreview('spreadsheet', 'spreadsheet'); });
|
||||
@@ -354,7 +353,7 @@ EOF
|
||||
}
|
||||
else if ($oAttDef instanceof AttributeTagSet)
|
||||
{
|
||||
$sField = $oObj->GetAsCSV($sAttCode, $this->bLocalizeOutput, '');
|
||||
$sField = utils::HtmlEntities($oObj->GetAsCSV($sAttCode, $this->bLocalizeOutput, ''));
|
||||
$sData .= "<td x:str>$sField</td>";
|
||||
}
|
||||
else
|
||||
|
||||
@@ -494,7 +494,17 @@ class SQLObjectQuery extends SQLQuery
|
||||
}
|
||||
}
|
||||
|
||||
private function PrepareSingleTable(SQLObjectQuery $oRootQuery, &$aFrom, $sCallerAlias = '', $aJoinData)
|
||||
/**
|
||||
* @param \SQLObjectQuery $oRootQuery
|
||||
* @param $aFrom
|
||||
* @param $sCallerAlias
|
||||
* @param $aJoinData
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $sCallerAlias for PHP 8.0 compat (Private method with only 2 calls in the class, both providing the optional parameter)
|
||||
*/
|
||||
private function PrepareSingleTable(SQLObjectQuery $oRootQuery, &$aFrom, $sCallerAlias, $aJoinData)
|
||||
{
|
||||
$aTranslationTable[$this->m_sTable]['*'] = $this->m_sTableAlias;
|
||||
$sJoinCond = '';
|
||||
@@ -613,6 +623,7 @@ class SQLObjectQuery extends SQLQuery
|
||||
$aTempFrom = array(); // temporary subset of 'from' specs, to be grouped in the final query
|
||||
foreach ($this->m_aJoinSelects as $aJoinData)
|
||||
{
|
||||
/** @var \SQLObjectQuery $oRightSelect */
|
||||
$oRightSelect = $aJoinData["select"];
|
||||
|
||||
$oRightSelect->PrepareSingleTable($oRootQuery, $aTempFrom, $this->m_sTableAlias, $aJoinData);
|
||||
|
||||
@@ -41,6 +41,7 @@ abstract class Trigger extends cmdbAbstractObject
|
||||
"db_table" => "priv_trigger",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "realclass",
|
||||
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-conflict.svg'),
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
//MetaModel::Init_InheritAttributes();
|
||||
@@ -238,7 +239,8 @@ abstract class TriggerOnObject extends Trigger
|
||||
* @param $iObjectId
|
||||
* @param array $aChanges
|
||||
*
|
||||
* @return bool
|
||||
* @return bool True if the object of ID $iObjectId is within the scope of the OQL defined by the "filter" attribute
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
@@ -252,6 +254,7 @@ abstract class TriggerOnObject extends Trigger
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL($sFilter);
|
||||
$oSearch->AddCondition('id', $iObjectId, '=');
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$bRet = ($oSet->Count() > 0);
|
||||
}
|
||||
@@ -582,13 +585,56 @@ class TriggerOnObjectMention extends TriggerOnObject
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeOQL("mentioned_filter", array("allowed_values" => null, "sql" => "mentioned_filter", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'mentioned_filter', 'action_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBObject $oObject
|
||||
*
|
||||
* @return bool True if $oObject is within the scope of the OQL defined by the "mentioned_filter" attribute OR if no mentioned_filter defined. Otherwise, returns false.
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function IsMentionedObjectInScope(DBObject $oObject)
|
||||
{
|
||||
$sFilter = trim($this->Get('mentioned_filter'));
|
||||
if (strlen($sFilter) > 0)
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL($sFilter);
|
||||
$sSearchClass = $oSearch->GetClass();
|
||||
|
||||
// If filter not on current object class (or descendants), consider it as not in scope
|
||||
if (is_a($oObject, $sSearchClass, true) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$oSearch->AddCondition('id', $oObject->GetKey(), '=');
|
||||
if (MetaModel::IsAbstract($oSearch->GetClass())) {
|
||||
$oSearch->AddCondition('finalclass', get_class($oObject), '=');
|
||||
}
|
||||
|
||||
$aParams = $oObject->ToArgs('this');
|
||||
$oSet = new DBObjectSet($oSearch, [], $aParams);
|
||||
$bRet = $oSet->CountExceeds(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
$bRet = true;
|
||||
}
|
||||
|
||||
return $bRet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
|
||||
define('UR_ALLOWED_NO', 0);
|
||||
define('UR_ALLOWED_YES', 1);
|
||||
define('UR_ALLOWED_DEPENDS', 2);
|
||||
@@ -318,7 +321,14 @@ abstract class User extends cmdbAbstractObject
|
||||
{
|
||||
if (MetaModel::IsValidAttCode(get_class($this), 'contactid') && ($this->Get('contactid') != 0))
|
||||
{
|
||||
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
|
||||
$this->oContactObject = null;
|
||||
// The User Contact is generally a Person, so try it first
|
||||
if (MetaModel::IsValidClass('Person')) {
|
||||
$this->oContactObject = MetaModel::GetObject('Person', $this->Get('contactid'), false);
|
||||
}
|
||||
if (is_null($this->oContactObject)) {
|
||||
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->oContactObject;
|
||||
@@ -381,9 +391,9 @@ abstract class User extends cmdbAbstractObject
|
||||
if (!in_array(ADMIN_PROFILE_NAME, $aProfiles)) {
|
||||
// Check if the user is yet allowed to modify Users
|
||||
if (method_exists($oAddon, 'ResetCache')) {
|
||||
$aCurrentProfiles = $_SESSION['profile_list'] ?? null;
|
||||
$aCurrentProfiles = Session::Get('profile_list');
|
||||
// Set the current profiles into a session variable (not yet in the database)
|
||||
$_SESSION['profile_list'] = $aProfiles;
|
||||
Session::Set('profile_list', $aProfiles);
|
||||
|
||||
$oAddon->ResetCache();
|
||||
if (!$oAddon->IsActionAllowed($this, 'User', UR_ACTION_MODIFY, null)) {
|
||||
@@ -392,9 +402,9 @@ abstract class User extends cmdbAbstractObject
|
||||
$oAddon->ResetCache();
|
||||
|
||||
if (is_null($aCurrentProfiles)) {
|
||||
unset($_SESSION['profile_list']);
|
||||
Session::IsSet('profile_list');
|
||||
} else {
|
||||
$_SESSION['profile_list'] = $aCurrentProfiles;
|
||||
Session::Set('profile_list', $aCurrentProfiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -628,7 +638,7 @@ abstract class UserInternal extends User
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core,grant_by_profile",
|
||||
"category" => "core,grant_by_profile,silo",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "login",
|
||||
"state_attcode" => "",
|
||||
@@ -840,10 +850,10 @@ class UserRights
|
||||
}
|
||||
self::$m_oUser = $oUser;
|
||||
|
||||
if (isset($_SESSION['impersonate_user']))
|
||||
if (Session::IsSet('impersonate_user'))
|
||||
{
|
||||
self::$m_oRealUser = self::$m_oUser;
|
||||
self::$m_oUser = self::FindUser($_SESSION['impersonate_user']);
|
||||
self::$m_oUser = self::FindUser(Session::Get('impersonate_user'));
|
||||
}
|
||||
|
||||
Dict::SetUserLanguage(self::GetUserLanguage());
|
||||
@@ -947,15 +957,15 @@ class UserRights
|
||||
{
|
||||
$bRet = false;
|
||||
}
|
||||
elseif (isset($_SESSION['archive_allowed']))
|
||||
elseif (Session::IsSet('archive_allowed'))
|
||||
{
|
||||
$bRet = $_SESSION['archive_allowed'];
|
||||
$bRet = Session::Get('archive_allowed');
|
||||
}
|
||||
else
|
||||
{
|
||||
// As of now, anybody can switch to the archive mode as soon as there is an archivable class
|
||||
$bRet = (count(MetaModel::EnumArchivableClasses()) > 0);
|
||||
$_SESSION['archive_allowed'] = $bRet;
|
||||
Session::Set('archive_allowed', $bRet);
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
@@ -1041,7 +1051,7 @@ class UserRights
|
||||
// Do impersonate!
|
||||
self::$m_oUser = $oUser;
|
||||
Dict::SetUserLanguage(self::GetUserLanguage());
|
||||
$_SESSION['impersonate_user'] = $sLogin;
|
||||
Session::Set('impersonate_user', $sLogin);
|
||||
self::_ResetSessionCache();
|
||||
}
|
||||
}
|
||||
@@ -1057,7 +1067,7 @@ class UserRights
|
||||
{
|
||||
self::$m_oUser = self::$m_oRealUser;
|
||||
Dict::SetUserLanguage(self::GetUserLanguage());
|
||||
unset($_SESSION['impersonate_user']);
|
||||
Session::Unset('impersonate_user');
|
||||
self::_ResetSessionCache();
|
||||
}
|
||||
}
|
||||
@@ -1193,17 +1203,26 @@ class UserRights
|
||||
// Then check if the user has a contact attached and if it has an picture defined
|
||||
$sContactId = UserRights::GetContactId($sLogin);
|
||||
if (!empty($sContactId)) {
|
||||
$oContact = MetaModel::GetObject('Contact', $sContactId, false, true);
|
||||
$oContact = null;
|
||||
// Picture if generally for Person, so try it first
|
||||
if (MetaModel::IsValidClass('Person')) {
|
||||
$oContact = MetaModel::GetObject('Person', $sContactId, false, true);
|
||||
}
|
||||
if (is_null($oContact)) {
|
||||
$oContact = MetaModel::GetObject('Contact', $sContactId, false, true);
|
||||
}
|
||||
$sContactClass = get_class($oContact);
|
||||
|
||||
// Check that Contact object still exists and that Contact class has a picture attribute
|
||||
if (!is_null($oContact) && MetaModel::IsValidAttCode($sContactClass, static::DEFAULT_CONTACT_PICTURE_ATTCODE)) {
|
||||
// - Try to get the semantic image attribute, or try to fallback on the default one if none defined
|
||||
$sContactPictureAttCode = MetaModel::HasImageAttributeCode($sContactClass) ? MetaModel::GetImageAttributeCode($sContactClass) : static::DEFAULT_CONTACT_PICTURE_ATTCODE;
|
||||
if (!is_null($oContact) && MetaModel::IsValidAttCode($sContactClass, $sContactPictureAttCode)) {
|
||||
/** @var \ormDocument $oPicture */
|
||||
$oPicture = $oContact->Get(static::DEFAULT_CONTACT_PICTURE_ATTCODE);
|
||||
$oPicture = $oContact->Get($sContactPictureAttCode);
|
||||
if ($oPicture->IsEmpty()) {
|
||||
if ($bAllowDefaultPicture === true) {
|
||||
/** @var \AttributeImage $oAttDef */
|
||||
$oAttDef = MetaModel::GetAttributeDef($sContactClass, static::DEFAULT_CONTACT_PICTURE_ATTCODE);
|
||||
$oAttDef = MetaModel::GetAttributeDef($sContactClass, $sContactPictureAttCode);
|
||||
$sPictureUrl = $oAttDef->Get('default_image');
|
||||
} else {
|
||||
$sPictureUrl = null;
|
||||
@@ -1211,9 +1230,9 @@ class UserRights
|
||||
} else {
|
||||
if (ContextTag::Check(ContextTag::TAG_PORTAL)) {
|
||||
$sSignature = $oPicture->GetSignature();
|
||||
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().'pages/exec.php/object/document/display/'.$sContactClass.'/'.$oContact->GetKey().'/'.static::DEFAULT_CONTACT_PICTURE_ATTCODE.'?cache=86400&s='.$sSignature.'&exec_module=itop-portal-base&exec_page=index.php&portal_id='.PORTAL_ID;
|
||||
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().'pages/exec.php/object/document/display/'.$sContactClass.'/'.$oContact->GetKey().'/'.$sContactPictureAttCode.'?cache=86400&s='.$sSignature.'&exec_module=itop-portal-base&exec_page=index.php&portal_id='.PORTAL_ID;
|
||||
} else {
|
||||
$sPictureUrl = $oPicture->GetDisplayURL($sContactClass, $oContact->GetKey(), static::DEFAULT_CONTACT_PICTURE_ATTCODE);
|
||||
$sPictureUrl = $oPicture->GetDisplayURL($sContactClass, $oContact->GetKey(), $sContactPictureAttCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1418,6 +1437,21 @@ class UserRights
|
||||
return self::$m_oRealUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|string ID of the connected user : if impersonate then use {@see m_oRealUser}, else {@see m_oUser}. If no user set then return ''
|
||||
* @since 2.6.5 2.7.6 3.0.0 N°4289 method creation
|
||||
*/
|
||||
public static function GetConnectedUserId() {
|
||||
if (false === is_null(static::$m_oRealUser)) {
|
||||
return static::$m_oRealUser->GetKey();
|
||||
}
|
||||
if (false === is_null(static::$m_oUser)) {
|
||||
return static::$m_oUser->GetKey();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -1473,7 +1507,7 @@ class UserRights
|
||||
try
|
||||
{
|
||||
// Check Bug 1436 for details
|
||||
if (MetaModel::HasCategory($sClass, 'bizmodel') || MetaModel::HasCategory($sClass, 'silo'))
|
||||
if (MetaModel::HasCategory($sClass, 'bizmodel') || MetaModel::HasCategory($sClass, 'silo') || MetaModel::HasCategory($sClass, 'filter'))
|
||||
{
|
||||
return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass, $aSettings);
|
||||
}
|
||||
@@ -1755,9 +1789,9 @@ class UserRights
|
||||
elseif ((self::$m_oUser !== null) && ($oUser->GetKey() == self::$m_oUser->GetKey()))
|
||||
{
|
||||
// Data about the current user can be found into the session data
|
||||
if (array_key_exists('profile_list', $_SESSION))
|
||||
if (Session::IsSet('profile_list'))
|
||||
{
|
||||
$aProfiles = $_SESSION['profile_list'];
|
||||
$aProfiles = Session::Get('profile_list');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1792,11 +1826,6 @@ class UserRights
|
||||
self::$m_aAdmins = array();
|
||||
self::$m_aPortalUsers = array();
|
||||
}
|
||||
if (!isset($_SESSION) && !utils::IsModeCLI())
|
||||
{
|
||||
session_name('itop-'.md5(APPROOT));
|
||||
session_start();
|
||||
}
|
||||
self::_ResetSessionCache();
|
||||
if (self::$m_oAddOn)
|
||||
{
|
||||
@@ -1830,7 +1859,7 @@ class UserRights
|
||||
{
|
||||
self::$m_aCacheUsers = array('internal' => array(), 'external' => array());
|
||||
}
|
||||
|
||||
|
||||
if (!isset(self::$m_aCacheUsers[$sAuthentication][$sLogin]))
|
||||
{
|
||||
switch($sAuthentication)
|
||||
@@ -1838,7 +1867,7 @@ class UserRights
|
||||
case 'external':
|
||||
$sBaseClass = 'UserExternal';
|
||||
break;
|
||||
|
||||
|
||||
case 'internal':
|
||||
$sBaseClass = 'UserInternal';
|
||||
break;
|
||||
@@ -1848,6 +1877,7 @@ class UserRights
|
||||
assert(false); // should never happen
|
||||
}
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sBaseClass WHERE login = :login");
|
||||
$oSearch->AllowAllData();
|
||||
if (!$bAllowDisabledUsers)
|
||||
{
|
||||
$oSearch->AddCondition('status', 'enabled');
|
||||
@@ -1881,10 +1911,7 @@ class UserRights
|
||||
public static function _InitSessionCache()
|
||||
{
|
||||
// Cache data about the current user into the session
|
||||
if (isset($_SESSION))
|
||||
{
|
||||
$_SESSION['profile_list'] = self::ListProfiles();
|
||||
}
|
||||
Session::Set('profile_list', self::ListProfiles());
|
||||
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$bSessionIdRegeneration = $oConfig->Get('regenerate_session_id_enabled');
|
||||
@@ -1906,14 +1933,8 @@ class UserRights
|
||||
|
||||
public static function _ResetSessionCache()
|
||||
{
|
||||
if (isset($_SESSION['profile_list']))
|
||||
{
|
||||
unset($_SESSION['profile_list']);
|
||||
}
|
||||
if (isset($_SESSION['archive_allowed']))
|
||||
{
|
||||
unset($_SESSION['archive_allowed']);
|
||||
}
|
||||
Session::Unset('profile_list');
|
||||
Session::Unset('archive_allowed');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
|
||||
require_once('MyHelpers.class.inc.php');
|
||||
|
||||
/**
|
||||
@@ -379,41 +381,35 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
|
||||
$this->m_aValues = array();
|
||||
|
||||
if ($this->m_bAllowAllData)
|
||||
{
|
||||
if ($this->m_bAllowAllData) {
|
||||
$oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
|
||||
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
}
|
||||
|
||||
if (!$oFilter) return false;
|
||||
if (!is_null($this->m_oExtraCondition))
|
||||
{
|
||||
if (!$oFilter) {
|
||||
return false;
|
||||
}
|
||||
if (!is_null($this->m_oExtraCondition)) {
|
||||
$oFilter = $oFilter->Intersect($this->m_oExtraCondition);
|
||||
}
|
||||
foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
|
||||
{
|
||||
foreach ($aProperties as $sProperty => $value)
|
||||
{
|
||||
foreach ($this->m_aModifierProperties as $sPluginClass => $aProperties) {
|
||||
foreach ($aProperties as $sProperty => $value) {
|
||||
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
|
||||
}
|
||||
}
|
||||
|
||||
//$oExpression = DBObjectSearch::GetPolymorphicExpression($oFilter->GetClass(), 'friendlyname');
|
||||
$sClass = $oFilter->GetClass();
|
||||
$sClassAlias = $oFilter->GetClassAlias();
|
||||
|
||||
switch ($sOperation)
|
||||
{
|
||||
switch ($sOperation) {
|
||||
case 'equals':
|
||||
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
|
||||
$sClassAlias = $oFilter->GetClassAlias();
|
||||
$aFilters = array();
|
||||
$oValueExpr = new ScalarExpression($sContains);
|
||||
foreach($aAttributes as $sAttribute)
|
||||
{
|
||||
foreach ($aAttributes as $sAttribute) {
|
||||
$oNewFilter = $oFilter->DeepClone();
|
||||
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
|
||||
$oCondition = new BinaryExpression($oNameExpr, '=', $oValueExpr);
|
||||
@@ -425,7 +421,6 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
break;
|
||||
case 'start_with':
|
||||
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
|
||||
$sClassAlias = $oFilter->GetClassAlias();
|
||||
$aFilters = array();
|
||||
$oValueExpr = new ScalarExpression($sContains.'%');
|
||||
foreach($aAttributes as $sAttribute)
|
||||
@@ -442,63 +437,67 @@ class ValueSetObjects extends ValueSetDefinition
|
||||
|
||||
default:
|
||||
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
|
||||
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
|
||||
$oNameExpr = new FieldExpression('friendlyname', $sClassAlias);
|
||||
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
|
||||
$oFilter->AddConditionExpression($oNewCondition);
|
||||
break;
|
||||
}
|
||||
|
||||
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort);
|
||||
if (empty($this->m_sValueAttCode))
|
||||
{
|
||||
$aAttToLoad = array($oFilter->GetClassAlias() => array('friendlyname'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAttToLoad = array($oFilter->GetClassAlias() => array($this->m_sValueAttCode));
|
||||
if (empty($this->m_sValueAttCode)) {
|
||||
$aAttToLoad = ['friendlyname'];
|
||||
} else {
|
||||
$aAttToLoad = [$this->m_sValueAttCode];
|
||||
}
|
||||
|
||||
$aComplementAttributeSpec = MetaModel::GetComplementAttributeSpec($sClass);
|
||||
$sImageAttr = MetaModel::GetImageAttributeCode($sClass);
|
||||
if (!empty($sImageAttr)) {
|
||||
$aAttToLoad [] = $sImageAttr;
|
||||
}
|
||||
|
||||
$aComplementAttributeSpec = MetaModel::GetNameSpec($sClass, FriendlyNameType::COMPLEMENTARY);
|
||||
$sFormatAdditionalField = $aComplementAttributeSpec[0];
|
||||
$aAdditionalField = $aComplementAttributeSpec[1];
|
||||
$aAdditionalField = $aComplementAttributeSpec[1];
|
||||
|
||||
if (count($aAdditionalField)>0)
|
||||
{
|
||||
$aAttToLoad = array_merge ($aAttToLoad, [$oFilter->GetClassAlias() => $aAdditionalField]);
|
||||
if (count($aAdditionalField) > 0) {
|
||||
if (is_array($aAdditionalField)) {
|
||||
$aAttToLoad = array_merge($aAttToLoad, $aAdditionalField);
|
||||
} else {
|
||||
$aAttToLoad [] = $aAdditionalField;
|
||||
}
|
||||
}
|
||||
|
||||
$oObjects->OptimizeColumnLoad($aAttToLoad);
|
||||
while ($oObject = $oObjects->Fetch())
|
||||
{
|
||||
$aData=[];
|
||||
if (empty($this->m_sValueAttCode))
|
||||
{
|
||||
$oObjects->OptimizeColumnLoad([$sClassAlias => $aAttToLoad]);
|
||||
while ($oObject = $oObjects->Fetch()) {
|
||||
$aData = [];
|
||||
if (empty($this->m_sValueAttCode)) {
|
||||
$aData['label'] = $oObject->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aData['label'] = $oObject->Get($this->m_sValueAttCode);
|
||||
}
|
||||
if($oObject->IsObsolete())
|
||||
{
|
||||
$aData['obsolescence_flag']='1';
|
||||
if ($oObject->IsObsolete()) {
|
||||
$aData['obsolescence_flag'] = '1';
|
||||
} else {
|
||||
$aData['obsolescence_flag'] = '0';
|
||||
}
|
||||
else
|
||||
{
|
||||
$aData['obsolescence_flag']='0';
|
||||
}
|
||||
if (count($aAdditionalField)>0)
|
||||
{
|
||||
if (count($aAdditionalField) > 0) {
|
||||
$aArguments = [];
|
||||
foreach ($aAdditionalField as $sAdditionalField)
|
||||
{
|
||||
array_push ($aArguments,$oObject->Get($sAdditionalField));
|
||||
foreach ($aAdditionalField as $sAdditionalField) {
|
||||
array_push($aArguments, $oObject->Get($sAdditionalField));
|
||||
}
|
||||
$aData['additional_field'] = vsprintf($sFormatAdditionalField, $aArguments);
|
||||
} else {
|
||||
$aData['additional_field'] = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$aData['additional_field']='';
|
||||
if (!empty($sImageAttr)) {
|
||||
/** @var \ormDocument $oImage */
|
||||
$oImage = $oObject->Get($sImageAttr);
|
||||
if (!$oImage->IsEmpty()) {
|
||||
$aData['picture_url'] = $oImage->GetDisplayURL($sClass, $oObject->GetKey(), $sImageAttr);
|
||||
$aData['initials'] = '';
|
||||
} else {
|
||||
$aData['initials'] = utils::ToAcronym($aData['label']);
|
||||
}
|
||||
}
|
||||
$this->m_aValues[$oObject->GetKey()] = $aData;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
/*!
|
||||
* Copyright (C) 2013-2021 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
//==========================================================================
|
||||
@@ -35,4 +22,8 @@ fieldset {
|
||||
|
||||
legend {
|
||||
@extend .ibo-fieldset-legend;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@extend .ibo-input-text;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/*!
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
@@ -42,4 +42,24 @@
|
||||
// N°2847 - Recolor svg illustrations with iTop's primary color
|
||||
.ibo-svg-illustration--container > svg *[fill="#6c63ff"]{
|
||||
fill: $ibo-svg-illustration--fill;
|
||||
}
|
||||
|
||||
// N°4481 - Restore HTML tables style identical between edition and visualization
|
||||
// This is a hack to compensate missing variables in the bulma lib, PR has been made here: https://github.com/jgthms/bulma/pull/3455
|
||||
// The following can't be reset to it's original value (from the browser stylesheet), so we have to hardcode it even though it might change in future browser versions...
|
||||
.ibo-is-html-content table {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
&:last-child {
|
||||
td,
|
||||
th {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,6 @@
|
||||
/*!
|
||||
* Copyright (C) 2013-2021 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@import "display-block/all";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*!
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-block-csv--textarea--width: 100% !default;
|
||||
$ibo-block-csv--textarea--min-height: 10em !default;
|
||||
$ibo-block-csv--textarea--margin-top: 10px !default;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*!
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
/*!
|
||||
* Copyright (C) 2013-2021 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@import "tabular-fields-selector";
|
||||
@@ -1,4 +1,4 @@
|
||||
/*!
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
@@ -8,7 +8,9 @@
|
||||
overflow-x: auto;
|
||||
|
||||
th {
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
padding-right: $ibo-spacing-600;
|
||||
border-width: 1px 1px 0;
|
||||
border-style: groove groove none;
|
||||
background: $ibo-color-white-200;
|
||||
@@ -30,7 +32,15 @@
|
||||
.ibo-preview-header {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.ibo-table-preview--remove-column {
|
||||
position: absolute;
|
||||
top: $ibo-spacing-300;
|
||||
right: $ibo-spacing-300;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
#form_part_interactive_fields_xlsx, #form_part_interactive_fields_csv, #form_part_interactive_fields_pdf {
|
||||
margin-top: $ibo-panel--spacing-top;
|
||||
margin-top: $ibo-spacing-600;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
/*!
|
||||
* Copyright (C) 2013-2021 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@import "base";
|
||||
|
||||
@@ -1,31 +1,19 @@
|
||||
/*!
|
||||
* Copyright (C) 2013-2021 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-scrollbar--scrollbar-width: 8px !default;
|
||||
$ibo-scrollbar--scrollbar-height: $ibo-scrollbar--scrollbar-width !default; /* For horizontal scrollbars */
|
||||
$ibo-scrollbar--scrollbar-track-background-color: $ibo-color-transparent !default;
|
||||
$ibo-scrollbar--scrollbar-track-border-radius: $ibo-border-radius-500 !default;
|
||||
$ibo-scrollbar--scrollbar-thumb-background-color: $ibo-color-grey-300 !default;
|
||||
$ibo-scrollbar--scrollbar-thumb-border: none !default;
|
||||
$ibo-scrollbar--scrollbar-thumb-border-radius: $ibo-border-radius-500 !default;
|
||||
|
||||
$ibo-hyperlink-color: $ibo-color-primary-500 !default;
|
||||
$ibo-hyperlink-color--on-hover: $ibo-color-primary-600 !default;
|
||||
$ibo-hyperlink-color--on-active: $ibo-color-primary-700 !default;
|
||||
$ibo-hyperlink-color: $ibo-color-primary-700 !default;
|
||||
$ibo-hyperlink-color--on-hover: $ibo-color-primary-800 !default;
|
||||
$ibo-hyperlink-color--on-active: $ibo-color-primary-900 !default;
|
||||
|
||||
$ibo-svg-illustration--fill: $ibo-color-primary-500 !default;
|
||||
|
||||
@@ -37,6 +25,7 @@ $ibo-content-block--border: 1px solid $ibo-color-grey-400 !default;
|
||||
--ibo-scrollbar--scrollbar-width: #{$ibo-scrollbar--scrollbar-width};
|
||||
--ibo-scrollbar--scrollbar-height: #{$ibo-scrollbar--scrollbar-height};
|
||||
--ibo-scrollbar--scrollbar-track-background-color: #{$ibo-scrollbar--scrollbar-track-background-color};
|
||||
--ibo-scrollbar--scrollbar-track-border-radius: #{$ibo-scrollbar--scrollbar-track-border-radius};
|
||||
--ibo-scrollbar--scrollbar-thumb-background-color: #{$ibo-scrollbar--scrollbar-thumb-background-color};
|
||||
--ibo-scrollbar--scrollbar-thumb-border: #{$ibo-scrollbar--scrollbar-thumb-border};
|
||||
--ibo-scrollbar--scrollbar-thumb-border-radius: #{$ibo-scrollbar--scrollbar-thumb-border-radius};
|
||||
@@ -66,6 +55,7 @@ $ibo-content-block--border: 1px solid $ibo-color-grey-400 !default;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: var(--ibo-scrollbar--scrollbar-track-background-color);
|
||||
border-radius: var(--ibo-scrollbar--scrollbar-track-border-radius);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--ibo-scrollbar--scrollbar-thumb-background-color);
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
/*!
|
||||
* Copyright (C) 2013-2021 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/* This is an overload of the default lib. stylesheet to use local fonts instead of the CDN */
|
||||
@@ -21,125 +8,143 @@
|
||||
font-family: Raleway;
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local('Raleway Thin'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Thin.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-100-normal.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 100;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
src: local('Raleway Thin'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Thin-Italic.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-100-italic.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 200;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local('Raleway ExtraLight'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraLight.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-200-normal.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
src: local('Raleway ExtraLight'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraLight-Italic.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-200-italic.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local('Raleway Light'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Light.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-300-normal.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
src: local('Raleway Light'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Light-Italic.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-300-italic.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local('Raleway'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Regular.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-400-normal.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
src: local('Raleway'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Regular-Italic.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-400-italic.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local('Raleway Medium'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Medium.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-500-normal.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
src: local('Raleway Medium'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Medium-Italic.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-500-italic.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local('Raleway SemiBold'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-SemiBold.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-600-normal.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 600;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
src: local('Raleway SemiBold'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-SemiBold-Italic.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-600-italic.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local('Raleway'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Bold.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-700-normal.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
src: local('Raleway'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Bold-Italic.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-700-italic.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local('Raleway ExtraBold'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraBold.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-800-normal.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 800;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
src: local('Raleway ExtraBold'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraBold-Italic.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-800-italic.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: local('Raleway Black'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Black.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-900-normal.woff') format('woff'),
|
||||
}
|
||||
@font-face {
|
||||
font-family: Raleway;
|
||||
font-weight: 900;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
src: local('Raleway Black'),
|
||||
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Black-Italic.ttf') format('truetype'),
|
||||
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-900-italic.woff') format('woff'),
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*!
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
/*!
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@import "dashlet-within-dashboard";
|
||||
@import "alert/all";
|
||||
@import "button/all";
|
||||
@import "collapsible-section/all";
|
||||
@import "datatable/all";
|
||||
@import "display-block/all";
|
||||
@import "field/all";
|
||||
@import "fieldset/all";
|
||||
@import "form/all";
|
||||
@import "input/all";
|
||||
@import "panel/all";
|
||||
@import "pill/all";
|
||||
@import "dashlet/all";
|
||||
@import "add-to-dashboard";
|
||||
@import "caselog-entry-form-within-activity-panel";
|
||||
@import "panel-with-datatable";
|
||||
@import "panel-with-tab-container";
|
||||
@import "panel-within-main-content";
|
||||
@import "panel-within-modal";
|
||||
@import "tab-container-within-panel";
|
||||
@import "object-details-with-tab-container";
|
||||
@import "medallion-with-blocklist";
|
||||
@import "input-within-datatable";
|
||||
@import "field-badge-within-datatable";
|
||||
@import "jquery-blockui-within-dialog";
|
||||
@import "jquery-blockui-within-datatable";
|
||||
@@ -1,4 +1,4 @@
|
||||
/*!
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-field-badge-within-datatable--ibo-field-badge--margin: $ibo-spacing-0 !default;
|
||||
$ibo-field-badge-within-datatable--ibo-field-badge--padding: $ibo-spacing-0 !default;
|
||||
$ibo-field-badge-within-datatable--ibo-field-badge--text-color: unset !default;
|
||||
$ibo-field-badge-within-datatable--ibo-field-badge--background-color: unset !default;
|
||||
$ibo-field-badge-within-datatable--ibo-field-badge-dot--size: 10px !default;
|
||||
$ibo-field-badge-within-datatable--ibo-field-badge-dot--spacing-x: $ibo-field-badge--label--spacing-x !default;
|
||||
$ibo-field-badge-within-datatable--ibo-field-badge-dot--background-color: $ibo-color-white-100 !default;
|
||||
|
||||
.ibo-datatable {
|
||||
.ibo-field-badge {
|
||||
margin: $ibo-field-badge-within-datatable--ibo-field-badge--margin;
|
||||
padding: $ibo-field-badge-within-datatable--ibo-field-badge--padding;
|
||||
color: $ibo-field-badge-within-datatable--ibo-field-badge--text-color;
|
||||
background-color: $ibo-field-badge-within-datatable--ibo-field-badge--background-color;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-flex;
|
||||
margin-right: $ibo-field-badge-within-datatable--ibo-field-badge-dot--spacing-x;
|
||||
width: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
|
||||
height: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
|
||||
min-width: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
|
||||
min-height: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
|
||||
background-color: var(--ibo-main-color);
|
||||
@extend %ibo-border-radius-full;
|
||||
}
|
||||
|
||||
.ibo-field-badge--decoration {
|
||||
display: none;
|
||||
+ .ibo-field-badge--label {
|
||||
margin-left: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
$ibo-input-within-datatable--attribute-set-item--padding-x: $ibo-input-set--item--padding-x !default;
|
||||
$ibo-input-within-datatable--attribute-set-item--box-shadow: $ibo-elevation-100 !default;
|
||||
|
||||
.ibo-datatable {
|
||||
.attribute-set {
|
||||
.attribute-set-item {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0 $ibo-input-within-datatable--attribute-set-item--padding-x;
|
||||
box-shadow: $ibo-input-within-datatable--attribute-set-item--box-shadow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-vendors-blockui--blockoverlay--within--datatable--background-color: $ibo-color-white-100 !default;
|
||||
|
||||
.ibo-datatable .blockUI.blockOverlay{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/*!
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-vendors-blockui--blockoverlay--within--dialog--background-color: $ibo-vendors-jqueryui--ui-dialog--background-color !default;
|
||||
|
||||
.ui-dialog .blockUI.blockOverlay{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user