mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-14 07:54:10 +01:00
Compare commits
1389 Commits
3
...
3.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
827a108a38 | ||
|
|
4b50f5e1db | ||
|
|
5b742c97c9 | ||
|
|
16a2137777 | ||
|
|
e8316782aa | ||
|
|
edcf9e6a1e | ||
|
|
7db97c6804 | ||
|
|
f039d54a51 | ||
|
|
dab0e372d0 | ||
|
|
dc8c6ed7a9 | ||
|
|
18e341b0b7 | ||
|
|
2722f305e0 | ||
|
|
469e2e6e0e | ||
|
|
05301d4191 | ||
|
|
3df5febd3e | ||
|
|
dfd1d5fe35 | ||
|
|
d289457c0c | ||
|
|
f52b3bff0d | ||
|
|
b6b17733bf | ||
|
|
fc2b00699e | ||
|
|
b00b08d08b | ||
|
|
c02ea05883 | ||
|
|
61a0028d02 | ||
|
|
c1c2d027c3 | ||
|
|
5269096ecd | ||
|
|
e6511e049a | ||
|
|
74fbd12709 | ||
|
|
4db5d4c08d | ||
|
|
c40394638a | ||
|
|
cc2862837a | ||
|
|
e27c6b1cd7 | ||
|
|
ae00686e58 | ||
|
|
7934f9b9dc | ||
|
|
7f2eef4a24 | ||
|
|
8a65a592f3 | ||
|
|
7d6b019cfa | ||
|
|
5e48400cb1 | ||
|
|
252562ace9 | ||
|
|
c9c32b0de1 | ||
|
|
770ac8ffe5 | ||
|
|
db137d3816 | ||
|
|
77768e8400 | ||
|
|
5621eb2191 | ||
|
|
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 | ||
|
|
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 | ||
|
|
007e1ded0d | ||
|
|
5651512f68 | ||
|
|
8c043f137c | ||
|
|
b3cce54ee9 | ||
|
|
e4e4e3b0bf | ||
|
|
1cb3b4bd96 | ||
|
|
97ee6570d2 | ||
|
|
8c8f711fe8 | ||
|
|
f140efd95c | ||
|
|
6b19758654 | ||
|
|
2899c82ef2 | ||
|
|
12c2929f1d | ||
|
|
9d28e43804 | ||
|
|
27217815d1 | ||
|
|
8e3cc471df | ||
|
|
58e35ea33b | ||
|
|
0b81601699 | ||
|
|
4d3ba6edd0 | ||
|
|
9de014c9cb | ||
|
|
75dbaada72 | ||
|
|
0d4dd0c67b | ||
|
|
fabdef37d2 | ||
|
|
e666631f63 | ||
|
|
280feca863 | ||
|
|
7690e43b39 | ||
|
|
0c66882990 | ||
|
|
7613ac7a9b | ||
|
|
7af10e5197 | ||
|
|
095c975ec6 | ||
|
|
9e1e5a8a47 | ||
|
|
2c026fa891 | ||
|
|
00d65aeb45 | ||
|
|
5702f603ac | ||
|
|
e3dc1b77cc | ||
|
|
c304f70ff4 | ||
|
|
53fd41e748 | ||
|
|
7577fbb8bf | ||
|
|
0fc912b357 | ||
|
|
c475e66176 | ||
|
|
cd1ba097cb | ||
|
|
fdca4d4cc3 | ||
|
|
4379b4d908 | ||
|
|
5b42f67a99 | ||
|
|
0b9ccc8e67 | ||
|
|
2d98ca2318 | ||
|
|
92add2bbfe | ||
|
|
f15bdb75ca | ||
|
|
a66830de17 | ||
|
|
714294e1b4 | ||
|
|
e3d2c1d761 | ||
|
|
59d95cc14b | ||
|
|
ddc5bbd1bb | ||
|
|
9bbee0d603 | ||
|
|
be1ef5b452 | ||
|
|
cbdc48b7e1 | ||
|
|
bb5679959e | ||
|
|
cdbb4470fc | ||
|
|
252d752bf5 | ||
|
|
99d0c05c1c | ||
|
|
5926e9d110 | ||
|
|
c8dd8c3806 | ||
|
|
70b07721e6 | ||
|
|
bd050dfe69 | ||
|
|
9eb477ce83 | ||
|
|
a742b6c610 | ||
|
|
fcf769666e | ||
|
|
72e628c5d5 | ||
|
|
bf28602ae6 | ||
|
|
b8a0d899f4 | ||
|
|
d335736cfc | ||
|
|
80f7d07378 | ||
|
|
0be6a8aef4 | ||
|
|
0fe857071d | ||
|
|
05981c8af8 | ||
|
|
0abea749fa | ||
|
|
78af6fdc84 | ||
|
|
1e97b5a8c0 | ||
|
|
0214243b63 | ||
|
|
d30871ac59 | ||
|
|
d247ea915d | ||
|
|
0e0aed1ba4 | ||
|
|
bc770ef3d5 | ||
|
|
56a4fb0b42 | ||
|
|
3139628dd8 | ||
|
|
7d9b19cd9e | ||
|
|
b3cb95d2f1 | ||
|
|
a6765cdc3a | ||
|
|
97d52fb539 | ||
|
|
ad936d7a2f | ||
|
|
090eb9a560 | ||
|
|
e4f58b5b98 | ||
|
|
c99a22c9f7 | ||
|
|
29cf20beaf | ||
|
|
e325d535ae | ||
|
|
11cfdb2a17 | ||
|
|
66720b9731 | ||
|
|
234f46cafa | ||
|
|
27c3ce0389 | ||
|
|
9d0e2fa64a | ||
|
|
e211633fed | ||
|
|
29967aa41a | ||
|
|
3ebb1cfc66 | ||
|
|
6c2221b8b6 | ||
|
|
c0b3aed12c | ||
|
|
53efc9f75e | ||
|
|
0d566f2e47 | ||
|
|
b294e3c734 | ||
|
|
6704f9eccf | ||
|
|
90cb6e1d49 | ||
|
|
c0aa4f2d69 | ||
|
|
01984c24c0 | ||
|
|
1da1e0b1bd | ||
|
|
39bcd3e4cd | ||
|
|
156cce6098 | ||
|
|
3130e95f4f | ||
|
|
e28f704f3e | ||
|
|
5c6c59941a | ||
|
|
bc9d47933e | ||
|
|
07a10e4146 | ||
|
|
e8a21870ad | ||
|
|
2e132d5c53 | ||
|
|
3359609349 | ||
|
|
2966759466 | ||
|
|
25395405e5 | ||
|
|
b589e2d001 | ||
|
|
27f3619cf5 | ||
|
|
616fc436d0 | ||
|
|
4f2f765207 | ||
|
|
9931fa1a6b | ||
|
|
a13f2750ea | ||
|
|
243d105f59 | ||
|
|
7783ba570e | ||
|
|
5a9fa2ac32 | ||
|
|
619e3de5a8 | ||
|
|
83064d68c7 | ||
|
|
f3c11e72cf | ||
|
|
c9e887a264 | ||
|
|
49fd482389 | ||
|
|
96de4e1796 | ||
|
|
44f413583c | ||
|
|
91104002ea | ||
|
|
f98ba1594c | ||
|
|
431dc5532b | ||
|
|
df473ae313 | ||
|
|
40ce74cffa | ||
|
|
7598c18ad6 | ||
|
|
d38b655f5f | ||
|
|
f4345ef312 | ||
|
|
13b548e95d | ||
|
|
3a988ab499 | ||
|
|
14a5f87d62 | ||
|
|
8dae459b12 | ||
|
|
8dc10424e8 | ||
|
|
54a6573948 | ||
|
|
1d5e0b6fe9 | ||
|
|
acb8a377dd | ||
|
|
d86f489c89 | ||
|
|
2c154b601d | ||
|
|
7dad079688 | ||
|
|
ace2eb6dab | ||
|
|
25f3c1cbc4 | ||
|
|
8f7e7c136d | ||
|
|
71a7e060e4 | ||
|
|
c450f1d02f | ||
|
|
4431762c10 | ||
|
|
97170892e6 | ||
|
|
5137d634e3 | ||
|
|
146a95baec | ||
|
|
2b71ea108a | ||
|
|
a7ac9126a2 | ||
|
|
ee544b646d | ||
|
|
344cce9fdd | ||
|
|
bfcfcdd4cc | ||
|
|
4410bf7e1f | ||
|
|
d1fda1dbc6 | ||
|
|
5c702be641 | ||
|
|
8a9ad78e9c | ||
|
|
778be8abce | ||
|
|
0760adcef6 | ||
|
|
29ca291860 | ||
|
|
93d0e4ae7c | ||
|
|
61bc07b598 | ||
|
|
00ee36f938 | ||
|
|
1aa5185c93 | ||
|
|
834ac00d37 | ||
|
|
957cb87b8d | ||
|
|
d0813f6607 | ||
|
|
cf4673d284 | ||
|
|
e3422f5fd9 | ||
|
|
d7a0878a39 | ||
|
|
076f0e00a7 | ||
|
|
eb04ecb5b4 | ||
|
|
3323d5532b | ||
|
|
d68d8204f2 | ||
|
|
4a50b95069 | ||
|
|
fd9e4f413c | ||
|
|
dca3fc996c | ||
|
|
c58c1bbf1d | ||
|
|
524919b946 | ||
|
|
e15953524a | ||
|
|
68f3c53faa | ||
|
|
82e3ddaacd | ||
|
|
dfbbee2a1c | ||
|
|
4843545171 | ||
|
|
4e58b8616a | ||
|
|
1a79dcd773 | ||
|
|
a4104d4315 | ||
|
|
4e1db7d7e2 | ||
|
|
8e6379a112 | ||
|
|
da217a1cb3 | ||
|
|
a683634a05 | ||
|
|
2f1b5d2736 | ||
|
|
c9bf784fce | ||
|
|
4ef10ae7c9 | ||
|
|
5174250ff1 | ||
|
|
7b6ac202c6 | ||
|
|
d960183403 | ||
|
|
ece3e0490d | ||
|
|
1562cb1f38 | ||
|
|
11a22abfd5 | ||
|
|
5254c9a633 | ||
|
|
f7a35072f5 | ||
|
|
b5f5780f35 | ||
|
|
f9064084f9 | ||
|
|
67afbd1d8d | ||
|
|
d6b9172e26 | ||
|
|
8e1e71c740 | ||
|
|
ebbf6e56be | ||
|
|
bd67b71f3d | ||
|
|
69ad10785b | ||
|
|
9aead898e2 | ||
|
|
a48ebfefba | ||
|
|
e2a6e6b846 | ||
|
|
7ca689e190 | ||
|
|
d8f36a8aa9 | ||
|
|
e279799bf8 | ||
|
|
a117906ff6 | ||
|
|
c76d4f12fd | ||
|
|
b16529e337 | ||
|
|
67e9212008 | ||
|
|
35b70bfc00 | ||
|
|
418312bca5 | ||
|
|
4748717e50 | ||
|
|
d90b1a3d82 | ||
|
|
76a237aad4 | ||
|
|
3694108f42 | ||
|
|
1a7755365c | ||
|
|
8cf75f826f | ||
|
|
a1da086a64 | ||
|
|
5d1d6d07a6 | ||
|
|
e6a38a8055 | ||
|
|
3b3f1806ce | ||
|
|
e46743af2a | ||
|
|
9048d09bf6 | ||
|
|
3cd03729b9 | ||
|
|
ff760dacbe | ||
|
|
94f662c71a | ||
|
|
c7d87ad5b0 | ||
|
|
ad9726b64c | ||
|
|
e32e275f40 | ||
|
|
195056492e | ||
|
|
aa9ab1ace5 | ||
|
|
bfe22b4ec0 | ||
|
|
f84bc9fbbc | ||
|
|
0dc03de7b2 | ||
|
|
af338de17f | ||
|
|
0cffd567c2 | ||
|
|
9d006c279b | ||
|
|
f410bff309 | ||
|
|
0e9ff71c0a | ||
|
|
a599a5c5b7 | ||
|
|
84dae0fb37 | ||
|
|
65e3ef32ed | ||
|
|
0396914068 | ||
|
|
0e55a30e5a | ||
|
|
c0fbf0c735 | ||
|
|
e59d472cec | ||
|
|
27e6840442 | ||
|
|
a6aa183e26 | ||
|
|
b5074c4cee | ||
|
|
fbb54c6d6d | ||
|
|
95066fbc49 | ||
|
|
e2958bd43f | ||
|
|
382135fde8 | ||
|
|
4d80235d89 | ||
|
|
8b9589744b | ||
|
|
250d95c3eb | ||
|
|
0997189969 | ||
|
|
410948fb4e | ||
|
|
9f75fae33f | ||
|
|
b1aebc6c7a | ||
|
|
85526b1983 | ||
|
|
2a1e0b7429 | ||
|
|
3a73bd3b24 | ||
|
|
d7ba0aac82 | ||
|
|
8259a79cd2 | ||
|
|
de71d490c1 | ||
|
|
60b015919d | ||
|
|
2ca11aa2b7 | ||
|
|
275daf3b0d | ||
|
|
671d112452 | ||
|
|
a23ea9a01f | ||
|
|
949b213f9d | ||
|
|
a940adc4ba | ||
|
|
5d994edd62 | ||
|
|
2a9ce75db0 | ||
|
|
f8fcfc4d7d | ||
|
|
663a9ab224 | ||
|
|
f604821bdf | ||
|
|
ac83c0334c | ||
|
|
457165b2fc | ||
|
|
b1ca1f2630 | ||
|
|
bce1bd8192 | ||
|
|
4d123e1450 | ||
|
|
58e315d7f6 | ||
|
|
1281d475e4 | ||
|
|
32ac999ff5 | ||
|
|
1059befa39 | ||
|
|
5db8bd06ba | ||
|
|
0f5130611d | ||
|
|
a1271da74a | ||
|
|
0d40235791 | ||
|
|
d1e594e225 | ||
|
|
bcc2d7140e | ||
|
|
ee5847ec82 | ||
|
|
108bc2fa1d | ||
|
|
efb7f84ec4 | ||
|
|
224031e0d6 | ||
|
|
e905838733 | ||
|
|
dd63f2b817 | ||
|
|
8f84c3b84b | ||
|
|
bc4f384948 | ||
|
|
00c58bb245 | ||
|
|
11f6c88ac7 | ||
|
|
77cf879f4f | ||
|
|
13f6f6ebe6 | ||
|
|
c413ac989e | ||
|
|
7a770b32ee | ||
|
|
9d8adae5c7 | ||
|
|
b0531597f0 | ||
|
|
0bdf43b085 | ||
|
|
27da8470b7 | ||
|
|
c0be48ceee | ||
|
|
6c561029e9 | ||
|
|
c417a454d6 | ||
|
|
d8e2a1cc7c | ||
|
|
51e24a4e35 | ||
|
|
a82f172ca4 | ||
|
|
8ef43452f8 | ||
|
|
18a4867b4d | ||
|
|
ff05a1d35e | ||
|
|
5a4c9393a4 | ||
|
|
4b03b42494 | ||
|
|
215dcfb465 | ||
|
|
b965138d57 | ||
|
|
485262ec0a | ||
|
|
dd8a51ea1b | ||
|
|
d85729803d | ||
|
|
3132d665e0 | ||
|
|
be03f3c6de | ||
|
|
3a876d5c75 | ||
|
|
147916062b | ||
|
|
bcfdf76b71 | ||
|
|
679542eaaa | ||
|
|
4d5ff22b60 | ||
|
|
d54c5b5f55 | ||
|
|
0de6f98add | ||
|
|
5bf6035bcf | ||
|
|
a076792e77 | ||
|
|
73132f981e | ||
|
|
80e4a46a20 | ||
|
|
95d9b845b6 | ||
|
|
609d9c7a60 | ||
|
|
3d338aff33 | ||
|
|
8a46900df9 | ||
|
|
8efbfd13d0 | ||
|
|
19264a7d64 | ||
|
|
742e3a2a20 | ||
|
|
117bf3046d | ||
|
|
b072e1bb3d | ||
|
|
d405fc0675 | ||
|
|
77527987ee | ||
|
|
2e6b773ba4 | ||
|
|
dd6a9fe16a | ||
|
|
d9ccc620c9 | ||
|
|
b245275c7d | ||
|
|
bf22c8b67a | ||
|
|
1b58d9b801 | ||
|
|
2d2a6857de | ||
|
|
373641e01d | ||
|
|
d11eceac62 | ||
|
|
3965806fa0 | ||
|
|
74eabafe31 | ||
|
|
2625d2da80 | ||
|
|
1b9eb2a6ad | ||
|
|
94c37b2e14 | ||
|
|
71d2d61c4e | ||
|
|
020dc8b819 | ||
|
|
c11ad30134 | ||
|
|
02d32a556d | ||
|
|
71fcc6f026 | ||
|
|
dd614f5931 | ||
|
|
499d429a0e | ||
|
|
7168860a0b | ||
|
|
a395c1760a | ||
|
|
4033773849 | ||
|
|
9fd6ba414d | ||
|
|
2aee978790 | ||
|
|
39efe1237e | ||
|
|
684c88e0b8 | ||
|
|
e7c7ca76ac | ||
|
|
a6e5969201 | ||
|
|
1e99ad436f | ||
|
|
8580eb6d2b | ||
|
|
c6b8526dc0 | ||
|
|
03c7d63588 | ||
|
|
32e714f4a6 | ||
|
|
b8ef4885e5 | ||
|
|
e669cfcea1 | ||
|
|
82f0cd5f3d | ||
|
|
b6661fde86 | ||
|
|
e46480032e | ||
|
|
aed1e7dfdc | ||
|
|
b32fd7e018 | ||
|
|
3ae8575c1d | ||
|
|
cc4b307bc9 | ||
|
|
6d004f83dd | ||
|
|
5691ca0327 | ||
|
|
8f68ca3f67 | ||
|
|
84741c19f0 | ||
|
|
96126d235f | ||
|
|
4de40e289f | ||
|
|
86f649affc | ||
|
|
4f5c987d8b | ||
|
|
9ccd26a541 | ||
|
|
e7b5953feb | ||
|
|
e441e5e78a | ||
|
|
6d57945bd1 | ||
|
|
0cb0f52b12 | ||
|
|
6be9a87c15 | ||
|
|
43daa2ef08 | ||
|
|
8f9a69fa60 | ||
|
|
1d94e12da8 | ||
|
|
0a04fe5917 | ||
|
|
b03389068e | ||
|
|
c9171ef30e | ||
|
|
3853d38d69 | ||
|
|
caa2a05bf4 | ||
|
|
fc39d8aca9 | ||
|
|
022887258f | ||
|
|
cf12578289 | ||
|
|
3105a7ef77 | ||
|
|
f5ee19370c | ||
|
|
923134d67d | ||
|
|
3c2c2d7a51 | ||
|
|
3ee4a14c56 | ||
|
|
dd284a6c1d | ||
|
|
c240a8991d | ||
|
|
0948c46dc7 | ||
|
|
d9755fe59d | ||
|
|
7f82e9262b | ||
|
|
44952d1ea0 | ||
|
|
7f15eed9a8 | ||
|
|
14930fbab2 | ||
|
|
2cbca93d77 | ||
|
|
c2f5cafaf3 | ||
|
|
81822efa0f | ||
|
|
104cf9479c | ||
|
|
a36632def6 | ||
|
|
6a1b1d7740 | ||
|
|
f544d53c36 | ||
|
|
3188204d05 | ||
|
|
a7294d48a6 | ||
|
|
42be0c20cb | ||
|
|
1c6ffab0e3 | ||
|
|
095d61b6f9 | ||
|
|
923a025f1c | ||
|
|
a4b6f4e37c | ||
|
|
78b2824c13 | ||
|
|
f0c73451a2 | ||
|
|
62f5eb5ae9 | ||
|
|
7d9416cc81 | ||
|
|
a82a2df6aa | ||
|
|
f1e4120364 | ||
|
|
3471d9d693 | ||
|
|
6d7bcb8a7c | ||
|
|
db6e813cba | ||
|
|
d74e3e6b42 | ||
|
|
e9648ad75e | ||
|
|
e53da47647 | ||
|
|
1128490d47 | ||
|
|
186ef1689e | ||
|
|
0487cb8701 | ||
|
|
27b930c31c | ||
|
|
a8ef1c7899 | ||
|
|
644f0bc94c | ||
|
|
f3b0f79a59 | ||
|
|
eaae79a5f0 | ||
|
|
bc10baed3e | ||
|
|
b740cb2afd | ||
|
|
c0ec7e02f8 | ||
|
|
5977c5dd9e | ||
|
|
1c2dcc7b9a | ||
|
|
6ad3c40c42 | ||
|
|
f49c8ce188 | ||
|
|
acf828b72e | ||
|
|
cb61d85572 | ||
|
|
626316e8d1 | ||
|
|
ab30bae46a | ||
|
|
bd2c0d1121 | ||
|
|
64764228a9 | ||
|
|
463593d6cf | ||
|
|
85aac7bf52 | ||
|
|
dc36b4648d | ||
|
|
25145a68af | ||
|
|
dab522caca | ||
|
|
88634bcd92 | ||
|
|
6ab6930986 | ||
|
|
1f3b467083 | ||
|
|
34647cc402 | ||
|
|
2a9e16bd2c | ||
|
|
ed1fdfa830 | ||
|
|
1124a584cd | ||
|
|
d2c014f718 | ||
|
|
e488ec8c38 | ||
|
|
18b2e54581 | ||
|
|
3c89313795 | ||
|
|
11278d7ba5 | ||
|
|
a6eeb3a50a | ||
|
|
1f1028bbc0 | ||
|
|
08f973f8c0 | ||
|
|
ad22113881 | ||
|
|
e5498e3292 | ||
|
|
16c9599739 | ||
|
|
a7040a911d | ||
|
|
8227796c87 | ||
|
|
bac92716f3 | ||
|
|
993dbed7e7 | ||
|
|
390dc1c945 | ||
|
|
09b12bd06e | ||
|
|
3a06f2eaa9 | ||
|
|
a8e1db8b3c | ||
|
|
2cc4448a15 | ||
|
|
d0d254ad59 | ||
|
|
8f62b63309 | ||
|
|
227c90b47f | ||
|
|
6fc3033100 | ||
|
|
cfab20519e | ||
|
|
cbc5bb70d0 | ||
|
|
d5d3a7af47 | ||
|
|
03fcc94b1f | ||
|
|
4a28aa316f | ||
|
|
88a615528f | ||
|
|
b3e1b7124a | ||
|
|
48d0fc6ddb | ||
|
|
8bf9987174 | ||
|
|
0f4c325aab | ||
|
|
93a1f027cb | ||
|
|
3e0971597b | ||
|
|
fce3934977 | ||
|
|
d8feda89a5 | ||
|
|
7aecdd0dc7 | ||
|
|
a478294211 | ||
|
|
9de1f1800d | ||
|
|
94c8dbac18 | ||
|
|
efc745f574 | ||
|
|
3e45dcdbb1 | ||
|
|
07257cc2d2 | ||
|
|
ae6e08d430 | ||
|
|
473a49ab6b | ||
|
|
0c58d67f41 | ||
|
|
d2c4a6e2e9 | ||
|
|
d1ccd5c8eb | ||
|
|
370a07f596 | ||
|
|
9ae056e663 | ||
|
|
ce596877a4 | ||
|
|
3d9540e895 | ||
|
|
27d8869665 | ||
|
|
3b3fa7b1f8 | ||
|
|
135b5e8adb | ||
|
|
52dbf23245 | ||
|
|
6b899d3f77 | ||
|
|
87ba67225a | ||
|
|
2ad3b3c27e | ||
|
|
46dc7709ab | ||
|
|
92a640e41a | ||
|
|
842df7646b | ||
|
|
01b38d2ed6 | ||
|
|
ddb5ffc11a | ||
|
|
2a62e28c66 | ||
|
|
973133ce0b | ||
|
|
be0a728f42 | ||
|
|
1bb7d9947c | ||
|
|
65d94bd914 | ||
|
|
d951d0def0 | ||
|
|
beadd9cd03 | ||
|
|
fac97324a3 | ||
|
|
f33919c312 | ||
|
|
74d8eda713 | ||
|
|
6790fdb9e8 | ||
|
|
d545123ca7 | ||
|
|
59678ef8c6 | ||
|
|
a6d20ab648 | ||
|
|
e5559a1899 | ||
|
|
f8757c6d5d | ||
|
|
7f4ef12c04 | ||
|
|
2a90557782 | ||
|
|
7b3595357a | ||
|
|
f6d9d0f08b | ||
|
|
f3cf154969 | ||
|
|
923a4048d3 | ||
|
|
0816d27456 | ||
|
|
d07199db7c | ||
|
|
903afff687 | ||
|
|
c0f5906dce | ||
|
|
fa3140cc13 | ||
|
|
22aaba3c65 | ||
|
|
5878b18b02 | ||
|
|
530ec111ef | ||
|
|
430049d7c4 | ||
|
|
e45fd92022 | ||
|
|
605165fc5a | ||
|
|
644944c6fd | ||
|
|
8313d61521 | ||
|
|
81d30ca9f4 | ||
|
|
6078ccc144 | ||
|
|
7b9d89e485 | ||
|
|
48bc8b2341 | ||
|
|
8f03ac4aa3 | ||
|
|
97f5f0b50d | ||
|
|
05594ccdd6 | ||
|
|
e2f301827a | ||
|
|
067cbe2afa | ||
|
|
a99f4c0666 | ||
|
|
d594f28ed9 | ||
|
|
deed948f54 | ||
|
|
25e88b889d | ||
|
|
d6695d3ad8 | ||
|
|
0bc9aed3fe | ||
|
|
98ec4bba53 | ||
|
|
e527324468 | ||
|
|
8ccada40d1 | ||
|
|
80d974f2b4 | ||
|
|
521c493fd1 | ||
|
|
b68ee1136e | ||
|
|
0f43b0a6de | ||
|
|
58d1a1d311 | ||
|
|
5b3ab2965f | ||
|
|
f97f55bcca | ||
|
|
3262ad7029 | ||
|
|
a5bb6a0c55 | ||
|
|
f0d6b41cee | ||
|
|
5c01d222c9 | ||
|
|
7e65541d3c | ||
|
|
83b8fbb6b5 | ||
|
|
5b9e5f8de9 | ||
|
|
ddceeafc52 | ||
|
|
56087d9686 | ||
|
|
5aef6f209b | ||
|
|
1b6d69037e | ||
|
|
3941f323c0 | ||
|
|
b19f9d4875 | ||
|
|
057bb1a296 | ||
|
|
192cb2df73 | ||
|
|
47c797daba | ||
|
|
33dcb35344 | ||
|
|
68ab84716b |
@@ -77,24 +77,20 @@ Then, **for a method** of an eligible class:
|
||||
|
||||
:notebook: as spaces are used to mark code, the templates (`.doc/phpdoc-templates/combodo-wiki/*`) have very few indentation, thus they are awful to read (sorry).
|
||||
|
||||
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
cd .doc
|
||||
composer require phpdocumentor/phpdocumentor:~2 --dev
|
||||
```
|
||||
|
||||
## Generation
|
||||
`./bin/build-doc-object-manipulation` and `./bin/build-doc-extensions` contains examples of doc. generation, beware: they have to be called from the .doc directory:
|
||||
```shell
|
||||
cd /path/to/itop/.doc
|
||||
./bin/build-doc-object-manipulation
|
||||
```
|
||||
|
||||
the resulting documentation is written into `data/phpdocumentor/output`
|
||||
|
||||
1. Switch to this directory : `cd /path/to/itop/.doc`
|
||||
2. `composer install`
|
||||
3. `./bin/build-doc-object-manipulation`
|
||||
3. `./bin/build-doc-extensions`
|
||||
4. Get the generated files from `/path/to/itop/data/phpdocumentor/output`
|
||||
|
||||
## Dokuwiki requirements
|
||||
* the template uses the [wrap plugin](https://www.dokuwiki.org/plugin:wrap).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh -x
|
||||
|
||||
rm -rf /tmp/phpdoc-twig-cache/ && rm -rf data/phpdocumentor/output/extensions/ && rm -rf data/phpdocumentor/temp/extensions/ && .doc/vendor/bin/phpdoc -c .doc/phpdoc-extensions.dist.xml -vvv
|
||||
rm -rf /tmp/phpdoc-twig-cache/ && rm -rf data/phpdocumentor/output/extensions/ && rm -rf data/phpdocumentor/temp/extensions/ && ./vendor/bin/phpdoc -c ./phpdoc-extensions.dist.xml -vvv
|
||||
|
||||
# now wee need to lowercase every generated file because dokuwiki can't handle uppercase
|
||||
cd data/phpdocumentor/output/extensions/ && for i in $( ls | grep [A-Z] ); do mv -i $i `echo $i | tr 'A-Z' 'a-z'`; done
|
||||
cd ../data/phpdocumentor/output/extensions/ && for i in $(ls | grep [A-Z]); do mv -i $i $(echo $i | tr 'A-Z' 'a-z'); done
|
||||
|
||||
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
|
||||
@@ -90,7 +90,7 @@ ij_xml_attribute_wrap = normal
|
||||
ij_xml_block_comment_at_first_column = true
|
||||
ij_xml_keep_blank_lines = 2
|
||||
ij_xml_keep_indents_on_empty_lines = false
|
||||
ij_xml_keep_line_breaks = false
|
||||
ij_xml_keep_line_breaks = true
|
||||
ij_xml_keep_line_breaks_in_text = true
|
||||
ij_xml_keep_whitespaces = false
|
||||
ij_xml_keep_whitespaces_around_cdata = preserve
|
||||
@@ -110,6 +110,7 @@ ij_shell_keep_column_alignment_padding = false
|
||||
ij_shell_minify_program = false
|
||||
ij_shell_redirect_followed_by_space = false
|
||||
ij_shell_switch_cases_indented = false
|
||||
ij_shell_use_unix_line_separator = true
|
||||
|
||||
[{*.cjs,*.js}]
|
||||
indent_style = tab
|
||||
@@ -285,11 +286,12 @@ 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 = false
|
||||
ij_php_align_multiline_array_initializer_expression = 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
|
||||
ij_php_align_multiline_extends_list = false
|
||||
@@ -297,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
|
||||
@@ -416,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
|
||||
@@ -430,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
|
||||
@@ -465,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
|
||||
@@ -514,7 +520,7 @@ ij_json_wrap_long_lines = false
|
||||
indent_style = tab
|
||||
ij_smart_tabs = true
|
||||
ij_visual_guides = none
|
||||
ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3
|
||||
ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
|
||||
ij_html_align_attributes = true
|
||||
ij_html_align_text = false
|
||||
ij_html_attribute_wrap = normal
|
||||
@@ -539,9 +545,22 @@ 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
|
||||
|
||||
[{*.yaml, *.yml}]
|
||||
[{*.markdown,*.md}]
|
||||
ij_visual_guides = none
|
||||
ij_markdown_force_one_space_after_blockquote_symbol = true
|
||||
ij_markdown_force_one_space_after_header_symbol = true
|
||||
ij_markdown_force_one_space_after_list_bullet = true
|
||||
ij_markdown_force_one_space_between_words = true
|
||||
ij_markdown_keep_indents_on_empty_lines = false
|
||||
ij_markdown_max_lines_around_block_elements = 1
|
||||
ij_markdown_max_lines_around_header = 1
|
||||
ij_markdown_max_lines_between_paragraphs = 1
|
||||
ij_markdown_min_lines_around_block_elements = 1
|
||||
ij_markdown_min_lines_around_header = 1
|
||||
ij_markdown_min_lines_between_paragraphs = 1
|
||||
|
||||
[{*.yaml,*.yml}]
|
||||
indent_size = 2
|
||||
ij_visual_guides = none
|
||||
ij_yaml_align_values_properties = do_not_align
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -32,6 +32,8 @@ test/vendor/*
|
||||
!/data/.htaccess
|
||||
!/data/index.php
|
||||
!/data/web.config
|
||||
!/data/exclude.txt
|
||||
!/data/.compilation-symlinks
|
||||
|
||||
# iTop extensions
|
||||
/extensions/**
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -10,10 +10,6 @@
|
||||
* `curl -L -o /usr/bin/jq.exe https://github.com/stedolan/jq/releases/latest/download/jq-win64.exe`
|
||||
* this is a Windows port : https://stedolan.github.io/jq/
|
||||
*
|
||||
* Known bug on Windows :
|
||||
* Licenses added from Composer contains a path in the product node (N°3870)
|
||||
* `<product scope="lib">C:\Dev\wamp64\www\itop-dev\.make\license/../..//lib/symfony/console</product>`
|
||||
*
|
||||
* Licenses sources :
|
||||
* * `composer licenses --format json` (see https://getcomposer.org/doc/03-cli.md#licenses)
|
||||
* * keep every existing nodes with `/licenses/license[11]/product/@scope` not in ['lib', 'datamodels']
|
||||
@@ -70,39 +66,83 @@ function get_license_nodes($file_path)
|
||||
$xp = new DOMXPath($dom);
|
||||
|
||||
$licenseList = $xp->query('/licenses/license');
|
||||
$licenses = iterator_to_array($licenseList);
|
||||
$licenses = iterator_to_array($licenseList);
|
||||
|
||||
usort($licenses, 'sort_by_product');
|
||||
|
||||
return $licenses;
|
||||
}
|
||||
|
||||
/** @noinspection SuspiciousAssignmentsInspection */
|
||||
function fix_product_name(DOMNode &$oProductNode)
|
||||
{
|
||||
$sProductNameOrig = $oProductNode->nodeValue;
|
||||
|
||||
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//lib/symfony/polyfill-ctype`
|
||||
$sProductNameFixed = remove_dir_from_string($sProductNameOrig, 'lib/');
|
||||
|
||||
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//datamodels/2.x/authent-cas/vendor/apereo/phpcas`
|
||||
$sProductNameFixed = remove_dir_from_string($sProductNameFixed, 'vendor/');
|
||||
|
||||
$oProductNode->nodeValue = $sProductNameFixed;
|
||||
}
|
||||
|
||||
function remove_dir_from_string($sString, $sNeedle)
|
||||
{
|
||||
if (strpos($sString, $sNeedle) === false) {
|
||||
return $sString;
|
||||
}
|
||||
|
||||
$sStringTmp = strstr($sString, $sNeedle);
|
||||
$sStringFixed = str_replace($sNeedle, '', $sStringTmp);
|
||||
|
||||
// DEBUG trace O:)
|
||||
// echo "$sNeedle = $sString => $sStringFixed\n";
|
||||
|
||||
return $sStringFixed;
|
||||
}
|
||||
|
||||
$old_licenses = get_license_nodes($xmlFilePath);
|
||||
|
||||
//generate file with updated licenses
|
||||
$generated_license_file_path = __DIR__."/provfile.xml";
|
||||
exec("bash " . __DIR__ . "/gen-community-license.sh $iTopFolder > ". $generated_license_file_path);
|
||||
echo "- Generating licences...";
|
||||
exec("bash ".__DIR__."/gen-community-license.sh $iTopFolder > ".$generated_license_file_path);
|
||||
echo "OK!\n";
|
||||
|
||||
echo "- Get licenses nodes...";
|
||||
$new_licenses = get_license_nodes($generated_license_file_path);
|
||||
exec("rm -f ". $generated_license_file_path);
|
||||
unlink($generated_license_file_path);
|
||||
|
||||
foreach ($old_licenses as $b) {
|
||||
$aProductNode = get_product_node($b);
|
||||
|
||||
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels" )
|
||||
{
|
||||
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels") {
|
||||
$new_licenses[] = $b;
|
||||
}
|
||||
}
|
||||
|
||||
usort($new_licenses, 'sort_by_product');
|
||||
echo "OK!\n";
|
||||
|
||||
echo "- Overwritting Combodo license file...";
|
||||
$new_dom = new DOMDocument("1.0");
|
||||
$new_dom->formatOutput = true;
|
||||
$root = $new_dom->createElement("licenses");
|
||||
$new_dom->appendChild($root);
|
||||
|
||||
foreach ($new_licenses as $b) {
|
||||
$node = $new_dom->importNode($b,true);
|
||||
$root->appendChild($new_dom->importNode($b,true));
|
||||
$node = $new_dom->importNode($b, true);
|
||||
|
||||
// N°3870 fix when running script in Windows
|
||||
// fix should be in gen-community-license.sh but it is easier to do it here !
|
||||
if (strncasecmp(PHP_OS, 'WIN', 3) === 0) {
|
||||
$oProductNodeOrig = get_product_node($node);
|
||||
fix_product_name($oProductNodeOrig);
|
||||
}
|
||||
|
||||
$root->appendChild($node);
|
||||
}
|
||||
|
||||
$new_dom->save($xmlFilePath);
|
||||
$new_dom->save($xmlFilePath);
|
||||
echo "OK!\n";
|
||||
77
.make/release/changelog.php
Normal file
77
.make/release/changelog.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* Usage :
|
||||
* `php changelog.php 2.7.4`
|
||||
*
|
||||
* As argument is passed the git ref (tag name or sha1) we want to use as reference
|
||||
*
|
||||
* Outputs :
|
||||
*
|
||||
* 1. List of bugs as CSV :
|
||||
* bug ref;link
|
||||
* Example :
|
||||
* <code>
|
||||
* Bug_ref;Bug_URL;sha1
|
||||
* 1234;https://support.combodo.com/pages/UI.php?operation=details&class=Bug&id=1234;949b213f9|b1ca1f263|a1271da74
|
||||
* </code>
|
||||
*
|
||||
* 2. List of commits sha1/message without bug ref
|
||||
* Example :
|
||||
* <code>
|
||||
* sha1;subject
|
||||
* a6aa183e2;:bookmark: Prepare 2.7.5
|
||||
* </code>
|
||||
*/
|
||||
|
||||
|
||||
if (count($argv) === 1) {
|
||||
echo '⚠ You must pass the base tag/sha1 as parameter';
|
||||
exit(1);
|
||||
}
|
||||
$sBaseReference = $argv[1];
|
||||
|
||||
|
||||
//--- Get log
|
||||
$sGitLogCommand = 'git log --decorate --pretty="%h;%s" --date-order --no-merges '.$sBaseReference.'..HEAD';
|
||||
$sGitLogRaw = shell_exec($sGitLogCommand);
|
||||
|
||||
|
||||
//--- Analyze log
|
||||
$aGitLogLines = preg_split('/\n/', trim($sGitLogRaw));;
|
||||
$aLogLinesWithBugRef = [];
|
||||
$aLogLineNoBug = [];
|
||||
foreach ($aGitLogLines as $sLogLine) {
|
||||
$sBugRef = preg_match('/[nN]°(\d{3,4})/', $sLogLine, $aLineBugRef);
|
||||
if (($sBugRef === false) || empty($aLineBugRef)) {
|
||||
$aLogLineNoBug[] = $sLogLine;
|
||||
continue;
|
||||
}
|
||||
|
||||
$iBugId = $aLineBugRef[1];
|
||||
$sSha = substr($sLogLine, 0, 9);
|
||||
|
||||
if (array_key_exists($iBugId, $aLogLinesWithBugRef)) {
|
||||
$aBugShaRefs = $aLogLinesWithBugRef[$iBugId];
|
||||
$aBugShaRefs[] = $sSha;
|
||||
$aLogLinesWithBugRef[$iBugId] = $aBugShaRefs;
|
||||
} else {
|
||||
$aLogLinesWithBugRef[$iBugId] = [$sSha];
|
||||
}
|
||||
}
|
||||
$aBugsList = array_keys($aLogLinesWithBugRef);
|
||||
sort($aBugsList, SORT_NUMERIC);
|
||||
|
||||
|
||||
//-- Output results
|
||||
echo "# Bugs included\n";
|
||||
echo "Bug_ref;Bug_URL;sha1\n";
|
||||
foreach ($aBugsList as $sBugRef) {
|
||||
$sShaRefs = implode('|', $aLogLinesWithBugRef[$sBugRef]);
|
||||
echo "{$sBugRef};https://support.combodo.com/pages/UI.php?operation=details&class=Bug&id={$sBugRef};$sShaRefs\n";
|
||||
}
|
||||
echo "\n";
|
||||
echo "# Logs line without bug referenced\n";
|
||||
echo "sha1;subject\n";
|
||||
foreach ($aLogLineNoBug as $sLogLine) {
|
||||
echo "$sLogLine\n";
|
||||
}
|
||||
@@ -125,8 +125,7 @@ Our tests are located in the `test/` directory, containing a PHPUnit config file
|
||||
* ⬆️ `:arrow_up:` when upgrading dependencies
|
||||
* ⬇️ `:arrow_down:` when downgrading dependencies
|
||||
* ♻️ `:recycle:` code refactoring
|
||||
* 💄 `:lipstick:` Updating the UI and style files.
|
||||
|
||||
* 💄 `:lipstick:` Updating the UI and style files.
|
||||
|
||||
## 👥 Pull request
|
||||
|
||||
@@ -137,3 +136,25 @@ When your code is working, please:
|
||||
* create a pull request.
|
||||
|
||||
Detailed procedure to work on fork and create PR is available [in GitHub help pages](https://help.github.com/articles/creating-a-pull-request-from-a-fork/).
|
||||
|
||||
You might check the ["Allow edits from maintainers" PR checkbox][allow_edits_checkbox] to ease review.
|
||||
|
||||
[allow_edits_checkbox]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork#enabling-repository-maintainer-permissions-on-existing-pull-requests
|
||||
|
||||
### 🙏 We are thankful
|
||||
|
||||
We are thankful for all your contributions to the iTop universe! As a thank you gift, we will send stickers to every iTop (& extensions) contributors!
|
||||
|
||||
Stickers' design might change from one year to another. For the first year we wanted to try a "craft beer label" look, see examples below:
|
||||
|
||||
* Bug hunter: Fix a bug
|
||||
* Translator: Add/update translations
|
||||
* 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,9 +219,9 @@ 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" => "userid",
|
||||
"name_attcode" => array("userlogin", "profile"),
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_urp_userprofile",
|
||||
@@ -246,11 +246,6 @@ class URP_UserProfile extends UserRightsBaseClassGUI
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form
|
||||
}
|
||||
|
||||
public function GetName()
|
||||
{
|
||||
return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile'));
|
||||
}
|
||||
|
||||
public function CheckToDelete(&$oDeletionPlan)
|
||||
{
|
||||
if (MetaModel::GetConfig()->Get('demo_mode')) {
|
||||
@@ -362,7 +357,7 @@ class URP_UserOrg extends UserRightsBaseClassGUI
|
||||
(
|
||||
"category" => "addon/userrights,grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "userid",
|
||||
"name_attcode" => array("userlogin", "allowed_org_name"),
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_urp_userorg",
|
||||
@@ -387,12 +382,6 @@ class URP_UserOrg extends UserRightsBaseClassGUI
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('userid', 'allowed_org_id')); // Criteria of the advanced search form
|
||||
}
|
||||
|
||||
public function GetName()
|
||||
{
|
||||
return Dict::Format('UI:UserManagement:LinkBetween_User_And_Org', $this->Get('userlogin'), $this->Get('allowed_org_name'));
|
||||
}
|
||||
|
||||
|
||||
protected function OnInsert()
|
||||
{
|
||||
$this->CheckIfOrgIsAllowed();
|
||||
@@ -621,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'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ class URP_UserProfile extends UserRightsBaseClassGUI
|
||||
(
|
||||
"category" => "addon/userrights",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "userid",
|
||||
"name_attcode" => array("userlogin", "profile"),
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_urp_userprofile",
|
||||
@@ -351,11 +351,6 @@ class URP_UserProfile extends UserRightsBaseClassGUI
|
||||
MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form
|
||||
}
|
||||
|
||||
public function GetName()
|
||||
{
|
||||
return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile'));
|
||||
}
|
||||
}
|
||||
|
||||
class URP_UserOrg extends UserRightsBaseClassGUI
|
||||
@@ -366,7 +361,7 @@ class URP_UserOrg extends UserRightsBaseClassGUI
|
||||
(
|
||||
"category" => "addon/userrights",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "userid",
|
||||
"name_attcode" => array("userlogin", "allowed_org_name"),
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_urp_userorg",
|
||||
@@ -390,11 +385,6 @@ class URP_UserOrg extends UserRightsBaseClassGUI
|
||||
MetaModel::Init_SetZListItems('standard_search', array('userid', 'allowed_org_id')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('userid', 'allowed_org_id')); // Criteria of the advanced search form
|
||||
}
|
||||
|
||||
public function GetName()
|
||||
{
|
||||
return Dict::Format('UI:UserManagement:LinkBetween_User_And_Org', $this->Get('userlogin'), $this->Get('allowed_org_name'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ class URP_UserProfile extends UserRightsBaseClass
|
||||
(
|
||||
"category" => "addon/userrights",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "userid",
|
||||
"name_attcode" => array("userlogin", "profile"),
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_urp_userprofile",
|
||||
@@ -294,11 +294,6 @@ class URP_UserProfile extends UserRightsBaseClass
|
||||
MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form
|
||||
}
|
||||
|
||||
public function GetName()
|
||||
{
|
||||
return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
60
application/DBSearchHelper.php
Normal file
60
application/DBSearchHelper.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class DBSearchHelper
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class DBSearchHelper
|
||||
{
|
||||
/**
|
||||
* Add context filter to DBUnionSearch
|
||||
*
|
||||
* @param \DBSearch|null $oSearch
|
||||
*
|
||||
* @throws \Exception
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function AddContextFilter(?DBSearch $oSearch): void
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sClass = $oSearch->GetClass();
|
||||
foreach ($oAppContext->GetNames() as $key) {
|
||||
// Find the value of the object corresponding to each 'context' parameter
|
||||
$aCallSpec = [$sClass, 'MapContextParam'];
|
||||
$sAttCode = '';
|
||||
if (is_callable($aCallSpec)) {
|
||||
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
|
||||
}
|
||||
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode)) {
|
||||
// Add Hierarchical condition if hierarchical key
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if (isset($oAttDef) && ($oAttDef->IsExternalKey())) {
|
||||
$iDefaultValue = intval($oAppContext->GetCurrentValue($key));
|
||||
if ($iDefaultValue != 0) {
|
||||
try {
|
||||
/** @var AttributeExternalKey $oAttDef */
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass);
|
||||
if ($sHierarchicalKeyCode !== false) {
|
||||
$oFilter = new DBObjectSearch($sTargetClass);
|
||||
$oFilter->AddCondition('id', $iDefaultValue);
|
||||
$oHKFilter = new DBObjectSearch($sTargetClass);
|
||||
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW);
|
||||
$oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode);
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// If filtering fails just ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/AjaxPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/AjaxPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
// cannot notify depreciation for now as this is still MASSIVELY used in iTop core !
|
||||
//DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/AjaxPage.php, now loadable using autoloader');
|
||||
|
||||
/**
|
||||
* Class ajax_page
|
||||
*
|
||||
* @deprecated will be removed in 3.1.0 - moved to AjaxPage
|
||||
*/
|
||||
class ajax_page extends AjaxPage
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -73,15 +73,14 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public abstract function ListSupportedLoginModes();
|
||||
abstract public function ListSupportedLoginModes();
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function LoginAction($sLoginState, &$iErrorCode)
|
||||
{
|
||||
switch ($sLoginState)
|
||||
{
|
||||
switch ($sLoginState) {
|
||||
case LoginWebPage::LOGIN_STATE_START:
|
||||
return $this->OnStart($iErrorCode);
|
||||
|
||||
@@ -777,6 +776,10 @@ abstract class ApplicationPopupMenuItem
|
||||
/** @ignore */
|
||||
protected $sLabel;
|
||||
/** @ignore */
|
||||
protected $sTooltip;
|
||||
/** @ignore */
|
||||
protected $sIconClass;
|
||||
/** @ignore */
|
||||
protected $aCssClasses;
|
||||
|
||||
/**
|
||||
@@ -791,6 +794,8 @@ abstract class ApplicationPopupMenuItem
|
||||
{
|
||||
$this->sUID = $sUID;
|
||||
$this->sLabel = $sLabel;
|
||||
$this->sTooltip = '';
|
||||
$this->sIconClass = '';
|
||||
$this->aCssClasses = array();
|
||||
}
|
||||
|
||||
@@ -845,6 +850,47 @@ abstract class ApplicationPopupMenuItem
|
||||
$this->aCssClasses[] = $sCssClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $sTooltip
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function SetTooltip($sTooltip)
|
||||
{
|
||||
$this->sTooltip = $sTooltip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function GetTooltip()
|
||||
{
|
||||
return $this->sTooltip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sIconClass
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function SetIconClass($sIconClass)
|
||||
{
|
||||
$this->sIconClass = $sIconClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function GetIconClass()
|
||||
{
|
||||
return $this->sIconClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the components to create a popup menu item in HTML
|
||||
*
|
||||
@@ -863,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
|
||||
@@ -892,7 +940,13 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
/** @ignore */
|
||||
public function GetMenuItem()
|
||||
{
|
||||
return array('label' => $this->GetLabel(), 'url' => $this->GetUrl(), 'target' => $this-> GetTarget(), 'css_classes' => $this->aCssClasses);
|
||||
return array('label' => $this->GetLabel(),
|
||||
'url' => $this->GetUrl(),
|
||||
'target' => $this-> GetTarget(),
|
||||
'css_classes' => $this->aCssClasses,
|
||||
'icon_class' => $this->sIconClass,
|
||||
'tooltip' => $this->sTooltip
|
||||
);
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
@@ -911,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
|
||||
@@ -952,6 +1008,8 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
|
||||
'onclick' => $this->GetJsCode().'; return false;',
|
||||
'url' => $this->GetUrl(),
|
||||
'css_classes' => $this->GetCssClasses(),
|
||||
'icon_class' => $this->sIconClass,
|
||||
'tooltip' => $this->sTooltip
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1043,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
|
||||
{
|
||||
@@ -1133,6 +1193,8 @@ abstract class AbstractPageUIExtension implements iPageUIExtension
|
||||
*/
|
||||
public function GetNorthPaneHtml(iTopWebPage $oPage)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use iPageUIBlockExtension instead');
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -1141,6 +1203,8 @@ abstract class AbstractPageUIExtension implements iPageUIExtension
|
||||
*/
|
||||
public function GetSouthPaneHtml(iTopWebPage $oPage)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use iPageUIBlockExtension instead');
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -1149,6 +1213,8 @@ abstract class AbstractPageUIExtension implements iPageUIExtension
|
||||
*/
|
||||
public function GetBannerHtml(iTopWebPage $oPage)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use iPageUIBlockExtension instead');
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -1188,6 +1254,119 @@ 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 content to any enhanced portal page
|
||||
*
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CaptureWebPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CaptureWebPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/CaptureWebPage.php, now loadable using autoloader');
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CLIPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CLIPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/CLIPage.php, now loadable using autoloader');
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CSVPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CSVPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/CSVPage.php, now loadable using autoloader');
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableSettings;
|
||||
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\Dashboard\DashboardLayout as DashboardLayoutUIBlock;
|
||||
|
||||
@@ -542,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();
|
||||
@@ -551,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
|
||||
);
|
||||
}
|
||||
@@ -859,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)
|
||||
@@ -888,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;
|
||||
}
|
||||
@@ -1147,7 +1149,8 @@ JS
|
||||
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_DASHBOARD_ACTIONS, $this, $aActions);
|
||||
|
||||
$oActionsMenu = $oPage->GetPopoverMenu($sPopoverMenuId, $aActions)
|
||||
->SetTogglerJSSelector("#$sMenuTogglerId");
|
||||
->SetTogglerJSSelector("#$sMenuTogglerId")
|
||||
->SetContainer(PopoverMenu::ENUM_CONTAINER_BODY);
|
||||
|
||||
$oToolbar->AddSubBlock($oActionButton)
|
||||
->AddSubBlock($oActionsMenu);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -906,17 +926,22 @@ class DashletObjectList extends Dashlet
|
||||
$sShowMenu = $this->aProperties['menu'] ? '1' : '0';
|
||||
$oFilter = $this->GetDBSearch($aExtraParams);
|
||||
$sClass = $oFilter->GetClass();
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle))
|
||||
->AddCSSClass('ibo-datatable-panel');
|
||||
//$oPanel = PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle))
|
||||
// ->AddCSSClass('ibo-datatable-panel');
|
||||
|
||||
$oBlock = new DisplayBlock($oFilter, 'list');
|
||||
$aParams = array(
|
||||
'menu' => $sShowMenu,
|
||||
'table_id' => self::APPUSERPREFERENCES_PREFIX.$this->sId,
|
||||
'surround_with_panel' => false,
|
||||
'surround_with_panel' => true,
|
||||
'max_height' => '500px',
|
||||
"panel_title" => Dict::S($sTitle),
|
||||
"panel_class" => $sClass,
|
||||
);
|
||||
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occurring in the same DOM)
|
||||
$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
//$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
|
||||
$oPanel = $oBlock->GetDisplay($oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
|
||||
return $oPanel;
|
||||
}
|
||||
@@ -926,24 +951,36 @@ class DashletObjectList extends Dashlet
|
||||
*/
|
||||
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
|
||||
{
|
||||
$oDashletContainer = new DashletContainer($this->sId, ['dashlet-content']);
|
||||
$sTitle = $this->aProperties['title'];
|
||||
$sQuery = $this->aProperties['query'];
|
||||
$bShowMenu = $this->aProperties['menu'];
|
||||
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
$sHtmlTitle = utils::HtmlEntities($this->oModelReflection->DictString($sTitle)); // done in the itop block
|
||||
$sHtmlTitle = utils::HtmlEntities($this->oModelReflection->DictString($sTitle));
|
||||
if ($sHtmlTitle != '') {
|
||||
$oPage->add('<h1>'.$sHtmlTitle.'</h1>');
|
||||
$sHtmlTitle = '<h1>'.$sHtmlTitle.'</h1>';
|
||||
}
|
||||
$oQuery = $this->oModelReflection->GetQuery($sQuery);
|
||||
$sClass = $oQuery->GetClass();
|
||||
$oPage->add('<div id="block_fake_'.$this->sId.'" class="display_block">');
|
||||
$oPage->p(Dict::S('UI:NoObjectToDisplay'));
|
||||
$sId = $this->sId;
|
||||
$sMessage = Dict::S('UI:NoObjectToDisplay');
|
||||
$sMenu = '';
|
||||
if ($bShowMenu) {
|
||||
$oPage->p('<a>'.Dict::Format('UI:ClickToCreateNew', $this->oModelReflection->GetName($sClass)).'</a>');
|
||||
$sMenu = '<p><a>'.Dict::Format('UI:ClickToCreateNew', $this->oModelReflection->GetName($sClass)).'</a></p>';
|
||||
}
|
||||
$oPage->add('</div>');
|
||||
$oPage->add('</div>');
|
||||
|
||||
$sHtml = <<<HTML
|
||||
<div class="dashlet-content">
|
||||
<h1>$sHtmlTitle</h1>
|
||||
<div id="block_fake_$sId" class="display_block">
|
||||
<p>$sMessage</p>
|
||||
$sMenu
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
|
||||
$oDashletContainer->AddHtml($sHtml);
|
||||
|
||||
return $oDashletContainer;
|
||||
}
|
||||
|
||||
public function GetDBSearch($aExtraParams = array())
|
||||
@@ -971,6 +1008,8 @@ class DashletObjectList extends Dashlet
|
||||
|
||||
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $this->aProperties['query']);
|
||||
$oField->SetMandatory();
|
||||
$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']);
|
||||
@@ -1007,6 +1046,8 @@ class DashletObjectList extends Dashlet
|
||||
|
||||
$oField = new DesignerHiddenField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $sOQL);
|
||||
$oField->SetMandatory();
|
||||
$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']);
|
||||
@@ -1243,15 +1284,21 @@ abstract class DashletGroupBy extends Dashlet
|
||||
break;
|
||||
}
|
||||
|
||||
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle));
|
||||
//$oPanel = \Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory::MakeStandard();
|
||||
//PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle));
|
||||
|
||||
|
||||
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occurring in the same DOM)
|
||||
$oBlock = new DisplayBlock($oFilter, $sType);
|
||||
$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
//$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
$aExtraParams["surround_with_panel"] = true;
|
||||
$aExtraParams["panel_title"] = Dict::S($sTitle);
|
||||
$aExtraParams["panel_class"] = $sClass;
|
||||
$oPanel = $oBlock->GetDisplay($oPage, $sBlockId, array_merge($aExtraParams, $aParams));
|
||||
if ($bEditMode) {
|
||||
$oPanel->AddHtml('<div class="dashlet-blocker"></div>');
|
||||
$oPanel->AddHtml('<div class="ibo-dashlet-blocker dashlet-blocker"></div>');
|
||||
}
|
||||
|
||||
return $oPanel;
|
||||
}
|
||||
|
||||
@@ -1350,10 +1397,11 @@ abstract class DashletGroupBy extends Dashlet
|
||||
|
||||
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $this->aProperties['query']);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-query-oql");
|
||||
$oField->AddCSSClass("ibo-is-code");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
// Group by field: build the list of possible values (attribute codes + ...)
|
||||
$aGroupBy = $this->GetGroupByOptions($this->aProperties['query']);
|
||||
|
||||
@@ -1607,16 +1655,15 @@ abstract class DashletGroupBy extends Dashlet
|
||||
|
||||
$oField = new DesignerHiddenField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $sOQL);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-query-oql");
|
||||
$oField->AddCSSClass("ibo-is-code");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
if (!is_null($sOQL))
|
||||
{
|
||||
if (!is_null($sOQL)) {
|
||||
$oField = new DesignerComboField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null);
|
||||
$aGroupBy = $this->GetGroupByOptions($sOQL);
|
||||
$oField->SetAllowedValues($aGroupBy);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Creating a form for reading parameters!
|
||||
$oField = new DesignerTextField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null);
|
||||
}
|
||||
@@ -1653,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
|
||||
*/
|
||||
@@ -2085,8 +2154,20 @@ class DashletHeaderDynamic extends Dashlet
|
||||
$sQuery = $this->aProperties['query'];
|
||||
$sGroupBy = $this->aProperties['group_by'];
|
||||
|
||||
$oQuery = $this->oModelReflection->GetQuery($sQuery);
|
||||
$sClass = $oQuery->GetClass();
|
||||
$aValueLabels = [];
|
||||
$aValues = [];
|
||||
try {
|
||||
$oQuery = $this->oModelReflection->GetQuery($sQuery);
|
||||
$sClass = $oQuery->GetClass();
|
||||
$aValues = $this->GetValues();
|
||||
foreach ($aValues as $sValue) {
|
||||
$aValueLabels[] = $this->oModelReflection->GetValueLabel($sClass, $sGroupBy, $sValue);
|
||||
}
|
||||
}
|
||||
catch (UnknownClassOqlException $e) {
|
||||
$aValueLabels[] = $e->GetUserFriendlyDescription();
|
||||
$aValues[] = 1;
|
||||
}
|
||||
|
||||
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
|
||||
$sIconPath = utils::HtmlEntities($oIconSelect->MakeFileUrl($sIcon));
|
||||
@@ -2099,20 +2180,18 @@ class DashletHeaderDynamic extends Dashlet
|
||||
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
|
||||
|
||||
$iTotal = 0;
|
||||
$aValues = $this->GetValues();
|
||||
|
||||
$sHtml .= '<div class="display_block" id="'.$sBlockId.'">';
|
||||
$sHtml .= '<div class="summary-details">';
|
||||
$sHtml .= '<table><tbody>';
|
||||
$sHtml .= '<tr>';
|
||||
foreach ($aValues as $sValue) {
|
||||
$sValueLabel = $this->oModelReflection->GetValueLabel($sClass, $sGroupBy, $sValue);
|
||||
foreach ($aValueLabels as $sValueLabel) {
|
||||
$sHtml .= ' <th>'.$sValueLabel.'</th>';
|
||||
}
|
||||
$sHtml .= '</tr>';
|
||||
$sHtml .= '<tr>';
|
||||
foreach ($aValues as $sValue) {
|
||||
$iCount = (int)rand(2, 100);
|
||||
$iCount = rand(2, 100);
|
||||
$iTotal += $iCount;
|
||||
$sHtml .= ' <td>'.$iCount.'</td>';
|
||||
}
|
||||
@@ -2127,7 +2206,6 @@ class DashletHeaderDynamic extends Dashlet
|
||||
$sHtml .= '<a class="summary">'.utils::HtmlEntities($sSubtitle).'</a>';
|
||||
$sHtml .= '</div>';
|
||||
|
||||
$sHtml .= '</div>';
|
||||
|
||||
$oDashletContainer->AddHtml($sHtml);
|
||||
|
||||
@@ -2151,6 +2229,8 @@ class DashletHeaderDynamic extends Dashlet
|
||||
|
||||
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletHeaderDynamic:Prop-Query'), $this->aProperties['query']);
|
||||
$oField->SetMandatory();
|
||||
$oField->AddCSSClass("ibo-query-oql");
|
||||
$oField->AddCSSClass("ibo-is-code");
|
||||
$oForm->AddField($oField);
|
||||
|
||||
try
|
||||
@@ -2309,18 +2389,19 @@ class DashletBadge extends Dashlet
|
||||
$oDashletContainer = new DashletContainer($this->sId, ['dashlet-content']);
|
||||
|
||||
$sClass = $this->aProperties['class'];
|
||||
$sIconUrl = $this->oModelReflection->GetClassIcon($sClass, false);
|
||||
$sIconUrl = utils::HtmlEntities($this->oModelReflection->GetClassIcon($sClass, false));
|
||||
$sClassLabel = $this->oModelReflection->GetName($sClass);
|
||||
$sId = $this->sId;
|
||||
$sClassCreate = Dict::Format('UI:ClickToCreateNew', $sClassLabel);
|
||||
|
||||
$sHtml = '';
|
||||
$sHtml .= '<div id="block_fake_'.$this->sId.'" class="display_block">';
|
||||
$sHtml .= '<p>';
|
||||
$sHtml .= ' <a class="actions"><img src="'.utils::HtmlEntities($sIconUrl).'" style="vertical-align:middle;float;left;margin-right:10px;border:0;">'.$sClassLabel.': 947</a>';
|
||||
$sHtml .= '</p>';
|
||||
$sHtml .= '<p>';
|
||||
$sHtml .= ' <a>'.Dict::Format('UI:ClickToCreateNew', $sClassLabel).'</a>';
|
||||
$sHtml .= '</p>';
|
||||
$sHtml .= '</div>';
|
||||
$sHtml = <<<HTML
|
||||
<div id="block_fake_$sId" class="display_block">
|
||||
<div class="ibo-dashlet-badge--body" data-role="ibo-dashlet-badge--body" title="$sClassLabel">
|
||||
<div class="ibo-dashlet-badge--icon-container"><img class="ibo-dashlet-badge--icon" src="$sIconUrl"></div>
|
||||
<div class="ibo-dashlet-badge--actions"><a class="ibo-dashlet-badge--action-list" href="#" data-role="ibo-dashlet-badge--action-list"><span class="ibo-dashlet-badge--action-list-count">4</span><span class="ibo-dashlet-badge--action-list-label">$sClassLabel</span></a><a class="ibo-dashlet-badge--action-create" href="#"><span class="ibo-dashlet-badge--action-create-icon fas fa-plus"></span><span class="ibo-dashlet-badge--action-create-label"> $sClassCreate </span></a></div>
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
|
||||
$oDashletContainer->AddHtml($sHtml);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -48,6 +48,7 @@ class DataTable
|
||||
*/
|
||||
public function __construct($iListId, $oSet, $aClassAliases, $sTableId = null)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use Combodo\iTop\Application\UI\Base\Component\DataTable\Datatable');
|
||||
$this->iListId = utils::GetSafeId($iListId); // Make a "safe" ID for jQuery
|
||||
$this->sDatatableContainerId = 'datatable_'.utils::GetSafeId($iListId);
|
||||
$this->oSet = $oSet;
|
||||
@@ -115,8 +116,7 @@ class DataTable
|
||||
// See if this column is a must to load
|
||||
$sClass = $this->aClassAliases[$sAlias];
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ($oAttDef->alwaysLoadInTables())
|
||||
{
|
||||
if ($oAttDef->AlwaysLoadInTables()) {
|
||||
$aColumnsToLoad[$sAlias][] = $sAttCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Pill\PillFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
|
||||
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItemFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Toolbar\Separator\ToolbarSeparatorUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\iUIBlock;
|
||||
@@ -168,6 +170,10 @@ class DisplayBlock
|
||||
/** bool add toolkit menu */
|
||||
'selectionMode',
|
||||
/**positive or negative*/
|
||||
'max_height',
|
||||
/** 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 */
|
||||
], DataTableUIBlockFactory::GetAllowedParams()),
|
||||
'list_search' => array_merge([
|
||||
'update_history',
|
||||
@@ -186,6 +192,8 @@ class DisplayBlock
|
||||
/** string */
|
||||
'open',
|
||||
/** bool open by default the search */
|
||||
'submit_on_load',
|
||||
/** bool submit the search on loading page */
|
||||
'class', /** class name */
|
||||
'search_header_force_dropdown', /** Html for <select> to choose the class to search */
|
||||
'this',
|
||||
@@ -195,6 +203,8 @@ class DisplayBlock
|
||||
/** string search root class */
|
||||
'open',
|
||||
/** bool open the search panel by default */
|
||||
'submit_on_load',
|
||||
/** bool submit the search on loading page */
|
||||
'result_list_outer_selector',
|
||||
/** string js selector of the search result display */
|
||||
'search_header_force_dropdown',
|
||||
@@ -254,6 +264,16 @@ class DisplayBlock
|
||||
'withJSRefreshCallBack',
|
||||
/** true if dashboard page */
|
||||
'from_dashboard_page',
|
||||
/** bool true if list may be render in panel block */
|
||||
'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])) {
|
||||
@@ -449,10 +469,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);
|
||||
}
|
||||
@@ -994,9 +1012,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;
|
||||
@@ -1023,10 +1042,11 @@ JS
|
||||
$sHyperlink = $aCount['link'];
|
||||
$sCountLabel = $aCount['label'];
|
||||
$oPill = PillFactory::MakeForState($sClass, $sStateValue)
|
||||
->SetUrl($sHyperlink)
|
||||
->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);
|
||||
}
|
||||
$oBlock->AddSubBlock($oPill);
|
||||
}
|
||||
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
|
||||
@@ -1138,7 +1158,7 @@ JS
|
||||
$aValues[$iRow] = $sValue;
|
||||
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
|
||||
$aLabels[$iRow] = $sHtmlValue;
|
||||
$aGroupBy[$iRow] = (int)$aRow[$sFctVar];
|
||||
$aGroupBy[$iRow] = (float)$aRow[$sFctVar];
|
||||
$iTotalCount += $aRow['_itop_count_'];
|
||||
}
|
||||
|
||||
@@ -1170,11 +1190,23 @@ JS
|
||||
),
|
||||
);
|
||||
$sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection';
|
||||
$sTitle = Dict::Format($sFormat, $iTotalCount);
|
||||
|
||||
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
|
||||
$aOption['dom'] = 'pl';
|
||||
$oBlock = DataTableUIBlockFactory::MakeForStaticData($sTitle, $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
|
||||
|
||||
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
|
||||
$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 {
|
||||
$sTitle = Dict::Format($sFormat, $iTotalCount);
|
||||
$oBlock = DataTableUIBlockFactory::MakeForStaticData($sTitle, $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Simply count the number of elements in the set
|
||||
@@ -1183,7 +1215,15 @@ JS
|
||||
if (isset($aExtraParams['format'])) {
|
||||
$sFormat = $aExtraParams['format'];
|
||||
}
|
||||
$oBlock = new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>');
|
||||
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>');
|
||||
}
|
||||
}
|
||||
|
||||
return $oBlock;
|
||||
@@ -1270,6 +1310,22 @@ JS
|
||||
$oBlock->bNotAuthorized = true;
|
||||
}
|
||||
} else {
|
||||
if (isset($aExtraParams['update_history']) && true == $aExtraParams['update_history']) {
|
||||
$sSearchFilter = $this->m_oSet->GetFilter()->serialize();
|
||||
// Limit the size of the URL (N°1585 - request uri too long)
|
||||
if (strlen($sSearchFilter) < SERVER_MAX_URL_LENGTH) {
|
||||
$oBlock->sEventAttachedData = json_encode(array(
|
||||
'filter' => $sSearchFilter,
|
||||
'breadcrumb_id' => "ui-search-".$this->m_oSet->GetClass(),
|
||||
'breadcrumb_label' => MetaModel::GetName($this->m_oSet->GetClass()),
|
||||
'breadcrumb_max_count' => utils::GetConfig()->Get('breadcrumb.max_count'),
|
||||
'breadcrumb_instance_id' => MetaModel::GetConfig()->GetItopInstanceid(),
|
||||
'breadcrumb_icon' => 'fas fa-search',
|
||||
'breadcrumb_icon_type' => iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// The list is made of only 1 class of objects, actions on the list are possible
|
||||
if (($this->m_oSet->CountWithLimit(1) > 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)) {
|
||||
$oBlock->AddSubBlock(cmdbAbstractObject::GetDisplaySetBlock($oPage, $this->m_oSet, $aExtraParams));
|
||||
@@ -1296,25 +1352,19 @@ JS
|
||||
$oBlock->bCreateNew = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($aExtraParams['update_history']) && true == $aExtraParams['update_history']) {
|
||||
$sSearchFilter = $this->m_oSet->GetFilter()->serialize();
|
||||
// Limit the size of the URL (N°1585 - request uri too long)
|
||||
if (strlen($sSearchFilter) < SERVER_MAX_URL_LENGTH) {
|
||||
$oBlock->sEventAttachedData = json_encode(array(
|
||||
'filter' => $sSearchFilter,
|
||||
'breadcrumb_id' => "ui-search-".$this->m_oSet->GetClass(),
|
||||
'breadcrumb_label' => MetaModel::GetName($this->m_oSet->GetClass()),
|
||||
'breadcrumb_max_count' => utils::GetConfig()->Get('breadcrumb.max_count'),
|
||||
'breadcrumb_instance_id' => MetaModel::GetConfig()->GetItopInstanceid(),
|
||||
'breadcrumb_icon' => 'fas fa-search',
|
||||
'breadcrumb_icon_type' => iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES,
|
||||
));
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $oBlock;
|
||||
}
|
||||
@@ -1503,6 +1553,16 @@ JS
|
||||
|
||||
$oBlock->sUrl = $sUrl;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return $oBlock;
|
||||
}
|
||||
|
||||
@@ -1538,7 +1598,7 @@ JS
|
||||
$aValues[] = array(
|
||||
'label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'),
|
||||
'label_html' => $sHtmlValue,
|
||||
'value' => (int)$aRow[$sFctVar],
|
||||
'value' => (float)$aRow[$sFctVar],
|
||||
);
|
||||
|
||||
// Build the search for this subset
|
||||
@@ -1573,7 +1633,7 @@ JS
|
||||
$aColumns = array();
|
||||
$aNames = array();
|
||||
foreach ($aValues as $idx => $aValue) {
|
||||
$aColumns[] = array('series_'.$idx, (int)$aValue['value']);
|
||||
$aColumns[] = array('series_'.$idx, (float)$aValue['value']);
|
||||
$aNames['series_'.$idx] = $aValue['label'];
|
||||
}
|
||||
$oBlock = new BlockChartAjaxPie();
|
||||
@@ -1584,6 +1644,16 @@ JS
|
||||
$oBlock->sURLForRefresh = str_replace("'", "\'", $sUrl);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
return $oBlock;
|
||||
}
|
||||
|
||||
@@ -1646,6 +1716,7 @@ class HistoryBlock extends DisplayBlock
|
||||
|
||||
public function __construct(DBSearch $oFilter, $sStyle = 'list', $bAsynchronous = false, $aParams = array(), $oSet = null)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
|
||||
parent::__construct($oFilter, $sStyle, $bAsynchronous, $aParams, $oSet);
|
||||
$this->iLimitStart = 0;
|
||||
$this->iLimitCount = 0;
|
||||
@@ -1771,6 +1842,13 @@ EOF
|
||||
*/
|
||||
class MenuBlock extends DisplayBlock
|
||||
{
|
||||
/**
|
||||
* @var string Prefix to use for the ID of the actions toolbar
|
||||
* @used-by static::GetRenderContent
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public const ACTIONS_TOOLBAR_ID_PREFIX = 'ibo-actions-toolbar-';
|
||||
|
||||
/**
|
||||
* Renders the "Actions" popup menu for the given set of objects
|
||||
*
|
||||
@@ -1812,6 +1890,8 @@ class MenuBlock extends DisplayBlock
|
||||
$aRegularActions = [];
|
||||
/** @var array $aTransitionActions Only transitions */
|
||||
$aTransitionActions = [];
|
||||
/** @var array $aToolkitActions Any "legacy" toolkit menu item, which are now displayed in the same menu as the $aRegularActions, after them */
|
||||
$aToolkitActions = [];
|
||||
if ((!isset($aExtraParams['selection_mode']) || $aExtraParams['selection_mode'] == "") && $this->m_sStyle != 'listInObject') {
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
@@ -1981,13 +2061,9 @@ class MenuBlock extends DisplayBlock
|
||||
|
||||
$this->AddMenuSeparator($aRegularActions);
|
||||
|
||||
/** @var \iApplicationUIExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) {
|
||||
$oSet->Rewind();
|
||||
foreach ($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $sUrl) {
|
||||
$aRegularActions[$sLabel] = array('label' => $sLabel, 'url' => $sUrl) + $aActionParams;
|
||||
}
|
||||
}
|
||||
$this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) {
|
||||
$aRegularActions[$sLabel] = array('label' => $sLabel, 'url' => $data) + $aActionParams;
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -2089,24 +2165,20 @@ class MenuBlock extends DisplayBlock
|
||||
|
||||
$this->AddMenuSeparator($aRegularActions);
|
||||
|
||||
/** @var \iApplicationUIExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) {
|
||||
$oSet->Rewind();
|
||||
foreach ($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $data) {
|
||||
if (is_array($data)) {
|
||||
// New plugins can provide javascript handlers via the 'onclick' property
|
||||
//TODO: enable extension of different menus by checking the 'target' property ??
|
||||
$aRegularActions[$sLabel] = [
|
||||
'label' => $sLabel,
|
||||
'url' => isset($data['url']) ? $data['url'] : '#',
|
||||
'onclick' => isset($data['onclick']) ? $data['onclick'] : '',
|
||||
];
|
||||
} else {
|
||||
// Backward compatibility with old plugins
|
||||
$aRegularActions[$sLabel] = ['label' => $sLabel, 'url' => $data] + $aActionParams;
|
||||
}
|
||||
$this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) {
|
||||
if (is_array($data)) {
|
||||
// New plugins can provide javascript handlers via the 'onclick' property
|
||||
//TODO: enable extension of different menus by checking the 'target' property ??
|
||||
$aRegularActions[$sLabel] = [
|
||||
'label' => $sLabel,
|
||||
'url' => isset($data['url']) ? $data['url'] : '#',
|
||||
'onclick' => isset($data['onclick']) ? $data['onclick'] : '',
|
||||
];
|
||||
} else {
|
||||
// Backward compatibility with old plugins
|
||||
$aRegularActions[$sLabel] = ['label' => $sLabel, 'url' => $data] + $aActionParams;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (empty($sRefreshAction) && $this->m_sStyle == 'list') {
|
||||
//for the detail page this var is defined way beyond this line
|
||||
@@ -2116,18 +2188,17 @@ class MenuBlock extends DisplayBlock
|
||||
//it's easier just display configure this list and MENU_OBJLIST_TOOLKIT
|
||||
}
|
||||
$param = null;
|
||||
$iMenuId = null;
|
||||
if (is_null($sId)) {
|
||||
$sId = uniqid();
|
||||
}
|
||||
|
||||
// New extensions based on iPopupMenuItem interface
|
||||
$oPopupMenuItemsBlock = new UIContentBlock();
|
||||
switch ($this->m_sStyle) {
|
||||
case 'list':
|
||||
case 'listInObject':
|
||||
$oSet->Rewind();
|
||||
$param = $oSet;
|
||||
$iMenuId = iPopupMenuExtension::MENU_OBJLIST_ACTIONS;
|
||||
$bToolkitMenu = true;
|
||||
if (isset($aExtraParams['toolkit_menu'])) {
|
||||
$bToolkitMenu = (bool)$aExtraParams['toolkit_menu'];
|
||||
@@ -2136,23 +2207,27 @@ class MenuBlock extends DisplayBlock
|
||||
$sLabel = Dict::S('UI:ConfigureThisList');
|
||||
$aRegularActions['iTop::ConfigureList'] = ['label' => $sLabel, 'url' => '#', 'onclick' => "$('#datatable_dlg_datatable_{$sId}').dialog('open'); return false;"];
|
||||
}
|
||||
utils::GetPopupMenuItemsBlock($oPopupMenuItemsBlock, iPopupMenuExtension::MENU_OBJLIST_ACTIONS, $param, $aRegularActions, $sId);
|
||||
utils::GetPopupMenuItemsBlock($oPopupMenuItemsBlock, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $param, $aToolkitActions, $sId);
|
||||
break;
|
||||
|
||||
case 'details':
|
||||
$oSet->Rewind();
|
||||
$param = $oSet->Fetch();
|
||||
$iMenuId = iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS;
|
||||
utils::GetPopupMenuItemsBlock($oPopupMenuItemsBlock, iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS, $param, $aRegularActions, $sId);
|
||||
break;
|
||||
|
||||
}
|
||||
$oRenderBlock->AddSubBlock(utils::GetPopupMenuItemsBlock($iMenuId, $param, $aRegularActions, $sId));
|
||||
if ($oPopupMenuItemsBlock->HasSubBlocks()) {
|
||||
$oRenderBlock->AddSubBlock($oPopupMenuItemsBlock);
|
||||
}
|
||||
|
||||
// Extract favorite actions from their menus
|
||||
$aFavoriteRegularActions = [];
|
||||
$aFavoriteTransitionActions = [];
|
||||
$aCallSpec = [$sClass, 'GetShortcutActions'];
|
||||
if (is_callable($aCallSpec)) {
|
||||
$aShortcutActions = call_user_func($aCallSpec, $sClass);
|
||||
if (is_callable([$sClass, 'GetShortcutActions'])) {
|
||||
/** @var cmdbAbstractObject $sClass */
|
||||
$aShortcutActions = $sClass::GetShortcutActions($sClass);
|
||||
foreach ($aShortcutActions as $key) {
|
||||
// Regular actions
|
||||
if (isset($aRegularActions[$key])) {
|
||||
@@ -2168,7 +2243,7 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
}
|
||||
|
||||
$oActionsToolbar = ToolbarUIBlockFactory::MakeForAction("ibo-actions-toolbar-{$sId}");
|
||||
$oActionsToolbar = ToolbarUIBlockFactory::MakeForAction(static::ACTIONS_TOOLBAR_ID_PREFIX.$sId);
|
||||
$oRenderBlock->AddSubBlock($oActionsToolbar);
|
||||
$sRegularActionsMenuTogglerId = "ibo-regular-actions-menu-toggler-{$sId}";
|
||||
$sRegularActionsPopoverMenuId = "ibo-regular-actions-popover-{$sId}";
|
||||
@@ -2247,13 +2322,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);
|
||||
}
|
||||
@@ -2261,7 +2348,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']);
|
||||
@@ -2276,7 +2363,7 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
|
||||
// - Others
|
||||
if (!empty($aRegularActions)) {
|
||||
if (!empty($aRegularActions) || !empty($aToolkitActions)) {
|
||||
if (count($aFavoriteRegularActions) > 0) {
|
||||
$sName = 'UI:Menu:OtherActions';
|
||||
} else {
|
||||
@@ -2291,15 +2378,75 @@ class MenuBlock extends DisplayBlock
|
||||
|
||||
$oActionsToolbar->AddSubBlock($oActionButton)
|
||||
->AddSubBlock($oRegularActionsMenu);
|
||||
|
||||
// Toolkit actions
|
||||
if (!empty($aToolkitActions)) {
|
||||
foreach ($aToolkitActions as $sActionId => $aActionData) {
|
||||
$oRegularActionsMenu->AddItem('toolkit-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItemData($sActionId, $aActionData));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $oRenderBlock;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If an extension doesn't return an array as expected :
|
||||
* - calls IssueLog:Warning
|
||||
* - if is dev env, then throw CoreUnexpectedValue exception
|
||||
*
|
||||
* @param \DBObjectSet $oSet
|
||||
* @param callable $callback EnumAllowedActions returns an array, we will call this anonymous function on each of its value
|
||||
* with two parameters : label (array index), data (array value)
|
||||
*
|
||||
* @throws \CoreUnexpectedValue
|
||||
*
|
||||
* @uses \MetaModel::EnumPlugins()
|
||||
* @uses \iApplicationUIExtension::EnumAllowedActions()
|
||||
* @uses \utils::IsDevelopmentEnvironment()
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
private function GetEnumAllowedActions(DBObjectSet $oSet, callable $callback): void
|
||||
{
|
||||
$aInvalidExtensions = [];
|
||||
|
||||
/** @var \iApplicationUIExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) {
|
||||
$oSet->Rewind();
|
||||
$aExtEnumAllowedActions = $oExtensionInstance->EnumAllowedActions($oSet);
|
||||
|
||||
if (!is_array($aExtEnumAllowedActions)) {
|
||||
$aInvalidExtensions[] = get_class($oExtensionInstance);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($aExtEnumAllowedActions as $sLabel => $data) {
|
||||
$callback($sLabel, $data);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($aInvalidExtensions)) {
|
||||
$sMessage = 'Some extensions returned non array value for EnumAllowedActions() method impl';
|
||||
|
||||
IssueLog::Warning(
|
||||
$sMessage,
|
||||
null,
|
||||
['extensions' => $aInvalidExtensions]
|
||||
);
|
||||
|
||||
if (utils::IsDevelopmentEnvironment()) {
|
||||
throw new CoreUnexpectedValue($sMessage, $aInvalidExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a menu separator to the current list of actions
|
||||
*
|
||||
* @param array $aActions The current actions list
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function AddMenuSeparator(&$aActions)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/ErrorPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/ErrorPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/ErrorPage.php, now loadable using autoloader');
|
||||
@@ -28,14 +28,14 @@ class CoreCannotSaveObjectException extends CoreException
|
||||
*
|
||||
* @param array $aContextData containing at least those keys : issues, id, class
|
||||
*/
|
||||
public function __construct($aContextData)
|
||||
public function __construct($aContextData, $oPrevious = null)
|
||||
{
|
||||
$this->aIssues = $aContextData['issues'];
|
||||
$this->iObjectId = $aContextData['id'];
|
||||
$this->sObjectClass = $aContextData['class'];
|
||||
|
||||
$sIssues = implode(', ', $this->aIssues);
|
||||
parent::__construct($sIssues, $aContextData);
|
||||
parent::__construct($sIssues, $aContextData, '', $oPrevious);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,5 +30,10 @@ class MySQLException extends CoreException
|
||||
$aContext['mysql_error'] = CMDBSource::GetError();
|
||||
}
|
||||
parent::__construct($sIssue, $aContext);
|
||||
//if is connection error, don't log the default message with password in
|
||||
if (mysqli_connect_errno()) {
|
||||
error_log($this->message);
|
||||
error_reporting(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,12 +102,20 @@ class DesignerForm
|
||||
$sReturn .= '<fieldset>';
|
||||
$sReturn .= '<legend>'.$sLabel.'</legend>';
|
||||
}
|
||||
/** @var \DesignerFormField $oField */
|
||||
foreach($aFields as $oField) {
|
||||
$aRow = $oField->Render($oP, $sFormId);
|
||||
if ($oField->IsVisible()) {
|
||||
$sValidation = '<span class="prop_apply ibo-prop--apply">'.$this->GetValidationArea($oField->GetFieldId()).'</span>';
|
||||
$sValidation = '<span class="prop_apply ibo-prop--apply ibo-button ibo-is-alternative">'.$this->GetValidationArea($oField->GetFieldId()).'</span>';
|
||||
$sField = $aRow['value'].$sValidation;
|
||||
$aDetails[] = array('label' => $aRow['label'], 'value' => $sField);
|
||||
$aDetails[] = array(
|
||||
'label' => $aRow['label'],
|
||||
'value' => $sField,
|
||||
'attcode' => $oField->GetCode(),
|
||||
'attlabel' => $aRow['label'],
|
||||
'inputid' => $this->GetFieldId($oField->GetCode()),
|
||||
'inputtype' => $oField->GetInputType(),
|
||||
);
|
||||
} else {
|
||||
$sHiddenFields .= $aRow['value'];
|
||||
}
|
||||
@@ -203,51 +211,41 @@ class DesignerForm
|
||||
$sActionUrl = addslashes($this->sSubmitTo);
|
||||
$sJSSubmitParams = json_encode($this->aSubmitParams);
|
||||
$sFormId = $this->GetFormId();
|
||||
if ($this->oParentForm == null)
|
||||
{
|
||||
if ($this->oParentForm == null) {
|
||||
$sReturn = '<form id="'.$sFormId.'" onsubmit="return false;">';
|
||||
$sReturn .= '<table class="prop_table">';
|
||||
$sReturn .= '<thead><tr><th class="prop_header">'.Dict::S('UI:Form:Property').'</th><th class="prop_header">'.Dict::S('UI:Form:Value').'</th><th colspan="2" class="prop_header"> </th></tr></thead><tbody>';
|
||||
$sReturn .= '<thead><tr><th class="ibo-prop-header">'.Dict::S('UI:Form:Property').'</th><th class="ibo-prop-header">'.Dict::S('UI:Form:Value').'</th><th colspan="2" class="ibo-prop-header"> </th></tr></thead><tbody>';
|
||||
}
|
||||
|
||||
$sHiddenFields = '';
|
||||
foreach($this->aFieldSets as $sLabel => $aFields)
|
||||
{
|
||||
foreach ($this->aFieldSets as $sLabel => $aFields) {
|
||||
$aDetails = array();
|
||||
if ($sLabel != '')
|
||||
{
|
||||
if ($sLabel != '') {
|
||||
$sReturn .= $this->StartRow().'<th colspan="4">'.$sLabel.'</th>'.$this->EndRow();
|
||||
}
|
||||
|
||||
|
||||
foreach($aFields as $oField)
|
||||
{
|
||||
foreach ($aFields as $oField) {
|
||||
$aRow = $oField->Render($oP, $sFormId, 'property');
|
||||
if ($oField->IsVisible())
|
||||
{
|
||||
if ($oField->IsVisible()) {
|
||||
$sFieldId = $this->GetFieldId($oField->GetCode());
|
||||
$sValidation = $this->GetValidationArea($sFieldId, '<span data-tooltip-content="Apply"><i class="fas fa-check"></i></span>');
|
||||
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply">'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel"><span data-tooltip-content="Revert"><i class="fas fa-times"></i></span></td>'.$this->EndRow();
|
||||
|
||||
$sPath = $this->GetHierarchyPath().'/'.$oField->GetCode();
|
||||
|
||||
if (is_null($aRow['label']))
|
||||
{
|
||||
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_value ibo-field--value" colspan="2">'.$aRow['value'];
|
||||
$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" colspan="2">'.$aRow['value'];
|
||||
} else {
|
||||
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_label">'.$aRow['label'].'</td><td class="prop_value">'.$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'];
|
||||
}
|
||||
if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField))
|
||||
{
|
||||
if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField)) {
|
||||
$sReturn .= $sValidationFields;
|
||||
}
|
||||
$sNotifyParentSelectorJS = is_null($sNotifyParentSelector) ? 'null' : "'".addslashes($sNotifyParentSelector)."'";
|
||||
$sAutoApply = $oField->IsAutoApply() ? 'true' : 'false';
|
||||
$sHandlerEquals = $oField->GetHandlerEquals();
|
||||
$sHandlerGetValue = $oField->GetHandlerGetValue();
|
||||
|
||||
|
||||
$sWidgetClass = $oField->GetWidgetClass();
|
||||
$sJSExtraParams = '';
|
||||
if (count($oField->GetWidgetExtraParams()) > 0)
|
||||
@@ -356,7 +354,7 @@ EOF
|
||||
<<<EOF
|
||||
$('#$sDialogId').dialog({
|
||||
height: 'auto',
|
||||
maxHeight: $(window).height() - 8,
|
||||
maxHeight: $(window).height() * 0.9,
|
||||
width: $iDialogWidth,
|
||||
modal: true,
|
||||
autoOpen: $sAutoOpen,
|
||||
@@ -712,11 +710,27 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Important, for now we use constants from the \cmdbAbstractObject class, introducing a coupling that should not exist.
|
||||
* This has been traced under N°4241 and will be discussed during the next modernization batch.
|
||||
*
|
||||
* @return string|null Return the input type of the field
|
||||
* @see \cmdbAbstractObject::ENUM_INPUT_TYPE_XXX
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function GetInputType(): ?string
|
||||
{
|
||||
return cmdbAbstractObject::ENUM_INPUT_TYPE_SINGLE_INPUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -1054,7 +1068,18 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetInputType(): string
|
||||
{
|
||||
return cmdbAbstractObject::ENUM_INPUT_TYPE_TEXTAREA;
|
||||
}
|
||||
|
||||
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
|
||||
@@ -1180,14 +1205,35 @@ 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
|
||||
}
|
||||
|
||||
public function SetAllowedValues($aAllowedValues)
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetInputType(): ?string
|
||||
{
|
||||
if ($this->bMultipleSelection) {
|
||||
return cmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_MULTIPLE_CHOICES;
|
||||
}
|
||||
else {
|
||||
return cmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_RAW;
|
||||
}
|
||||
}
|
||||
|
||||
public function SetAllowedValues(?array $aAllowedValues)
|
||||
{
|
||||
// Make sure to have an actual array for values
|
||||
if (is_null($aAllowedValues)) {
|
||||
$aAllowedValues = [];
|
||||
}
|
||||
|
||||
$this->aAllowedValues = $aAllowedValues;
|
||||
}
|
||||
|
||||
@@ -1264,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>";
|
||||
@@ -1288,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"/>';
|
||||
@@ -1319,7 +1365,17 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetInputType(): ?string
|
||||
{
|
||||
return cmdbAbstractObject::ENUM_INPUT_TYPE_CHECKBOX;
|
||||
}
|
||||
|
||||
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
|
||||
@@ -1380,6 +1436,14 @@ class DesignerHiddenField extends DesignerFormField
|
||||
{
|
||||
parent::__construct($sCode, $sLabel, $defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetInputType(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function IsVisible()
|
||||
{
|
||||
@@ -1407,6 +1471,14 @@ class DesignerIconSelectionField extends DesignerFormField
|
||||
$this->bAutoApply = true;
|
||||
$this->sUploadUrl = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetInputType(): ?string
|
||||
{
|
||||
return cmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_DECORATED;
|
||||
}
|
||||
|
||||
public function SetAllowedValues($aAllowedValues)
|
||||
{
|
||||
@@ -1423,28 +1495,33 @@ class DesignerIconSelectionField extends DesignerFormField
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
$idx = 0;
|
||||
foreach($this->aAllowedValues as $index => $aValue)
|
||||
{
|
||||
if ($aValue['value'] == $this->defaultValue)
|
||||
{
|
||||
$idxFallback = 0;
|
||||
foreach ($this->aAllowedValues as $index => $aValue) {
|
||||
if ($aValue['value'] == $this->defaultValue) {
|
||||
$idx = $index;
|
||||
break;
|
||||
}
|
||||
//fallback if url of default value contains ../
|
||||
//for contact, icon is http://localhost/env-production/itop-structure/../../images/icons/icons8-customer.svg => not found http://localhost/images/icons/icons8-customer.svg
|
||||
if (basename($aValue['value']) == basename($this->defaultValue)) {
|
||||
$idxFallback = $index;
|
||||
}
|
||||
}
|
||||
if ($idx == 0) {
|
||||
$idx = $idxFallback;
|
||||
}
|
||||
$sJSItems = json_encode($this->aAllowedValues);
|
||||
$sPostUploadTo = ($this->sUploadUrl == null) ? 'null' : "'{$this->sUploadUrl}'";
|
||||
if (!$this->IsReadOnly())
|
||||
{
|
||||
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
|
||||
<<<EOF
|
||||
$('#$sId').icon_select({current_idx: $idx, items: $sJSItems, post_upload_to: $sPostUploadTo});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sValue = '<span style="display:inline-block;line-height:48px;height:48px;"><span><img style="vertical-align:middle" src="'.$this->aAllowedValues[$idx]['icon'].'" /> '.htmlentities($this->aAllowedValues[$idx]['label'], ENT_QUOTES, 'UTF-8').'</span></span>';
|
||||
}
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled' : '';
|
||||
@@ -1459,18 +1536,21 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
public function __construct($sCode, $sLabel = '', $defaultValue = '')
|
||||
{
|
||||
parent::__construct($sCode, $sLabel, $defaultValue);
|
||||
$aFolderList = [
|
||||
APPROOT.'env-'.utils::GetCurrentEnvironment() => utils::GetAbsoluteUrlModulesRoot(),
|
||||
APPROOT.'images/icons' => utils::GetAbsoluteUrlAppRoot().'images/icons',
|
||||
];
|
||||
if (count(self::$aAllIcons) == 0) {
|
||||
foreach ($aFolderList as $sFolderPath => $sUrlPrefix) {
|
||||
$aIcons = self::FindIconsOnDisk($sFolderPath);
|
||||
ksort($aIcons);
|
||||
|
||||
if (count(self::$aAllIcons) == 0)
|
||||
{
|
||||
self::$aAllIcons = self::FindIconsOnDisk(APPROOT.'env-'.utils::GetCurrentEnvironment());
|
||||
ksort(self::$aAllIcons);
|
||||
foreach ($aIcons as $sFilePath) {
|
||||
self::$aAllIcons[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => $sUrlPrefix.$sFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
$aValues = array();
|
||||
foreach(self::$aAllIcons as $sFilePath)
|
||||
{
|
||||
$aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
|
||||
}
|
||||
$this->SetAllowedValues($aValues);
|
||||
$this->SetAllowedValues(self::$aAllIcons);
|
||||
}
|
||||
|
||||
static protected function FindIconsOnDisk($sBaseDir, $sDir = '')
|
||||
@@ -1501,26 +1581,29 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
SetupUtils::builddir(dirname($sCacheFile));
|
||||
file_put_contents($sCacheFile, $sAvailableIcons, LOCK_EX);
|
||||
}
|
||||
|
||||
return $aFiles;
|
||||
}
|
||||
|
||||
static protected function _FindIconsOnDisk($sBaseDir, $sDir = '')
|
||||
static protected function _FindIconsOnDisk($sBaseDir, $sDir = '', &$aFilesSpecs = [])
|
||||
{
|
||||
$aResult = array();
|
||||
$aResult = [];
|
||||
// Populate automatically the list of icon files
|
||||
if ($hDir = @opendir($sBaseDir.'/'.$sDir))
|
||||
{
|
||||
while (($sFile = readdir($hDir)) !== false)
|
||||
{
|
||||
if ($hDir = @opendir($sBaseDir.'/'.$sDir)) {
|
||||
while (($sFile = readdir($hDir)) !== false) {
|
||||
$aMatches = array();
|
||||
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
|
||||
{
|
||||
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile)) {
|
||||
$sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
|
||||
$aResult = array_merge($aResult, self::_FindIconsOnDisk($sBaseDir, $sDirSubPath));
|
||||
$aResult = array_merge($aResult, self::_FindIconsOnDisk($sBaseDir, $sDirSubPath, $aFilesSpecs));
|
||||
}
|
||||
if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
|
||||
$sSize = filesize($sBaseDir.'/'.$sDir.'/'.$sFile);
|
||||
if (isset($aFilesSpecs[$sFile]) && $aFilesSpecs[$sFile] == $sSize) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match("/\.(png|jpg|jpeg|gif|svg)$/i", $sFile, $aMatches)) // png, jp(e)g, gif and svg are considered valid
|
||||
{
|
||||
$aResult[$sFile.'_'.$sDir] = $sDir.'/'.$sFile;
|
||||
$aFilesSpecs[$sFile] = $sSize;
|
||||
}
|
||||
}
|
||||
closedir($hDir);
|
||||
@@ -1566,6 +1649,14 @@ class DesignerSortableField extends DesignerFormField
|
||||
parent::__construct($sCode, $sLabel, $defaultValue);
|
||||
$this->aAllowedValues = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetInputType(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function SetAllowedValues($aAllowedValues)
|
||||
{
|
||||
@@ -1602,7 +1693,17 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetInputType(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function IsSorted()
|
||||
@@ -1645,27 +1746,23 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
{
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
|
||||
|
||||
$this->aCSSClasses[] = 'formSelector';
|
||||
|
||||
|
||||
$sCSSClasses = '';
|
||||
if (count($this->aCSSClasses) > 0)
|
||||
{
|
||||
if (count($this->aCSSClasses) > 0) {
|
||||
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
|
||||
}
|
||||
|
||||
if ($this->IsSorted())
|
||||
{
|
||||
if ($this->IsSorted()) {
|
||||
uasort($this->aSubForms, array(get_class($this), 'SortOnFormLabel'));
|
||||
}
|
||||
|
||||
if ($this->IsReadOnly())
|
||||
{
|
||||
|
||||
if ($this->IsReadOnly()) {
|
||||
$sDisplayValue = '';
|
||||
$sHiddenValue = '';
|
||||
foreach($this->aSubForms as $iKey => $aFormData)
|
||||
{
|
||||
foreach ($this->aSubForms as $iKey => $aFormData) {
|
||||
if ($iKey == $this->defaultValue) // Default value is actually the index
|
||||
{
|
||||
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
|
||||
@@ -1674,35 +1771,29 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
}
|
||||
}
|
||||
$sHtml = "<span $sCSSClasses>".$sDisplayValue.$sHiddenValue."</span>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
|
||||
foreach($this->aSubForms as $iKey => $aFormData)
|
||||
{
|
||||
} else {
|
||||
$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"><span title="Apply" class="ui-icon ui-icon-circle-check"/></td><td class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td></tr>';
|
||||
|
||||
if ($sRenderMode == 'property') {
|
||||
$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)
|
||||
{
|
||||
foreach ($this->aSubForms as $sKey => $aFormData) {
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sStyle = (($sKey == $this->defaultValue) && $this->oForm->IsDisplayed()) ? '' : 'style="display:none"';
|
||||
$oSubForm = $aFormData['form'];
|
||||
$oSubForm->SetParentForm($this->oForm);
|
||||
$oSubForm->CopySubmitParams($this->oForm);
|
||||
$oSubForm->SetPrefix($this->oForm->GetPrefix().$sKey.'_');
|
||||
|
||||
if ($sRenderMode == 'property')
|
||||
{
|
||||
|
||||
if ($sRenderMode == 'property') {
|
||||
// Note: Managing the visibility of nested subforms had several implications
|
||||
// 1) Attributes are displayed in a table and we have to group them in as many tbodys as necessary to hide/show the various options depending on the current selection
|
||||
// 2) It is not possible to nest tbody tags. Therefore, it is not possible to manage the visibility the same way as it is done for the dialog mode (using nested divs).
|
||||
@@ -1800,6 +1891,14 @@ class DesignerSubFormField extends DesignerFormField
|
||||
parent::__construct('', $sLabel, '');
|
||||
$this->oSubForm = $oSubForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetInputType(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
|
||||
{
|
||||
@@ -1844,6 +1943,14 @@ class DesignerStaticTextField extends DesignerFormField
|
||||
parent::__construct($sCode, $sLabel, $defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetInputType(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
|
||||
{
|
||||
return array('label' => $this->sLabel, 'value' => $this->defaultValue);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/iTopWebPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/iTopWebPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
|
||||
// cannot notify depreciation for now as this is still MASSIVELY used in iTop core !
|
||||
//DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/iTopWebPage.php, now loadable using autoloader');
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/iTopWizardWebPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/iTopWizardWebPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/iTopWizardWebPage.php, now loadable using autoloader');
|
||||
@@ -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)
|
||||
@@ -500,7 +496,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 +522,7 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
if ($bFilterWithMode)
|
||||
{
|
||||
$sCurrentLoginMode = isset($_SESSION['login_mode']) ? $_SESSION['login_mode'] : '';
|
||||
$sCurrentLoginMode = Session::Get('login_mode', '');
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -665,8 +661,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 +677,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 +708,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 +825,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 +879,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 +920,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 +1007,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,19 +1086,23 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
else if ($operation == 'change_pwd')
|
||||
{
|
||||
if (isset($_SESSION['auth_user']))
|
||||
if (Session::IsSet('auth_user'))
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
$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
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayChangePwdForm();
|
||||
$oPage->DisplayChangePwdForm($bFailedLogin, $sIssue);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
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();
|
||||
@@ -1121,9 +1120,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');
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Helper\WebResourcesHelper;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
@@ -71,6 +59,10 @@ class ApplicationMenu
|
||||
* @var array
|
||||
*/
|
||||
static $aMenusIndex = array();
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
static $aMenusById = [];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@@ -179,6 +171,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
|
||||
{
|
||||
@@ -342,15 +335,17 @@ class ApplicationMenu
|
||||
*/
|
||||
public static function DisplayMenu($oPage, $aExtraParams)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use static::GetMenuGroups() instead');
|
||||
self::LoadAdditionalMenus();
|
||||
// Sort the root menu based on the rank
|
||||
usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
|
||||
$iAccordion = 0;
|
||||
$iActiveAccordion = $iAccordion;
|
||||
$iActiveMenu = self::GetMenuIndexById(self::GetActiveNodeId());
|
||||
foreach(self::$aRootMenus as $aMenu)
|
||||
{
|
||||
if (!self::CanDisplayMenu($aMenu)) { continue; }
|
||||
foreach (self::$aRootMenus as $aMenu) {
|
||||
if (!self::CanDisplayMenu($aMenu)) {
|
||||
continue;
|
||||
}
|
||||
$oMenuNode = self::GetMenuNode($aMenu['index']);
|
||||
$oPage->AddToMenu('<h3 id="'.utils::GetSafeId('AccordionMenu_'.$oMenuNode->GetMenuID()).'" class="navigation-menu-group" data-menu-id="'.$oMenuNode->GetMenuId().'">'.$oMenuNode->GetTitle().'</h3>');
|
||||
$oPage->AddToMenu('<div>');
|
||||
@@ -418,13 +413,12 @@ EOF
|
||||
*/
|
||||
protected static function DisplaySubMenu($oPage, $aMenus, $aExtraParams, $iActiveMenu = -1)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use static::GetSubMenuNodes() instead');
|
||||
// Sort the menu based on the rank
|
||||
$bActive = false;
|
||||
usort($aMenus, array('ApplicationMenu', 'CompareOnRank'));
|
||||
foreach($aMenus as $aMenu)
|
||||
{
|
||||
if (!self::CanDisplayMenu($aMenu))
|
||||
{
|
||||
foreach ($aMenus as $aMenu) {
|
||||
if (!self::CanDisplayMenu($aMenu)) {
|
||||
continue;
|
||||
}
|
||||
$index = $aMenu['index'];
|
||||
@@ -518,17 +512,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -727,42 +715,9 @@ abstract class MenuNode
|
||||
{
|
||||
// Count the entries up to 99
|
||||
$oSearch = DBSearch::FromOQL($sOQL);
|
||||
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
DBSearchHelper::AddContextFilter($oSearch);
|
||||
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sClass = $oSearch->GetClass();
|
||||
foreach ($oAppContext->GetNames() as $key) {
|
||||
// Find the value of the object corresponding to each 'context' parameter
|
||||
$aCallSpec = [$sClass, 'MapContextParam'];
|
||||
$sAttCode = '';
|
||||
if (is_callable($aCallSpec)) {
|
||||
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
|
||||
}
|
||||
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode)) {
|
||||
// Add Hierarchical condition if hierarchical key
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if (isset($oAttDef) && ($oAttDef->IsExternalKey())) {
|
||||
$iDefaultValue = intval($oAppContext->GetCurrentValue($key));
|
||||
if ($iDefaultValue != 0) {
|
||||
try {
|
||||
/** @var AttributeExternalKey $oAttDef */
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass);
|
||||
if ($sHierarchicalKeyCode !== false) {
|
||||
$oFilter = new DBObjectSearch($sTargetClass);
|
||||
$oFilter->AddCondition('id', $iDefaultValue);
|
||||
$oHKFilter = new DBObjectSearch($sTargetClass);
|
||||
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW);
|
||||
$oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode);
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// If filtering fails just ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$iCount = $oSet->CountWithLimit(99);
|
||||
@@ -1171,15 +1126,13 @@ class OQLMenuNode extends MenuNode
|
||||
{
|
||||
$sUsageId = utils::GetSafeId($sUsageId);
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql);
|
||||
//$sIcon = MetaModel::GetClassIcon($oSearch->GetClass(), false);
|
||||
|
||||
if ($bSearchPane) {
|
||||
$aParams = array_merge(array('open' => $bSearchOpen, 'table_id' => $sUsageId), $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("<p class=\"page-header\">$sIcon ".utils::HtmlEntities(Dict::S($sTitle))."</p>");
|
||||
$oPage->add("<div class='sf_results_area' data-target='search_results'>");
|
||||
$oTitle = TitleUIBlockFactory::MakeForPage($sTitle);
|
||||
$oPage->AddUiBlock($oTitle);
|
||||
@@ -1255,7 +1208,8 @@ class SearchMenuNode extends MenuNode
|
||||
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
|
||||
|
||||
$oSearch = new DBObjectSearch($this->sClass);
|
||||
$aParams = array_merge(array('table_id' => 'Menu_'.utils::GetSafeId($this->GetMenuId())), $aExtraParams);
|
||||
$sUsageId = 'Menu_'.utils::GetSafeId($this->GetMenuId());
|
||||
$aParams = array_merge(array('table_id' =>$sUsageId), $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
|
||||
$oBlock->Display($oPage, 0);
|
||||
}
|
||||
@@ -1461,6 +1415,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;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/NiceWebPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/NiceWebPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/NiceWebPage.php, now loadable using autoloader');
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/PDFPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/PDFPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/PDFPage.php, now loadable using autoloader');
|
||||
@@ -17,6 +17,12 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Field\Field;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Field\FieldUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\TextArea;
|
||||
|
||||
abstract class Query extends cmdbAbstractObject
|
||||
{
|
||||
/**
|
||||
@@ -120,36 +126,37 @@ class QueryOQL extends Query
|
||||
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
|
||||
{
|
||||
$aFieldsMap = parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams);
|
||||
|
||||
if (!$bEditMode)
|
||||
{
|
||||
$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'));
|
||||
$bExportV1Recommended = ($sFields == '');
|
||||
if ($bExportV1Recommended)
|
||||
{
|
||||
if ($bExportV1Recommended) {
|
||||
$oFieldAttDef = MetaModel::GetAttributeDef('QueryOQL', 'fields');
|
||||
$oPage->add('<div class="message message_error" style="padding-left: 30px;"><div style="padding: 10px;">'.Dict::Format('UI:Query:UrlV1', $oFieldAttDef->GetLabel()).'</div></div>');
|
||||
$oAlert = AlertUIBlockFactory::MakeForFailure()
|
||||
->SetIsClosable(false)
|
||||
->SetIsCollapsible(false);
|
||||
$oAlert->AddCSSClass('mb-5');
|
||||
$oAlert->AddSubBlock(new Html(Dict::Format('UI:Query:UrlV1', '')));
|
||||
$oPage->AddSubBlock($oAlert);
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?format=spreadsheet&login_mode=basic&query='.$this->GetKey();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php?format=spreadsheet&login_mode=basic&date_format='.urlencode((string)AttributeDateTime::GetFormat()).'&query='.$this->GetKey();
|
||||
}
|
||||
$sOql = $this->Get('oql');
|
||||
$sMessage = null;
|
||||
try
|
||||
{
|
||||
try {
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql);
|
||||
$aParameters = $oSearch->GetQueryParams();
|
||||
foreach($aParameters as $sParam => $val)
|
||||
{
|
||||
foreach ($aParameters as $sParam => $val) {
|
||||
$sUrl .= '&arg_'.$sParam.'=["'.$sParam.'"]';
|
||||
}
|
||||
|
||||
$oPage->p(Dict::S('UI:Query:UrlForExcel').':<br/><textarea cols="80" rows="3" READONLY>'.$sUrl.'</textarea>');
|
||||
$oTextArea = new TextArea("", $sUrl, null, 80, 3);
|
||||
$oFieldUrl = FieldUIBlockFactory::MakeFromObject(Dict::S('UI:Query:UrlForExcel'), $oTextArea, Field::ENUM_FIELD_LAYOUT_LARGE);
|
||||
$oPage->AddSubBlock($oFieldUrl);
|
||||
|
||||
if (count($aParameters) == 0)
|
||||
{
|
||||
if (count($aParameters) == 0) {
|
||||
$oBlock = new DisplayBlock($oSearch, 'list');
|
||||
$aExtraParams = array(
|
||||
//'menu' => $sShowMenu,
|
||||
@@ -159,10 +166,13 @@ class QueryOQL extends Query
|
||||
$oBlock->Display($oPage, $sBlockId, $aExtraParams);
|
||||
}
|
||||
}
|
||||
catch (OQLException $e)
|
||||
{
|
||||
$sMessage = '<div class="message message_error" style="padding-left: 30px;"><div style="padding: 10px;">'.Dict::Format('UI:RunQuery:Error', $e->getHtmlDesc()).'</div></div>';
|
||||
$oPage->p($sMessage);
|
||||
catch
|
||||
(OQLException $e) {
|
||||
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::Format('UI:RunQuery:Error'), $e->getHtmlDesc())
|
||||
->SetIsClosable(false)
|
||||
->SetIsCollapsible(false);
|
||||
$oAlert->AddCSSClass('mb-5');
|
||||
$oPage->AddSubBlock($oAlert);
|
||||
}
|
||||
}
|
||||
return $aFieldsMap;
|
||||
|
||||
@@ -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,39 @@ 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);
|
||||
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();
|
||||
Session::WriteClose();
|
||||
$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 +89,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);
|
||||
|
||||
@@ -31,14 +31,15 @@ class DisplayTemplate
|
||||
|
||||
public function __construct($sTemplate)
|
||||
{
|
||||
$this->m_aTags = array (
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
|
||||
$this->m_aTags = array(
|
||||
'itopblock',
|
||||
'itopcheck',
|
||||
'itoptabs',
|
||||
'itoptab',
|
||||
'itoptoggle',
|
||||
'itopstring',
|
||||
'sqlblock'
|
||||
'sqlblock',
|
||||
);
|
||||
$this->m_sTemplate = $sTemplate;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ class ThemeHandler
|
||||
{
|
||||
const IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg'];
|
||||
|
||||
/** @var \CompileCSSService */
|
||||
private static $oCompileCSSService;
|
||||
|
||||
public static function GetAppRootWithSlashes()
|
||||
@@ -46,11 +47,10 @@ class ThemeHandler
|
||||
'name' => 'fullmoon',
|
||||
'parameters' => [
|
||||
'variables' => [],
|
||||
'imports' => [
|
||||
],
|
||||
'imports' => [],
|
||||
'stylesheets' => [
|
||||
'main' => '../css/backoffice/main.scss',
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -58,22 +58,27 @@ class ThemeHandler
|
||||
/**
|
||||
* Return the ID of the theme currently defined in the config. file
|
||||
*
|
||||
* @deprecated 3.0.0, will be removed in 3.1, see N°3898
|
||||
* @return string
|
||||
*/
|
||||
public static function GetCurrentThemeId()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (is_null(MetaModel::GetConfig()))
|
||||
{
|
||||
throw new CoreException('no config');
|
||||
}
|
||||
$sThemeId = MetaModel::GetConfig()->Get('backoffice_default_theme');
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
|
||||
static::GetCurrentUserThemeId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string ID of the theme currently defined in the config. file, which applies to all users by default. If non defined, fallback on the default one.
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetApplicationThemeId(): string
|
||||
{
|
||||
try {
|
||||
$sThemeId = utils::GetConfig()->Get('backoffice_default_theme');
|
||||
}
|
||||
catch(CoreException $oCompileException)
|
||||
{
|
||||
catch (CoreException $oCompileException) {
|
||||
// Fallback on our default theme in case the config. is not available yet
|
||||
$aDefaultTheme = ThemeHandler::GetDefaultThemeInformation();
|
||||
$aDefaultTheme = ThemeHandler::GetDefaultThemeInformation();
|
||||
$sThemeId = $aDefaultTheme['name'];
|
||||
}
|
||||
|
||||
@@ -81,49 +86,173 @@ class ThemeHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the absolute path of the compiled theme folder.
|
||||
*
|
||||
* @return string ID of the theme to use for the current user as per they preferences. If non defined, fallback on the app. theme ID.
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetCurrentUserThemeId(): string
|
||||
{
|
||||
$sThemeId = null;
|
||||
|
||||
try {
|
||||
if (true === utils::GetConfig()->Get('user_preferences.allow_backoffice_theme_override')) {
|
||||
$sThemeId = appUserPreferences::GetPref('backoffice_theme', null);
|
||||
}
|
||||
}
|
||||
catch (Exception $oException) {
|
||||
// Do nothing, already handled by $sThemeId null by default
|
||||
}
|
||||
|
||||
// Fallback on the app. theme
|
||||
if (is_null($sThemeId)) {
|
||||
$sThemeId = static::GetApplicationThemeId();
|
||||
}
|
||||
|
||||
return $sThemeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sThemeId
|
||||
*
|
||||
* @return string
|
||||
* @return string Label of the theme which is either a dict entry ('theme:<THEME_ID>') or the ID if no localized dict. entry found.
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetCompiledThemeFolderAbsolutePath($sThemeId)
|
||||
public static function GetThemeLabel(string $sThemeId): string
|
||||
{
|
||||
return APPROOT.'env-'.utils::GetCurrentEnvironment().'/branding/themes/'.$sThemeId.'/';
|
||||
$sDictEntryCode = 'theme:'.$sThemeId;
|
||||
$sDictEntryValue = Dict::S('theme:'.$sThemeId);
|
||||
|
||||
return ($sDictEntryCode === $sDictEntryValue) ? $sThemeId : $sDictEntryValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array Associative array of <THEME_ID> => <THEME_LABEL>, ordered by labels
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetAvailableThemes(): array
|
||||
{
|
||||
$aThemes = [];
|
||||
|
||||
foreach (glob(static::GetCompiledThemesFolderAbsolutePath().'/*') as $sPath) {
|
||||
if (is_dir($sPath)) {
|
||||
$sThemeId = basename($sPath);
|
||||
$sThemeLabel = static::GetThemeLabel($sThemeId);
|
||||
|
||||
$aThemes[$sThemeId] = $sThemeLabel;
|
||||
}
|
||||
}
|
||||
asort($aThemes);
|
||||
|
||||
return $aThemes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sThemeId
|
||||
*
|
||||
* @return bool True if $sThemeId is a valid theme that can be used.
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function IsValidTheme(string $sThemeId): bool
|
||||
{
|
||||
return array_key_exists($sThemeId, static::GetAvailableThemes());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Absolute path to the folder containing all the compiled themes
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetCompiledThemesFolderAbsolutePath(): string
|
||||
{
|
||||
return APPROOT.'env-'.utils::GetCurrentEnvironment().'/branding/themes/';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sThemeId
|
||||
*
|
||||
* @return string Absolute path to the folder containing the $sThemeId theme
|
||||
*/
|
||||
public static function GetCompiledThemeFolderAbsolutePath(string $sThemeId): string
|
||||
{
|
||||
return static::GetCompiledThemesFolderAbsolutePath().$sThemeId.'/';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sThemeId
|
||||
*
|
||||
* @return string Absolute path of the compiled file for the $sThemeId theme (Note: It doesn't mean that the theme is actually compiled)
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetCompiledThemeFileAbsolutePath(string $sThemeId): string
|
||||
{
|
||||
return static::GetCompiledThemeFolderAbsolutePath($sThemeId).'main.css';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sThemeId
|
||||
*
|
||||
* @return string Absolute URL of the compiled file for the $sThemeId theme (Note: It doesn't mean that the theme is actually compiled)
|
||||
* @throws \Exception
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetCompiledThemeFileAbsoluteUrl(string $sThemeId): string
|
||||
{
|
||||
return utils::GetAbsoluteUrlModulesRoot().'branding/themes/'.$sThemeId.'/main.css';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the absolute URL for the current theme CSS file
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function GetCurrentThemeUrl()
|
||||
public static function GetCurrentThemeUrl(): string
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
// Try to compile theme defined in the configuration
|
||||
$sThemeId = static::GetCurrentThemeId();
|
||||
static::CompileTheme($sThemeId);
|
||||
// Note: In maintenance mode we should stick to the app theme (also we don't have access to many PHP classes, including the user preferences)
|
||||
$sThemeId = SetupUtils::IsInMaintenanceMode() ? static::GetApplicationThemeId() : static::GetCurrentUserThemeId();
|
||||
if (static::ShouldThemeSignatureCheckBeForced($sThemeId)) {
|
||||
static::CompileTheme($sThemeId);
|
||||
}
|
||||
}
|
||||
catch(CoreException $oCompileException)
|
||||
{
|
||||
catch (CoreException $oCompileException) {
|
||||
// Fallback on our default theme (should always be compilable) in case the previous theme doesn't exists
|
||||
$aDefaultTheme = ThemeHandler::GetDefaultThemeInformation();
|
||||
$aDefaultTheme = ThemeHandler::GetDefaultThemeInformation();
|
||||
$sThemeId = $aDefaultTheme['name'];
|
||||
$sDefaultThemeDirPath = static::GetCompiledThemeFolderAbsolutePath($sThemeId);
|
||||
|
||||
|
||||
// Create our theme dir if it doesn't exist (XML theme node removed, renamed etc..)
|
||||
if(!is_dir($sDefaultThemeDirPath))
|
||||
{
|
||||
if (!is_dir($sDefaultThemeDirPath)) {
|
||||
SetupUtils::builddir($sDefaultThemeDirPath);
|
||||
}
|
||||
|
||||
static::CompileTheme($sThemeId, false, "", $aDefaultTheme['parameters']);
|
||||
|
||||
if (static::ShouldThemeSignatureCheckBeForced($sThemeId)) {
|
||||
static::CompileTheme($sThemeId, false, "", $aDefaultTheme['parameters']);
|
||||
}
|
||||
}
|
||||
|
||||
// Return absolute url to theme compiled css
|
||||
return utils::GetAbsoluteUrlModulesRoot().'branding/themes/'.$sThemeId.'/main.css';
|
||||
return static::GetCompiledThemeFileAbsoluteUrl($sThemeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sThemeId
|
||||
*
|
||||
* @return bool True if the $sThemeId signature check -and possibly the compilation- should be forced (dev. environment, missing compiled file, ...)
|
||||
*/
|
||||
protected static function ShouldThemeSignatureCheckBeForced(string $sThemeId): bool
|
||||
{
|
||||
if (utils::IsDevelopmentEnvironment()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (false === file_exists(static::GetCompiledThemeFileAbsolutePath($sThemeId))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (true === utils::GetConfig()->Get('theme.force_signature_check_at_runtime')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,7 +264,7 @@ class ThemeHandler
|
||||
* @param boolean $bSetup
|
||||
* @param string $sSetupCompilationTimestamp : setup compilation timestamp in micro secunds
|
||||
* @param array|null $aThemeParameters Parameters (variables, imports, stylesheets) for the theme, if not passed, will be retrieved from compiled DM
|
||||
* @param array|null $aImportsPaths Paths where imports can be found. Must end with '/'
|
||||
* @param array|null $aImportsPaths Folder paths where imports can be found. Must end with '/'
|
||||
* @param string|null $sWorkingPath Path of the folder used during compilation. Must end with a '/'
|
||||
*
|
||||
* @throws \CoreException
|
||||
@@ -180,15 +309,16 @@ class ThemeHandler
|
||||
}
|
||||
}
|
||||
|
||||
$aThemeParametersWithVersion = self::CloneThemeParameterAndIncludeVersion($aThemeParameters, $sSetupCompilationTimestampInSecunds);
|
||||
$aThemeParametersWithVersion = self::CloneThemeParameterAndIncludeVersion($aThemeParameters, $sSetupCompilationTimestampInSecunds, $aImportsPaths);
|
||||
|
||||
clearstatcache();
|
||||
|
||||
// Loading files to import and stylesheet to compile, also getting most recent modification time on overall files
|
||||
$sTmpThemeScssContent = '';
|
||||
$oFindStylesheetObject = new FindStylesheetObject();
|
||||
if (isset($aThemeParameters['imports_utility'])) {
|
||||
foreach ($aThemeParameters['imports_utility'] as $sImport) {
|
||||
|
||||
if (isset($aThemeParameters['utility_imports'])) {
|
||||
foreach ($aThemeParameters['utility_imports'] as $sImport) {
|
||||
static::FindStylesheetFile($sImport, $aImportsPaths, $oFindStylesheetObject);
|
||||
}
|
||||
}
|
||||
@@ -203,6 +333,12 @@ class ThemeHandler
|
||||
$sTmpThemeScssContent .= '@import "'.$sStylesheet.'";'."\n";
|
||||
}
|
||||
|
||||
if (isset($aThemeParameters['variable_imports'])) {
|
||||
foreach ($aThemeParameters['variable_imports'] as $sImport) {
|
||||
static::FindStylesheetFile($sImport, $aImportsPaths, $oFindStylesheetObject);
|
||||
}
|
||||
}
|
||||
|
||||
$iStyleLastModified = $oFindStylesheetObject->GetLastModified();
|
||||
|
||||
$aIncludedImages=static::GetIncludedImages($aThemeParametersWithVersion, $oFindStylesheetObject->GetAllStylesheetPaths(), $sThemeId);
|
||||
@@ -262,11 +398,12 @@ CSS;
|
||||
static::$oCompileCSSService = new CompileCSSService();
|
||||
}
|
||||
//store it again to change $version with latest compiled time
|
||||
SetupLog::Info("Compiling theme $sThemeId...");
|
||||
$sTmpThemeCssContent = static::$oCompileCSSService->CompileCSSFromSASS($sTmpThemeScssContent, $aImportsPaths,
|
||||
$aThemeParametersWithVersion);
|
||||
SetupLog::Info("$sThemeId theme compilation done.");
|
||||
file_put_contents($sThemeFolderPath.'/theme-parameters.json', json_encode($aThemeParameters));
|
||||
file_put_contents($sThemeCssPath, $sSignatureComment.$sTmpThemeCssContent);
|
||||
SetupLog::Info("Theme $sThemeId file compiled.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -292,27 +429,29 @@ CSS;
|
||||
$aSignature = [
|
||||
'variables' => md5(json_encode($aThemeParameters['variables'])),
|
||||
'stylesheets' => [],
|
||||
'imports' => [],
|
||||
'images' => []
|
||||
'variable_imports' => [],
|
||||
'images' => [],
|
||||
'utility_imports' => []
|
||||
];
|
||||
|
||||
$oFindStylesheetObject = new FindStylesheetObject();
|
||||
|
||||
if (isset($aThemeParameters['imports_variable'])) {
|
||||
foreach ($aThemeParameters['imports_variable'] as $key => $sImport) {
|
||||
if (isset($aThemeParameters['variable_imports'])) {
|
||||
foreach ($aThemeParameters['variable_imports'] as $key => $sImport) {
|
||||
static::FindStylesheetFile($sImport, $aImportsPaths, $oFindStylesheetObject);
|
||||
$sFile = $oFindStylesheetObject->GetLastStylesheetFile();
|
||||
if (!empty($sFile)) {
|
||||
$aSignature['stylesheets'][$key] = md5_file($sFile);
|
||||
$aSignature['variable_imports'][$key] = md5_file($sFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($aThemeParameters['imports_utility'])) {
|
||||
foreach ($aThemeParameters['imports_utility'] as $key => $sImport) {
|
||||
|
||||
if (isset($aThemeParameters['utility_imports'])) {
|
||||
foreach ($aThemeParameters['utility_imports'] as $key => $sImport) {
|
||||
static::FindStylesheetFile($sImport, $aImportsPaths, $oFindStylesheetObject);
|
||||
$sFile = $oFindStylesheetObject->GetLastStylesheetFile();
|
||||
if (!empty($sFile)) {
|
||||
$aSignature['stylesheets'][$key] = md5_file($sFile);
|
||||
$aSignature['utility_imports'][$key] = md5_file($sFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -330,7 +469,7 @@ CSS;
|
||||
$aFiles = $oFindStylesheetObject->GetImportPaths();
|
||||
if (count($aFiles) !== 0) {
|
||||
foreach ($aFiles as $sFileURI => $sFilePath) {
|
||||
$aSignature['imports_utility'][$sFileURI] = md5_file($sFilePath);
|
||||
$aSignature['utility_imports'][$sFileURI] = md5_file($sFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,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" ;
|
||||
@@ -775,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;
|
||||
}
|
||||
}
|
||||
@@ -785,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);
|
||||
}
|
||||
|
||||
@@ -793,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){
|
||||
@@ -847,10 +987,11 @@ CSS;
|
||||
* Clone variable array and include $version with bSetupCompilationTimestamp value
|
||||
* @param $aThemeParameters
|
||||
* @param $bSetupCompilationTimestamp
|
||||
* @param $aImportsPaths
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp)
|
||||
public static function CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp, $aImportsPaths)
|
||||
{
|
||||
$aThemeParametersVariable = [];
|
||||
if (array_key_exists('variables', $aThemeParameters))
|
||||
@@ -859,12 +1000,13 @@ CSS;
|
||||
{
|
||||
$aThemeParametersVariable = array_merge([], $aThemeParameters['variables']);
|
||||
}
|
||||
}
|
||||
if (array_key_exists('imports_variable', $aThemeParameters))
|
||||
}
|
||||
|
||||
if (array_key_exists('variable_imports', $aThemeParameters))
|
||||
{
|
||||
if (is_array($aThemeParameters['imports_variable']))
|
||||
if (is_array($aThemeParameters['variable_imports']))
|
||||
{
|
||||
$aThemeParametersVariable = array_merge($aThemeParametersVariable, static::GetVariablesFromFile($aThemeParameters['imports_variable']));
|
||||
$aThemeParametersVariable = array_merge($aThemeParametersVariable, static::GetVariablesFromFile($aThemeParameters['variable_imports'], $aImportsPaths));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -874,20 +1016,30 @@ CSS;
|
||||
|
||||
/**
|
||||
* @param $aVariableFiles
|
||||
* @param $aImportsPaths
|
||||
*
|
||||
* @return array
|
||||
* @since 3.0.0 N°3593
|
||||
*/
|
||||
public static function GetVariablesFromFile($aVariableFiles){
|
||||
public static function GetVariablesFromFile($aVariableFiles, $aImportsPaths){
|
||||
$aVariablesResults = [];
|
||||
foreach ($aVariableFiles as $sVariableFile)
|
||||
{
|
||||
$sFileContent = file_get_contents(APPROOT.'env-'.utils::GetCurrentEnvironment().'/'.$sVariableFile);
|
||||
$aVariableMatches = [];
|
||||
|
||||
preg_match_all( '/\$(.*?):(.*?);/', $sFileContent,$aVariableMatches);
|
||||
$aVariableMatches = array_combine( $aVariableMatches[1], array_map( function($sVariableValue) { return ltrim($sVariableValue); }, $aVariableMatches[2] ) );
|
||||
$aVariablesResults = array_merge($aVariablesResults, $aVariableMatches);
|
||||
foreach($aImportsPaths as $sPath) {
|
||||
$sFilePath = $sPath.'/'.$sVariableFile;
|
||||
$sImportedFile = realpath($sFilePath);
|
||||
if ($sImportedFile !== false) {
|
||||
$sFileContent = file_get_contents($sImportedFile);
|
||||
$aVariableMatches = [];
|
||||
|
||||
preg_match_all('/\s*\$(.*?)\s*:\s*[\"\']{0,1}(.*?)[\"\']{0,1}\s*[;!]/', $sFileContent, $aVariableMatches);
|
||||
$aVariableMatches = array_combine($aVariableMatches[1], array_map(function ($sVariableValue) {
|
||||
return $sVariableValue;
|
||||
}, $aVariableMatches[2]));
|
||||
$aVariablesResults = array_merge($aVariablesResults, $aVariableMatches);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
array_map( function($sVariableValue) { return ltrim($sVariableValue); }, $aVariablesResults );
|
||||
return $aVariablesResults;
|
||||
|
||||
35
application/themehandlerservice.class.inc.php
Normal file
35
application/themehandlerservice.class.inc.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2020 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class ThemeHandlerService : used to ease testing MFCompiler::CompileThemes class via mocks
|
||||
*
|
||||
* @author Olivier DAIN <olivier.dain@combodo.com>
|
||||
* @since 3.0.0 N°2982
|
||||
*/
|
||||
class ThemeHandlerService
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -195,8 +197,31 @@ class privUITransactionSession
|
||||
class privUITransactionFile
|
||||
{
|
||||
/**
|
||||
* @return int
|
||||
* @throws \SecurityException if no connected user
|
||||
*
|
||||
* @since 2.6.5 2.7.6 3.0.0 N°4289 method creation
|
||||
*/
|
||||
private static function GetCurrentUserId() {
|
||||
$iCurrentUserId = UserRights::GetConnectedUserId();
|
||||
if ('' === $iCurrentUserId) {
|
||||
throw new SecurityException('Cannot creation transaction_id when no user logged');
|
||||
}
|
||||
|
||||
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 +238,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 +281,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 +400,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,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
|
||||
{
|
||||
/**
|
||||
@@ -80,30 +87,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 +114,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 +136,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');
|
||||
|
||||
/**
|
||||
@@ -82,6 +86,12 @@ class UIExtKeyWidget
|
||||
$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();
|
||||
@@ -160,7 +170,8 @@ class UIExtKeyWidget
|
||||
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
||||
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
|
||||
|
||||
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey ibo-input-wrapper ibo-input-select-wrapper--with-buttons\" data-attcode=\"".$this->sAttCode."\" data-validation=\"untouched\">";
|
||||
|
||||
|
||||
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
|
||||
if ($this->bSearchMode) {
|
||||
$sWizHelper = 'null';
|
||||
@@ -183,48 +194,46 @@ class UIExtKeyWidget
|
||||
$bDoSearch = !utils::IsHighCardinality($this->sTargetClass);
|
||||
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
|
||||
|
||||
$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\" 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 (!$oAllowedValues->CountExceeds($iMaxComboLength)) {
|
||||
if (!$bIsAutocomplete) {
|
||||
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
|
||||
$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()) {
|
||||
@@ -235,23 +244,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::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 = 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
|
||||
@@ -295,12 +317,12 @@ EOF
|
||||
EOF
|
||||
);
|
||||
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\">";
|
||||
$sHTMLValue .= " <div class=\"ibo-input-select--action-button ibo-input-select--action-button--clear ibo-is-hidden\" id=\"mini_clear_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Clear();\" data-tooltip-content='".Dict::S('UI:Button:Clear')."''><i class=\"fas fa-times\"></i></div>";
|
||||
$sHTMLValue .= " <div class=\"ibo-input-select--action-button ibo-input-select--action-button--clear ibo-is-hidden\" id=\"mini_clear_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Clear();\" data-tooltip-content='".Dict::S('UI:Button:Clear')."'><i class=\"fas fa-times\"></i></div>";
|
||||
}
|
||||
if ($bCreate && $bExtensions) {
|
||||
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
|
||||
|
||||
$sHTMLValue .= "<div class=\"ibo-input-select--action-button ibo-input-select--action-button--create\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\" data-tooltip-content='".Dict::S('UI:Button:Create')."''><i class=\"fas fa-plus\"></i></div>";
|
||||
$sHTMLValue .= "<div class=\"ibo-input-select--action-button ibo-input-select--action-button--create\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\" data-tooltip-content='".Dict::S('UI:Button:Create')."'><i class=\"fas fa-plus\"></i></div>";
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
if ($('#ajax_{$this->iId}').length == 0)
|
||||
@@ -311,7 +333,7 @@ JS
|
||||
);
|
||||
}
|
||||
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false) {
|
||||
$sHTMLValue .= "<div class=\"ibo-input-select--action-button ibo-input-select--action-button--hierarchy\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\" data-tooltip-content='".Dict::S('UI:Button:SearchInHierarchy')."''><i class=\"fas fa-sitemap\"></i></div>";
|
||||
$sHTMLValue .= "<div class=\"ibo-input-select--action-button ibo-input-select--action-button--hierarchy\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\" data-tooltip-content='".Dict::S('UI:Button:SearchInHierarchy')."'><i class=\"fas fa-sitemap\"></i></div>";
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
if ($('#ac_tree_{$this->iId}').length == 0)
|
||||
@@ -322,7 +344,7 @@ JS
|
||||
);
|
||||
}
|
||||
if ($oAllowedValues->CountExceeds($iMaxComboLength)) {
|
||||
$sHTMLValue .= " <div class=\"ibo-input-select--action-button ibo-input-select--action-button--search\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\" data-tooltip-content='".Dict::S('UI:Button:Search')."''><i class=\"fas fa-search\"></i></div>";
|
||||
$sHTMLValue .= " <div class=\"ibo-input-select--action-button ibo-input-select--action-button--search\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\" data-tooltip-content='".Dict::S('UI:Button:Search')."'><i class=\"fas fa-search\"></i></div>";
|
||||
}
|
||||
$sHTMLValue .= "</div>";
|
||||
$sHTMLValue .= "</div>";
|
||||
@@ -516,7 +538,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";
|
||||
}
|
||||
|
||||
@@ -588,7 +610,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";
|
||||
@@ -673,20 +695,38 @@ JS
|
||||
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
||||
$oPage->add(<<<HTML
|
||||
<form id="fr_{$this->iId}" OnSubmit="return oACWidget_{$this->iId}.DoOk();">
|
||||
<div id="dr_{$this->iId}" style="vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;">
|
||||
<div style="background: #fff; border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
|
||||
<div id="dr_{$this->iId}">
|
||||
<div><p>{$sEmptyList}</p></div>
|
||||
</div>
|
||||
<input type="button" id="btn_cancel_{$this->iId}" value="{$sCancel}" onClick="$('#ac_dlg_{$this->iId}').dialog('close');">
|
||||
<input type="button" id="btn_ok_{$this->iId}_results" value="{$sOK}" onClick="oACWidget_{$this->iId}.DoOk();">
|
||||
<input type="hidden" id="count_{$this->iId}_results" value="0">
|
||||
</form>
|
||||
</div></div>
|
||||
HTML
|
||||
);
|
||||
|
||||
$sDialogTitle = addslashes($sTitle);
|
||||
$sDialogTitleSanitized = 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', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });
|
||||
$('#ac_dlg_{$this->iId}').dialog({
|
||||
width: $(window).width()*0.8,
|
||||
height: $(window).height()*0.8,
|
||||
autoOpen: false,
|
||||
modal: true,
|
||||
title: '$sDialogTitleSanitized',
|
||||
resizeStop: oACWidget_{$this->iId}.UpdateSizes,
|
||||
close: oACWidget_{$this->iId}.OnClose,
|
||||
buttons: [
|
||||
{ text: "$sCancel",
|
||||
class: "ibo-is-alternative ibo-is-neutral",
|
||||
click: function() {
|
||||
$(this).dialog('close');
|
||||
} },
|
||||
{ text: "$sOK",
|
||||
class: "ibo-is-regular ibo-is-primary",
|
||||
click: function() {
|
||||
oACWidget_{$this->iId}.DoOk();
|
||||
} },
|
||||
],
|
||||
});
|
||||
$('#fs_{$this->iId}').on('submit.uiAutocomplete', oACWidget_{$this->iId}.DoSearchObjects);
|
||||
$('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes);
|
||||
JS
|
||||
@@ -782,21 +822,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'] = $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:
|
||||
@@ -863,26 +906,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>');
|
||||
$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));
|
||||
|
||||
$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);");
|
||||
$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("$('#ac_create_{$this->iId} form').removeAttr('onsubmit');");
|
||||
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -953,7 +987,7 @@ JS
|
||||
public function DisplayHierarchy(WebPage $oPage, $sFilter, $currValue, $oObj)
|
||||
{
|
||||
$sDialogTitle = addslashes(Dict::Format('UI:HierarchyOf_Class', MetaModel::GetName($this->sTargetClass)));
|
||||
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="overflow:auto;background:#fff;margin-bottom:5px;" id="tree_'.$this->iId.'">');
|
||||
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="margin-bottom:5px;" id="tree_'.$this->iId.'">');
|
||||
$oPage->add('<table style="width:100%"><tr><td>');
|
||||
if (is_null($sFilter))
|
||||
{
|
||||
@@ -977,8 +1011,8 @@ JS
|
||||
$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("<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_tree_{$this->iId}').dialog('close');\"> ");
|
||||
$oPage->add("<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoHKOk();\">");
|
||||
$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>');
|
||||
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -337,11 +337,10 @@ class UILinksWidgetDirect
|
||||
|
||||
$oPage->add(<<<HTML
|
||||
<form id="ObjectsAddForm_{$this->sInputid}">
|
||||
<div id="SearchResultsToAdd_{$this->sInputid}" style="vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;">
|
||||
<div id="SearchResultsToAdd_{$this->sInputid}">
|
||||
<div style="background: #fff; border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
|
||||
</div>
|
||||
<input type="hidden" id="count_{$this->sInputid}" value="0"/>
|
||||
<button type="button" class="cancel">{$sCancel}</button> <button type="button" class="ok" disabled="disabled">{$sAdd}</button>
|
||||
</form>
|
||||
HTML
|
||||
);
|
||||
|
||||
@@ -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>';
|
||||
|
||||
|
||||
@@ -64,16 +64,42 @@ class UISearchFormForeignKeys
|
||||
<div style="background: #fff; border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
|
||||
</div>
|
||||
<input type="hidden" id="count_{$this->m_iInputId}" value="0"/>
|
||||
<input type="button" value="{$sCancel}" onClick="$('#dlg_{$this->m_iInputId}').dialog('close');">
|
||||
<input id="btn_ok_add_{$this->m_iInputId}" disabled="disabled" type="button" onclick="return oForeignKeysWidget{$this->m_iInputId}.DoAddObjects(this.id);" value="{$sAdd}">
|
||||
</form>
|
||||
HTML
|
||||
);
|
||||
|
||||
$oPage->add_ready_script("$('#dlg_{$this->m_iInputId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oForeignKeysWidget{$this->m_iInputId}.UpdateSizes });");
|
||||
$oPage->add_ready_script("$('#dlg_{$this->m_iInputId}').dialog('option', {title:'$sTitle'});");
|
||||
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_iInputId} form').on('submit.uilinksWizard', oForeignKeysWidget{$this->m_iInputId}.SearchObjectsToAdd);");
|
||||
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_iInputId}').resize(oForeignKeysWidget{$this->m_iInputId}.UpdateSizes);");
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
$('#dlg_{$this->m_iInputId}').dialog({
|
||||
width: $(window).width()*0.8,
|
||||
height: $(window).height()*0.8,
|
||||
autoOpen: false,
|
||||
modal: true,
|
||||
resizeStop: oForeignKeysWidget{$this->m_iInputId}.UpdateSizes,
|
||||
buttons: [
|
||||
{
|
||||
text: Dict.S('UI:Button:Cancel'),
|
||||
class: "cancel ibo-is-alternative ibo-is-neutral",
|
||||
click: function() {
|
||||
$('#dlg_{$this->m_iInputId}').dialog('close');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: Dict.S('UI:Button:Add'),
|
||||
id: 'btn_ok_{$this->m_iInputId}',
|
||||
class: "ok ibo-is-regular ibo-is-primary",
|
||||
click: function() {
|
||||
oForeignKeysWidget{$this->m_iInputId}.DoAddObjects(this.id);
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
});
|
||||
$('#dlg_{$this->m_iInputId}').dialog('option', {title:'$sTitle'});
|
||||
$('#SearchFormToAdd_{$this->m_iInputId} form').on('submit.uilinksWizard', oForeignKeysWidget{$this->m_iInputId}.SearchObjectsToAdd);
|
||||
$('#SearchFormToAdd_{$this->m_iInputId}').resize(oForeignKeysWidget{$this->m_iInputId}.UpdateSizes);
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
public function GetFullListForeignKeysFromSelection($oPage, $oFullSetFilter)
|
||||
|
||||
@@ -270,23 +270,20 @@ $sJSHandlerCode
|
||||
$aFields = array(); // reset
|
||||
foreach(MetaModel::ListAttributeDefs($this->m_sClass) as $sAttCode=>$oAttDef)
|
||||
{
|
||||
$iOptions = (isset($aStates[$this->m_sTargetState]['attribute_list'][$sAttCode])) ? $aStates[$this->m_sTargetState]['attribute_list'][$sAttCode] : 0;
|
||||
if ( ($sStateAttCode != $sAttCode) &&
|
||||
(!$oAttDef->IsExternalField()) &&
|
||||
(($iOptions & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) == 0) &&
|
||||
(!isset($aFieldsDone[$sAttCode])) )
|
||||
|
||||
{
|
||||
// 'State', external fields, read-only and hidden fields
|
||||
$iOptions = (isset($aStates[$this->m_sTargetState]['attribute_list'][$sAttCode])) ? $aStates[$this->m_sTargetState]['attribute_list'][$sAttCode] : 0;
|
||||
if (($sStateAttCode != $sAttCode) &&
|
||||
(!$oAttDef->IsExternalField()) &&
|
||||
($oAttDef->IsWritable()) &&
|
||||
(($iOptions & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) == 0) &&
|
||||
(!isset($aFieldsDone[$sAttCode]))) {
|
||||
// 'State', external fields, read-only (both because of the flags or the attribute type) and hidden fields
|
||||
// and fields that are already listed in the wizard
|
||||
// are removed from the 'optional' part of the wizard
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
$aPrerequisites = $oAttDef->GetPrerequisiteAttributes();
|
||||
$aFields[$sAttCode] = array();
|
||||
foreach($aPrerequisites as $sCode)
|
||||
{
|
||||
if (!isset($aFieldsDone[$sCode]))
|
||||
{
|
||||
foreach ($aPrerequisites as $sCode) {
|
||||
if (!isset($aFieldsDone[$sCode])) {
|
||||
// retain only the dependencies that were not covered
|
||||
// in the 'mandatory' part of the wizard
|
||||
$aFields[$sAttCode][$sCode] = '';
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
* 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;
|
||||
|
||||
@@ -90,12 +92,31 @@ class utils
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_VARIABLE_NAME = 'variable_name';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_RAW_DATA = 'raw_data';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @used-by static::GetMentionedObjectsFromText
|
||||
*/
|
||||
public const ENUM_TEXT_FORMAT_PLAIN = 'text';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @used-by static::GetMentionedObjectsFromText
|
||||
*/
|
||||
public const ENUM_TEXT_FORMAT_HTML = 'html';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @used-by static::GetMentionedObjectsFromText
|
||||
*/
|
||||
public const ENUM_TEXT_FORMAT_MARKDOWN = 'markdown';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
@@ -219,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
|
||||
{
|
||||
@@ -229,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);
|
||||
@@ -520,12 +541,10 @@ class utils
|
||||
{
|
||||
$aSelectedObj = utils::ReadParam('selectObject', array());
|
||||
$sSelectionMode = utils::ReadParam('selectionMode', '');
|
||||
if ($sSelectionMode != '')
|
||||
{
|
||||
if ($sSelectionMode != '') {
|
||||
// Paginated selection
|
||||
$aExceptions = utils::ReadParam('storedSelection', array());
|
||||
if ($sSelectionMode == 'positive')
|
||||
{
|
||||
if ($sSelectionMode == 'positive') {
|
||||
// Only the explicitely listed items are selected
|
||||
$aSelectedObj = $aExceptions;
|
||||
}
|
||||
@@ -638,48 +657,93 @@ class utils
|
||||
|
||||
public static function ReadFromFile($sFileName)
|
||||
{
|
||||
if (!file_exists($sFileName)) return false;
|
||||
if (!file_exists($sFileName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return file_get_contents($sFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert a value expressed in a 'user friendly format'
|
||||
* as in php.ini, e.g. 256k, 2M, 1G etc. Into a number of bytes
|
||||
* @param mixed $value The value as read from php.ini
|
||||
* @return number
|
||||
* @param mixed $value The value as read from php.ini (eg 256k, 2M, 1G etc.)
|
||||
*
|
||||
* @return int conversion to number of bytes
|
||||
*
|
||||
* @since 2.7.5 3.0.0 convert to int numeric values
|
||||
*
|
||||
* @link https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes Shorthand bytes value reference in PHP.net FAQ
|
||||
*/
|
||||
public static function ConvertToBytes( $value )
|
||||
public static function ConvertToBytes($value)
|
||||
{
|
||||
$iReturn = $value;
|
||||
if ( !is_numeric( $value ) )
|
||||
{
|
||||
$iLength = strlen( $value );
|
||||
$iReturn = substr( $value, 0, $iLength - 1 );
|
||||
$sUnit = strtoupper( substr( $value, $iLength - 1 ) );
|
||||
switch ( $sUnit )
|
||||
{
|
||||
case 'G':
|
||||
$iReturn *= 1024;
|
||||
case 'M':
|
||||
$iReturn *= 1024;
|
||||
case 'K':
|
||||
$iReturn *= 1024;
|
||||
}
|
||||
}
|
||||
return $iReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the memory limit is at least what is required
|
||||
*
|
||||
* @param int $memoryLimit set limit in bytes
|
||||
* @param int $requiredLimit required limit in bytes
|
||||
* @return bool
|
||||
*/
|
||||
public static function IsMemoryLimitOk($memoryLimit, $requiredLimit)
|
||||
{
|
||||
return ($memoryLimit >= $requiredLimit) || ($memoryLimit == -1);
|
||||
}
|
||||
if (!is_numeric($value)) {
|
||||
$iLength = strlen($value);
|
||||
$iReturn = substr($value, 0, $iLength - 1);
|
||||
$sUnit = strtoupper(substr($value, $iLength - 1));
|
||||
switch ($sUnit) {
|
||||
case 'G':
|
||||
$iReturn *= 1024;
|
||||
case 'M':
|
||||
$iReturn *= 1024;
|
||||
case 'K':
|
||||
$iReturn *= 1024;
|
||||
}
|
||||
} else {
|
||||
$iReturn = (int)$value;
|
||||
}
|
||||
|
||||
return $iReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the memory limit is at least what is required
|
||||
*
|
||||
* @param int $iMemoryLimit set limit in bytes, use {@link utils::ConvertToBytes()} to convert current php.ini value
|
||||
* @param int $iRequiredLimit required limit in bytes
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function IsMemoryLimitOk($iMemoryLimit, $iRequiredLimit)
|
||||
{
|
||||
if ($iMemoryLimit === -1) {
|
||||
// -1 means : no limit (see https://www.php.net/manual/fr/ini.core.php#ini.memory-limit)
|
||||
return true;
|
||||
}
|
||||
|
||||
return ($iMemoryLimit >= $iRequiredLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set memory_limit to required value
|
||||
*
|
||||
* @param string $sRequiredLimit required limit, for example '512M'
|
||||
*
|
||||
* @return bool|null null if nothing was done, true if modifying memory_limit was successful, false otherwise
|
||||
*
|
||||
* @uses utils::ConvertToBytes()
|
||||
* @uses \ini_get('memory_limit')
|
||||
* @uses \ini_set()
|
||||
* @uses utils::ConvertToBytes()
|
||||
*
|
||||
* @since 2.7.5 N°3806
|
||||
*/
|
||||
public static function SetMinMemoryLimit($sRequiredLimit)
|
||||
{
|
||||
$iRequiredLimit = static::ConvertToBytes($sRequiredLimit);
|
||||
$sMemoryLimit = trim(ini_get('memory_limit'));
|
||||
if (empty($sMemoryLimit)) {
|
||||
// On some PHP installations, memory_limit does not exist as a PHP setting!
|
||||
// (encountered on a 5.2.0 under Windows)
|
||||
// In that case, ini_set will not work
|
||||
return false;
|
||||
}
|
||||
$iMemoryLimit = static::ConvertToBytes($sMemoryLimit);
|
||||
|
||||
if (static::IsMemoryLimitOk($iMemoryLimit, $iRequiredLimit)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ini_set('memory_limit', $iRequiredLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a value into a more friendly format (KB, MB, GB, TB) instead a juste a Bytes amount.
|
||||
@@ -797,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
|
||||
@@ -808,26 +874,27 @@ class utils
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @since 2.7.0 N°2478 always call {@link MetaModel::GetConfig} first, cache is only set when loading from disk
|
||||
* @since 2.7.0 N°2478 this method will now always call {@link MetaModel::GetConfig} first, and cache in this class is only set when loading from disk
|
||||
* @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;
|
||||
}
|
||||
|
||||
@@ -1147,7 +1214,7 @@ class utils
|
||||
*/
|
||||
static function CanLogOff()
|
||||
{
|
||||
return (isset($_SESSION['can_logoff']) ? $_SESSION['can_logoff'] : false);
|
||||
return Session::Get('can_logoff', false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1156,7 +1223,7 @@ class utils
|
||||
*/
|
||||
public static function GetSessionLog()
|
||||
{
|
||||
return print_r($_SESSION, true);
|
||||
return Session::GetLog();
|
||||
}
|
||||
|
||||
static function DebugBacktrace($iLimit = 5)
|
||||
@@ -1249,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() . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1267,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
|
||||
@@ -1291,12 +1362,15 @@ class utils
|
||||
*/
|
||||
public static function GetPopupMenuItems($oPage, $iMenuId, $param, &$aActions, $sTableId = null, $sDataTableId = null)
|
||||
{
|
||||
$oPage->AddUiBlock(static::GetPopupMenuItemsBlock($iMenuId, $param, $aActions, $sDataTableId));
|
||||
$oBlock = new UIContentBlock();
|
||||
static::GetPopupMenuItemsBlock($oBlock, $iMenuId, $param, $aActions, $sDataTableId);
|
||||
$oPage->AddUiBlock($oBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge standard menu items with plugin provided menus items
|
||||
*
|
||||
* @param \Combodo\iTop\Application\UI\Base\iUIBlock $oContainerBlock The UIBlock containing the menu
|
||||
* @param int $iMenuId
|
||||
* @param \DBObjectSet $param
|
||||
* @param array $aActions
|
||||
@@ -1306,9 +1380,8 @@ class utils
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public static function GetPopupMenuItemsBlock($iMenuId, $param, &$aActions, $sDataTableId = null)
|
||||
public static function GetPopupMenuItemsBlock(iUIBlock &$oContainerBlock, $iMenuId, $param, &$aActions, $sDataTableId = null)
|
||||
{
|
||||
$oBlock = new UIContentBlock();
|
||||
// 1st - add standard built-in menu items
|
||||
//
|
||||
switch($iMenuId)
|
||||
@@ -1322,9 +1395,9 @@ class utils
|
||||
$sOQL = addslashes($param->GetFilter()->ToOQL(true));
|
||||
$sFilter = urlencode($param->GetFilter()->serialize());
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
|
||||
$oBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
|
||||
$oBlock->AddJsFileRelPath('js/jquery.dragtable.js');
|
||||
$oBlock->AddCssFileRelPath('css/dragtable.css');
|
||||
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
|
||||
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
|
||||
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
|
||||
|
||||
$aResult = array();
|
||||
if (strlen($sUrl) < SERVER_MAX_URL_LENGTH)
|
||||
@@ -1357,12 +1430,12 @@ class utils
|
||||
$oObj = $param;
|
||||
$sOQL = "SELECT ".get_class($oObj)." WHERE id=".$oObj->GetKey();
|
||||
$sUrl = ApplicationContext::MakeObjectUrl(get_class($oObj), $oObj->GetKey());
|
||||
$oBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
|
||||
$oBlock->AddJsFileRelPath('js/jquery.dragtable.js');
|
||||
$oBlock->AddCssFileRelPath('css/dragtable.css');
|
||||
$oBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
|
||||
$oBlock->AddJsFileRelPath('js/jquery.dragtable.js');
|
||||
$oBlock->AddCssFileRelPath('css/dragtable.css');
|
||||
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
|
||||
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
|
||||
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
|
||||
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
|
||||
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
|
||||
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
|
||||
|
||||
$aResult = array(
|
||||
new SeparatorPopupMenuItem(),
|
||||
@@ -1430,13 +1503,11 @@ class utils
|
||||
|
||||
foreach($oMenuItem->GetLinkedScripts() as $sLinkedScript)
|
||||
{
|
||||
$oBlock->AddJsFileRelPath($sLinkedScript);
|
||||
$oContainerBlock->AddJsFileRelPath($sLinkedScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $oBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1859,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
|
||||
@@ -2567,7 +2638,9 @@ class utils
|
||||
$aDefaultConf = array(
|
||||
'language'=> $sLanguage,
|
||||
'contentsLanguage' => $sLanguage,
|
||||
'extraPlugins' => 'disabler,codesnippet,mentions',
|
||||
'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
|
||||
@@ -2575,24 +2648,36 @@ class utils
|
||||
if(!empty($aMentionsAllowedClasses)) {
|
||||
$aDefaultConf['mentions'] = [];
|
||||
|
||||
foreach($aMentionsAllowedClasses as $sMentionChar => $sMentionClass) {
|
||||
foreach($aMentionsAllowedClasses as $sMentionMarker => $sMentionScope) {
|
||||
// Retrieve mention class
|
||||
// - First test if the conf is a simple Datamodel class
|
||||
if (MetaModel::IsValidClass($sMentionScope)) {
|
||||
$sMentionClass = $sMentionScope;
|
||||
}
|
||||
// - Otherwise it must be a valid OQL
|
||||
else {
|
||||
$oTmpSearch = DBSearch::FromOQL($sMentionScope);
|
||||
$sMentionClass = $oTmpSearch->GetClass();
|
||||
unset($oTmpSearch);
|
||||
}
|
||||
|
||||
// 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&target_class='.$sMentionClass.'&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>
|
||||
HTML;
|
||||
$sMentionOutputTemplate = <<<HTML
|
||||
<a href="$sMentionItemUrl" data-role="object-mention" data-object-class="{class}" data-object-id="{id}">{$sMentionChar}{friendlyname}</a>
|
||||
<a href="$sMentionItemUrl" data-role="object-mention" data-object-class="{class}" data-object-id="{id}">{$sMentionMarker}{friendlyname}</a>
|
||||
HTML;
|
||||
|
||||
$aDefaultConf['mentions'][] = [
|
||||
'feed' => $sMentionEndpoint,
|
||||
'marker' => $sMentionChar,
|
||||
'marker' => $sMentionMarker,
|
||||
'minChars' => MetaModel::GetConfig()->Get('min_autocomplete_chars'),
|
||||
'itemTemplate' => $sMentionItemTemplate,
|
||||
'outputTemplate' => $sMentionOutputTemplate,
|
||||
@@ -2684,15 +2769,13 @@ HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return keyboard shortcuts config as an array
|
||||
*
|
||||
* @return array
|
||||
* @return array All keyboard shortcuts config as an array
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetKeyboardShortcutPref(): array
|
||||
public static function GetAllKeyboardShortcutsPrefs(): array
|
||||
{
|
||||
$aResultPref = [];
|
||||
$aShortcutPrefs = appUserPreferences::GetPref('keyboard_shortcuts', []);
|
||||
@@ -2703,29 +2786,113 @@ HTML;
|
||||
$sTriggeredElement = $cShortcutPlugin::GetShortcutTriggeredElementSelector();
|
||||
foreach ($cShortcutPlugin::GetShortcutKeys() as $aShortcutKey) {
|
||||
$sKey = isset($aShortcutPrefs[$aShortcutKey['id']]) ? $aShortcutPrefs[$aShortcutKey['id']] : $aShortcutKey['key'];
|
||||
$aResultPref[$aShortcutKey['id']] = ['key' => $sKey, 'label' => $aShortcutKey['label'], 'event' => $aShortcutKey['event'], 'triggered_element_selector' => $sTriggeredElement];
|
||||
|
||||
// Format key for display
|
||||
$aKeyParts = explode('+', $sKey);
|
||||
$aFormattedKeyParts = [];
|
||||
foreach ($aKeyParts as $sKeyPart) {
|
||||
$aFormattedKeyParts[] = ucfirst(trim($sKeyPart));
|
||||
}
|
||||
$sFormattedKey = implode(' + ', $aFormattedKeyParts);
|
||||
|
||||
$aResultPref[$aShortcutKey['id']] = [
|
||||
'key' => $sKey,
|
||||
'key_for_display' => $sFormattedKey,
|
||||
'label' => $aShortcutKey['label'],
|
||||
'event' => $aShortcutKey['event'],
|
||||
'triggered_element_selector' => $sTriggeredElement,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aResultPref;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sShortcutId
|
||||
*
|
||||
* @return array The properties of the $sShortcutId shorcut
|
||||
* @throws \Exception
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetKeyboardShortcutPref(string $sShortcutId): array
|
||||
{
|
||||
$aPrefs = static::GetAllKeyboardShortcutsPrefs();
|
||||
if (false === array_key_exists($sShortcutId, $aPrefs)) {
|
||||
throw new Exception('No shortcut identified as "'.$sShortcutId.'" is currently handled by the application.');
|
||||
}
|
||||
|
||||
return $aPrefs[$sShortcutId];
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
// Environment helpers
|
||||
//----------------------------------------------
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @uses GetDeveloperModeParam
|
||||
* @uses ITOP_REVISION constant (check 'svn' value)
|
||||
*/
|
||||
public static function IsDevelopmentEnvironment()
|
||||
{
|
||||
$bIsDevEnvInConfig = static::GetDeveloperModeParam();
|
||||
if ($bIsDevEnvInConfig === true) {
|
||||
return true;
|
||||
}
|
||||
if ($bIsDevEnvInConfig === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!defined('ITOP_REVISION')) {
|
||||
//defensive behaviour: by default we are not in dev environment
|
||||
//can happen even in production (unattended install for example) or with exotic use of iTop
|
||||
return false;
|
||||
}
|
||||
|
||||
return ITOP_REVISION === 'svn';
|
||||
}
|
||||
|
||||
/**
|
||||
* @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()
|
||||
@@ -2830,4 +2997,87 @@ HTML;
|
||||
|
||||
return $sAcronym;
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
// Text manipulation
|
||||
//----------------------------------------------
|
||||
|
||||
/**
|
||||
* Note: Only works for backoffice URLs for now
|
||||
*
|
||||
* @param string $sText Text containing the mentioned objects to be found
|
||||
* @param string $sFormat {@uses static::ENUM_TEXT_FORMAT_HTML, ...}
|
||||
*
|
||||
* @return array Array of object classes / IDs for the ones found in $sText
|
||||
*
|
||||
* [
|
||||
* 'ClassA' => ['ID1', 'ID2', ...],
|
||||
* 'ClassB' => ['ID3'],
|
||||
* ]
|
||||
*
|
||||
* @throws \Exception
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static function GetMentionedObjectsFromText(string $sText, string $sFormat = self::ENUM_TEXT_FORMAT_HTML): array
|
||||
{
|
||||
// First transform text so it can be parsed
|
||||
switch ($sFormat) {
|
||||
case static::ENUM_TEXT_FORMAT_HTML:
|
||||
$sText = static::HtmlToText($sText);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Don't transform it
|
||||
break;
|
||||
}
|
||||
|
||||
// Then parse text to find objects
|
||||
$aMentionedObjects = array();
|
||||
$aMentionMatches = array();
|
||||
|
||||
// Note: As the sanitizer (or CKEditor autocomplete plugin? 🤔) removes data-* attributes from the hyperlink,
|
||||
// - we can't use the following (simpler) regexp that only checks data attributes on hyperlinks, which would have worked for hyperlinks pointing to any GUIs: '/<a\s*([^>]*)data-object-class="([^"]*)"\s*data-object-id="([^"]*)">/i'
|
||||
// - instead we use a regexp to match the following pattern '[Some object label](<APP_ROOT_URL>...&class=<OBJECT_CLASS>&id=<OBJECT_ID>...)' which only works for the backoffice
|
||||
// If we change the sanitizer, we might want to switch to the other regexp as it's universal and easier to read
|
||||
$sAppRootUrlForRegExp = addcslashes(utils::GetAbsoluteUrlAppRoot(), '/&');
|
||||
preg_match_all("/\[([^\]]*)\]\({$sAppRootUrlForRegExp}[^\)]*\&class=([^\)\&]*)\&id=([\d]*)[^\)]*\)/i", $sText, $aMentionMatches);
|
||||
|
||||
foreach ($aMentionMatches[0] as $iMatchIdx => $sCompleteMatch) {
|
||||
$sMatchedClass = $aMentionMatches[2][$iMatchIdx];
|
||||
$sMatchedId = $aMentionMatches[3][$iMatchIdx];
|
||||
|
||||
// Prepare array for matched class if not already present
|
||||
if (!array_key_exists($sMatchedClass, $aMentionedObjects)) {
|
||||
$aMentionedObjects[$sMatchedClass] = array();
|
||||
}
|
||||
// Add matched ID if not already there
|
||||
if (!in_array($sMatchedId, $aMentionedObjects[$sMatchedClass])) {
|
||||
$aMentionedObjects[$sMatchedClass][] = $sMatchedId;
|
||||
}
|
||||
}
|
||||
|
||||
return $aMentionedObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/WebPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/WebPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/WebPage.php, now loadable using autoloader');
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/XMLPage.php
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/XMLPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
*/
|
||||
|
||||
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/application/WebPage/XMLPage.php, now loadable using autoloader');
|
||||
@@ -3,4 +3,9 @@
|
||||
define('APPROOT', dirname(__FILE__).'/');
|
||||
define('APPCONF', APPROOT.'conf/');
|
||||
|
||||
/**
|
||||
* iTop framework Version
|
||||
*/
|
||||
define('ITOP_DESIGN_LATEST_VERSION', '3.0');
|
||||
|
||||
require_once APPROOT.'bootstrap.inc.php';
|
||||
|
||||
@@ -44,14 +44,9 @@ 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)
|
||||
{
|
||||
if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) {
|
||||
require_once APPROOT.'/lib/autoload.php';
|
||||
}
|
||||
|
||||
@@ -60,8 +55,7 @@ if (! isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false
|
||||
//
|
||||
|
||||
// Use 'maintenance' parameter to bypass maintenance mode
|
||||
if (!isset($bBypassMaintenance))
|
||||
{
|
||||
if (!isset($bBypassMaintenance)) {
|
||||
$bBypassMaintenance = isset($_REQUEST['maintenance']) ? boolval($_REQUEST['maintenance']) : false;
|
||||
}
|
||||
|
||||
|
||||
@@ -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": "*",
|
||||
@@ -11,9 +11,9 @@
|
||||
"ext-mysqli": "*",
|
||||
"ext-soap": "*",
|
||||
"combodo/tcpdf": "6.3.5",
|
||||
"nikic/php-parser": "^3.1",
|
||||
"pear/archive_tar": "1.4.10",
|
||||
"pelago/emogrifier": "2.1.0",
|
||||
"nikic/php-parser": "^4.12.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.*",
|
||||
@@ -37,24 +37,21 @@
|
||||
},
|
||||
"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": [
|
||||
"core",
|
||||
"application",
|
||||
"sources/application",
|
||||
"sources/Composer",
|
||||
"sources/Controller",
|
||||
"sources/Form",
|
||||
"sources/Renderer"
|
||||
"sources"
|
||||
],
|
||||
"exclude-from-classmap": [
|
||||
"core/dbobjectsearch.class.php",
|
||||
|
||||
128
composer.lock
generated
128
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "27af144ea2acf2c138f587052a4ceddc",
|
||||
"content-hash": "4c5cdd1e0feb4abaab8f86959ffc7a64",
|
||||
"packages": [
|
||||
{
|
||||
"name": "combodo/tcpdf",
|
||||
@@ -68,24 +68,25 @@
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v3.1.5",
|
||||
"version": "v4.12.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce"
|
||||
"reference": "6608f01670c3cc5079e18c1dab1104e002579143"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce",
|
||||
"reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143",
|
||||
"reference": "6608f01670c3cc5079e18c1dab1104e002579143",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-tokenizer": "*",
|
||||
"php": ">=5.5"
|
||||
"php": ">=7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0|~5.0"
|
||||
"ircmaxell/php-yacc": "^0.0.7",
|
||||
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/php-parse"
|
||||
@@ -93,7 +94,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
"dev-master": "4.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -115,7 +116,11 @@
|
||||
"parser",
|
||||
"php"
|
||||
],
|
||||
"time": "2018-02-28T20:30:58+00:00"
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0"
|
||||
},
|
||||
"time": "2021-07-21T10:44:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
@@ -168,16 +173,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pear/archive_tar",
|
||||
"version": "1.4.10",
|
||||
"version": "1.4.14",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pear/Archive_Tar.git",
|
||||
"reference": "bbb4f10f71a1da2715ec6d9a683f4f23c507a49b"
|
||||
"reference": "4d761c5334c790e45ef3245f0864b8955c562caa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pear/Archive_Tar/zipball/bbb4f10f71a1da2715ec6d9a683f4f23c507a49b",
|
||||
"reference": "bbb4f10f71a1da2715ec6d9a683f4f23c507a49b",
|
||||
"url": "https://api.github.com/repos/pear/Archive_Tar/zipball/4d761c5334c790e45ef3245f0864b8955c562caa",
|
||||
"reference": "4d761c5334c790e45ef3245f0864b8955c562caa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -230,7 +235,21 @@
|
||||
"archive",
|
||||
"tar"
|
||||
],
|
||||
"time": "2020-09-15T14:13:23+00:00"
|
||||
"support": {
|
||||
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Archive_Tar",
|
||||
"source": "https://github.com/pear/Archive_Tar"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/mrook",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/michielrook",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2021-07-20T13:53:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pear/console_getopt",
|
||||
@@ -277,6 +296,10 @@
|
||||
}
|
||||
],
|
||||
"description": "More info available on: http://pear.php.net/package/Console_Getopt",
|
||||
"support": {
|
||||
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Getopt",
|
||||
"source": "https://github.com/pear/Console_Getopt"
|
||||
},
|
||||
"time": "2019-11-20T18:27:48+00:00"
|
||||
},
|
||||
{
|
||||
@@ -321,27 +344,31 @@
|
||||
}
|
||||
],
|
||||
"description": "Minimal set of PEAR core files to be used as composer dependency",
|
||||
"support": {
|
||||
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR",
|
||||
"source": "https://github.com/pear/pear-core-minimal"
|
||||
},
|
||||
"time": "2019-11-19T19:00:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pear/pear_exception",
|
||||
"version": "v1.0.1",
|
||||
"version": "v1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pear/PEAR_Exception.git",
|
||||
"reference": "dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7"
|
||||
"reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7",
|
||||
"reference": "dbb42a5a0e45f3adcf99babfb2a1ba77b8ac36a7",
|
||||
"url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0",
|
||||
"reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=4.4.0"
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "*"
|
||||
"phpunit/phpunit": "<9"
|
||||
},
|
||||
"type": "class",
|
||||
"extra": {
|
||||
@@ -376,38 +403,42 @@
|
||||
"keywords": [
|
||||
"exception"
|
||||
],
|
||||
"time": "2019-12-10T10:24:42+00:00"
|
||||
"support": {
|
||||
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception",
|
||||
"source": "https://github.com/pear/PEAR_Exception"
|
||||
},
|
||||
"time": "2021-03-21T15:43:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pelago/emogrifier",
|
||||
"version": "v2.1.0",
|
||||
"version": "v3.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MyIntervals/emogrifier.git",
|
||||
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f"
|
||||
"reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
|
||||
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
|
||||
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/f6a5c7d44612d86c3901c93f1592f5440e6b2cd8",
|
||||
"reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0",
|
||||
"symfony/css-selector": "^3.4.0 || ^4.0.0"
|
||||
"php": "^5.6 || ~7.0 || ~7.1 || ~7.2 || ~7.3 || ~7.4",
|
||||
"symfony/css-selector": "^2.8 || ^3.0 || ^4.0 || ^5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.2.0",
|
||||
"phpmd/phpmd": "^2.6.0",
|
||||
"phpunit/phpunit": "^4.8.0",
|
||||
"squizlabs/php_codesniffer": "^3.3.2"
|
||||
"friendsofphp/php-cs-fixer": "^2.15.3",
|
||||
"phpmd/phpmd": "^2.7.0",
|
||||
"phpunit/phpunit": "^5.7.27",
|
||||
"squizlabs/php_codesniffer": "^3.5.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1.x-dev"
|
||||
"dev-master": "4.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -420,16 +451,6 @@
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "John Reeve",
|
||||
"email": "jreeve@pelagodesign.com"
|
||||
},
|
||||
{
|
||||
"name": "Cameron Brooks"
|
||||
},
|
||||
{
|
||||
"name": "Jaime Prado"
|
||||
},
|
||||
{
|
||||
"name": "Oliver Klee",
|
||||
"email": "github@oliverklee.de"
|
||||
@@ -438,9 +459,19 @@
|
||||
"name": "Zoli Szabó",
|
||||
"email": "zoli.szabo+github@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "John Reeve",
|
||||
"email": "jreeve@pelagodesign.com"
|
||||
},
|
||||
{
|
||||
"name": "Jake Hotson",
|
||||
"email": "jake@qzdesign.co.uk"
|
||||
},
|
||||
{
|
||||
"name": "Cameron Brooks"
|
||||
},
|
||||
{
|
||||
"name": "Jaime Prado"
|
||||
}
|
||||
],
|
||||
"description": "Converts CSS styles into inline style attributes in your HTML code",
|
||||
@@ -450,7 +481,11 @@
|
||||
"email",
|
||||
"pre-processing"
|
||||
],
|
||||
"time": "2018-12-08T13:55:46+00:00"
|
||||
"support": {
|
||||
"issues": "https://github.com/MyIntervals/emogrifier/issues",
|
||||
"source": "https://github.com/MyIntervals/emogrifier"
|
||||
},
|
||||
"time": "2019-12-26T19:37:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/cache",
|
||||
@@ -2578,7 +2613,7 @@
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=5.6.0",
|
||||
"php": ">=7.1.3 <8.0.0",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
@@ -2589,6 +2624,7 @@
|
||||
},
|
||||
"platform-dev": [],
|
||||
"platform-overrides": {
|
||||
"php": "5.6.0"
|
||||
}
|
||||
"php": "7.1.3"
|
||||
},
|
||||
"plugin-api-version": "2.1.0"
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -366,8 +367,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 +381,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');
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -388,7 +449,12 @@ class AsyncSendEmail extends AsyncTask
|
||||
return "Bug - the email should be sent in synchronous mode";
|
||||
|
||||
case EMAIL_SEND_ERROR:
|
||||
return "Failed: ".implode(', ', $aIssues);
|
||||
if (is_array($aIssues)) {
|
||||
$sMessage = "Sending eMail failed: ".implode(', ', $aIssues);
|
||||
} else {
|
||||
$sMessage = "Sending eMail failed.";
|
||||
}
|
||||
throw new Exception($sMessage);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\FieldBadge\FieldBadgeUIBlockFactory;
|
||||
use Combodo\iTop\Form\Field\LabelField;
|
||||
use Combodo\iTop\Form\Field\TextAreaField;
|
||||
use Combodo\iTop\Form\Form;
|
||||
use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator;
|
||||
use Combodo\iTop\Form\Validator\Validator;
|
||||
use Combodo\iTop\Renderer\BlockRenderer;
|
||||
@@ -317,6 +305,8 @@ abstract class AttributeDefinition
|
||||
*/
|
||||
public function ListDBJoins()
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
|
||||
|
||||
return "";
|
||||
// e.g: return array("Site", "infrid", "name");
|
||||
}
|
||||
@@ -842,9 +832,9 @@ abstract class AttributeDefinition
|
||||
*/
|
||||
public function MakeValue()
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
|
||||
$sComputeFunc = $this->Get("compute_func");
|
||||
if (empty($sComputeFunc))
|
||||
{
|
||||
if (empty($sComputeFunc)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1803,7 +1793,7 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
public function GetImportColumns()
|
||||
{
|
||||
$aColumns = array();
|
||||
$aColumns[$this->GetCode()] = 'TEXT';
|
||||
$aColumns[$this->GetCode()] = 'TEXT'.CMDBSource::GetSqlStringColumnDefinition();
|
||||
|
||||
return $aColumns;
|
||||
}
|
||||
@@ -4133,7 +4123,8 @@ class AttributeText extends AttributeString
|
||||
{
|
||||
// Propose a std link to the object
|
||||
$sClassLabel = MetaModel::GetName($sClass);
|
||||
$sReplacement = "<span class=\"wiki_broken_link\">$sClassLabel:$sName" . (!empty($sLabel) ? " ($sLabel)" : "") . "</span>";
|
||||
$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
|
||||
// Anyhow... there is no easy way to suggest default values based on the given FRIENDLY name
|
||||
@@ -4168,14 +4159,15 @@ class AttributeText extends AttributeString
|
||||
{
|
||||
$sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize);
|
||||
$sValue = self::RenderWikiHtml($sValue);
|
||||
$sValue = nl2br($sValue);
|
||||
|
||||
return "<div $sStyle>".str_replace("\n", "<br>\n", $sValue).'</div>';
|
||||
return "<div $sStyle>$sValue</div>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = self::RenderWikiHtml($sValue, true /* wiki only */);
|
||||
|
||||
return "<div class=\"HTML\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
|
||||
return "<div class=\"HTML ibo-is-html-content\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6003,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;
|
||||
}
|
||||
@@ -6491,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;
|
||||
}
|
||||
@@ -6818,23 +6810,6 @@ class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
return (int)$proposedValue;
|
||||
}
|
||||
|
||||
public function GetPrerequisiteAttributes($sClass = null)
|
||||
{
|
||||
$aAttributes = parent::GetPrerequisiteAttributes($sClass);
|
||||
$oExpression = DBSearch::FromOQL($this->GetValuesDef()->GetFilterExpression())->GetCriteria();
|
||||
foreach ($oExpression->GetParameters('this') as $sAttCode)
|
||||
{
|
||||
// Skip the id as it cannot change anyway
|
||||
if ($sAttCode =='id') continue;
|
||||
|
||||
if (!in_array($sAttCode, $aAttributes))
|
||||
{
|
||||
$aAttributes[] = $sAttCode;
|
||||
}
|
||||
}
|
||||
return $aAttributes;
|
||||
}
|
||||
|
||||
public function GetMaximumComboLength()
|
||||
{
|
||||
return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length'));
|
||||
@@ -8155,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}
|
||||
@@ -8185,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>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -10309,7 +10306,6 @@ abstract class AttributeSet extends AttributeDBFieldVoid
|
||||
} else {
|
||||
$sTooltipContent = <<<HTML
|
||||
<h4>$sLabel</h4>
|
||||
<br>
|
||||
<div>$sDescription</div>
|
||||
HTML;
|
||||
$sTooltipHtmlEnabled = 'true';
|
||||
@@ -10856,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)
|
||||
{
|
||||
@@ -11049,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>';
|
||||
}
|
||||
}
|
||||
@@ -11598,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>';
|
||||
@@ -11804,13 +11800,12 @@ class AttributeFriendlyName extends AttributeDefinition
|
||||
// Code duplicated with AttributeObsolescenceFlag
|
||||
$aAttributes = $this->GetOptional("depends_on", array());
|
||||
$oExpression = $this->GetOQLExpression();
|
||||
foreach ($oExpression->ListRequiredFields() as $sClass => $sAttCode)
|
||||
{
|
||||
if (!in_array($sAttCode, $aAttributes))
|
||||
{
|
||||
foreach ($oExpression->ListRequiredFields() as $sAttCode) {
|
||||
if (!in_array($sAttCode, $aAttributes)) {
|
||||
$aAttributes[] = $sAttCode;
|
||||
}
|
||||
}
|
||||
|
||||
return $aAttributes;
|
||||
}
|
||||
|
||||
@@ -12572,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)
|
||||
@@ -12634,16 +12631,15 @@ class AttributeCustomFields extends AttributeDefinition
|
||||
*/
|
||||
public function GetForm(DBObject $oHostObject, $sFormPrefix = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
$oValue = $oHostObject->Get($this->GetCode());
|
||||
$oHandler = $this->GetHandler($oValue->GetValues());
|
||||
$sFormId = is_null($sFormPrefix) ? 'cf_'.$this->GetCode() : $sFormPrefix.'_cf_'.$this->GetCode();
|
||||
$oHandler->BuildForm($oHostObject, $sFormId);
|
||||
$oForm = $oHandler->GetForm();
|
||||
} catch (Exception $e)
|
||||
{
|
||||
$oForm = new \Combodo\iTop\Form\Form('');
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oForm = new Form('');
|
||||
$oField = new LabelField('');
|
||||
$oField->SetLabel('Custom field error: '.$e->getMessage());
|
||||
$oForm->AddField($oField);
|
||||
|
||||
@@ -452,20 +452,23 @@ class BulkChange
|
||||
foreach ($this->m_aAttList as $sAttCode => $iCol)
|
||||
{
|
||||
// skip the private key, if any
|
||||
if ($sAttCode == 'id') continue;
|
||||
if (($sAttCode == 'id') || ($sAttCode == 'friendlyname')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
|
||||
// skip reconciliation keys
|
||||
if (!$oAttDef->IsWritable() && in_array($sAttCode, $this->m_aReconcilKeys)){ continue; }
|
||||
if (!$oAttDef->IsWritable() && in_array($sAttCode, $this->m_aReconcilKeys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aReasons = array();
|
||||
$iFlags = ($oTargetObj->IsNew())
|
||||
? $oTargetObj->GetInitialStateAttributeFlags($sAttCode, $aReasons)
|
||||
: $oTargetObj->GetAttributeFlags($sAttCode, $aReasons);
|
||||
if ( (($iFlags & OPT_ATT_READONLY) == OPT_ATT_READONLY) && ( $oTargetObj->Get($sAttCode) != $aRowData[$iCol]) )
|
||||
{
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-Readonly', $sAttCode, $oTargetObj->Get($sAttCode), $aRowData[$iCol]);
|
||||
if ( (($iFlags & OPT_ATT_READONLY) == OPT_ATT_READONLY) && ( $oTargetObj->Get($sAttCode) != $aRowData[$iCol]) ) {
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-Readonly', $sAttCode, $oTargetObj->Get($sAttCode), $aRowData[$iCol]);
|
||||
}
|
||||
else if ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
|
||||
{
|
||||
@@ -1180,25 +1183,22 @@ class BulkChange
|
||||
$oPage->add('<p>'.$sCollapsedLabel.' <a class="truncated" onclick="OnTruncatedHistoryToggle(true);">'.$sLinkLabel.'</p>');
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
<<<EOF
|
||||
$('#$sAjaxDivId table.listResults').addClass('truncated');
|
||||
$('#$sAjaxDivId table.listResults tr:last td').addClass('truncated');
|
||||
EOF
|
||||
);
|
||||
|
||||
|
||||
|
||||
$sAppContext = $oAppContext->GetForLink();
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
<<<EOF
|
||||
function OnTruncatedHistoryToggle(bShowAll)
|
||||
{
|
||||
$('#csv_history_reload').html('<img src="../images/indicator.gif"/>');
|
||||
$.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
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -376,10 +376,11 @@ abstract class BulkExport
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 3.0 replaced by GetFormPart
|
||||
* @deprecated 3.0.0 use GetFormPart instead
|
||||
*/
|
||||
public function DisplayFormPart(WebPage $oP, $sPartId)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use GetFormPart instead');
|
||||
$oP->AddSubBlock($this->GetFormPart($oP, $sPartId));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,4 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-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
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Persistent class (internal) cmdbChange
|
||||
*
|
||||
@@ -24,6 +6,7 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Core\CMDBChange\CMDBChangeOrigin;
|
||||
|
||||
/**
|
||||
* A change as requested/validated at once by user, may groups many atomic changes
|
||||
@@ -53,7 +36,7 @@ class CMDBChange extends DBObject
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("allowed_values"=>null, "sql"=>"user_id", "targetclass"=>"User", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("origin", array("allowed_values"=>new ValueSetEnum('interactive,csv-interactive,csv-import.php,webservice-soap,webservice-rest,synchro-data-source,email-processing,custom-extension'), "sql"=>"origin", "default_value"=>"interactive", "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("origin", array("allowed_values"=>new ValueSetEnum(implode(',', [CMDBChangeOrigin::INTERACTIVE, CMDBChangeOrigin::CSV_INTERACTIVE, CMDBChangeOrigin::CSV_IMPORT, CMDBChangeOrigin::WEBSERVICE_SOAP, CMDBChangeOrigin::WEBSERVICE_REST, CMDBChangeOrigin::SYNCHRO_DATA_SOURCE, CMDBChangeOrigin::EMAIL_PROCESSING, CMDBChangeOrigin::CUSTOM_EXTENSION])), "sql"=>"origin", "default_value"=>CMDBChangeOrigin::INTERACTIVE, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -436,28 +436,31 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
|
||||
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
|
||||
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
|
||||
{
|
||||
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode')))
|
||||
{
|
||||
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
|
||||
$sAttName = $oAttDef->GetLabel();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// The attribute was renamed or removed from the object ?
|
||||
$sAttName = $this->Get('attcode');
|
||||
}
|
||||
/** @var \ormDocument $oPrevDoc */
|
||||
$oPrevDoc = $this->Get('prevdata');
|
||||
if ($oPrevDoc->IsEmpty())
|
||||
{
|
||||
if ($oPrevDoc->IsEmpty()) {
|
||||
$sPrevious = '';
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sPrevious);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDocView = $oPrevDoc->GetAsHtml();
|
||||
$sDocView .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_', $oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n";
|
||||
$sDocView .= Dict::Format('UI:DownloadDocument_', $oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata'))."\n";
|
||||
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
|
||||
} else {
|
||||
$sFieldAsHtml = $oPrevDoc->GetAsHTML();
|
||||
|
||||
$sDisplayLabel = Dict::S('UI:OpenDocumentInNewWindow_');
|
||||
$sDisplayUrl = $oPrevDoc->GetDisplayURL(get_class($this), $this->GetKey(), 'prevdata');
|
||||
|
||||
$sDownloadLabel = Dict::Format('UI:DownloadDocument_');
|
||||
$sDownloadUrl = $oPrevDoc->GetDownloadURL(get_class($this), $this->GetKey(), 'prevdata');
|
||||
|
||||
$sDocView = <<<HTML
|
||||
{$sFieldAsHtml}
|
||||
<a href="{$sDisplayUrl}" target="_blank">{$sDisplayLabel}</a> / <a href="{$sDownloadUrl}">{$sDownloadLabel}</a>
|
||||
HTML;
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sDocView);
|
||||
}
|
||||
}
|
||||
@@ -770,18 +773,15 @@ class CMDBChangeOpSetAttributeHTML extends CMDBChangeOpSetAttributeLongText
|
||||
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
|
||||
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
|
||||
{
|
||||
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode')))
|
||||
{
|
||||
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
|
||||
$sAttName = $oAttDef->GetLabel();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// The attribute was renamed or removed from the object ?
|
||||
$sAttName = $this->Get('attcode');
|
||||
}
|
||||
$sTextView = '<div class="history_entry history_entry_truncated"><div class="history_html_content">'.$this->Get('prevdata').'</div></div>';
|
||||
|
||||
$sTextView = $this->Get('prevdata');
|
||||
|
||||
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView);
|
||||
}
|
||||
|
||||
@@ -566,8 +566,8 @@ abstract class CMDBObject extends DBObject
|
||||
*/
|
||||
protected function CheckUserRights($bSkipStrongSecurity, $iActionCode)
|
||||
{
|
||||
if (is_null($bSkipStrongSecurity))
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
|
||||
if (is_null($bSkipStrongSecurity)) {
|
||||
// This is temporary
|
||||
// We have implemented this safety net right before releasing iTop 1.0
|
||||
// and we decided that it was too risky to activate it
|
||||
|
||||
@@ -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,9 +68,6 @@ class CMDBSource
|
||||
*/
|
||||
protected static $m_sDBTlsCA;
|
||||
|
||||
/** @var mysqli $m_oMysqli */
|
||||
protected static $m_oMysqli;
|
||||
|
||||
/**
|
||||
* @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
|
||||
@@ -133,7 +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);
|
||||
$oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
|
||||
DbConnectionWrapper::SetDbConnection($oMysqli);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,12 +147,12 @@ class CMDBSource
|
||||
*
|
||||
* @return \mysqli
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @uses IsOpenedDbConnectionUsingTls when asking for a TLS connection, to check if it was really opened using TLS
|
||||
*/
|
||||
public static function GetMysqliInstance(
|
||||
$sDbHost, $sUser, $sPwd, $sSource = '', $bTlsEnabled = false, $sTlsCa = null, $bCheckTlsAfterConnection = false
|
||||
) {
|
||||
$oMysqli = null;
|
||||
|
||||
$sServer = null;
|
||||
$iPort = null;
|
||||
self::InitServerAndPort($sDbHost, $sServer, $iPort);
|
||||
@@ -182,7 +182,7 @@ class CMDBSource
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser), $e);
|
||||
throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser),$e);
|
||||
}
|
||||
|
||||
if ($bTlsEnabled
|
||||
@@ -251,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)
|
||||
{
|
||||
@@ -344,7 +344,8 @@ 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`"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -360,7 +361,7 @@ class CMDBSource
|
||||
*/
|
||||
public static function GetServerInfo()
|
||||
{
|
||||
return mysqli_get_server_info ( self::$m_oMysqli );
|
||||
return mysqli_get_server_info(DbConnectionWrapper::GetDbConnection());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,9 +392,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;
|
||||
}
|
||||
@@ -444,33 +445,29 @@ class CMDBSource
|
||||
|
||||
/**
|
||||
* @return \mysqli
|
||||
*
|
||||
* @since 2.5.0 N°1260
|
||||
*/
|
||||
public static function GetMysqli()
|
||||
{
|
||||
return self::$m_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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,7 +507,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;
|
||||
}
|
||||
@@ -571,7 +569,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
|
||||
*
|
||||
@@ -585,29 +583,33 @@ class CMDBSource
|
||||
*/
|
||||
private static function DBQuery($sSql)
|
||||
{
|
||||
$sShortSQL = substr(preg_replace("/\s+/", " ", substr($sSql, 0, 180)), 0, 150);
|
||||
if (substr_compare($sShortSQL, "SELECT", 0, strlen("SELECT")) !== 0) {
|
||||
IssueLog::Trace("$sShortSQL", LogChannels::CMDB_SOURCE);
|
||||
}
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -616,23 +618,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];
|
||||
}
|
||||
@@ -652,7 +655,7 @@ class CMDBSource
|
||||
);
|
||||
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
|
||||
|
||||
IssueLog::Error($sMessage, 'DeadLock', $e->getMessage());
|
||||
IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -668,10 +671,14 @@ 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)
|
||||
{
|
||||
if (!$bHasExistingTransactions) {
|
||||
IssueLog::Trace("START TRANSACTION $sCaller", LogChannels::CMDB_SOURCE);
|
||||
self::DBQuery('START TRANSACTION');
|
||||
} else {
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") START TRANSACTION $sCaller", LogChannels::CMDB_SOURCE);
|
||||
}
|
||||
|
||||
self::AddTransactionLevel();
|
||||
@@ -689,18 +696,22 @@ class CMDBSource
|
||||
*/
|
||||
private static function Commit()
|
||||
{
|
||||
if (!self::IsInsideTransaction())
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if (!self::IsInsideTransaction()) {
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction COMMIT $sCaller", LogChannels::CMDB_SOURCE);
|
||||
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
|
||||
}
|
||||
|
||||
self::RemoveLastTransactionLevel();
|
||||
|
||||
if (self::IsInsideTransaction())
|
||||
{
|
||||
if (self::IsInsideTransaction()) {
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") COMMIT $sCaller", LogChannels::CMDB_SOURCE);
|
||||
|
||||
return;
|
||||
}
|
||||
IssueLog::Trace("COMMIT $sCaller", LogChannels::CMDB_SOURCE);
|
||||
self::DBQuery('COMMIT');
|
||||
}
|
||||
|
||||
@@ -719,17 +730,21 @@ class CMDBSource
|
||||
*/
|
||||
private static function Rollback()
|
||||
{
|
||||
if (!self::IsInsideTransaction())
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if (!self::IsInsideTransaction()) {
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction ROLLBACK $sCaller", LogChannels::CMDB_SOURCE);
|
||||
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
|
||||
}
|
||||
self::RemoveLastTransactionLevel();
|
||||
if (self::IsInsideTransaction())
|
||||
{
|
||||
if (self::IsInsideTransaction()) {
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") ROLLBACK $sCaller", LogChannels::CMDB_SOURCE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IssueLog::Trace("ROLLBACK $sCaller", LogChannels::CMDB_SOURCE);
|
||||
self::DBQuery('ROLLBACK');
|
||||
}
|
||||
|
||||
@@ -779,9 +794,21 @@ class CMDBSource
|
||||
self::$m_iTransactionLevel = 0;
|
||||
}
|
||||
|
||||
public static function IsDeadlockException(Exception $e)
|
||||
{
|
||||
while ($e instanceof Exception) {
|
||||
if (($e instanceof MySQLException) && ($e->getCode() == 1213)) {
|
||||
return true;
|
||||
}
|
||||
$e = $e->getPrevious();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static function GetInsertId()
|
||||
{
|
||||
$iRes = self::$m_oMysqli->insert_id;
|
||||
$iRes = DbConnectionWrapper::GetDbConnection()->insert_id;
|
||||
if (is_null($iRes))
|
||||
{
|
||||
return 0;
|
||||
@@ -813,26 +840,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::$m_oMysqli->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));
|
||||
}
|
||||
|
||||
@@ -853,17 +882,19 @@ class CMDBSource
|
||||
|
||||
/**
|
||||
* @param string $sSql
|
||||
* @param int $iMode
|
||||
*
|
||||
* @return array
|
||||
* @throws \MySQLException if query cannot be processed
|
||||
*/
|
||||
public static function QueryToArray($sSql)
|
||||
public static function QueryToArray($sSql, $iMode = MYSQLI_BOTH)
|
||||
{
|
||||
$aData = array();
|
||||
$oKPI = new ExecutionKPI();
|
||||
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)
|
||||
{
|
||||
@@ -876,7 +907,7 @@ class CMDBSource
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
|
||||
}
|
||||
|
||||
while ($aRow = $oResult->fetch_array(MYSQLI_BOTH))
|
||||
while ($aRow = $oResult->fetch_array($iMode))
|
||||
{
|
||||
$aData[] = $aRow;
|
||||
}
|
||||
@@ -913,7 +944,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)
|
||||
{
|
||||
@@ -945,7 +977,8 @@ class CMDBSource
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -970,7 +1003,7 @@ class CMDBSource
|
||||
|
||||
public static function AffectedRows()
|
||||
{
|
||||
return self::$m_oMysqli->affected_rows;
|
||||
return DbConnectionWrapper::GetDbConnection()->affected_rows;
|
||||
}
|
||||
|
||||
public static function FetchArray($oResult)
|
||||
@@ -1088,7 +1121,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
|
||||
@@ -1433,7 +1466,8 @@ class CMDBSource
|
||||
$sSql = "SELECT * FROM `$sTable`";
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -107,7 +107,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' => '',
|
||||
@@ -1062,72 +1070,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 +1165,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 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 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',
|
||||
@@ -1229,6 +1285,38 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'activity_panel.prefilter_only_current_log' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether the "Logs" filter should only be set to the log from the current tab or to all of them.',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'activity_panel.prefilter_state_changes_on_logs' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether the "State changes" filter should be set by default on all log tabs.',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'activity_panel.prefilter_edits_on_logs' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether the "Edits" filter should be set by default on all log tabs.',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'activity_panel.hide_avatars' => [
|
||||
'type' => 'array',
|
||||
'description' => 'GUIs IDs ("backoffice", "itop-portal" for the standard end-users portal, ...) in which the user avatars should be hidden and replaced if possible by their initials (eg. array("backoffice", "itop-portal", "another-portal-id"))',
|
||||
'default' => [],
|
||||
'value' => [],
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'activity_panel.show_author_name_below_entries' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to show the author friendlyname next to the date on the last entry.',
|
||||
@@ -1255,7 +1343,7 @@ 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 (eg. "@" => "Person")',
|
||||
'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' => [
|
||||
'@' => 'Person',
|
||||
],
|
||||
@@ -1279,6 +1367,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'user_preferences.allow_backoffice_theme_override' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether the user can choose which theme to use in the backoffice. If set to false, all users will have the theme defined in "backoffice_default_theme"',
|
||||
'default' => true,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'secure_rest_services' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'When set to true, only the users with the profile "REST Services User" are allowed to use the REST web services.',
|
||||
@@ -1375,6 +1471,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)',
|
||||
@@ -1383,6 +1495,30 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'developer_mode.enabled' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true then unlocks dev env functionalities, see \utils::IsDevelopmentEnvironment',
|
||||
'default' => null,
|
||||
'value' => null,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'theme.enable_precompilation' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If false, theme compilation will not use any precompiled file setup optimization.)',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'theme.force_signature_check_at_runtime' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, checking that the current theme signature matches the compiled file -to recompile it if necessary- will be done for each page. This can slow the application, only use it if you are experiencing issues while customizing a theme.)',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
];
|
||||
|
||||
public function IsProperty($sPropCode)
|
||||
|
||||
@@ -3,4 +3,6 @@
|
||||
* This file is only here for compatibility issues. Will be removed in iTop 3.1.0 (N°3664)
|
||||
*
|
||||
* @deprecated 3.0.0 N°3663 Exception classes were moved to `/application/exceptions`, use autoloader instead of require !
|
||||
*/
|
||||
*/
|
||||
require_once '../approot.inc.php';
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('Classes were moved to /application/exceptions');
|
||||
|
||||
@@ -185,13 +185,13 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$oFieldSetLocalization->AddSubBlock(new Html('</br>'));
|
||||
|
||||
$oSelect = SelectUIBlockFactory::MakeForSelectWithLabel("charset", Dict::S('UI:CSVImport:Encoding'));
|
||||
$oSelect->SetBeforeInput(true);
|
||||
$oSelect->SetIsLabelBefore(true);
|
||||
$oFieldSetLocalization->AddSubBlock($oSelect);
|
||||
|
||||
$aPossibleEncodings = utils::GetPossibleEncodings(MetaModel::GetConfig()->GetCSVImportCharsets());
|
||||
$sDefaultEncoding = MetaModel::GetConfig()->Get('csv_file_default_charset');
|
||||
foreach ($aPossibleEncodings as $sIconvCode => $sDisplayName) {
|
||||
$oSelect->GetInput()->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($sIconvCode, $sDisplayName, ($sIconvCode == $sDefaultEncoding)));
|
||||
$oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($sIconvCode, $sDisplayName, ($sIconvCode == $sDefaultEncoding)));
|
||||
}
|
||||
//markup
|
||||
$oFieldSetMarkup = FieldSetUIBlockFactory::MakeStandard(Dict::S('Core:BulkExport:TextFormat'));
|
||||
@@ -211,15 +211,15 @@ class CSVBulkExport extends TabularBulkExport
|
||||
|
||||
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
|
||||
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
|
||||
$oRadioDefault = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample), "csv_custom_date_time_format", "default", "csv_date_time_format_default", "radio");
|
||||
$oRadioDefault = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample), "csv_date_format_radio", "default", "csv_date_time_format_default", "radio");
|
||||
$oRadioDefault->GetInput()->SetIsChecked(($sDateTimeFormat == (string)AttributeDateTime::GetFormat()));
|
||||
$oRadioDefault->SetBeforeInput(false);
|
||||
$oRadioDefault->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
$oFieldSetDate->AddSubBlock($oRadioDefault);
|
||||
$oFieldSetDate->AddSubBlock(new Html('</br>'));
|
||||
|
||||
$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), "csv_custom_date_time_format", "custom", "csv_date_time_format_custom", "radio");
|
||||
$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->GetInput()->SetIsChecked($sDateTimeFormat !== (string)AttributeDateTime::GetFormat());
|
||||
$oRadioCustom->SetBeforeInput(false);
|
||||
$oRadioCustom->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
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.
|
||||
@@ -803,7 +792,7 @@ abstract class DBObject implements iDisplay
|
||||
//
|
||||
/** @var \AttributeExternalField $oAttDef */
|
||||
$sExtKeyAttCode = $oAttDef->GetKeyAttCode();
|
||||
|
||||
|
||||
if (($iRemote = $this->Get($sExtKeyAttCode)) && ($iRemote > 0)) // Objects in memory have negative IDs
|
||||
{
|
||||
$oExtKeyAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyAttCode);
|
||||
@@ -959,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);
|
||||
}
|
||||
@@ -1290,13 +1278,20 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
// If the object if not issued from a query but constructed programmatically
|
||||
// the label may be empty. In this case run a query to get the object's friendly name
|
||||
$oTmpObj = MetaModel::GetObject($sObjClass, $sObjKey, false);
|
||||
if (is_object($oTmpObj))
|
||||
{
|
||||
$sObjOql = 'SELECT '.$sObjClass.' WHERE id='.$sObjKey;
|
||||
$oObjFilter = DBSearch::FromOQL($sObjOql);
|
||||
$oSet = new DBObjectSet($oObjFilter);
|
||||
|
||||
// we will only use id and friendlyname, so let's remove other fields !
|
||||
$aAttToLoad = [
|
||||
$sObjClass => [],
|
||||
];
|
||||
$oSet->OptimizeColumnLoad($aAttToLoad);
|
||||
|
||||
$oTmpObj = $oSet->Fetch();
|
||||
if (is_object($oTmpObj)) {
|
||||
$sHtmlLabel = $oTmpObj->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// May happen in case the target object is not in the list of allowed values for this attribute
|
||||
$sHtmlLabel = "<em>$sObjClass::$sObjKey</em>";
|
||||
}
|
||||
@@ -1475,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1530,31 +1579,40 @@ abstract class DBObject implements iDisplay
|
||||
/**
|
||||
* Helper to get the friendly name in a safe manner for displaying inside a web page
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @internal
|
||||
* @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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the friendly name
|
||||
*
|
||||
* This is not safe for displaying inside a web page since the " < > characters are not escaped.
|
||||
* In example, the name may contain some XSS script instructions.
|
||||
* Helper to get the friendly name
|
||||
*
|
||||
* This is not safe for displaying inside a web page since the " < > characters are not escaped.
|
||||
* In example, the name may contain some XSS script instructions.
|
||||
* Use this function only for internal computations or for an output to a non-HTML destination
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @internal
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1870,9 +1928,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
/** @var \AttributeExternalKey $oAtt */
|
||||
$sTargetClass = $oAtt->GetTargetClass();
|
||||
$oTargetObj = MetaModel::GetObject($sTargetClass, $toCheck, false /*must be found*/, true /*allow all data*/);
|
||||
if (is_null($oTargetObj))
|
||||
{
|
||||
if (false === MetaModel::IsObjectInDB($sTargetClass, $toCheck)) {
|
||||
return "Target object not found ($sTargetClass::$toCheck)";
|
||||
}
|
||||
}
|
||||
@@ -2140,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)) {
|
||||
@@ -2434,11 +2485,10 @@ abstract class DBObject implements iDisplay
|
||||
* Reset during {@see DBObject::DBUpdate()}
|
||||
* @throws Exception
|
||||
* @uses m_aCurrValues
|
||||
*/
|
||||
*/
|
||||
public function ListChanges()
|
||||
{
|
||||
if ($this->m_bIsInDB)
|
||||
{
|
||||
if ($this->m_bIsInDB) {
|
||||
return $this->ListChangedValues($this->m_aCurrValues);
|
||||
}
|
||||
|
||||
@@ -2784,50 +2834,75 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
$iTransactionRetry = 1;
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
try
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
}
|
||||
|
||||
// First query built upon on the root class, because the ID must be created first
|
||||
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
|
||||
|
||||
// Then do the leaf class, if different from the root class
|
||||
if ($sClass != $sRootClass)
|
||||
{
|
||||
$this->DBInsertSingleTable($sClass);
|
||||
}
|
||||
|
||||
// Then do the other classes
|
||||
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
|
||||
{
|
||||
if ($sParentClass == $sRootClass)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$this->DBInsertSingleTable($sParentClass);
|
||||
}
|
||||
|
||||
$this->OnObjectKeyReady();
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
while ($iTransactionRetry > 0) {
|
||||
try {
|
||||
$iTransactionRetry--;
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
}
|
||||
|
||||
// First query built upon on the root class, because the ID must be created first
|
||||
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
|
||||
|
||||
// Then do the leaf class, if different from the root class
|
||||
if ($sClass != $sRootClass) {
|
||||
$this->DBInsertSingleTable($sClass);
|
||||
}
|
||||
|
||||
// Then do the other classes
|
||||
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) {
|
||||
if ($sParentClass == $sRootClass) {
|
||||
continue;
|
||||
}
|
||||
$this->DBInsertSingleTable($sParentClass);
|
||||
}
|
||||
|
||||
$this->OnObjectKeyReady();
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
// Write object creation history within the transaction
|
||||
$this->RecordObjCreation();
|
||||
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Insert TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Error("Insert Deadlock TRANSACTION prevention failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->m_bIsInDB = true;
|
||||
@@ -2860,8 +2935,6 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
$this->RecordObjCreation();
|
||||
|
||||
return $this->m_iKey;
|
||||
}
|
||||
|
||||
@@ -3136,52 +3209,35 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
// 2 - Find mentioned objects
|
||||
$aMentionedObjects = array();
|
||||
foreach($aUpdatedLogAttCodes as $sAttCode)
|
||||
{
|
||||
foreach ($aUpdatedLogAttCodes as $sAttCode) {
|
||||
/** @var \ormCaseLog $oUpdatedCaseLog */
|
||||
$oUpdatedCaseLog = $this->Get($sAttCode);
|
||||
$aMentionMatches = array();
|
||||
// Note: As the sanitizer (or CKEditor autocomplete plugin? 🤔) removes data-* attributes from the hyperlink, we can't use the following (simpler) regexp: '/<a\s*([^>]*)data-object-class="([^"]*)"\s*data-object-id="([^"]*)">/i'
|
||||
// If we change the sanitizer, we might want to use this regexp as it's easier to read
|
||||
// Note 2: This is only working for backoffice URLs...
|
||||
$sAppRootUrlForRegExp = addcslashes(utils::GetAbsoluteUrlAppRoot(), '/&');
|
||||
preg_match_all("/\[([^\]]*)\]\({$sAppRootUrlForRegExp}[^\)]*\&class=([^\)\&]*)\&id=([\d]*)[^\)]*\)/i", $oUpdatedCaseLog->GetModifiedEntry(), $aMentionMatches);
|
||||
|
||||
foreach($aMentionMatches[0] as $iMatchIdx => $sCompleteMatch)
|
||||
{
|
||||
$sMatchedClass = $aMentionMatches[2][$iMatchIdx];
|
||||
$sMatchedId = $aMentionMatches[3][$iMatchIdx];
|
||||
|
||||
// Prepare array for matched class if not already present
|
||||
if(!array_key_exists($sMatchedClass, $aMentionedObjects))
|
||||
{
|
||||
$aMentionedObjects[$sMatchedClass] = array();
|
||||
}
|
||||
// Add matched ID if not already there
|
||||
if(!in_array($sMatchedId, $aMentionedObjects[$sMatchedClass]))
|
||||
{
|
||||
$aMentionedObjects[$sMatchedClass][] = $sMatchedId;
|
||||
}
|
||||
}
|
||||
$aMentionedObjects = array_merge_recursive($aMentionedObjects, utils::GetMentionedObjectsFromText($oUpdatedCaseLog->GetModifiedEntry()));
|
||||
}
|
||||
// 3 - Trigger for those objects
|
||||
foreach($aMentionedObjects as $sMentionedClass => $aMentionedIds)
|
||||
{
|
||||
foreach($aMentionedIds as $sMentionedId)
|
||||
{
|
||||
// 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);
|
||||
$aTriggerArgs = $this->ToArgs('this') + array('mentioned->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);
|
||||
$aParams = array('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)"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
$oTrigger->DoActivate($aTriggerArgs);
|
||||
/** @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3210,9 +3266,11 @@ abstract class DBObject implements iDisplay
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
$iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iIsTransactionRetryCount;
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
while ($iTransactionRetry > 0)
|
||||
{
|
||||
@@ -3228,7 +3286,7 @@ abstract class DBObject implements iDisplay
|
||||
// Update the left & right indexes for each hierarchical key
|
||||
foreach ($aHierarchicalKeys as $sAttCode => $oAttDef)
|
||||
{
|
||||
$sTable = $sTable = MetaModel::DBGetTable(get_class($this), $sAttCode);
|
||||
$sTable = MetaModel::DBGetTable(get_class($this), $sAttCode);
|
||||
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` AS `right`, `".$oAttDef->GetSQLLeft()."` AS `left` FROM `$sTable` WHERE id=".$this->GetKey();
|
||||
$aRes = CMDBSource::QueryToArray($sSQL);
|
||||
$iMyLeft = $aRes[0]['left'];
|
||||
@@ -3236,8 +3294,7 @@ abstract class DBObject implements iDisplay
|
||||
$iDelta = $iMyRight - $iMyLeft + 1;
|
||||
MetaModel::HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable);
|
||||
|
||||
if ($aDBChanges[$sAttCode] == 0)
|
||||
{
|
||||
if ($aDBChanges[$sAttCode] == 0) {
|
||||
// No new parent, insert completely at the right of the tree
|
||||
$sSQL = "SELECT max(`".$oAttDef->GetSQLRight()."`) AS max FROM `$sTable`";
|
||||
$aRes = CMDBSource::QueryToArray($sSQL);
|
||||
@@ -3280,38 +3337,29 @@ abstract class DBObject implements iDisplay
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
|
||||
// new values are already in the object (call {@see DBObject::Get()} to get them)
|
||||
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
|
||||
$this->m_bDirty = false;
|
||||
$this->m_aTouchedAtt = array();
|
||||
$this->m_aModifiedAtt = array();
|
||||
|
||||
if (count($aChanges) != 0)
|
||||
{
|
||||
if (count($aChanges) != 0) {
|
||||
$this->RecordAttChanges($aChanges, $aOriginalValues);
|
||||
}
|
||||
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if ($e->getCode() == 1213)
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction
|
||||
IssueLog::Error($e->getMessage());
|
||||
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Update TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry));
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -3325,10 +3373,11 @@ abstract class DBObject implements iDisplay
|
||||
'id' => $this->GetKey(),
|
||||
'class' => get_class($this),
|
||||
'issues' => $aErrors
|
||||
));
|
||||
), $e);
|
||||
}
|
||||
catch (CoreCannotSaveObjectException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
@@ -3337,6 +3386,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
@@ -3350,13 +3400,20 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
|
||||
// new values are already in the object (call {@see DBObject::Get()} to get them)
|
||||
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
|
||||
$this->m_bDirty = false;
|
||||
$this->m_aTouchedAtt = array();
|
||||
$this->m_aModifiedAtt = array();
|
||||
|
||||
try {
|
||||
// - TriggerOnObjectUpdate
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectUpdate AS t WHERE t.target_class IN (:class_list)"),
|
||||
array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \Trigger $oTrigger */
|
||||
/** @var \TriggerOnObjectUpdate $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
@@ -3539,9 +3596,11 @@ abstract class DBObject implements iDisplay
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
$iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iIsTransactionRetryCount;
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
while ($iTransactionRetry > 0)
|
||||
{
|
||||
@@ -3564,18 +3623,18 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if ($e->getCode() == 1213)
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Delete TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry));
|
||||
usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -3728,16 +3787,22 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
/**
|
||||
* Apply a stimulus (workflow)
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $sStimulusCode
|
||||
* @param bool $bDoNotWrite
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $sStimulusCode
|
||||
* @param bool $bDoNotWrite if true we won't save the object !
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
*
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
*
|
||||
* @uses \AttributeStopWatch::Start
|
||||
* @uses \AttributeStopWatch::Stop
|
||||
* @uses \DBObject::DBWrite
|
||||
* @uses \TriggerOnStateLeave::DoActivate
|
||||
* @uses \TriggerOnStateEnter::DoActivate
|
||||
*/
|
||||
public function ApplyStimulus($sStimulusCode, $bDoNotWrite = false)
|
||||
{
|
||||
@@ -3852,17 +3917,14 @@ abstract class DBObject implements iDisplay
|
||||
if (in_array($sNewState, $oAttDef->GetStates()))
|
||||
{
|
||||
$oSW->Start($this, $oAttDef);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$oSW->Stop($this, $oAttDef);
|
||||
}
|
||||
$this->Set($sAttCode, $oSW);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bDoNotWrite)
|
||||
{
|
||||
if (!$bDoNotWrite) {
|
||||
$this->DBWrite();
|
||||
}
|
||||
|
||||
@@ -3870,30 +3932,26 @@ abstract class DBObject implements iDisplay
|
||||
$aParams = array(
|
||||
'class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL),
|
||||
'previous_state' => $sPreviousState,
|
||||
'new_state' => $sNewState);
|
||||
'new_state' => $sNewState,
|
||||
);
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class IN (:class_list) AND t.state=:previous_state"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
try
|
||||
{
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnStateLeave $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
catch (Exception $e) {
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class IN (:class_list) AND t.state=:new_state"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
try{
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnStateEnter $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
catch (Exception $e) {
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
@@ -4003,6 +4061,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
|
||||
*
|
||||
@@ -4021,40 +4131,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()."'");
|
||||
}
|
||||
}
|
||||
@@ -4573,8 +4678,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;
|
||||
}
|
||||
|
||||
@@ -4718,8 +4824,10 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function GetMasterReplica()
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
|
||||
$sOQL = "SELECT replica,datasource FROM SynchroReplica AS replica JOIN SynchroDataSource AS datasource ON replica.sync_source_id=datasource.id WHERE replica.dest_class = :dest_class AND replica.dest_id = :dest_id";
|
||||
$oReplicaSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array() /* order by*/, array('dest_class' => get_class($this), 'dest_id' => $this->GetKey()));
|
||||
|
||||
return $oReplicaSet;
|
||||
}
|
||||
|
||||
@@ -5552,27 +5660,31 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
$aFields = $oExpression->ListRequiredFields();
|
||||
$aArgs = array();
|
||||
foreach ($aFields as $sFieldDesc)
|
||||
{
|
||||
foreach ($aFields as $sFieldDesc) {
|
||||
$aFieldParts = explode('.', $sFieldDesc);
|
||||
if (count($aFieldParts) == 2)
|
||||
{
|
||||
if (count($aFieldParts) == 2) {
|
||||
$sClass = $aFieldParts[0];
|
||||
$sAttCode = $aFieldParts[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sClass = get_class($this);
|
||||
$sAttCode = $aFieldParts[0];
|
||||
}
|
||||
if (get_class($this) != $sClass) continue;
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode)) continue;
|
||||
if (get_class($this) != $sClass) {
|
||||
continue;
|
||||
}
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$aSQLValues = $oAttDef->GetSQLValues($this->m_aCurrValues[$sAttCode]);
|
||||
$value = reset($aSQLValues);
|
||||
if ($oAttDef->IsNull($value)) {
|
||||
return '';
|
||||
}
|
||||
$aArgs[$sFieldDesc] = $value;
|
||||
}
|
||||
|
||||
return $oExpression->Evaluate($aArgs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,22 +63,23 @@ else
|
||||
|
||||
abstract class DBSearch
|
||||
{
|
||||
/** @internal */
|
||||
/** @internal */
|
||||
const JOIN_POINTING_TO = 0;
|
||||
/** @internal */
|
||||
/** @internal */
|
||||
const JOIN_REFERENCED_BY = 1;
|
||||
|
||||
protected $m_bNoContextParameters = false;
|
||||
/** @var array For {@see iQueryModifier} impl */
|
||||
protected $m_aModifierProperties = array();
|
||||
protected $m_bArchiveMode = false;
|
||||
protected $m_bShowObsoleteData = true;
|
||||
|
||||
/**
|
||||
* DBSearch constructor.
|
||||
*
|
||||
* @api
|
||||
* @see DBSearch::FromOQL()
|
||||
*/
|
||||
/**
|
||||
* DBSearch constructor.
|
||||
*
|
||||
* @api
|
||||
* @see DBSearch::FromOQL()
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->Init();
|
||||
@@ -280,62 +281,97 @@ abstract class DBSearch
|
||||
|
||||
abstract public function TranslateConditions($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return mixed
|
||||
*/
|
||||
/**
|
||||
* @internal
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function IsAny();
|
||||
|
||||
/**
|
||||
* @deprecated use ToOQL() instead
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function Describe(){return 'deprecated - use ToOQL() instead';}
|
||||
/**
|
||||
* @deprecated use ToOQL() instead
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function DescribeConditionPointTo($sExtKeyAttCode, $aPointingTo){return 'deprecated - use ToOQL() instead';}
|
||||
/**
|
||||
* @deprecated use ToOQL() instead
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode){return 'deprecated - use ToOQL() instead';}
|
||||
/**
|
||||
* @deprecated use ToOQL() instead
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function DescribeConditionRelTo($aRelInfo){return 'deprecated - use ToOQL() instead';}
|
||||
/**
|
||||
* @deprecated use ToOQL() instead
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function DescribeConditions(){return 'deprecated - use ToOQL() instead';}
|
||||
/**
|
||||
* @deprecated use ToOQL() instead
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function __DescribeHTML(){return 'deprecated - use ToOQL() instead';}
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated use ToOQL() instead
|
||||
* @return string
|
||||
*/
|
||||
public function Describe()
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return mixed
|
||||
*/
|
||||
return 'deprecated - use ToOQL() instead';
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated use ToOQL() instead
|
||||
* @return string
|
||||
*/
|
||||
public function DescribeConditionPointTo($sExtKeyAttCode, $aPointingTo)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
|
||||
|
||||
return 'deprecated - use ToOQL() instead';
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated use ToOQL() instead
|
||||
* @return string
|
||||
*/
|
||||
public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
|
||||
|
||||
return 'deprecated - use ToOQL() instead';
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated use ToOQL() instead
|
||||
* @return string
|
||||
*/
|
||||
public function DescribeConditionRelTo($aRelInfo)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
|
||||
|
||||
return 'deprecated - use ToOQL() instead';
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated use ToOQL() instead
|
||||
* @return string
|
||||
*/
|
||||
public function DescribeConditions()
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
|
||||
|
||||
return 'deprecated - use ToOQL() instead';
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated use ToOQL() instead
|
||||
* @return string
|
||||
*/
|
||||
public function __DescribeHTML()
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ToOQL() instead');
|
||||
|
||||
return 'deprecated - use ToOQL() instead';
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function ResetCondition();
|
||||
|
||||
/**
|
||||
* add $oExpression as a OR
|
||||
*
|
||||
* @api
|
||||
* @see DBSearch::AddConditionExpression()
|
||||
*
|
||||
* @param Expression $oExpression
|
||||
/**
|
||||
* add $oExpression as a OR
|
||||
*
|
||||
* @api
|
||||
* @see DBSearch::AddConditionExpression()
|
||||
*
|
||||
* @param Expression $oExpression
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -1004,10 +1040,8 @@ abstract class DBSearch
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a SQL query from the current search
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* Generate a SQL query from the current search
|
||||
*
|
||||
* @param array $aOrderBy Array of '[<classalias>.]attcode' => bAscending
|
||||
* @param array $aArgs
|
||||
* @param null $aAttToLoad
|
||||
@@ -1015,12 +1049,16 @@ abstract class DBSearch
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @param bool $bGetCount
|
||||
* @param bool $bBeautifulSQL
|
||||
*
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
* @throws MissingQueryArgument
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false)
|
||||
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulSQL = true)
|
||||
{
|
||||
// Check the order by specification, and prefix with the class alias
|
||||
// and make sure that the ordering columns are going to be selected
|
||||
@@ -1085,8 +1123,7 @@ abstract class DBSearch
|
||||
}
|
||||
try
|
||||
{
|
||||
// $bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
|
||||
$sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, true);
|
||||
$sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, $bBeautifulSQL);
|
||||
if ($sClassAlias == '_itop_')
|
||||
{
|
||||
IssueLog::Info('SQL Query (_itop_): '.$sRes);
|
||||
|
||||
@@ -242,7 +242,7 @@ class DeletionPlan
|
||||
|
||||
public function SetDeletionIssues($oObject, $aIssues, $bSecurityIssue)
|
||||
{
|
||||
if (count($aIssues) > 0)
|
||||
if (count($aIssues ?? []) > 0)
|
||||
{
|
||||
$sClass = get_class($oObject);
|
||||
$iId = $oObject->GetKey();
|
||||
|
||||
@@ -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;
|
||||
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\Panel;
|
||||
use Combodo\iTop\Renderer\BlockRenderer;
|
||||
@@ -119,48 +120,41 @@ class DisplayableNode extends GraphNode
|
||||
$Alpha = 1.0;
|
||||
$oPdf->SetFillColor(200, 200, 200);
|
||||
$oPdf->setAlpha(1);
|
||||
|
||||
|
||||
$sIconUrl = $this->GetProperty('icon_url');
|
||||
$sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl);
|
||||
|
||||
if ($this->GetProperty('source'))
|
||||
{
|
||||
$oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(204, 51, 51)));
|
||||
|
||||
if ($this->GetProperty('source')) {
|
||||
$oPdf->SetLineStyle(array('width' => 2 * $fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(204, 51, 51)));
|
||||
$oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
|
||||
} else if ($this->GetProperty('sink')) {
|
||||
$oPdf->SetLineStyle(array('width' => 2 * $fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(51, 51, 204)));
|
||||
$oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
|
||||
}
|
||||
else if ($this->GetProperty('sink'))
|
||||
{
|
||||
$oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => array(51, 51, 204)));
|
||||
$oPdf->Circle($this->x * $fScale, $this->y * $fScale, 16 * 1.25 * $fScale, 0, 360, 'D');
|
||||
}
|
||||
|
||||
if (!$this->GetProperty('is_reached'))
|
||||
{
|
||||
|
||||
if (!$this->GetProperty('is_reached')) {
|
||||
$sTempImageName = $this->CreateWhiteIcon($oGraph, $sIconPath);
|
||||
if ($sTempImageName != null)
|
||||
{
|
||||
$oPdf->Image($sTempImageName, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale, 'PNG');
|
||||
if ($sTempImageName != null) {
|
||||
$oPdf->AddImage($sTempImageName, ($this->x - 16) * $fScale, ($this->y - 16) * $fScale, 32 * $fScale, 32 * $fScale, 'PNG');
|
||||
}
|
||||
$Alpha = 0.4;
|
||||
$oPdf->setAlpha($Alpha);
|
||||
}
|
||||
|
||||
$oPdf->Image($sIconPath, ($this->x - 16)*$fScale, ($this->y - 16)*$fScale, 32*$fScale, 32*$fScale);
|
||||
|
||||
|
||||
$oPdf->AddImage($sIconPath, ($this->x - 16) * $fScale, ($this->y - 16) * $fScale, 32 * $fScale, 32 * $fScale);
|
||||
|
||||
$aContextRootCauses = $this->GetProperty('context_root_causes');
|
||||
if (!is_null($aContextRootCauses))
|
||||
{
|
||||
if (!is_null($aContextRootCauses)) {
|
||||
$idx = 0;
|
||||
foreach($aContextRootCauses as $key => $aObjects)
|
||||
{
|
||||
$sgn = 2*($idx %2) -1;
|
||||
$coef = floor((1+$idx)/2) * $sgn;
|
||||
$alpha = $coef*pi()/4 - pi()/2;
|
||||
$x = $this->x * $fScale + cos($alpha) * 16*1.25 * $fScale;
|
||||
$y = $this->y * $fScale + sin($alpha) * 16*1.25 * $fScale;
|
||||
foreach ($aContextRootCauses as $key => $aObjects) {
|
||||
$sgn = 2 * ($idx % 2) - 1;
|
||||
$coef = floor((1 + $idx) / 2) * $sgn;
|
||||
$alpha = $coef * pi() / 4 - pi() / 2;
|
||||
$x = $this->x * $fScale + cos($alpha) * 16 * 1.25 * $fScale;
|
||||
$y = $this->y * $fScale + sin($alpha) * 16 * 1.25 * $fScale;
|
||||
$l = 32 * $fScale / 3;
|
||||
$sIconPath = APPROOT.'env-'.utils::GetCurrentEnvironment().'/'.$aContextDefs[$key]['icon'];
|
||||
$oPdf->Image($sIconPath, $x - $l/2, $y - $l/2, $l, $l);
|
||||
$oPdf->AddImage($sIconPath, $x - $l / 2, $y - $l / 2, $l, $l);
|
||||
$idx++;
|
||||
}
|
||||
}
|
||||
@@ -413,7 +407,7 @@ class DisplayableNode extends GraphNode
|
||||
{
|
||||
$oNewNode = new DisplayableGroupNode($oGraph, $sNewId);
|
||||
$oNewNode->SetProperty('label', 'x'.$aGroupProps['count']);
|
||||
$oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
|
||||
$oNewNode->SetProperty('icon_url', MetaModel::GetClassIcon($sClass, false));
|
||||
$oNewNode->SetProperty('class', $sClass);
|
||||
$oNewNode->SetProperty('is_reached', ($sStatus == 'reached'));
|
||||
$oNewNode->SetProperty('count', $aGroupProps['count']);
|
||||
@@ -469,7 +463,7 @@ class DisplayableNode extends GraphNode
|
||||
{
|
||||
$aRootCauses[] = $oRootCause->GetHyperlink();
|
||||
}
|
||||
$sHtml .= '<p><img style="max-height: 24px; vertical-align:bottom;" src="'.utils::GetAbsoluteUrlModulesRoot().$aContext['icon'].'" title="'.htmlentities(Dict::S($aContext['dict'])).'"> '.implode(', ', $aRootCauses).'</p>';
|
||||
$sHtml .= '<p><img style="max-height: 24px; vertical-align:bottom;" class="ibo-class-icon ibo-is-small" src="'.utils::GetAbsoluteUrlModulesRoot().$aContext['icon'].'" title="'.htmlentities(Dict::S($aContext['dict'])).'"> '.implode(', ', $aRootCauses).'</p>';
|
||||
}
|
||||
$sHtml .= '<hr/>';
|
||||
}
|
||||
@@ -593,7 +587,7 @@ class DisplayableRedundancyNode extends DisplayableNode
|
||||
{
|
||||
$oNewNode = new DisplayableGroupNode($oGraph, '-'.$this->GetId().'::'.$sClass.'/'.$sStatus);
|
||||
$oNewNode->SetProperty('label', 'x'.count($aGroupProps['nodes']));
|
||||
$oNewNode->SetProperty('icon_url', $aGroupProps['icon_url']);
|
||||
$oNewNode->SetProperty('icon_url', MetaModel::GetClassIcon($sClass, false));
|
||||
$oNewNode->SetProperty('is_reached', ($sStatus == 'is_reached'));
|
||||
$oNewNode->SetProperty('class', $sClass);
|
||||
$oNewNode->SetProperty('count', count($aGroupProps['nodes']));
|
||||
@@ -771,46 +765,39 @@ class DisplayableGroupNode extends DisplayableNode
|
||||
{
|
||||
$bReached = $this->GetProperty('is_reached');
|
||||
$oPdf->SetFillColor(255, 255, 255);
|
||||
if ($bReached)
|
||||
{
|
||||
if ($bReached) {
|
||||
$aBorderColor = array(100, 100, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aBorderColor = array(200, 200, 200);
|
||||
}
|
||||
$oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aBorderColor));
|
||||
|
||||
$oPdf->SetLineStyle(array('width' => 2 * $fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aBorderColor));
|
||||
|
||||
$sIconUrl = $this->GetProperty('icon_url');
|
||||
$sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl);
|
||||
$oPdf->SetAlpha(1);
|
||||
$oPdf->Circle($this->x*$fScale, $this->y*$fScale, $this->GetWidth() / 2 * $fScale, 0, 360, 'DF');
|
||||
|
||||
if ($bReached)
|
||||
{
|
||||
$oPdf->Circle($this->x * $fScale, $this->y * $fScale, $this->GetWidth() / 2 * $fScale, 0, 360, 'DF');
|
||||
|
||||
if ($bReached) {
|
||||
$oPdf->SetAlpha(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$oPdf->SetAlpha(0.4);
|
||||
}
|
||||
$oPdf->Image($sIconPath, ($this->x - 17)*$fScale, ($this->y - 17)*$fScale, 16*$fScale, 16*$fScale);
|
||||
$oPdf->Image($sIconPath, ($this->x + 1)*$fScale, ($this->y - 17)*$fScale, 16*$fScale, 16*$fScale);
|
||||
$oPdf->Image($sIconPath, ($this->x -8)*$fScale, ($this->y +1)*$fScale, 16*$fScale, 16*$fScale);
|
||||
$oPdf->AddImage($sIconPath, ($this->x - 17) * $fScale, ($this->y - 17) * $fScale, 16 * $fScale, 16 * $fScale);
|
||||
$oPdf->AddImage($sIconPath, ($this->x + 1) * $fScale, ($this->y - 17) * $fScale, 16 * $fScale, 16 * $fScale);
|
||||
$oPdf->AddImage($sIconPath, ($this->x - 8) * $fScale, ($this->y + 1) * $fScale, 16 * $fScale, 16 * $fScale);
|
||||
$oPdf->SetFontParams('', 24 * $fScale, '', true);
|
||||
$width = $oPdf->GetStringWidth($this->GetProperty('label'));
|
||||
$oPdf->SetTextColor(0, 0, 0);
|
||||
$oPdf->Text($this->x*$fScale - $width/2, ($this->y + 25)*$fScale, $this->GetProperty('label'));
|
||||
$oPdf->Text($this->x * $fScale - $width / 2, ($this->y + 25) * $fScale, $this->GetProperty('label'));
|
||||
}
|
||||
|
||||
public function GetTooltip($aContextDefs)
|
||||
{
|
||||
$sHtml = '';
|
||||
$iGroupIdx = $this->GetProperty('group_index');
|
||||
$sHtml .= '<a href="#" onclick="$(\'.itop-simple-graph\').simple_graph(\'show_group\', \'relation_group_'.$iGroupIdx.'\');">'.Dict::Format('UI:RelationGroupNumber_N', (1+$iGroupIdx))."</a>";
|
||||
$sHtml = '<a href="#" id="a_'.$iGroupIdx.'" onclick="$(this).closest(\'.tippy-box\').parent().hide();$(\'.itop-simple-graph\').simple_graph(\'show_group\', \'relation_group_'.$iGroupIdx.'\');return false;">'.Dict::Format('UI:RelationGroupNumber_N', (1+$iGroupIdx))."</a>";
|
||||
$sHtml .= '<hr/>';
|
||||
$sHtml .= '<table><tbody><tr>';
|
||||
$sHtml .= '<td style="vertical-align:top;padding-right: 0.5em;"><img src="'.$this->GetProperty('icon_url').'"></td><td style="vertical-align:top">'.MetaModel::GetName($this->GetObjectClass()).'<br/>';
|
||||
$sHtml .= '<td style="vertical-align:top;padding-right: 0.5em;"><img class="ibo-class-icon ibo-is-small" src="'.$this->GetProperty('icon_url').'"></td><td style="vertical-align:top">'.MetaModel::GetName($this->GetObjectClass()).'<br/>';
|
||||
$sHtml .= Dict::Format('UI_CountOfObjectsShort', $this->GetObjectCount()).'</td>';
|
||||
$sHtml .= '</tr></tbody></table>';
|
||||
return $sHtml;
|
||||
@@ -859,66 +846,68 @@ class DisplayableGraph extends SimpleGraph
|
||||
@unlink($sTempFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a DisplayableGraph from a RelationGraph
|
||||
*
|
||||
* @param RelationGraph $oGraph
|
||||
* @param number $iGroupingThreshold
|
||||
* @param string $bDirectionDown
|
||||
*
|
||||
* @return DisplayableGraph
|
||||
*/
|
||||
public static function FromRelationGraph(RelationGraph $oGraph, $iGroupingThreshold = 20, $bDirectionDown = true)
|
||||
public static function FromRelationGraph(RelationGraph $oGraph, $iGroupingThreshold = 20, $bDirectionDown = true, $bForPdf = false)
|
||||
{
|
||||
$oNewGraph = new DisplayableGraph();
|
||||
$oNewGraph->bDirectionDown = $bDirectionDown;
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
|
||||
|
||||
|
||||
$oNodesIter = new RelationTypeIterator($oGraph, 'Node');
|
||||
foreach($oNodesIter as $oNode)
|
||||
{
|
||||
foreach ($oNodesIter as $oNode) {
|
||||
set_time_limit(intval($iLoopTimeLimit));
|
||||
switch(get_class($oNode))
|
||||
{
|
||||
case 'RelationObjectNode':
|
||||
$oNewNode = new DisplayableNode($oNewGraph, $oNode->GetId(), 0, 0);
|
||||
|
||||
$oObj = $oNode->GetProperty('object');
|
||||
$sClass = get_class($oObj);
|
||||
if ($oNode->GetProperty('source'))
|
||||
{
|
||||
if (!array_key_exists($sClass, $oNewGraph->aSourceObjects))
|
||||
{
|
||||
$oNewGraph->aSourceObjects[$sClass] = array();
|
||||
switch (get_class($oNode)) {
|
||||
case 'RelationObjectNode':
|
||||
$oNewNode = new DisplayableNode($oNewGraph, $oNode->GetId(), 0, 0);
|
||||
|
||||
$oObj = $oNode->GetProperty('object');
|
||||
$sClass = get_class($oObj);
|
||||
if ($oNode->GetProperty('source')) {
|
||||
if (!array_key_exists($sClass, $oNewGraph->aSourceObjects)) {
|
||||
$oNewGraph->aSourceObjects[$sClass] = array();
|
||||
}
|
||||
$oNewGraph->aSourceObjects[$sClass][] = $oObj->GetKey();
|
||||
$oNewNode->SetProperty('source', true);
|
||||
}
|
||||
$oNewGraph->aSourceObjects[$sClass][] = $oObj->GetKey();
|
||||
$oNewNode->SetProperty('source', true);
|
||||
}
|
||||
if ($oNode->GetProperty('sink'))
|
||||
{
|
||||
if (!array_key_exists($sClass, $oNewGraph->aSinkObjects))
|
||||
{
|
||||
$oNewGraph->aSinkObjects[$sClass] = array();
|
||||
if ($oNode->GetProperty('sink')) {
|
||||
if (!array_key_exists($sClass, $oNewGraph->aSinkObjects)) {
|
||||
$oNewGraph->aSinkObjects[$sClass] = array();
|
||||
}
|
||||
$oNewGraph->aSinkObjects[$sClass][] = $oObj->GetKey();
|
||||
$oNewNode->SetProperty('sink', true);
|
||||
}
|
||||
$oNewGraph->aSinkObjects[$sClass][] = $oObj->GetKey();
|
||||
$oNewNode->SetProperty('sink', true);
|
||||
}
|
||||
$oNewNode->SetProperty('object', $oObj);
|
||||
$oNewNode->SetProperty('icon_url', $oObj->GetIcon(false));
|
||||
$oNewNode->SetProperty('label', $oObj->GetRawName());
|
||||
$oNewNode->SetProperty('is_reached', $bDirectionDown ? $oNode->GetProperty('is_reached') : true); // When going "up" is_reached does not matter
|
||||
$oNewNode->SetProperty('is_reached_allowed', $oNode->GetProperty('is_reached_allowed'));
|
||||
$oNewNode->SetProperty('context_root_causes', $oNode->GetProperty('context_root_causes'));
|
||||
break;
|
||||
|
||||
$oNewNode->SetProperty('object', $oObj);
|
||||
$sIconUrl = $oObj->GetIcon(false);
|
||||
//when icons are displayed in a PDF file, puts the image data in place of the url
|
||||
if ($bForPdf && strpos($sIconUrl, 'display_document') > 0) {
|
||||
$sImageAttCode = MetaModel::GetImageAttributeCode($sClass);
|
||||
$sIconUrl = '@'.$oObj->Get($sImageAttCode)->GetData();
|
||||
}
|
||||
$oNewNode->SetProperty('icon_url', $sIconUrl);
|
||||
$oNewNode->SetProperty('label', $oObj->GetRawName());
|
||||
$oNewNode->SetProperty('is_reached', $bDirectionDown ? $oNode->GetProperty('is_reached') : true); // When going "up" is_reached does not matter
|
||||
$oNewNode->SetProperty('is_reached_allowed', $oNode->GetProperty('is_reached_allowed'));
|
||||
$oNewNode->SetProperty('context_root_causes', $oNode->GetProperty('context_root_causes'));
|
||||
break;
|
||||
|
||||
default:
|
||||
$oNewNode = new DisplayableRedundancyNode($oNewGraph, $oNode->GetId(), 0, 0);
|
||||
$iNbReached = (is_null($oNode->GetProperty('is_reached_count'))) ? 0 : $oNode->GetProperty('is_reached_count');
|
||||
$oNewNode->SetProperty('label', $iNbReached."/".($oNode->GetProperty('min_up') + $oNode->GetProperty('threshold')));
|
||||
$oNewNode->SetProperty('min_up', $oNode->GetProperty('min_up'));
|
||||
$oNewNode->SetProperty('threshold', $oNode->GetProperty('threshold'));
|
||||
$oNewNode->SetProperty('is_reached_count', $iNbReached);
|
||||
$oNewNode->SetProperty('is_reached', true);
|
||||
$oNewNode = new DisplayableRedundancyNode($oNewGraph, $oNode->GetId(), 0, 0);
|
||||
$iNbReached = (is_null($oNode->GetProperty('is_reached_count'))) ? 0 : $oNode->GetProperty('is_reached_count');
|
||||
$oNewNode->SetProperty('label', $iNbReached."/".($oNode->GetProperty('min_up') + $oNode->GetProperty('threshold')));
|
||||
$oNewNode->SetProperty('min_up', $oNode->GetProperty('min_up'));
|
||||
$oNewNode->SetProperty('threshold', $oNode->GetProperty('threshold'));
|
||||
$oNewNode->SetProperty('is_reached_count', $iNbReached);
|
||||
$oNewNode->SetProperty('is_reached', true);
|
||||
}
|
||||
}
|
||||
$oEdgesIter = new RelationTypeIterator($oGraph, 'Edge');
|
||||
@@ -1185,7 +1174,7 @@ class DisplayableGraph extends SimpleGraph
|
||||
|
||||
return json_encode($aData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort class "codes" based on their localized name
|
||||
* @param string $sClass1
|
||||
@@ -1196,12 +1185,12 @@ class DisplayableGraph extends SimpleGraph
|
||||
{
|
||||
return strcasecmp(MetaModel::GetName($sClass1), MetaModel::GetName($sClass2));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders the graph in a PDF document: centered in the current page
|
||||
* @param PDFPage $oPage The PDFPage representing the PDF document to draw into
|
||||
* @param string $sComments An optional comment to display next to the graph (HTML entities will be escaped, \n replaced by <br/>)
|
||||
* @param string $sContextKey The key to fetch the queries in the configuration. Example: itop-tickets/relation_context/UserRequest/impacts/down
|
||||
* @param string $sContextKey The key to fetch the queries in the configuration. Example: itop-tickets/relation_context/UserRequest/impacts/down
|
||||
* @param float $xMin Left coordinate of the bounding box to display the graph
|
||||
* @param float $xMax Right coordinate of the bounding box to display the graph
|
||||
* @param float $yMin Top coordinate of the bounding box to display the graph
|
||||
@@ -1329,20 +1318,18 @@ class DisplayableGraph extends SimpleGraph
|
||||
$yPos = $yMin + $fPadding;
|
||||
$oPdf->SetFillColor(225, 225, 225);
|
||||
$oPdf->Cell($fIconSize + $fPadding + $fMaxWidth, $fIconSize + $fPadding, Dict::S('UI:Relation:Key'), 0 /* border */, 1 /* ln */, 'C', true /* fill */);
|
||||
$yPos += $fIconSize + 2*$fPadding;
|
||||
foreach($aClasses as $sClass => $sLabel)
|
||||
{
|
||||
$yPos += $fIconSize + 2 * $fPadding;
|
||||
foreach ($aClasses as $sClass => $sLabel) {
|
||||
$oPdf->SetX($xMin + $fIconSize + $fPadding);
|
||||
$oPdf->Cell(0, $fIconSize + 2*$fPadding, $sLabel, 0 /* border */, 1 /* ln */);
|
||||
$oPdf->Image($aIcons[$sClass], $xMin+1, $yPos, $fIconSize, $fIconSize);
|
||||
$yPos += $fIconSize + 2*$fPadding;
|
||||
$oPdf->Cell(0, $fIconSize + 2 * $fPadding, $sLabel, 0 /* border */, 1 /* ln */);
|
||||
$oPdf->AddImage($aIcons[$sClass], $xMin + 1, $yPos, $fIconSize, $fIconSize);
|
||||
$yPos += $fIconSize + 2 * $fPadding;
|
||||
}
|
||||
foreach($aContexts as $key => $sLabel)
|
||||
{
|
||||
foreach ($aContexts as $key => $sLabel) {
|
||||
$oPdf->SetX($xMin + $fIconSize + $fPadding);
|
||||
$oPdf->Cell(0, $fIconSize + 2*$fPadding, $sLabel, 0 /* border */, 1 /* ln */);
|
||||
$oPdf->Image($aContextIcons[$key], $xMin+1+$fIconSize*0.125, $yPos+$fIconSize*0.125, $fIconSize*0.75, $fIconSize*0.75);
|
||||
$yPos += $fIconSize + 2*$fPadding;
|
||||
$oPdf->Cell(0, $fIconSize + 2 * $fPadding, $sLabel, 0 /* border */, 1 /* ln */);
|
||||
$oPdf->AddImage($aContextIcons[$key], $xMin + 1 + $fIconSize * 0.125, $yPos + $fIconSize * 0.125, $fIconSize * 0.75, $fIconSize * 0.75);
|
||||
$yPos += $fIconSize + 2 * $fPadding;
|
||||
}
|
||||
$oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yMin, 'D');
|
||||
|
||||
@@ -1453,9 +1440,9 @@ class DisplayableGraph extends SimpleGraph
|
||||
$sSftShort = Dict::S('UI:ElementsDisplayed');
|
||||
$sSearchToggle = Dict::S('UI:Search:Toggle');
|
||||
$oP->add("<div class=\"not-printable\">\n");
|
||||
$oUiSearchBlock = new Panel($sSftShort, [],Panel::ENUM_COLOR_CYAN, 'ds_flash');
|
||||
$oUiSearchBlock = new Panel($sSftShort, [],Panel::ENUM_COLOR_SCHEME_CYAN, 'ds_flash');
|
||||
$oUiSearchBlock->SetCSSClasses(["ibo-search-form-panel", "display_block"]);
|
||||
|
||||
$oUiSearchBlock->SetIsCollapsible(true);
|
||||
$oUiHtmlBlock = new Combodo\iTop\Application\UI\Base\Component\Html\Html(
|
||||
<<<EOF
|
||||
<div id="ds_flash" class="search_box ibo-display-graph--search-box">
|
||||
@@ -1506,11 +1493,8 @@ EOF
|
||||
|
||||
$sDirection = utils::ReadParam('d', 'horizontal');
|
||||
$iGroupingThreshold = utils::ReadParam('g', 5);
|
||||
|
||||
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js');
|
||||
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.contextMenu.css');
|
||||
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.contextMenu.js');
|
||||
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js');
|
||||
|
||||
WebResourcesHelper::EnableSimpleGraphInWebPage($oP);
|
||||
try
|
||||
{
|
||||
$this->InitFromGraphviz();
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
*/
|
||||
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Parser;
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\PrettyPrinter\Standard;
|
||||
|
||||
@@ -80,38 +82,49 @@ class iTopConfigParser
|
||||
* @param \PhpParser\Parser $oParser
|
||||
* @param $sConfig
|
||||
*
|
||||
* @return \Combodo\iTop\Config\Validator\ConfigNodesVisitor
|
||||
* @return void
|
||||
*/
|
||||
private function BrowseFile(\PhpParser\Parser $oParser, $sConfig)
|
||||
private function BrowseFile(Parser $oParser, $sConfig)
|
||||
{
|
||||
$prettyPrinter = new Standard();
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
$aNodes = $oParser->parse($sConfig);
|
||||
}
|
||||
catch (\Error $e)
|
||||
{
|
||||
catch (\Error $e) {
|
||||
$sMessage = Dict::Format('config-parse-error', $e->getMessage(), $e->getLine());
|
||||
$this->oException = new \Exception($sMessage, 0, $e);
|
||||
}
|
||||
|
||||
foreach ($aNodes as $oAssignation)
|
||||
{
|
||||
if (! $oAssignation instanceof Assign)
|
||||
{
|
||||
foreach ($aNodes as $sKey => $oNode) {
|
||||
// With PhpParser 3 we had an Assign node at root
|
||||
// In PhpParser 4 the root node is now an Expression
|
||||
|
||||
if (false === ($oNode instanceof \PhpParser\Node\Stmt\Expression)) {
|
||||
continue;
|
||||
}
|
||||
/** @var \PhpParser\Node\Stmt\Expression $oNode */
|
||||
|
||||
if (false === ($oNode->expr instanceof Assign)) {
|
||||
continue;
|
||||
}
|
||||
/** @var Assign $oAssignation */
|
||||
$oAssignation = $oNode->expr;
|
||||
|
||||
if (false === ($oAssignation->var instanceof Variable)) {
|
||||
continue;
|
||||
}
|
||||
if (false === ($oAssignation->expr instanceof PhpParser\Node\Expr\Array_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sCurrentRootVar = $oAssignation->var->name;
|
||||
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap))
|
||||
{
|
||||
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap)) {
|
||||
continue;
|
||||
}
|
||||
$aCurrentRootVarMap =& $this->aVarsMap[$sCurrentRootVar];
|
||||
|
||||
foreach ($oAssignation->expr->items as $oItem)
|
||||
{
|
||||
foreach ($oAssignation->expr->items as $oItem) {
|
||||
$sValue = $prettyPrinter->prettyPrintExpr($oItem->value);
|
||||
$aCurrentRootVarMap[$oItem->key->value] = $sValue;
|
||||
}
|
||||
|
||||
@@ -198,31 +198,29 @@ class InlineImage extends DBObject
|
||||
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
|
||||
$oSearch = DBObjectSearch::FromOQL($sOQL);
|
||||
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
|
||||
$aInlineImagesId = array();
|
||||
while($oInlineImage = $oSet->Fetch())
|
||||
{
|
||||
$aInlineImagesId[] = $oInlineImage->GetKey();
|
||||
$aInlineImagesId = array();
|
||||
while ($oInlineImage = $oSet->Fetch()) {
|
||||
$aInlineImagesId[] = $oInlineImage->GetKey();
|
||||
$oInlineImage->SetItem($oObject);
|
||||
$oInlineImage->Set('temp_id', '');
|
||||
$oInlineImage->DBUpdate();
|
||||
}
|
||||
IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', 'InlineImage', array(
|
||||
'$sObjectClass' => get_class($oObject),
|
||||
'$sTransactionId' => $iTransactionId,
|
||||
'$sTempId' => $sTempId,
|
||||
'$aInlineImagesId' => $aInlineImagesId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', LogChannels::INLINE_IMAGE, array(
|
||||
'$sObjectClass' => get_class($oObject),
|
||||
'$sTransactionId' => $iTransactionId,
|
||||
'$sTempId' => $sTempId,
|
||||
'$aInlineImagesId' => $aInlineImagesId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', 'InlineImage', array(
|
||||
'$sObjectClass' => get_class($oObject),
|
||||
'$sTransactionId' => $iTransactionId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
else {
|
||||
IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', LogChannels::INLINE_IMAGE, array(
|
||||
'$sObjectClass' => get_class($oObject),
|
||||
'$sTransactionId' => $iTransactionId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +229,7 @@ class InlineImage extends DBObject
|
||||
*
|
||||
* @param string $sTempId
|
||||
*
|
||||
* @return void
|
||||
* @return bool True if cleaning was successful, false if anything aborted it
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
@@ -241,8 +239,19 @@ class InlineImage extends DBObject
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public static function OnFormCancel($sTempId)
|
||||
public static function OnFormCancel($sTempId): bool
|
||||
{
|
||||
// Protection against unfortunate massive delete of inline images when a null temp ID is passed
|
||||
if (strlen($sTempId) === 0) {
|
||||
IssueLog::Trace('OnFormCancel "error" $sTempId is null or empty', LogChannels::INLINE_IMAGE, array(
|
||||
'$sTempId' => $sTempId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete all "pending" InlineImages for this form
|
||||
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
|
||||
$oSearch = DBObjectSearch::FromOQL($sOQL);
|
||||
@@ -253,12 +262,14 @@ class InlineImage extends DBObject
|
||||
$aInlineImagesId[] = $oInlineImage->GetKey();
|
||||
$oInlineImage->DBDelete();
|
||||
}
|
||||
IssueLog::Trace('OnFormCancel', 'InlineImage', array(
|
||||
'$sTempId' => $sTempId,
|
||||
'$aInlineImagesId' => $aInlineImagesId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
IssueLog::Trace('OnFormCancel', LogChannels::INLINE_IMAGE, array(
|
||||
'$sTempId' => $sTempId,
|
||||
'$aInlineImagesId' => $aInlineImagesId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -537,8 +548,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;
|
||||
@@ -579,7 +588,7 @@ JS
|
||||
oEditor.on( 'instanceReady', function() {
|
||||
if(!CKEDITOR.env.iOS && $('#'+oEditor.id+'_toolbox .ibo-vendors-ckeditor--toolbar-fullscreen-button').length == 0)
|
||||
{
|
||||
$('#'+oEditor.id+'_toolbox').append('<span class="ibo-vendors-ckeditor--toolbar-fullscreen-button" data-role="ibo-vendors-ckeditor--toolbar-fullscreen-button" title="$sToggleFullScreen" style="background-image:url(\\'$sAbsoluteUrlAppRoot/images/full-screen.png\\')"> </span>');
|
||||
$('#'+oEditor.id+'_toolbox').append('<span class="ibo-vendors-ckeditor--toolbar-fullscreen-button editor-fullscreen-button" data-role="ibo-vendors-ckeditor--toolbar-fullscreen-button" title="$sToggleFullScreen"> </span>');
|
||||
$('#'+oEditor.id+'_toolbox .ibo-vendors-ckeditor--toolbar-fullscreen-button').on('click', function() {
|
||||
oEditor.execCommand('maximize');
|
||||
if ($(this).closest('.cke_maximized').length != 0)
|
||||
@@ -608,17 +617,17 @@ JS
|
||||
*/
|
||||
protected function AfterInsert()
|
||||
{
|
||||
IssueLog::Trace(__METHOD__, 'InlineImage', array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
));
|
||||
|
||||
parent::AfterInsert();
|
||||
@@ -629,17 +638,17 @@ JS
|
||||
*/
|
||||
protected function AfterUpdate()
|
||||
{
|
||||
IssueLog::Trace(__METHOD__, 'InlineImage', array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
));
|
||||
|
||||
parent::AfterUpdate();
|
||||
@@ -650,17 +659,17 @@ JS
|
||||
*/
|
||||
protected function AfterDelete()
|
||||
{
|
||||
IssueLog::Trace(__METHOD__, 'InlineImage', array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
));
|
||||
|
||||
parent::AfterDelete();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -399,10 +399,10 @@ class MonthlyRotatingLogFileNameBuilder extends RotatingLogFileNameBuilder
|
||||
*/
|
||||
protected function GetFileSuffix($oDate)
|
||||
{
|
||||
$sWeekYear = $oDate->format('o');
|
||||
$sWeekNumber = $oDate->format('m');
|
||||
$sMonthYear = $oDate->format('o');
|
||||
$sMonthNumber = $oDate->format('m');
|
||||
|
||||
return $sWeekYear.'-month'.$sWeekNumber;
|
||||
return $sMonthYear.'-month'.$sMonthNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -502,27 +502,27 @@ class FileLog
|
||||
|
||||
protected function Write($sText, $sLevel = '', $sChannel = '', $aContext = array())
|
||||
{
|
||||
$sTextPrefix = empty($sLevel) ? '' : (str_pad($sLevel, 7).' | ');
|
||||
$sTextSuffix = empty($sChannel) ? '' : " | $sChannel";
|
||||
$sText = "{$sTextPrefix}{$sText}{$sTextSuffix}";
|
||||
$sLogFilePath = $this->oFileNameBuilder->GetLogFilePath();
|
||||
$sTextPrefix = empty($sLevel) ? '' : (str_pad($sLevel, 7));
|
||||
$sTextPrefix .= ' | ';
|
||||
$sTextPrefix .= str_pad(LogAPI::GetUserInfo(), 5)." | ";
|
||||
|
||||
if (empty($sLogFilePath))
|
||||
{
|
||||
$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))
|
||||
{
|
||||
if (empty($aContext)) {
|
||||
fwrite($hLogFile, "$sDate | $sText\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sContext = var_export($aContext, true);
|
||||
fwrite($hLogFile, "$sDate | $sText\n$sContext\n");
|
||||
}
|
||||
@@ -533,22 +533,51 @@ class FileLog
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple enum like class to factorize channels values as constants
|
||||
* Channels are used especially as parameters in {@see \LogAPI} methods
|
||||
*
|
||||
* @since 2.7.5 3.0.0 N°4012
|
||||
*/
|
||||
class LogChannels
|
||||
{
|
||||
public const CLI = 'CLI';
|
||||
public const CONSOLE = 'console';
|
||||
public const DEADLOCK = 'DeadLock';
|
||||
public const INLINE_IMAGE = 'InlineImage';
|
||||
public const PORTAL = 'portal';
|
||||
public const CMDB_SOURCE = 'cmdbsource';
|
||||
public const CORE = 'core';
|
||||
}
|
||||
|
||||
|
||||
abstract class LogAPI
|
||||
{
|
||||
const CHANNEL_DEFAULT = '';
|
||||
public const CHANNEL_DEFAULT = '';
|
||||
|
||||
public const LEVEL_ERROR = 'Error';
|
||||
public const LEVEL_WARNING = 'Warning';
|
||||
public const LEVEL_INFO = 'Info';
|
||||
public const LEVEL_OK = 'Ok';
|
||||
public const LEVEL_DEBUG = 'Debug';
|
||||
public const LEVEL_TRACE = 'Trace';
|
||||
|
||||
const LEVEL_ERROR = 'Error';
|
||||
const LEVEL_WARNING = 'Warning';
|
||||
const LEVEL_INFO = 'Info';
|
||||
const LEVEL_OK = 'Ok';
|
||||
const LEVEL_DEBUG = 'Debug';
|
||||
const LEVEL_TRACE = 'Trace';
|
||||
/**
|
||||
* @var string default log level, can be overrided
|
||||
* @see GetMinLogLevel
|
||||
* @see GetMinLogLevel
|
||||
* @used-by GetLevelDefault
|
||||
* @var string default log level.
|
||||
* @since 2.7.1 N°2977
|
||||
*/
|
||||
const LEVEL_DEFAULT = self::LEVEL_OK;
|
||||
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,
|
||||
@@ -556,18 +585,29 @@ abstract class LogAPI
|
||||
self::LEVEL_INFO => 200,
|
||||
self::LEVEL_OK => 200,
|
||||
self::LEVEL_DEBUG => 100,
|
||||
self::LEVEL_TRACE => 50,
|
||||
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);
|
||||
}
|
||||
|
||||
public static function MockStaticObjects($oFileLog, $oMetaModelConfig=null)
|
||||
/**
|
||||
* @internal uses only for testing purpose.
|
||||
*/
|
||||
public static function MockStaticObjects($oFileLog, $oMetaModelConfig = null)
|
||||
{
|
||||
static::$m_oFileLog = $oFileLog;
|
||||
static::$m_oMockMetaModelConfig = $oMetaModelConfig;
|
||||
@@ -603,85 +643,250 @@ abstract class LogAPI
|
||||
static::Log(self::LEVEL_TRACE, $sMessage, $sChannel, $aContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ConfigException if log wrongly configured
|
||||
*/
|
||||
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array())
|
||||
{
|
||||
if (! static::$m_oFileLog)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset(self::$aLevelsPriority[$sLevel]))
|
||||
{
|
||||
if (!isset(self::$aLevelsPriority[$sLevel])) {
|
||||
IssueLog::Error("invalid log level '{$sLevel}'");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($sChannel))
|
||||
{
|
||||
if (is_null($sChannel)) {
|
||||
$sChannel = static::CHANNEL_DEFAULT;
|
||||
}
|
||||
|
||||
$sMinLogLevel = self::GetMinLogLevel($sChannel);
|
||||
|
||||
if ($sMinLogLevel === false || $sMinLogLevel === 'false')
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (is_string($sMinLogLevel))
|
||||
{
|
||||
if (! isset(self::$aLevelsPriority[$sMinLogLevel]))
|
||||
{
|
||||
throw new Exception("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority)));
|
||||
}
|
||||
elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel])
|
||||
{
|
||||
//priority too low regarding the conf, do not log this
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static::$m_oFileLog->$sLevel($sMessage, $sChannel, $aContext);
|
||||
static::WriteLog($sLevel, $sMessage, $sChannel, $aContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sChannel
|
||||
*
|
||||
* @return string one of the LEVEL_* const value
|
||||
* @uses \LogAPI::LEVEL_DEFAULT
|
||||
* @throws \ConfigException
|
||||
*/
|
||||
private static function GetMinLogLevel($sChannel)
|
||||
protected static function WriteLog(string $sLevel, string $sMessage, ?string $sChannel = null, ?array $aContext = array()): void
|
||||
{
|
||||
$oConfig = (static::$m_oMockMetaModelConfig !== null) ? static::$m_oMockMetaModelConfig : \MetaModel::GetConfig();
|
||||
if (!$oConfig instanceof Config)
|
||||
{
|
||||
return static::LEVEL_DEFAULT;
|
||||
if (
|
||||
(null !== static::$m_oFileLog)
|
||||
&& static::IsLogLevelEnabled($sLevel, $sChannel, static::ENUM_CONFIG_PARAM_FILE)
|
||||
) {
|
||||
static::$m_oFileLog->$sLevel($sMessage, $sChannel, $aContext);
|
||||
}
|
||||
|
||||
$sLogLevelMin = $oConfig->Get('log_level_min');
|
||||
if (static::IsLogLevelEnabled($sLevel, $sChannel, static::ENUM_CONFIG_PARAM_DB)) {
|
||||
self::WriteToDb($sMessage, $sChannel, $aContext);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($sLogLevelMin))
|
||||
{
|
||||
return static::LEVEL_DEFAULT;
|
||||
public static function GetUserInfo(): ?string
|
||||
{
|
||||
$oConnectedUser = UserRights::GetUserObject();
|
||||
if (is_null($oConnectedUser)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!is_array($sLogLevelMin))
|
||||
{
|
||||
return $oConnectedUser->GetKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ConfigException if log wrongly configured
|
||||
* @uses GetMinLogLevel
|
||||
*/
|
||||
final public static function IsLogLevelEnabled(string $sLevel, string $sChannel, string $sConfigKey = self::ENUM_CONFIG_PARAM_FILE): bool
|
||||
{
|
||||
$sMinLogLevel = self::GetMinLogLevel($sChannel, $sConfigKey);
|
||||
|
||||
// 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)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset(self::$aLevelsPriority[$sMinLogLevel])) {
|
||||
throw new ConfigException("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority)));
|
||||
} elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 :
|
||||
* * globally : `'log_level_min' => LogAPI::LEVEL_TRACE,`
|
||||
* * per channel :
|
||||
* ```
|
||||
* 'log_level_min' => [
|
||||
* '' => LogAPI::LEVEL_ERROR, // default log level for channels not listed below
|
||||
* 'InlineImage' => LogAPI::LEVEL_TRACE,
|
||||
* 'UserRequest' => LogAPI::LEVEL_TRACE
|
||||
* ],
|
||||
* ```
|
||||
*
|
||||
* @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, $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($sConfigKey);
|
||||
}
|
||||
|
||||
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($sConfigKey);
|
||||
}
|
||||
|
||||
if (!is_array($sLogLevelMin)) {
|
||||
return $sLogLevelMin;
|
||||
}
|
||||
|
||||
if (isset($sLogLevelMin[$sChannel]))
|
||||
{
|
||||
if (isset($sLogLevelMin[$sChannel])) {
|
||||
return $sLogLevelMin[$sChannel];
|
||||
}
|
||||
|
||||
if (isset($sLogLevelMin[static::CHANNEL_DEFAULT]))
|
||||
{
|
||||
return $sLogLevelMin[$sChannel];
|
||||
}
|
||||
|
||||
return static::LEVEL_DEFAULT;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static function GetMinLogLevelFromDefault($sLogLevelMin, $sChannel, $sConfigKey)
|
||||
{
|
||||
if (isset($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 children classes, don't remove this test to factorize it with the previous one.
|
||||
if (isset($sLogLevelMin[''])) {
|
||||
return $sLogLevelMin[''];
|
||||
}
|
||||
|
||||
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 ?? \utils::GetConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to override if default log level needs to be computed. Otherwise, simply override the corresponding constants
|
||||
*
|
||||
* @used-by GetMinLogLevel
|
||||
*
|
||||
* @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 $sConfigKey)
|
||||
{
|
||||
switch ($sConfigKey) {
|
||||
case static::ENUM_CONFIG_PARAM_DB:
|
||||
return static::LEVEL_DEFAULT_DB;
|
||||
case static::ENUM_CONFIG_PARAM_FILE:
|
||||
default:
|
||||
return static::LEVEL_DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SetupLog extends LogAPI
|
||||
@@ -695,6 +900,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
|
||||
@@ -733,6 +948,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)
|
||||
@@ -741,7 +957,7 @@ class DeadLockLog extends LogAPI
|
||||
return self::CHANNEL_WAIT_TIMEOUT;
|
||||
break;
|
||||
case 1213:
|
||||
return self::CHANNEL_DEADLOCK_FOUND;
|
||||
return self::CHANNEL_DEADLOCK_FOUND;
|
||||
break;
|
||||
default:
|
||||
return self::CHANNEL_DEFAULT;
|
||||
@@ -750,17 +966,250 @@ class DeadLockLog extends LogAPI
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $iMySQLErrNo will be converted to channel using {@link GetChannelFromMysqlErrorNo}
|
||||
* @param string $sLevel
|
||||
* @param string $sMessage
|
||||
* @param null $iMysqlErroNo
|
||||
* @param int $iMysqlErrorNumber will be converted to channel using {@link GetChannelFromMysqlErrorNo}
|
||||
* @param array $aContext
|
||||
*
|
||||
* @throws \Exception
|
||||
* @noinspection PhpParameterNameChangedDuringInheritanceInspection
|
||||
*
|
||||
* @since 2.7.1 method creation
|
||||
* @since 2.7.5 3.0.0 rename param names and fix phpdoc (thanks Hipska !)
|
||||
*/
|
||||
public static function Log($iMySQLErrNo, $sMessage, $iMysqlErroNo = null, $aContext = array())
|
||||
public static function Log($sLevel, $sMessage, $iMysqlErrorNumber = null, $aContext = array())
|
||||
{
|
||||
$sChannel = self::GetChannelFromMysqlErrorNo($iMysqlErroNo);
|
||||
parent::Log($iMySQLErrNo, $sMessage, $sChannel, $aContext);
|
||||
$sChannel = self::GetChannelFromMysqlErrorNo($iMysqlErrorNumber);
|
||||
parent::Log($sLevel, $sMessage, $sChannel, $aContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 3.0.0 N°3731
|
||||
*/
|
||||
class DeprecatedCallsLog extends LogAPI
|
||||
{
|
||||
public const ENUM_CHANNEL_PHP_METHOD = 'deprecated-php-method';
|
||||
public const ENUM_CHANNEL_PHP_LIBMETHOD = 'deprecated-php-libmethod';
|
||||
public const ENUM_CHANNEL_FILE = 'deprecated-file';
|
||||
public const CHANNEL_DEFAULT = self::ENUM_CHANNEL_PHP_METHOD;
|
||||
|
||||
public const LEVEL_DEFAULT = self::LEVEL_ERROR;
|
||||
|
||||
/** @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
|
||||
*
|
||||
* @since 3.0.0 N°3002 logs deprecated notices in called code
|
||||
*/
|
||||
public static function Enable($sTargetFile = null): void
|
||||
{
|
||||
if (empty($sTargetFile)) {
|
||||
$sTargetFile = APPROOT.'log/deprecated-calls.log';
|
||||
}
|
||||
parent::Enable($sTargetFile);
|
||||
|
||||
if (static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)) {
|
||||
set_error_handler([static::class, 'DeprecatedNoticesErrorHandler']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will catch a message for all E_DEPRECATED and E_USER_DEPRECATED errors.
|
||||
* This handler is set in DeprecatedCallsLog::Enable
|
||||
*
|
||||
* @param int $errno
|
||||
* @param string $errstr
|
||||
* @param string $errfile
|
||||
* @param int $errline
|
||||
*
|
||||
* @return bool
|
||||
* @since 3.0.0 N°3002
|
||||
* @noinspection SpellCheckingInspection
|
||||
*/
|
||||
public static function DeprecatedNoticesErrorHandler(int $errno, string $errstr, string $errfile, int $errline): bool
|
||||
{
|
||||
if (
|
||||
(\E_USER_DEPRECATED !== $errno)
|
||||
&& (\E_DEPRECATED !== $errno)
|
||||
) {
|
||||
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'];
|
||||
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
|
||||
if (($sDeprecatedObject === __CLASS__) && ($sDeprecatedMethod === 'Log')) {
|
||||
// We are generating a trigger_error ourselves, we don't want to trace them !
|
||||
return false;
|
||||
}
|
||||
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
|
||||
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
|
||||
$sMessage = "Call to {$sDeprecatedObject}::{$sDeprecatedMethod} in {$sCallerFile}#L{$sCallerLine}";
|
||||
|
||||
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 3 = caller of the deprecated method
|
||||
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
|
||||
$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)) {
|
||||
$sMessage .= ' : '.$errstr;
|
||||
}
|
||||
|
||||
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_LIBMETHOD);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 parent::GetLevelDefault($sConfigKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ConfigException
|
||||
* @link https://www.php.net/debug_backtrace
|
||||
* @uses \debug_backtrace()
|
||||
*/
|
||||
public static function NotifyDeprecatedFile(?string $sAdditionalMessage = null): void
|
||||
{
|
||||
if (!static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_FILE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
$sDeprecatedFile = $aStack[0]['file'];
|
||||
if (array_key_exists(1, $aStack)) {
|
||||
$sCallerFile = $aStack[1]['file'];
|
||||
$sCallerLine = $aStack[1]['line'];
|
||||
} else {
|
||||
$sCallerFile = 'N/A';
|
||||
$sCallerLine = 'N/A';
|
||||
}
|
||||
|
||||
$sMessage = "{$sCallerFile} L{$sCallerLine} including/requiring {$sDeprecatedFile}";
|
||||
|
||||
if (!is_null($sAdditionalMessage)) {
|
||||
$sMessage .= ' : '.$sAdditionalMessage;
|
||||
}
|
||||
|
||||
static::Warning($sMessage, static::ENUM_CHANNEL_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $sAdditionalMessage
|
||||
*
|
||||
* @link https://www.php.net/debug_backtrace
|
||||
* @uses \debug_backtrace()
|
||||
*/
|
||||
public static function NotifyDeprecatedPhpMethod(?string $sAdditionalMessage = null): void
|
||||
{
|
||||
try {
|
||||
if (!static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_METHOD)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ConfigException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||
$iStackDeprecatedMethodLevel = 1; // level 0 = current method, level 1 = method containing the `NotifyDeprecatedPhpMethod` call
|
||||
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
|
||||
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
|
||||
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
|
||||
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
|
||||
$sMessage = "Call to {$sDeprecatedObject}::{$sDeprecatedMethod} in {$sCallerFile}#L{$sCallerLine}";
|
||||
|
||||
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 2 = caller of the deprecated method
|
||||
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
|
||||
$sCallerObject = $aStack[$iStackCallerMethodLevel]['class'];
|
||||
$sCallerMethod = $aStack[$iStackCallerMethodLevel]['function'];
|
||||
$sMessage .= " ({$sCallerObject}::{$sCallerMethod})";
|
||||
}
|
||||
|
||||
if (!is_null($sAdditionalMessage)) {
|
||||
$sMessage .= ' : '.$sAdditionalMessage;
|
||||
}
|
||||
|
||||
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_METHOD);
|
||||
}
|
||||
|
||||
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array()): void
|
||||
{
|
||||
if (true === utils::IsDevelopmentEnvironment()) {
|
||||
trigger_error($sMessage, E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
try {
|
||||
parent::Log($sLevel, $sMessage, $sChannel, $aContext);
|
||||
}
|
||||
catch (ConfigException $e) {
|
||||
// nothing much we can do... and we don't want to crash the caller !
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -769,6 +1218,7 @@ class LogFileRotationProcess implements iScheduledProcess
|
||||
{
|
||||
/**
|
||||
* Cannot get this list from anywhere as log file name is provided by the caller using LogAPI::Enable
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
const LOGFILES_TO_ROTATE = array(
|
||||
@@ -832,3 +1282,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;
|
||||
}
|
||||
|
||||
@@ -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,80 +815,103 @@ abstract class MetaModel
|
||||
}
|
||||
}
|
||||
|
||||
return array($sFormat, $nameRawSpec);
|
||||
}
|
||||
elseif (empty($nameRawSpec))
|
||||
{
|
||||
return array($sClass, array());
|
||||
}
|
||||
else
|
||||
{
|
||||
return [$sFormat, $nameRawSpec];
|
||||
} elseif (empty($nameRawSpec)) {
|
||||
return [$sClass, []];
|
||||
} else {
|
||||
// string -> attcode
|
||||
return array('%1$s', array($nameRawSpec));
|
||||
return ['%1$s', [$nameRawSpec]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return array
|
||||
* @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
|
||||
* @throws \DictExceptionMissingString
|
||||
* @since 3.0.0
|
||||
*/
|
||||
final static public function GetComplementAttributeSpec($sClass)
|
||||
final public static function GetNameAttributes(string $sClass, $bWithAttributeDefinition = false, $sType = FriendlyNameType::SHORT): array
|
||||
{
|
||||
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());
|
||||
$aNameAttCodes = [];
|
||||
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 {
|
||||
return static::GetComplementAttributeSpec($sParentClass);
|
||||
$aNameAttCodes = $rawNameAttCodes;
|
||||
}
|
||||
}
|
||||
$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';
|
||||
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);
|
||||
}
|
||||
}
|
||||
return array($sFormat, $nameRawSpec);
|
||||
}
|
||||
elseif (empty($nameRawSpec))
|
||||
{
|
||||
return array($sClass, array());
|
||||
}
|
||||
else
|
||||
{
|
||||
// string -> attcode
|
||||
return array('%1$s', array($nameRawSpec));
|
||||
if ($bWithAttributeDefinition) {
|
||||
$aResults = [];
|
||||
foreach ($aNameAttCodes as $sAttCode) {
|
||||
$aResults[$sAttCode] = self::GetAttributeDef($sClass, $sAttCode);
|
||||
}
|
||||
|
||||
return $aResults;
|
||||
}
|
||||
|
||||
return $aNameAttCodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param false $bWithAttributeDefinition
|
||||
*
|
||||
* @return array of attributes to always reload in tables
|
||||
* @throws \CoreException
|
||||
* @since 3.0.0
|
||||
*/
|
||||
final public static function GetAttributesToAlwaysLoadInTables(string $sClass, $bWithAttributeDefinition = false): array
|
||||
{
|
||||
$aResults = [];
|
||||
foreach (self::GetAttributesList($sClass) as $sAttCode) {
|
||||
$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
|
||||
if ($oAttDef->AlwaysLoadInTables()) {
|
||||
if ($bWithAttributeDefinition) {
|
||||
$aResults[$sAttCode] = $oAttDef;
|
||||
} else {
|
||||
$aResults[] = $sAttCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $aResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
|
||||
@@ -879,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')) {
|
||||
@@ -902,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;
|
||||
@@ -1077,7 +1147,8 @@ abstract class MetaModel
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
$oAtt = self::GetAttributeDef($sClass, $sAttCode);
|
||||
return $oAtt->GetPrerequisiteAttributes();
|
||||
|
||||
return $oAtt->GetPrerequisiteAttributes($sClass);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1419,7 +1490,10 @@ abstract class MetaModel
|
||||
*/
|
||||
final public static function GetFiltersList($sClass)
|
||||
{
|
||||
// cannot notify depreciation for now as this is still MASSIVELY used in iTop core !
|
||||
//DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
self::_check_subclass($sClass);
|
||||
|
||||
return array_keys(self::$m_aFilterDefs[$sClass]);
|
||||
}
|
||||
|
||||
@@ -1526,6 +1600,8 @@ abstract class MetaModel
|
||||
*/
|
||||
final public static function IsValidFilterCode($sClass, $sFilterCode)
|
||||
{
|
||||
// cannot notify depreciation for now as this is still MASSIVELY used in iTop core !
|
||||
//DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
if (!array_key_exists($sClass, self::$m_aFilterDefs)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1837,7 +1913,10 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function GetClassFilterDefs($sClass)
|
||||
{
|
||||
// cannot notify depreciation for now as this is still MASSIVELY used in iTop core !
|
||||
//DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
self::_check_subclass($sClass);
|
||||
|
||||
return self::$m_aFilterDefs[$sClass];
|
||||
}
|
||||
|
||||
@@ -1852,6 +1931,7 @@ abstract class MetaModel
|
||||
*/
|
||||
final public static function GetClassFilterDef($sClass, $sFilterCode)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
self::_check_subclass($sClass);
|
||||
if (!array_key_exists($sFilterCode, self::$m_aFilterDefs[$sClass])) {
|
||||
throw new CoreException("Unknown filter code '$sFilterCode' for class '$sClass'");
|
||||
@@ -1871,9 +1951,9 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function GetFilterLabel($sClass, $sFilterCode)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
$oFilter = self::GetClassFilterDef($sClass, $sFilterCode);
|
||||
if ($oFilter)
|
||||
{
|
||||
if ($oFilter) {
|
||||
return $oFilter->GetLabel();
|
||||
}
|
||||
|
||||
@@ -1890,11 +1970,12 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function GetFilterDescription($sClass, $sFilterCode)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
$oFilter = self::GetClassFilterDef($sClass, $sFilterCode);
|
||||
if ($oFilter)
|
||||
{
|
||||
if ($oFilter) {
|
||||
return $oFilter->GetDescription();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -1908,11 +1989,12 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function GetFilterOperators($sClass, $sFilterCode)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
$oFilter = self::GetClassFilterDef($sClass, $sFilterCode);
|
||||
if ($oFilter)
|
||||
{
|
||||
if ($oFilter) {
|
||||
return $oFilter->GetOperators();
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
@@ -1926,9 +2008,9 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function GetFilterLooseOperator($sClass, $sFilterCode)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
$oFilter = self::GetClassFilterDef($sClass, $sFilterCode);
|
||||
if ($oFilter)
|
||||
{
|
||||
if ($oFilter) {
|
||||
return $oFilter->GetLooseOperator();
|
||||
}
|
||||
|
||||
@@ -1946,9 +2028,9 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function GetFilterOpDescription($sClass, $sFilterCode, $sOpCode)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
$oFilter = self::GetClassFilterDef($sClass, $sFilterCode);
|
||||
if ($oFilter)
|
||||
{
|
||||
if ($oFilter) {
|
||||
return $oFilter->GetOpDescription($sOpCode);
|
||||
}
|
||||
|
||||
@@ -1963,6 +2045,8 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function GetFilterHTMLInput($sFilterCode)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
|
||||
return "<INPUT name=\"$sFilterCode\">";
|
||||
}
|
||||
|
||||
@@ -2137,24 +2221,21 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function EnumRelations($sClass = '')
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('Use EnumRelationsEx instead');
|
||||
$aResult = array_keys(self::$m_aRelationInfos);
|
||||
if (!empty($sClass))
|
||||
{
|
||||
if (!empty($sClass)) {
|
||||
// Return only the relations that have a meaning (i.e. for which at least one query is defined)
|
||||
// for the specified class
|
||||
$aClassRelations = array();
|
||||
foreach($aResult as $sRelCode)
|
||||
{
|
||||
foreach ($aResult as $sRelCode) {
|
||||
$aQueriesDown = self::EnumRelationQueries($sClass, $sRelCode);
|
||||
if (count($aQueriesDown) > 0)
|
||||
{
|
||||
if (count($aQueriesDown) > 0) {
|
||||
$aClassRelations[] = $sRelCode;
|
||||
}
|
||||
// Temporary patch: until the impact analysis GUI gets rewritten,
|
||||
// let's consider that "depends on" is equivalent to "impacts/up"
|
||||
// The current patch has been implemented in DBObject and MetaModel
|
||||
if ($sRelCode == 'impacts')
|
||||
{
|
||||
if ($sRelCode == 'impacts') {
|
||||
$aQueriesUp = self::EnumRelationQueries($sClass, 'impacts', false);
|
||||
if (count($aQueriesUp) > 0)
|
||||
{
|
||||
@@ -3002,8 +3083,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']));
|
||||
@@ -4051,19 +4131,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;
|
||||
@@ -4450,15 +4537,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)
|
||||
@@ -5048,7 +5135,7 @@ abstract class MetaModel
|
||||
foreach($aErrors as $sClass => $aMessages)
|
||||
{
|
||||
echo "<p>Wrong declaration for class <b>$sClass</b></p>\n";
|
||||
echo "<ul class=\"treeview\">\n";
|
||||
echo "<ul >\n";
|
||||
$i = 0;
|
||||
foreach($aMessages as $sMsg)
|
||||
{
|
||||
@@ -5512,7 +5599,7 @@ abstract class MetaModel
|
||||
$aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` ADD $sKeyFieldDefinition";
|
||||
if (!$bTableToCreate)
|
||||
{
|
||||
$aAlterTableItems[$sTable][$sKeyField] = "ADD $sKeyFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sKeyField] = "ADD $sKeyFieldDefinition";
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -5525,7 +5612,7 @@ abstract class MetaModel
|
||||
$aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)";
|
||||
if (!$bTableToCreate)
|
||||
{
|
||||
$aAlterTableItems[$sTable][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
|
||||
}
|
||||
}
|
||||
if (self::IsAutoIncrementKey($sClass) && !CMDBSource::IsAutoIncrement($sTable, $sKeyField))
|
||||
@@ -5534,7 +5621,7 @@ abstract class MetaModel
|
||||
$aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` $sKeyFieldDefinition";
|
||||
if (!$bTableToCreate)
|
||||
{
|
||||
$aAlterTableItems[$sTable][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sKeyField] = "CHANGE `$sKeyField` $sKeyFieldDefinition";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5597,7 +5684,7 @@ abstract class MetaModel
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAlterTableItems[$sTable][$sField] = "ADD $sFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sField] = "ADD $sFieldDefinition";
|
||||
$aAdditionalRequests = self::GetAdditionalRequestAfterAlter($sClass, $sTable, $sField);
|
||||
if (!empty($aAdditionalRequests))
|
||||
{
|
||||
@@ -5641,7 +5728,7 @@ abstract class MetaModel
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAlterTableItems[$sTable][] = "ADD $sIndexType `$sIndexName` ($sColumns)";
|
||||
$aAlterTableItems[$sTable]['index'][] = "ADD $sIndexType `$sIndexName` ($sColumns)";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5682,7 +5769,7 @@ abstract class MetaModel
|
||||
if (CMDBSource::HasIndex($sTable, $sField))
|
||||
{
|
||||
$aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` DROP INDEX `$sIndexName`";
|
||||
$aAlterTableItems[$sTable][] = "DROP INDEX `$sIndexName`";
|
||||
$aAlterTableItems[$sTable]['index'][] = "DROP INDEX `$sIndexName`";
|
||||
}
|
||||
$sSugFixAfterChange = "ALTER TABLE `$sTable` ADD $sIndexType `$sIndexName` ($sColumns)";
|
||||
$sAlterTableItemsAfterChange = "ADD $sIndexType `$sIndexName` ($sColumns)";
|
||||
@@ -5696,7 +5783,7 @@ abstract class MetaModel
|
||||
{
|
||||
$aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found <code>$sActualFieldSpec</code> while expecting <code>$sDBFieldSpec</code>";
|
||||
$aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` $sFieldDefinition";
|
||||
$aAlterTableItems[$sTable][$sField] = "CHANGE `$sField` $sFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sField] = "CHANGE `$sField` $sFieldDefinition";
|
||||
}
|
||||
|
||||
// Create indexes (external keys only... so far)
|
||||
@@ -5711,7 +5798,7 @@ abstract class MetaModel
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAlterTableItems[$sTable][] = $sAlterTableItemsAfterChange;
|
||||
$aAlterTableItems[$sTable]['index'][] = $sAlterTableItemsAfterChange;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5771,9 +5858,12 @@ abstract class MetaModel
|
||||
{
|
||||
$aAlterTableItems[$sTable] = array();
|
||||
}
|
||||
array_unshift($aAlterTableItems[$sTable], "DROP INDEX `$sIndexId`");
|
||||
if (isset($aAlterTableItems[$sTable]['index']))
|
||||
{
|
||||
array_unshift($aAlterTableItems[$sTable]['index'], "DROP INDEX `$sIndexId`");
|
||||
}
|
||||
}
|
||||
$aAlterTableItems[$sTable][] = "ADD INDEX `$sIndexId` ($sColumns)";
|
||||
$aAlterTableItems[$sTable]['index'][] = "ADD INDEX `$sIndexId` ($sColumns)";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5791,7 +5881,7 @@ abstract class MetaModel
|
||||
// without specifying the value of this unknown column
|
||||
$sFieldDefinition = "`$sField` ".CMDBSource::GetFieldType($sTable, $sField).' NULL';
|
||||
$aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` $sFieldDefinition";
|
||||
$aAlterTableItems[$sTable][$sField] = "CHANGE `$sField` $sFieldDefinition";
|
||||
$aAlterTableItems[$sTable]['field'][$sField] = "CHANGE `$sField` $sFieldDefinition";
|
||||
}
|
||||
$aSugFix[$sClass][$sAttCode][] = "-- Recommended action: ALTER TABLE `$sTable` DROP `$sField`";
|
||||
}
|
||||
@@ -5810,7 +5900,10 @@ abstract class MetaModel
|
||||
{
|
||||
$aAlterTableItems[$sTable] = array();
|
||||
}
|
||||
array_unshift($aAlterTableItems[$sTable], "DROP INDEX `$sIndexId`");
|
||||
if (isset($aAlterTableItems[$sTable]['index']))
|
||||
{
|
||||
array_unshift($aAlterTableItems[$sTable]['index'], "DROP INDEX `$sIndexId`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5842,8 +5935,16 @@ abstract class MetaModel
|
||||
}
|
||||
foreach ($aAlterTableItems as $sTable => $aChangeList)
|
||||
{
|
||||
$sChangeList = implode(', ', $aChangeList);
|
||||
$aCondensedQueries[] = "ALTER TABLE `$sTable` $sChangeList";
|
||||
if (isset($aAlterTableItems[$sTable]['field']))
|
||||
{
|
||||
$sChangeList = implode(', ', $aChangeList['field']);
|
||||
$aCondensedQueries[] = "ALTER TABLE `$sTable` $sChangeList";
|
||||
}
|
||||
if (isset($aAlterTableItems[$sTable]['index']))
|
||||
{
|
||||
$sChangeList = implode(', ', $aChangeList['index']);
|
||||
$aCondensedQueries[] = "ALTER TABLE `$sTable` $sChangeList";
|
||||
}
|
||||
// Add request right after the ALTER TABLE
|
||||
if (isset($aPostTableAlteration[$sTable]))
|
||||
{
|
||||
@@ -5869,7 +5970,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
|
||||
@@ -5893,6 +5995,7 @@ abstract class MetaModel
|
||||
$aSugFix[$sClass]['*'][] = "DROP VIEW `$sView`";
|
||||
}
|
||||
}
|
||||
|
||||
return array($aErrors, $aSugFix);
|
||||
}
|
||||
|
||||
@@ -6428,6 +6531,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
|
||||
@@ -6438,8 +6542,7 @@ abstract class MetaModel
|
||||
// Set log ASAP
|
||||
if (self::$m_oConfig->GetLogGlobal())
|
||||
{
|
||||
if (self::$m_oConfig->GetLogIssue())
|
||||
{
|
||||
if (self::$m_oConfig->GetLogIssue()) {
|
||||
self::$m_bLogIssue = true;
|
||||
IssueLog::Enable(APPROOT.'log/error.log');
|
||||
}
|
||||
@@ -6448,6 +6551,8 @@ abstract class MetaModel
|
||||
|
||||
ToolsLog::Enable(APPROOT.'log/tools.log');
|
||||
DeadLockLog::Enable();
|
||||
DeprecatedCallsLog::Enable();
|
||||
ExceptionLog::Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -6504,6 +6609,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)
|
||||
{
|
||||
@@ -6875,7 +6981,7 @@ abstract class MetaModel
|
||||
* @param int $iKey id value of the object to retrieve
|
||||
* @param bool $bMustBeFound see throws ArchivedObjectException
|
||||
* @param bool $bAllowAllData if true then user rights will be bypassed - use with care!
|
||||
* @param null $aModifierProperties
|
||||
* @param array $aModifierProperties properties for {@see iQueryModifier} impl
|
||||
*
|
||||
* @return \DBObject null if : (the object is not found) or (archive mode disabled and object is archived and
|
||||
* $bMustBeFound=false)
|
||||
@@ -6895,12 +7001,9 @@ abstract class MetaModel
|
||||
|
||||
if (!utils::IsArchiveMode() && $oObject->IsArchived())
|
||||
{
|
||||
if ($bMustBeFound)
|
||||
{
|
||||
if ($bMustBeFound) {
|
||||
throw new ArchivedObjectException("The object $sClass::$iKey is archived");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -6908,6 +7011,35 @@ abstract class MetaModel
|
||||
return $oObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param int $iKey
|
||||
*
|
||||
* @return bool True if the object of $sClass and $iKey exists in the DB -no matter the current user restrictions-, false otherwise meaning:
|
||||
* - It could be in memory for now and is not persisted yet
|
||||
* - It is neither in memory nor DB
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
* @since 3.0.0 N°4173
|
||||
*/
|
||||
public static function IsObjectInDB(string $sClass, int $iKey): bool
|
||||
{
|
||||
// Note: We take the root class to ensure that there is a corresponding table in the DB
|
||||
// as some intermediate classes can have no table in the DB.
|
||||
$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
|
||||
$sTable = MetaModel::DBGetTable($sRootClass);
|
||||
$sKeyCol = MetaModel::DBGetKey($sRootClass);
|
||||
$sEscapedKey = CMDBSource::Quote($iKey);
|
||||
|
||||
$sQuery = "SELECT count(*) FROM `{$sTable}` WHERE `{$sKeyCol}` = {$sEscapedKey}";
|
||||
$iCount = (int) CMDBSource::QueryToScalar($sQuery);
|
||||
|
||||
return $iCount === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the specified class and id. If the object is archived it will be returned anyway (this is for pre-2.4
|
||||
* module compatibility, see N.1108)
|
||||
@@ -7108,19 +7240,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)
|
||||
{
|
||||
return ItopCounter::IncClass($sClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletion of records, bypassing {@link DBObject::DBDelete} !!!
|
||||
* It is NOT recommended to use this shortcut
|
||||
@@ -7141,9 +7260,9 @@ abstract class MetaModel
|
||||
*/
|
||||
public static function BulkDelete(DBObjectSearch $oFilter)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
|
||||
$sSQL = $oFilter->MakeDeleteQuery();
|
||||
if (!self::DBIsReadOnly())
|
||||
{
|
||||
if (!self::DBIsReadOnly()) {
|
||||
CMDBSource::Query($sSQL);
|
||||
}
|
||||
}
|
||||
@@ -7162,10 +7281,10 @@ abstract class MetaModel
|
||||
{
|
||||
// $aValues is an array of $sAttCode => $value
|
||||
$sSQL = $oFilter->MakeUpdateQuery($aValues);
|
||||
if (!self::DBIsReadOnly())
|
||||
{
|
||||
if (!self::DBIsReadOnly()) {
|
||||
CMDBSource::Query($sSQL);
|
||||
}
|
||||
|
||||
return CMDBSource::AffectedRows();
|
||||
}
|
||||
|
||||
@@ -7345,9 +7464,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);
|
||||
|
||||
@@ -7357,7 +7478,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))
|
||||
@@ -7606,14 +7727,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");
|
||||
|
||||
@@ -242,6 +242,8 @@ class iTopMutex
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @since 2.7.5 3.0.0 N°3968 specify `wait_timeout` for the mutex dedicated connection
|
||||
*/
|
||||
public function InitMySQLSession()
|
||||
{
|
||||
@@ -254,10 +256,36 @@ class iTopMutex
|
||||
|
||||
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
|
||||
|
||||
if (!$this->hDBLink)
|
||||
{
|
||||
if (!$this->hDBLink) {
|
||||
throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
|
||||
}
|
||||
|
||||
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,
|
||||
// since the lock will be released if/when the connection times out.
|
||||
// Source https://dev.mysql.com/doc/refman/5.7/en/locking-functions.html :
|
||||
// > A lock obtained with GET_LOCK() is released explicitly by executing RELEASE_LOCK() or implicitly when your session terminates
|
||||
//
|
||||
// BEWARE: If you want to check the value of this variable, when run from an interactive console `SHOW VARIABLES LIKE 'wait_timeout'`
|
||||
// will actually returns the value of the variable `interactive_timeout` which may be quite different.
|
||||
$sSql = "SHOW VARIABLES LIKE 'wait_timeout'";
|
||||
$result = mysqli_query($this->hDBLink, $sSql);
|
||||
if (!$result) {
|
||||
throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')');
|
||||
}
|
||||
if ($aRow = mysqli_fetch_array($result, MYSQLI_BOTH)) {
|
||||
$iTimeout = (int)$aRow[1];
|
||||
} else {
|
||||
mysqli_free_result($result);
|
||||
throw new Exception("No result for query '".$sSql."'");
|
||||
}
|
||||
mysqli_free_result($result);
|
||||
|
||||
if ($iTimeout < 86400) {
|
||||
$result = mysqli_query($this->hDBLink, 'SET SESSION wait_timeout=86400');
|
||||
if ($result === false) {
|
||||
throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -117,6 +117,9 @@ abstract class Expression {
|
||||
*/
|
||||
public function Render(&$aArgs = null, $bRetrofitParams = false)
|
||||
{
|
||||
// cannot notify depreciation for now as this is still MASSIVELY used in iTop core !
|
||||
//DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use RenderExpression');
|
||||
|
||||
return $this->RenderExpression(false, $aArgs, $bRetrofitParams);
|
||||
}
|
||||
|
||||
|
||||
@@ -597,7 +597,7 @@ static public $yy_action = array(
|
||||
** defined, then do no error processing.
|
||||
*/
|
||||
const YYNOCODE = 119;
|
||||
const YYSTACKDEPTH = 100;
|
||||
const YYSTACKDEPTH = 1000;
|
||||
const YYNSTATE = 175;
|
||||
const YYNRULE = 125;
|
||||
const YYERRORSYMBOL = 76;
|
||||
@@ -1175,6 +1175,10 @@ static public $yy_action = array(
|
||||
}
|
||||
/* Here code is inserted which will execute if the parser
|
||||
** stack ever overflows */
|
||||
#line 30 "..\oql-parser.y"
|
||||
|
||||
throw new OQLParserStackOverFlowException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
|
||||
#line 1186 "..\oql-parser.php"
|
||||
return;
|
||||
}
|
||||
$yytos = new OQLParser_yyStackEntry;
|
||||
@@ -1474,116 +1478,116 @@ static public $yy_action = array(
|
||||
** function yy_r0($yymsp){ ... } // User supplied code
|
||||
** #line <lineno> <thisfile>
|
||||
*/
|
||||
#line 29 "..\oql-parser.y"
|
||||
#line 37 "..\oql-parser.y"
|
||||
function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1483 "..\oql-parser.php"
|
||||
#line 33 "..\oql-parser.y"
|
||||
#line 1488 "..\oql-parser.php"
|
||||
#line 41 "..\oql-parser.y"
|
||||
function yy_r3(){
|
||||
$this->_retvalue = new OqlUnionQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
#line 1488 "..\oql-parser.php"
|
||||
#line 40 "..\oql-parser.y"
|
||||
#line 1493 "..\oql-parser.php"
|
||||
#line 48 "..\oql-parser.y"
|
||||
function yy_r5(){
|
||||
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor));
|
||||
}
|
||||
#line 1493 "..\oql-parser.php"
|
||||
#line 43 "..\oql-parser.y"
|
||||
#line 1498 "..\oql-parser.php"
|
||||
#line 51 "..\oql-parser.y"
|
||||
function yy_r6(){
|
||||
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor));
|
||||
}
|
||||
#line 1498 "..\oql-parser.php"
|
||||
#line 47 "..\oql-parser.y"
|
||||
#line 1503 "..\oql-parser.php"
|
||||
#line 55 "..\oql-parser.y"
|
||||
function yy_r7(){
|
||||
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -4]->minor);
|
||||
}
|
||||
#line 1503 "..\oql-parser.php"
|
||||
#line 50 "..\oql-parser.y"
|
||||
#line 1508 "..\oql-parser.php"
|
||||
#line 58 "..\oql-parser.y"
|
||||
function yy_r8(){
|
||||
$this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -6]->minor);
|
||||
}
|
||||
#line 1508 "..\oql-parser.php"
|
||||
#line 55 "..\oql-parser.y"
|
||||
#line 1513 "..\oql-parser.php"
|
||||
#line 63 "..\oql-parser.y"
|
||||
function yy_r9(){
|
||||
$this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
#line 1513 "..\oql-parser.php"
|
||||
#line 58 "..\oql-parser.y"
|
||||
#line 1518 "..\oql-parser.php"
|
||||
#line 66 "..\oql-parser.y"
|
||||
function yy_r10(){
|
||||
array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
|
||||
$this->_retvalue = $this->yystack[$this->yyidx + -2]->minor;
|
||||
}
|
||||
#line 1519 "..\oql-parser.php"
|
||||
#line 63 "..\oql-parser.y"
|
||||
#line 1524 "..\oql-parser.php"
|
||||
#line 71 "..\oql-parser.y"
|
||||
function yy_r11(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1522 "..\oql-parser.php"
|
||||
#line 64 "..\oql-parser.y"
|
||||
#line 1527 "..\oql-parser.php"
|
||||
#line 72 "..\oql-parser.y"
|
||||
function yy_r12(){ $this->_retvalue = null; }
|
||||
#line 1525 "..\oql-parser.php"
|
||||
#line 66 "..\oql-parser.y"
|
||||
#line 1530 "..\oql-parser.php"
|
||||
#line 74 "..\oql-parser.y"
|
||||
function yy_r13(){
|
||||
// insert the join statement on top of the existing list
|
||||
array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor);
|
||||
// and return the updated array
|
||||
$this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
|
||||
}
|
||||
#line 1533 "..\oql-parser.php"
|
||||
#line 72 "..\oql-parser.y"
|
||||
#line 1538 "..\oql-parser.php"
|
||||
#line 80 "..\oql-parser.y"
|
||||
function yy_r14(){
|
||||
$this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
#line 1538 "..\oql-parser.php"
|
||||
#line 78 "..\oql-parser.y"
|
||||
#line 1543 "..\oql-parser.php"
|
||||
#line 86 "..\oql-parser.y"
|
||||
function yy_r16(){
|
||||
// create an array with one single item
|
||||
$this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
#line 1544 "..\oql-parser.php"
|
||||
#line 83 "..\oql-parser.y"
|
||||
#line 1549 "..\oql-parser.php"
|
||||
#line 91 "..\oql-parser.y"
|
||||
function yy_r17(){
|
||||
// create an array with one single item
|
||||
$this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
#line 1550 "..\oql-parser.php"
|
||||
#line 88 "..\oql-parser.y"
|
||||
function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1553 "..\oql-parser.php"
|
||||
#line 89 "..\oql-parser.y"
|
||||
function yy_r19(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1556 "..\oql-parser.php"
|
||||
#line 90 "..\oql-parser.y"
|
||||
function yy_r20(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1559 "..\oql-parser.php"
|
||||
#line 91 "..\oql-parser.y"
|
||||
function yy_r21(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1562 "..\oql-parser.php"
|
||||
#line 92 "..\oql-parser.y"
|
||||
function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1565 "..\oql-parser.php"
|
||||
#line 93 "..\oql-parser.y"
|
||||
function yy_r23(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1568 "..\oql-parser.php"
|
||||
#line 94 "..\oql-parser.y"
|
||||
function yy_r24(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1571 "..\oql-parser.php"
|
||||
#line 95 "..\oql-parser.y"
|
||||
function yy_r25(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1574 "..\oql-parser.php"
|
||||
#line 1555 "..\oql-parser.php"
|
||||
#line 96 "..\oql-parser.y"
|
||||
function yy_r26(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1577 "..\oql-parser.php"
|
||||
function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1558 "..\oql-parser.php"
|
||||
#line 97 "..\oql-parser.y"
|
||||
function yy_r19(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1561 "..\oql-parser.php"
|
||||
#line 98 "..\oql-parser.y"
|
||||
function yy_r27(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1580 "..\oql-parser.php"
|
||||
function yy_r20(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1564 "..\oql-parser.php"
|
||||
#line 99 "..\oql-parser.y"
|
||||
function yy_r21(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1567 "..\oql-parser.php"
|
||||
#line 100 "..\oql-parser.y"
|
||||
function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1570 "..\oql-parser.php"
|
||||
#line 101 "..\oql-parser.y"
|
||||
function yy_r23(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1573 "..\oql-parser.php"
|
||||
#line 102 "..\oql-parser.y"
|
||||
function yy_r24(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1576 "..\oql-parser.php"
|
||||
#line 103 "..\oql-parser.y"
|
||||
function yy_r31(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); }
|
||||
#line 1583 "..\oql-parser.php"
|
||||
function yy_r25(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1579 "..\oql-parser.php"
|
||||
#line 104 "..\oql-parser.y"
|
||||
function yy_r32(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; }
|
||||
#line 1586 "..\oql-parser.php"
|
||||
#line 105 "..\oql-parser.y"
|
||||
function yy_r33(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1589 "..\oql-parser.php"
|
||||
function yy_r26(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1582 "..\oql-parser.php"
|
||||
#line 106 "..\oql-parser.y"
|
||||
function yy_r27(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1585 "..\oql-parser.php"
|
||||
#line 111 "..\oql-parser.y"
|
||||
function yy_r31(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); }
|
||||
#line 1588 "..\oql-parser.php"
|
||||
#line 112 "..\oql-parser.y"
|
||||
function yy_r32(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; }
|
||||
#line 1591 "..\oql-parser.php"
|
||||
#line 113 "..\oql-parser.y"
|
||||
function yy_r33(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1594 "..\oql-parser.php"
|
||||
#line 119 "..\oql-parser.y"
|
||||
function yy_r37(){
|
||||
if ($this->yystack[$this->yyidx + -1]->minor == 'MATCHES')
|
||||
{
|
||||
@@ -1594,44 +1598,44 @@ static public $yy_action = array(
|
||||
$this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor);
|
||||
}
|
||||
}
|
||||
#line 1601 "..\oql-parser.php"
|
||||
#line 128 "..\oql-parser.y"
|
||||
#line 1606 "..\oql-parser.php"
|
||||
#line 136 "..\oql-parser.y"
|
||||
function yy_r42(){
|
||||
$this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor);
|
||||
}
|
||||
#line 1606 "..\oql-parser.php"
|
||||
#line 131 "..\oql-parser.y"
|
||||
#line 1611 "..\oql-parser.php"
|
||||
#line 139 "..\oql-parser.y"
|
||||
function yy_r43(){
|
||||
$this->_retvalue = new NestedQueryOqlExpression($this->yystack[$this->yyidx + -1]->minor);
|
||||
}
|
||||
#line 1611 "..\oql-parser.php"
|
||||
#line 146 "..\oql-parser.y"
|
||||
#line 1616 "..\oql-parser.php"
|
||||
#line 154 "..\oql-parser.y"
|
||||
function yy_r47(){
|
||||
$this->_retvalue = array();
|
||||
}
|
||||
#line 1616 "..\oql-parser.php"
|
||||
#line 157 "..\oql-parser.y"
|
||||
#line 1621 "..\oql-parser.php"
|
||||
#line 165 "..\oql-parser.y"
|
||||
function yy_r51(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1619 "..\oql-parser.php"
|
||||
#line 170 "..\oql-parser.y"
|
||||
#line 1624 "..\oql-parser.php"
|
||||
#line 178 "..\oql-parser.y"
|
||||
function yy_r61(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1622 "..\oql-parser.php"
|
||||
#line 172 "..\oql-parser.y"
|
||||
#line 1627 "..\oql-parser.php"
|
||||
#line 180 "..\oql-parser.y"
|
||||
function yy_r63(){ $this->_retvalue = new ScalarOqlExpression(null); }
|
||||
#line 1625 "..\oql-parser.php"
|
||||
#line 174 "..\oql-parser.y"
|
||||
#line 1630 "..\oql-parser.php"
|
||||
#line 182 "..\oql-parser.y"
|
||||
function yy_r64(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1628 "..\oql-parser.php"
|
||||
#line 175 "..\oql-parser.y"
|
||||
#line 1633 "..\oql-parser.php"
|
||||
#line 183 "..\oql-parser.y"
|
||||
function yy_r65(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); }
|
||||
#line 1631 "..\oql-parser.php"
|
||||
#line 176 "..\oql-parser.y"
|
||||
#line 1636 "..\oql-parser.php"
|
||||
#line 184 "..\oql-parser.y"
|
||||
function yy_r66(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1634 "..\oql-parser.php"
|
||||
#line 179 "..\oql-parser.y"
|
||||
#line 1639 "..\oql-parser.php"
|
||||
#line 187 "..\oql-parser.y"
|
||||
function yy_r67(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); }
|
||||
#line 1637 "..\oql-parser.php"
|
||||
#line 181 "..\oql-parser.y"
|
||||
#line 1642 "..\oql-parser.php"
|
||||
#line 189 "..\oql-parser.y"
|
||||
function yy_r68(){
|
||||
if ($this->yystack[$this->yyidx + 0]->minor[0] == '`')
|
||||
{
|
||||
@@ -1643,22 +1647,22 @@ static public $yy_action = array(
|
||||
}
|
||||
$this->_retvalue = new OqlName($name, $this->m_iColPrev);
|
||||
}
|
||||
#line 1650 "..\oql-parser.php"
|
||||
#line 192 "..\oql-parser.y"
|
||||
#line 1655 "..\oql-parser.php"
|
||||
#line 200 "..\oql-parser.y"
|
||||
function yy_r69(){$this->_retvalue=(int)$this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1653 "..\oql-parser.php"
|
||||
#line 193 "..\oql-parser.y"
|
||||
#line 1658 "..\oql-parser.php"
|
||||
#line 201 "..\oql-parser.y"
|
||||
function yy_r70(){$this->_retvalue=(int)-$this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1656 "..\oql-parser.php"
|
||||
#line 194 "..\oql-parser.y"
|
||||
#line 1661 "..\oql-parser.php"
|
||||
#line 202 "..\oql-parser.y"
|
||||
function yy_r71(){$this->_retvalue=new OqlHexValue($this->yystack[$this->yyidx + 0]->minor); }
|
||||
#line 1659 "..\oql-parser.php"
|
||||
#line 195 "..\oql-parser.y"
|
||||
#line 1664 "..\oql-parser.php"
|
||||
#line 203 "..\oql-parser.y"
|
||||
function yy_r72(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); }
|
||||
#line 1662 "..\oql-parser.php"
|
||||
#line 198 "..\oql-parser.y"
|
||||
#line 1667 "..\oql-parser.php"
|
||||
#line 206 "..\oql-parser.y"
|
||||
function yy_r73(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; }
|
||||
#line 1665 "..\oql-parser.php"
|
||||
#line 1670 "..\oql-parser.php"
|
||||
|
||||
/**
|
||||
* placeholder for the left hand side in a reduce operation.
|
||||
@@ -1759,6 +1763,10 @@ static public $yy_action = array(
|
||||
}
|
||||
/* Here code is inserted which will be executed whenever the
|
||||
** parser fails */
|
||||
#line 33 "..\oql-parser.y"
|
||||
|
||||
throw new OQLParserParseFailureException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
|
||||
#line 1775 "..\oql-parser.php"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1772,8 +1780,8 @@ static public $yy_action = array(
|
||||
{
|
||||
#line 25 "..\oql-parser.y"
|
||||
|
||||
throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
|
||||
#line 1781 "..\oql-parser.php"
|
||||
throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
|
||||
#line 1791 "..\oql-parser.php"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1940,19 +1948,47 @@ throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCo
|
||||
} while ($yymajor != self::YYNOCODE && $this->yyidx >= 0);
|
||||
}
|
||||
}
|
||||
#line 263 "..\oql-parser.y"
|
||||
#line 271 "..\oql-parser.y"
|
||||
|
||||
|
||||
class OQLParserException extends OQLException
|
||||
{
|
||||
public function __construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue)
|
||||
{
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserSyntaxErrorException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue)
|
||||
{
|
||||
$sIssue = "Unexpected token $sTokenName";
|
||||
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserStackOverFlowException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol)
|
||||
{
|
||||
$sIssue = "Stack overflow";
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserParseFailureException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol)
|
||||
{
|
||||
$sIssue = "Unexpected token $sTokenName";
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParser extends OQLParserRaw
|
||||
{
|
||||
// dirty, but working for us (no other mean to get the final result :-(
|
||||
@@ -2005,4 +2041,4 @@ class OQLParser extends OQLParserRaw
|
||||
}
|
||||
}
|
||||
|
||||
#line 2014 "..\oql-parser.php"
|
||||
#line 2052 "..\oql-parser.php"
|
||||
|
||||
@@ -23,7 +23,15 @@ later : solve the 2 remaining shift-reduce conflicts (JOIN)
|
||||
%name OQLParser_
|
||||
%declare_class {class OQLParserRaw}
|
||||
%syntax_error {
|
||||
throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
|
||||
throw new OQLParserSyntaxErrorException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
|
||||
}
|
||||
/* Bug N°4052 Parser stack size too small for huge OQL requests */
|
||||
%stack_size 1000
|
||||
%stack_overflow {
|
||||
throw new OQLParserStackOverFlowException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
|
||||
}
|
||||
%parse_failure {
|
||||
throw new OQLParserParseFailureException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol);
|
||||
}
|
||||
|
||||
result ::= union(X). { $this->my_result = X; }
|
||||
@@ -263,15 +271,43 @@ func_name(A) ::= F_INET_NTOA(X). { A=X; }
|
||||
%code {
|
||||
|
||||
class OQLParserException extends OQLException
|
||||
{
|
||||
public function __construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue)
|
||||
{
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserSyntaxErrorException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue)
|
||||
{
|
||||
$sIssue = "Unexpected token $sTokenName";
|
||||
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserStackOverFlowException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol)
|
||||
{
|
||||
$sIssue = "Stack overflow";
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParserParseFailureException extends OQLParserException
|
||||
{
|
||||
public function __construct($sInput, $iLine, $iCol)
|
||||
{
|
||||
$sIssue = "Unexpected token $sTokenName";
|
||||
|
||||
parent::__construct($sIssue, $sInput, $iLine, $iCol, '');
|
||||
}
|
||||
}
|
||||
|
||||
class OQLParser extends OQLParserRaw
|
||||
{
|
||||
// dirty, but working for us (no other mean to get the final result :-(
|
||||
|
||||
@@ -1 +1 @@
|
||||
2020-09-29
|
||||
2021-06-03
|
||||
@@ -55,7 +55,7 @@ class OQLClassTreeOptimizer
|
||||
$sJoinedClass = $oJoin->GetOOQLClassNode()->GetNodeClass();
|
||||
$sExtKeyAttCode = $oJoin->GetLeftField();
|
||||
$oExtKeyAttDef = MetaModel::GetAttributeDef($oCurrentClassNode->GetNodeClass(), $sExtKeyAttCode);
|
||||
if ($sJoinedClass == $oExtKeyAttDef->GetTargetClass()) {
|
||||
if (($oExtKeyAttDef instanceof AttributeExternalKey) && ($sJoinedClass == $oExtKeyAttDef->GetTargetClass())) {
|
||||
// The join is not used, remove from tree
|
||||
$oCurrentClassNode->RemoveJoin($sLeftKey, $index);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -168,7 +174,6 @@ 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
|
||||
* @return string
|
||||
@@ -201,6 +206,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()
|
||||
{
|
||||
@@ -389,7 +403,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 +417,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'))
|
||||
{
|
||||
$sCSSClass= 'caselog_entry';
|
||||
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
|
||||
if (!is_null($aTransfoHandler))
|
||||
{
|
||||
@@ -433,7 +443,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 +465,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,48 +483,84 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a new entry to the log or merge the given text into the currently modified entry
|
||||
* Add a new entry to the log or merge the given text into the currently modified entry
|
||||
* and updates the internal index
|
||||
* @param $sText string The text of the new entry
|
||||
*
|
||||
* @param string $sText The text of the new entry
|
||||
* @param string $sOnBehalfOf Display this name instead of current user name
|
||||
* @param null|int $iOnBehalfOfId Use this UserId to author this Entry. If $sOnBehalfOf equals '', it'll be replaced by this User friendlyname
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*
|
||||
* @since 3.0.0 New $iOnBehalfOfId parameter
|
||||
* @since 3.0.0 May throw \ArchivedObjectException exception
|
||||
*/
|
||||
public function AddLogEntry($sText, $sOnBehalfOf = '')
|
||||
public function AddLogEntry(string $sText, $sOnBehalfOf = '', $iOnBehalfOfId = null)
|
||||
{
|
||||
$sText = HTMLSanitizer::Sanitize($sText);
|
||||
$sDate = date(AttributeDateTime::GetInternalFormat());
|
||||
if ($sOnBehalfOf == '')
|
||||
{
|
||||
if ($sOnBehalfOf == '' && $iOnBehalfOfId === null) {
|
||||
$sOnBehalfOf = UserRights::GetUserFriendlyName();
|
||||
$iUserId = UserRights::GetUserId();
|
||||
}
|
||||
elseif ($iOnBehalfOfId !== null) {
|
||||
$iUserId = $iOnBehalfOfId;
|
||||
/* @var User $oUser */
|
||||
$oUser = MetaModel::GetObject('User', $iUserId, false, true);
|
||||
if ($oUser !== null && $sOnBehalfOf === '') {
|
||||
$sOnBehalfOf = $oUser->GetFriendlyName();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$iUserId = null;
|
||||
@@ -553,7 +595,6 @@ class ormCaseLog {
|
||||
$this->m_bModified = true;
|
||||
}
|
||||
|
||||
|
||||
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
|
||||
{
|
||||
if (isset($oJson->user_id))
|
||||
@@ -630,7 +671,6 @@ class ormCaseLog {
|
||||
$this->m_bModified = true;
|
||||
}
|
||||
|
||||
|
||||
public function GetModifiedEntry($sFormat = 'text')
|
||||
{
|
||||
$sModifiedEntry = '';
|
||||
@@ -709,4 +749,3 @@ class ormCaseLog {
|
||||
return $sText;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -118,7 +118,8 @@ class ormDocument
|
||||
else
|
||||
{
|
||||
$data = $this->GetData();
|
||||
$sResult = htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8').' [ '.$this->GetMimeType().', size: '.strlen($data).' byte(s) ]<br/>';
|
||||
$sSize = utils::BytesToFriendlyFormat(strlen($data));
|
||||
$sResult = htmlentities($this->GetFileName(), ENT_QUOTES, 'UTF-8').' ('.$sSize.')<br/>';
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user