mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-22 01:58:47 +02:00
Compare commits
1164 Commits
3.0.1-desi
...
3.1.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c4289a491 | ||
|
|
8940051c3d | ||
|
|
72dad5dd07 | ||
|
|
dc38db1c3d | ||
|
|
45df1002ec | ||
|
|
737388053f | ||
|
|
d0b000d125 | ||
|
|
1d0ce1862d | ||
|
|
c0589715c1 | ||
|
|
8b18fd7cc0 | ||
|
|
9f81e4875a | ||
|
|
33717b9610 | ||
|
|
b97a464bb1 | ||
|
|
58c0da7e87 | ||
|
|
4203382920 | ||
|
|
825e402dd6 | ||
|
|
fa75c8964a | ||
|
|
fc58c6f4ce | ||
|
|
ef4e38f6b9 | ||
|
|
4f102d764a | ||
|
|
dbd58cfeb6 | ||
|
|
1086eaaffe | ||
|
|
f7ee21f1d7 | ||
|
|
35b0b16e20 | ||
|
|
ca72ec736e | ||
|
|
e63d66aa5d | ||
|
|
2803f0be49 | ||
|
|
278b0d643f | ||
|
|
569045f675 | ||
|
|
25989bc00f | ||
|
|
fc60759db5 | ||
|
|
3a931ff79e | ||
|
|
f6bd380ad0 | ||
|
|
97c4ecf8bc | ||
|
|
5f4ef74d34 | ||
|
|
6184dbafff | ||
|
|
630c1dcdc8 | ||
|
|
cb84cbf7a8 | ||
|
|
b025f86b21 | ||
|
|
5a76175ec1 | ||
|
|
ce162f05da | ||
|
|
d358c9e8fa | ||
|
|
86a208a86d | ||
|
|
372888c7a7 | ||
|
|
547a520ab1 | ||
|
|
8bf788dcb9 | ||
|
|
a157e754a9 | ||
|
|
eae84ab399 | ||
|
|
4d4d3bd9a2 | ||
|
|
9f9630d011 | ||
|
|
de1c58a84c | ||
|
|
1fe9520b7e | ||
|
|
3fbc5e1ce0 | ||
|
|
786e7d647c | ||
|
|
99b7d66cf2 | ||
|
|
1768274aaf | ||
|
|
7cc1e33950 | ||
|
|
72a586e490 | ||
|
|
0cce58e0cf | ||
|
|
53b68f88b8 | ||
|
|
7fb10da013 | ||
|
|
0b1352fe37 | ||
|
|
e0cc00e772 | ||
|
|
002e52cc3e | ||
|
|
bbea978259 | ||
|
|
bddfa4cd7a | ||
|
|
12ef74ec42 | ||
|
|
cd1b441025 | ||
|
|
0f97b93617 | ||
|
|
b61c8ea4ca | ||
|
|
f48df74933 | ||
|
|
6fb00cbbf3 | ||
|
|
af2421a2cb | ||
|
|
0374e303e4 | ||
|
|
27bd78d76b | ||
|
|
bc3b43958c | ||
|
|
fd4a2f17d2 | ||
|
|
d741656028 | ||
|
|
f7e8bd31f5 | ||
|
|
1e37370789 | ||
|
|
5844717980 | ||
|
|
9437968d0e | ||
|
|
e72ed33a40 | ||
|
|
2a825c6ba0 | ||
|
|
6b8d9ea08d | ||
|
|
52d57293c9 | ||
|
|
40cebf1eb7 | ||
|
|
f1d6f3e5c2 | ||
|
|
834c4a2654 | ||
|
|
ed1a076ebb | ||
|
|
1dcf38f2ea | ||
|
|
2f034253e7 | ||
|
|
a10ac7fdcb | ||
|
|
3775ccea74 | ||
|
|
f743971c93 | ||
|
|
dd213df9c4 | ||
|
|
96825c646e | ||
|
|
1152b2f401 | ||
|
|
8f7003c694 | ||
|
|
fb1ceebaa4 | ||
|
|
9482139b5a | ||
|
|
7ea2315323 | ||
|
|
9487d63b9c | ||
|
|
3d9a85d577 | ||
|
|
b2397fc570 | ||
|
|
0d15b01b3f | ||
|
|
f7e87c1ea1 | ||
|
|
3ca4122673 | ||
|
|
f21f6aec30 | ||
|
|
c76d351379 | ||
|
|
efa20e77d0 | ||
|
|
effc4141c7 | ||
|
|
b4813de0a5 | ||
|
|
f75a51d59e | ||
|
|
8e3bad11ae | ||
|
|
13550fd643 | ||
|
|
44977d69b6 | ||
|
|
15f108152c | ||
|
|
9be167cf5e | ||
|
|
34688c2bf6 | ||
|
|
9dc54d6e4c | ||
|
|
640b1b9176 | ||
|
|
c1922e2b3f | ||
|
|
e1ffa65d8b | ||
|
|
cc2881a7b0 | ||
|
|
0d8a21f6ef | ||
|
|
3459dc5997 | ||
|
|
01d9d34118 | ||
|
|
de7c9d965e | ||
|
|
6549c95d4f | ||
|
|
da07fadfb3 | ||
|
|
b2c256e51e | ||
|
|
7305a30b50 | ||
|
|
4c127b6f61 | ||
|
|
61be903eb2 | ||
|
|
59792b2a3d | ||
|
|
19dda61d4f | ||
|
|
a50ed02057 | ||
|
|
0a7c8f9fd1 | ||
|
|
f65e14397c | ||
|
|
06a5d645da | ||
|
|
5d1852fe45 | ||
|
|
d7b94fb123 | ||
|
|
ced4f82585 | ||
|
|
de0110abc6 | ||
|
|
a53bea5eb9 | ||
|
|
47435869b3 | ||
|
|
80a9ded404 | ||
|
|
c696a81c3a | ||
|
|
e04ea2d178 | ||
|
|
42ce397e13 | ||
|
|
22162aa55f | ||
|
|
c693d03a77 | ||
|
|
b9ed00d53f | ||
|
|
29e0df9bbc | ||
|
|
c4508ea80c | ||
|
|
16390c9b00 | ||
|
|
1271a895d1 | ||
|
|
030f1e2463 | ||
|
|
ec90c0b6cd | ||
|
|
7b79da3f32 | ||
|
|
5729d6a9cd | ||
|
|
4d7bac89f3 | ||
|
|
a5efef900c | ||
|
|
c851a10982 | ||
|
|
845adf43c6 | ||
|
|
5916e4ea39 | ||
|
|
7f37de777e | ||
|
|
bcf880f327 | ||
|
|
b593beb8c7 | ||
|
|
57ab3a0336 | ||
|
|
4a54b2f8ff | ||
|
|
6aa9aa2831 | ||
|
|
9b47ee12e7 | ||
|
|
d177ee4a7f | ||
|
|
b354058eb5 | ||
|
|
fbc0a898ae | ||
|
|
36f8e58e25 | ||
|
|
6a7dbb06b0 | ||
|
|
43dd0b7df8 | ||
|
|
43d86ad8e2 | ||
|
|
d3d89b1ee2 | ||
|
|
15d3201a40 | ||
|
|
cf4616d9a1 | ||
|
|
5721a324c1 | ||
|
|
7de6c72154 | ||
|
|
c0cee02351 | ||
|
|
bb674fb873 | ||
|
|
6136eadd31 | ||
|
|
87cb73c038 | ||
|
|
11d8547cef | ||
|
|
0998c73a1a | ||
|
|
471f66649a | ||
|
|
e8bf9cf688 | ||
|
|
4f88a0e7d2 | ||
|
|
c6b0e273e6 | ||
|
|
d9539f9d01 | ||
|
|
a3e309acb5 | ||
|
|
c06cbfd4a9 | ||
|
|
1d7e4e1a42 | ||
|
|
083d19c688 | ||
|
|
4e9a34d8d8 | ||
|
|
524e65a29b | ||
|
|
25048ba0cc | ||
|
|
4f7a1ea9da | ||
|
|
a62a373f64 | ||
|
|
6b23171d65 | ||
|
|
09b69728a0 | ||
|
|
bc0dbc7860 | ||
|
|
a693c343e8 | ||
|
|
cd17eb484a | ||
|
|
259eb60f97 | ||
|
|
1afa3e2003 | ||
|
|
df7d7c877d | ||
|
|
4c4d7c12e8 | ||
|
|
97c3cacfaa | ||
|
|
98ec568788 | ||
|
|
626ba7ee66 | ||
|
|
3920f84f45 | ||
|
|
30d2557342 | ||
|
|
4bcad431aa | ||
|
|
f3cea730af | ||
|
|
5fe32aac15 | ||
|
|
88d9a29599 | ||
|
|
92a36dcfdd | ||
|
|
23b4d4cb6b | ||
|
|
b37e74b407 | ||
|
|
0fad249124 | ||
|
|
94d7d94bff | ||
|
|
48645dd519 | ||
|
|
9df9077595 | ||
|
|
756241918e | ||
|
|
f819fdae98 | ||
|
|
7127b192a2 | ||
|
|
1cac189077 | ||
|
|
0383fb124b | ||
|
|
f378fd3d72 | ||
|
|
44fac686f9 | ||
|
|
eff2afbc69 | ||
|
|
86b3129c08 | ||
|
|
bdd5741290 | ||
|
|
53091fc5df | ||
|
|
06db3561dc | ||
|
|
5cb2f19ea0 | ||
|
|
a668b92051 | ||
|
|
eda11e97e3 | ||
|
|
64146b6e1b | ||
|
|
7b85cb92b0 | ||
|
|
a28fcb7c8c | ||
|
|
f8ec180a0f | ||
|
|
0810a113c1 | ||
|
|
1b4a21fafa | ||
|
|
fae857175b | ||
|
|
9c21ca2ee6 | ||
|
|
ccdb5ec79f | ||
|
|
cb6232cc05 | ||
|
|
0d49c605e2 | ||
|
|
207d312b12 | ||
|
|
08d83478da | ||
|
|
8665f4ff06 | ||
|
|
6a5beb89cc | ||
|
|
3d09e3b57b | ||
|
|
6d47b0f4f8 | ||
|
|
27cf8159d0 | ||
|
|
314dd7d7ad | ||
|
|
6412f1ab23 | ||
|
|
32ee1a8226 | ||
|
|
e1c28a5c22 | ||
|
|
296ee019a0 | ||
|
|
fe997d1254 | ||
|
|
e38951ebae | ||
|
|
7c2f8f4d93 | ||
|
|
1f76ff940d | ||
|
|
fde6cac7a2 | ||
|
|
93209265f2 | ||
|
|
b50675fc29 | ||
|
|
deb6d1fd9a | ||
|
|
432579657c | ||
|
|
f09bd5fdff | ||
|
|
4f37a9e447 | ||
|
|
32297d93dd | ||
|
|
7083e1263d | ||
|
|
329d1a7c41 | ||
|
|
22749caeb4 | ||
|
|
baaa4474f9 | ||
|
|
af804a9dc1 | ||
|
|
899f4fdc0a | ||
|
|
8cced3a79e | ||
|
|
bb26e48d38 | ||
|
|
4eb45f1b34 | ||
|
|
c2f503e2ac | ||
|
|
e4e654100e | ||
|
|
3270c3f775 | ||
|
|
7f88a6aa1a | ||
|
|
26042b990f | ||
|
|
cf433f2f80 | ||
|
|
c02f84969a | ||
|
|
bdebea62b6 | ||
|
|
4a22361f39 | ||
|
|
025f8b9dc3 | ||
|
|
9defedb862 | ||
|
|
ae94e58a43 | ||
|
|
e4d8489292 | ||
|
|
f66692d5cf | ||
|
|
2b917f6647 | ||
|
|
cda017fa4f | ||
|
|
dad22f6f83 | ||
|
|
0579f1b1dc | ||
|
|
a551bf0499 | ||
|
|
4f36819ab2 | ||
|
|
26359cf1ee | ||
|
|
00a25f3e8b | ||
|
|
2d122448c8 | ||
|
|
e5d67d2219 | ||
|
|
4ac0de5fff | ||
|
|
dbe7fae82e | ||
|
|
98a53a46f0 | ||
|
|
e6946d33e6 | ||
|
|
9474f43d61 | ||
|
|
9077f7ba37 | ||
|
|
3548251997 | ||
|
|
4d365c8a44 | ||
|
|
957ff40f30 | ||
|
|
93e0dde1a1 | ||
|
|
7bb12c35ea | ||
|
|
aff9c7748b | ||
|
|
e518d34bc9 | ||
|
|
f02e95e8bf | ||
|
|
51a305b445 | ||
|
|
5577f4a62e | ||
|
|
f0141530b9 | ||
|
|
98bcfeb2ba | ||
|
|
669c69ff39 | ||
|
|
92a802947d | ||
|
|
ce5096a896 | ||
|
|
c5e00194b7 | ||
|
|
5efd45eafc | ||
|
|
23e0ed5e56 | ||
|
|
d412a52fcc | ||
|
|
ded2f8af9a | ||
|
|
084f3ec52b | ||
|
|
bedc8bbf46 | ||
|
|
cf5745b985 | ||
|
|
3e18ad590f | ||
|
|
b174aa9aeb | ||
|
|
4ec3f22173 | ||
|
|
34f03715b6 | ||
|
|
ebf3f48584 | ||
|
|
6a0cdb8705 | ||
|
|
22111bf667 | ||
|
|
6d0c46595d | ||
|
|
8db89c3d5f | ||
|
|
1e2205ecb0 | ||
|
|
d292a6b0c3 | ||
|
|
74702c8d06 | ||
|
|
649674c786 | ||
|
|
038e6d7f3f | ||
|
|
df4a205ba0 | ||
|
|
e9c91d986d | ||
|
|
feafd5e2c9 | ||
|
|
bdb29fd99a | ||
|
|
a0a5037554 | ||
|
|
70a6b276ca | ||
|
|
f77361ceb2 | ||
|
|
75f4751b82 | ||
|
|
6af62c4a50 | ||
|
|
65b6c0f4ea | ||
|
|
c05684945f | ||
|
|
6b1c033ec1 | ||
|
|
4f14d1fb23 | ||
|
|
0ef5e9d3ff | ||
|
|
1c705bdb94 | ||
|
|
28a1324d3a | ||
|
|
af51f05c56 | ||
|
|
1a7b3f0631 | ||
|
|
4ff82f5c12 | ||
|
|
e2e08351b0 | ||
|
|
d83c45812d | ||
|
|
6258b97b92 | ||
|
|
3aab49c372 | ||
|
|
ecfae75e10 | ||
|
|
f60331e0f9 | ||
|
|
8f6065d4f4 | ||
|
|
52c984d5e7 | ||
|
|
6585f717c5 | ||
|
|
363a1507cf | ||
|
|
8c95bd5a65 | ||
|
|
b56f2f56f1 | ||
|
|
0bba9b0fad | ||
|
|
282d47aed4 | ||
|
|
2f8f0b658c | ||
|
|
e4884470ad | ||
|
|
68d44fa981 | ||
|
|
7e5307bd96 | ||
|
|
75c8738538 | ||
|
|
a553616ea2 | ||
|
|
8c14d36dfe | ||
|
|
4486842169 | ||
|
|
f5b216fd9a | ||
|
|
dcdce52608 | ||
|
|
73139e82cb | ||
|
|
36ec455e6d | ||
|
|
df3fc8be7f | ||
|
|
3191caa700 | ||
|
|
9a3324de07 | ||
|
|
5839d0e8e3 | ||
|
|
07fc8c1e5b | ||
|
|
cd010afb48 | ||
|
|
e14cf23ecb | ||
|
|
be5a252be7 | ||
|
|
974dddbe19 | ||
|
|
5e0698b7f3 | ||
|
|
073488a7bd | ||
|
|
2dccedf8d7 | ||
|
|
0cf8d731bb | ||
|
|
3d083fca05 | ||
|
|
e496513275 | ||
|
|
e70e9de1c8 | ||
|
|
fcdfd54066 | ||
|
|
efdf780c79 | ||
|
|
4e7df37931 | ||
|
|
e90335e943 | ||
|
|
f0bce86e28 | ||
|
|
fac689ecf7 | ||
|
|
6e8949a880 | ||
|
|
4b670cfa90 | ||
|
|
7028b2f76c | ||
|
|
a547358be4 | ||
|
|
ddcff1f8ba | ||
|
|
1d7c527ea1 | ||
|
|
4baff1b3d1 | ||
|
|
9c4f1419b1 | ||
|
|
848c4ca204 | ||
|
|
e9aa9cc8df | ||
|
|
2eadb632df | ||
|
|
a57056a035 | ||
|
|
87db88254b | ||
|
|
c1dde51404 | ||
|
|
5ddc006f51 | ||
|
|
1fcb3af4c0 | ||
|
|
9ccff4f8de | ||
|
|
1e0fc4714d | ||
|
|
8fc8342358 | ||
|
|
14673ff1f5 | ||
|
|
ae95cf37f5 | ||
|
|
85a72d6a10 | ||
|
|
189ca3c555 | ||
|
|
567f333017 | ||
|
|
acd8944aec | ||
|
|
5b1b5b0cc1 | ||
|
|
c02b36a00c | ||
|
|
1e1f1f78bf | ||
|
|
1494604740 | ||
|
|
30664bc5ec | ||
|
|
59b20ac1ee | ||
|
|
f32f36fc74 | ||
|
|
5157f511fc | ||
|
|
3196e105a1 | ||
|
|
b9d865f881 | ||
|
|
2e39a650eb | ||
|
|
c753b57265 | ||
|
|
583ab98210 | ||
|
|
e4f6a02de6 | ||
|
|
a5b5518533 | ||
|
|
88d743b1cc | ||
|
|
7ac4bc95bb | ||
|
|
fac455da48 | ||
|
|
b01627f39d | ||
|
|
766c9f0e7e | ||
|
|
d1414a3f34 | ||
|
|
6386a302b2 | ||
|
|
7071712a0a | ||
|
|
31454b2946 | ||
|
|
e55ac6002a | ||
|
|
e9c6549847 | ||
|
|
4aad555649 | ||
|
|
93ee565d29 | ||
|
|
6c097a128b | ||
|
|
eea3f78cec | ||
|
|
6c4caf64c8 | ||
|
|
88f0013330 | ||
|
|
aa31da34e5 | ||
|
|
71464f6d0e | ||
|
|
e5a9648206 | ||
|
|
abf2120097 | ||
|
|
2f5eaa2dfd | ||
|
|
091aaac55e | ||
|
|
d431811725 | ||
|
|
b161a863df | ||
|
|
203cf17c67 | ||
|
|
cea75e07f2 | ||
|
|
7d1893a2b8 | ||
|
|
7512f721e9 | ||
|
|
b32d82f11e | ||
|
|
bdfe3a3b35 | ||
|
|
5cf391c3bb | ||
|
|
1ee5432b96 | ||
|
|
9f963686b5 | ||
|
|
56f8dd2942 | ||
|
|
e4e4365cce | ||
|
|
425362f515 | ||
|
|
b061c29a5b | ||
|
|
5d9e58f194 | ||
|
|
d50fe1573c | ||
|
|
2a064fd97d | ||
|
|
e9a3974b98 | ||
|
|
f02bc529fe | ||
|
|
4c1df9927d | ||
|
|
ca3c0cb163 | ||
|
|
e6821f1d16 | ||
|
|
56e7992d4a | ||
|
|
74003f12c1 | ||
|
|
3bc12b0434 | ||
|
|
44c0e236b0 | ||
|
|
320eb83c10 | ||
|
|
cb2a597267 | ||
|
|
7621a0fd31 | ||
|
|
57bb0160c4 | ||
|
|
6c4cd6c99c | ||
|
|
6190429f51 | ||
|
|
2b84c0384d | ||
|
|
146ed0f6a6 | ||
|
|
9826b40ec6 | ||
|
|
bdda29ef6a | ||
|
|
b154952869 | ||
|
|
ef07f8366d | ||
|
|
9c57b0b1b7 | ||
|
|
02a0969b53 | ||
|
|
6e3ff0e429 | ||
|
|
9a9707e15c | ||
|
|
bc7749602d | ||
|
|
14facb4d6c | ||
|
|
7c6cc23aa8 | ||
|
|
d78a25ee4e | ||
|
|
06c3e295d6 | ||
|
|
6f438d52b2 | ||
|
|
7a6a3d1ac0 | ||
|
|
f923ac879f | ||
|
|
0b46ab9e48 | ||
|
|
bdf11e32a7 | ||
|
|
bbb5b86864 | ||
|
|
7b60c9c71a | ||
|
|
2caf3e2217 | ||
|
|
1441b8a1d2 | ||
|
|
b1bc2cec1b | ||
|
|
e280f99996 | ||
|
|
2f90b8e6a6 | ||
|
|
d33bd88832 | ||
|
|
a92b1971c0 | ||
|
|
428827ed91 | ||
|
|
0efaf47325 | ||
|
|
a31451d5af | ||
|
|
a8f1b1d9d8 | ||
|
|
6b32be0899 | ||
|
|
33c2168af2 | ||
|
|
99f75111ce | ||
|
|
a2c6b08cf4 | ||
|
|
efd2fbb19a | ||
|
|
25af155113 | ||
|
|
c8b82e6e48 | ||
|
|
ae021064a4 | ||
|
|
d5917299fc | ||
|
|
25103b9f0b | ||
|
|
c5d5379c49 | ||
|
|
5442768229 | ||
|
|
83b2096d2d | ||
|
|
8f0a62c1c9 | ||
|
|
c8cb64db24 | ||
|
|
0a61169326 | ||
|
|
f70073cf3e | ||
|
|
fc9ac1b441 | ||
|
|
02d5f8560f | ||
|
|
47becb3be8 | ||
|
|
c94e24c0ff | ||
|
|
7320005c08 | ||
|
|
d7e5705520 | ||
|
|
bacdd63dd4 | ||
|
|
9438d063cd | ||
|
|
b91ab8940e | ||
|
|
9bf65da420 | ||
|
|
35a8b501c9 | ||
|
|
bb948fdd1b | ||
|
|
8e7aef17c6 | ||
|
|
24f2416427 | ||
|
|
6a384b0405 | ||
|
|
cc6cf79835 | ||
|
|
df6d459bb0 | ||
|
|
f10e9c2d64 | ||
|
|
bd97d9c7ca | ||
|
|
94b9fda354 | ||
|
|
37cb882507 | ||
|
|
7a7498537f | ||
|
|
ebab664c1a | ||
|
|
b6e22e47ca | ||
|
|
d17e399a5f | ||
|
|
48742f334f | ||
|
|
a577799fee | ||
|
|
ec01ab73aa | ||
|
|
26b779cd0e | ||
|
|
931bafe380 | ||
|
|
5164976b76 | ||
|
|
7a9e16766f | ||
|
|
5854eefcce | ||
|
|
706b81b929 | ||
|
|
5845972941 | ||
|
|
8fab14f072 | ||
|
|
7f7538ed58 | ||
|
|
8fa616f440 | ||
|
|
08387e2814 | ||
|
|
62aaaabd7e | ||
|
|
1f3e91efae | ||
|
|
b3750e46cf | ||
|
|
b43b2e9741 | ||
|
|
77e9eaba00 | ||
|
|
3d593faad8 | ||
|
|
118622493f | ||
|
|
2aacb58e7f | ||
|
|
e44523e623 | ||
|
|
cc7dceed1a | ||
|
|
69749a5cbe | ||
|
|
43b49034df | ||
|
|
5829e698da | ||
|
|
8d8cefe948 | ||
|
|
d2041c0334 | ||
|
|
f9487e55d5 | ||
|
|
83b4f2bf34 | ||
|
|
b8ecb68ad7 | ||
|
|
3b83d3f209 | ||
|
|
11d9435f08 | ||
|
|
952bceb3c3 | ||
|
|
029e6114ad | ||
|
|
4b0c94d609 | ||
|
|
de5fee8876 | ||
|
|
02afa2f19b | ||
|
|
8cdc251378 | ||
|
|
80fa4ec71f | ||
|
|
b1432ef1c6 | ||
|
|
0599341515 | ||
|
|
8e840d4529 | ||
|
|
b4880beb5b | ||
|
|
57c36d0e51 | ||
|
|
30021d9236 | ||
|
|
95c43adc4e | ||
|
|
c6b88c4db0 | ||
|
|
df347b90e5 | ||
|
|
47683f3bd4 | ||
|
|
8357e5b8d0 | ||
|
|
02af3cf197 | ||
|
|
1655a377f0 | ||
|
|
aad4a5e20c | ||
|
|
b704e0d131 | ||
|
|
bb861aa262 | ||
|
|
f9ac07830e | ||
|
|
78a29ab3ca | ||
|
|
1ab35c68c8 | ||
|
|
79021fba1f | ||
|
|
cf21025cd7 | ||
|
|
b718d40eb9 | ||
|
|
ef81bdb1e1 | ||
|
|
784f568575 | ||
|
|
f1acc956f4 | ||
|
|
b30bc64319 | ||
|
|
ca5bbc596e | ||
|
|
8188d76f63 | ||
|
|
57a3da1cca | ||
|
|
eb9b9cfce7 | ||
|
|
8b8e6bab33 | ||
|
|
549bfcafd9 | ||
|
|
435dce91d7 | ||
|
|
a2973f6f28 | ||
|
|
614a586b4c | ||
|
|
0f4c7ac90f | ||
|
|
8c7fa53696 | ||
|
|
d60109d3c9 | ||
|
|
12c9aad445 | ||
|
|
0c90b2f1aa | ||
|
|
8c27476d2a | ||
|
|
dfd0406160 | ||
|
|
af36177b03 | ||
|
|
7e15c0874d | ||
|
|
86e1f165cd | ||
|
|
ee783257d9 | ||
|
|
77aa154388 | ||
|
|
414e1542d0 | ||
|
|
0687f9a0a9 | ||
|
|
6e75ab2889 | ||
|
|
d4a0c141b3 | ||
|
|
192dd86469 | ||
|
|
cd861263fb | ||
|
|
dbd5c32535 | ||
|
|
76062ca8b2 | ||
|
|
a80cfb6b05 | ||
|
|
6ee67a2a36 | ||
|
|
a11bbd667a | ||
|
|
ce7f251064 | ||
|
|
7268cf7311 | ||
|
|
5e497aff90 | ||
|
|
70d1504cd4 | ||
|
|
742ef2b23b | ||
|
|
f6c50733fc | ||
|
|
25a612da04 | ||
|
|
31cc294cc3 | ||
|
|
d93a0b698b | ||
|
|
9cf329c475 | ||
|
|
f978cc4e22 | ||
|
|
6cda9e001f | ||
|
|
b90d29d448 | ||
|
|
494b70950c | ||
|
|
808092acdd | ||
|
|
b9c716a247 | ||
|
|
f71fadda17 | ||
|
|
bf7b2e1e7e | ||
|
|
1f78bf4119 | ||
|
|
91c73b562a | ||
|
|
28df2dad60 | ||
|
|
5d25e77189 | ||
|
|
d9dabf25da | ||
|
|
73af605892 | ||
|
|
3ae862d690 | ||
|
|
51d5c00bcd | ||
|
|
99b4fceaf3 | ||
|
|
a71d8a660f | ||
|
|
e91e53c0ec | ||
|
|
03b56b2941 | ||
|
|
01ee91d003 | ||
|
|
f70f95c119 | ||
|
|
badc5ff3ec | ||
|
|
8dbdcdcb25 | ||
|
|
13533a2fa9 | ||
|
|
e4343ded01 | ||
|
|
e105cc6d44 | ||
|
|
53c50cf6fc | ||
|
|
f19d1472c5 | ||
|
|
eef00502cd | ||
|
|
0b1caac195 | ||
|
|
e900a44d47 | ||
|
|
a3de9fa898 | ||
|
|
aa8de912fd | ||
|
|
f8648e2929 | ||
|
|
33054d306d | ||
|
|
2c265aab44 | ||
|
|
8b0154cc62 | ||
|
|
1a225bf55b | ||
|
|
24d19cd8d6 | ||
|
|
c25a4a7346 | ||
|
|
65c6e99a3f | ||
|
|
8e85058495 | ||
|
|
20fb7b241f | ||
|
|
a0553e1195 | ||
|
|
fe28319d22 | ||
|
|
f40141072a | ||
|
|
c759856a61 | ||
|
|
237b181eec | ||
|
|
c56cb516ed | ||
|
|
48957fd2f0 | ||
|
|
db9b239d71 | ||
|
|
d793a4e3ab | ||
|
|
8a99c37200 | ||
|
|
dbf4389307 | ||
|
|
e62c1c629f | ||
|
|
797184f452 | ||
|
|
9f0ac99abb | ||
|
|
d388c3fd3d | ||
|
|
9fd2b7f4da | ||
|
|
b43e6d7af3 | ||
|
|
1b8e48539d | ||
|
|
6d025479c6 | ||
|
|
616381a16c | ||
|
|
e1bf3d9ee0 | ||
|
|
5ff4db5a4d | ||
|
|
01023801b8 | ||
|
|
00bdcf14dd | ||
|
|
b98e1889d5 | ||
|
|
d1eef0853b | ||
|
|
29f23fa992 | ||
|
|
7ec12f1e12 | ||
|
|
139be3a9b7 | ||
|
|
7f6f5c0c3b | ||
|
|
f5389a2d2d | ||
|
|
31dffcf5e0 | ||
|
|
4afadc39aa | ||
|
|
f13ed9494b | ||
|
|
acd5547618 | ||
|
|
8733ac416c | ||
|
|
ab40b1b556 | ||
|
|
682ab44dea | ||
|
|
0ff25728a4 | ||
|
|
104beff158 | ||
|
|
4712569a36 | ||
|
|
56fdf8dd63 | ||
|
|
0b2510f6cc | ||
|
|
eb408e2ea4 | ||
|
|
958a86bbb5 | ||
|
|
b45da1eaa5 | ||
|
|
6facaec353 | ||
|
|
7dc4a2776b | ||
|
|
09b0ac38ef | ||
|
|
9d62e87de4 | ||
|
|
04dbaef257 | ||
|
|
ff12dd99b5 | ||
|
|
7ee5184afc | ||
|
|
212565b01e | ||
|
|
130b4b176f | ||
|
|
779ac0e4d9 | ||
|
|
4a20a6e525 | ||
|
|
2392f4a902 | ||
|
|
cb1203fde9 | ||
|
|
0c14423213 | ||
|
|
1135896b3d | ||
|
|
39cd933ebb | ||
|
|
2fcc386e1e | ||
|
|
bf01be6f19 | ||
|
|
b47d3cda12 | ||
|
|
01c76f19ed | ||
|
|
682f20bbba | ||
|
|
eb8ea0fbd4 | ||
|
|
784e553d23 | ||
|
|
74f7775332 | ||
|
|
a0f28a9098 | ||
|
|
93a69cbc49 | ||
|
|
de040a0922 | ||
|
|
4af54f26b6 | ||
|
|
15b9ea45a0 | ||
|
|
02dab4eb72 | ||
|
|
1b76e96720 | ||
|
|
f0b94dd0f7 | ||
|
|
6df622e8ed | ||
|
|
54eb9d081b | ||
|
|
9f60f27636 | ||
|
|
306e16147a | ||
|
|
aae1e12b2e | ||
|
|
ba59643f52 | ||
|
|
01c02a75a8 | ||
|
|
f5b3e5f341 | ||
|
|
9b825cb529 | ||
|
|
3f326f0913 | ||
|
|
ec86bd246a | ||
|
|
aa90d5b6ab | ||
|
|
d6798470e7 | ||
|
|
073388436b | ||
|
|
84d1be07cd | ||
|
|
bd3724be8f | ||
|
|
26747b8c7e | ||
|
|
683ab25e83 | ||
|
|
d0b8c560f6 | ||
|
|
4d180eb303 | ||
|
|
53d2129bd1 | ||
|
|
977a8add67 | ||
|
|
79da71ecf8 | ||
|
|
abb13b70b9 | ||
|
|
023ead39ec | ||
|
|
8890940b06 | ||
|
|
d21b577dd7 | ||
|
|
00e8c11ec2 | ||
|
|
617b6b991f | ||
|
|
b3ea1050eb | ||
|
|
ca98066d68 | ||
|
|
352f7c8675 | ||
|
|
44a8bfd764 | ||
|
|
df5d514c28 | ||
|
|
16663797b2 | ||
|
|
7176218a5f | ||
|
|
4099376472 | ||
|
|
fb64fbab0b | ||
|
|
6aef59e42d | ||
|
|
2b885beb82 | ||
|
|
86024107af | ||
|
|
754f87fd0c | ||
|
|
6e4fb5888c | ||
|
|
c94c727058 | ||
|
|
6d3118d9e9 | ||
|
|
a0c78a94c6 | ||
|
|
f014ebfeb1 | ||
|
|
220b384479 | ||
|
|
4c585614cd | ||
|
|
9674378c56 | ||
|
|
9e314ba77b | ||
|
|
cdd7dcdc5c | ||
|
|
34bed5ec4f | ||
|
|
3ea82e37d5 | ||
|
|
596c62aec8 | ||
|
|
972e894bc5 | ||
|
|
86a2db7e7f | ||
|
|
4c31081de2 | ||
|
|
834084a3e2 | ||
|
|
0819b9baba | ||
|
|
23c95ebbf3 | ||
|
|
265415030e | ||
|
|
e77f21a0b5 | ||
|
|
35e1f080b8 | ||
|
|
3d26f28f9b | ||
|
|
812c1f6bb4 | ||
|
|
0abec767e3 | ||
|
|
9adb7f20ce | ||
|
|
c7e54c66c8 | ||
|
|
f6855b0d2b | ||
|
|
9fd10bd73e | ||
|
|
1ceef602f0 | ||
|
|
93cc29f4d9 | ||
|
|
c9317542c8 | ||
|
|
aed8337c51 | ||
|
|
95dafc87c0 | ||
|
|
fe1790793e | ||
|
|
af4a5e1b8d | ||
|
|
ddb95dc64e | ||
|
|
f6f9ee26e1 | ||
|
|
21faa92904 | ||
|
|
1fd792fed9 | ||
|
|
e7c09c83f0 | ||
|
|
56103d1952 | ||
|
|
301c308fec | ||
|
|
b827c68187 | ||
|
|
622f40c06c | ||
|
|
964134cb60 | ||
|
|
3d541b2a2d | ||
|
|
f0d1c3ac60 | ||
|
|
72f498a63b | ||
|
|
f9a1f68295 | ||
|
|
9b67b0b9d5 | ||
|
|
44ca8acf37 | ||
|
|
f798ef1d76 | ||
|
|
754946bf62 | ||
|
|
a6580e3cd8 | ||
|
|
da6621f2ff | ||
|
|
f2d42a7e56 | ||
|
|
d01e4b4a85 | ||
|
|
b371072a61 | ||
|
|
d874b749d0 | ||
|
|
cff26563cc | ||
|
|
f57d1f1de3 | ||
|
|
fd1064b044 | ||
|
|
a3f122184c | ||
|
|
16fcddc249 | ||
|
|
2a9c9be36a | ||
|
|
6086131d3c | ||
|
|
227814e6c3 | ||
|
|
ca3aae23a1 | ||
|
|
4dd384e418 | ||
|
|
80e7313b24 | ||
|
|
183c3c1baf | ||
|
|
160c52fe81 | ||
|
|
5f0a820b4a | ||
|
|
2b0cc4d0e7 | ||
|
|
60aaa01e2e | ||
|
|
4eb43cd02d | ||
|
|
d3fb08ba81 | ||
|
|
03ef4246bf | ||
|
|
5574eabfed | ||
|
|
6a332ca60b | ||
|
|
87f606f768 | ||
|
|
534e7cf59d | ||
|
|
e1645f6903 | ||
|
|
61a2d200b4 | ||
|
|
3d6bbe4029 | ||
|
|
3d04cf1cd6 | ||
|
|
44eda676a3 | ||
|
|
eac6f07823 | ||
|
|
424e2a5745 | ||
|
|
46713236c4 | ||
|
|
0ef4fee0b4 | ||
|
|
82f4736ad2 | ||
|
|
1d45eff9b0 | ||
|
|
8e97279401 | ||
|
|
932ef780fd | ||
|
|
59424c3126 | ||
|
|
562dd8fc21 | ||
|
|
f3e601e340 | ||
|
|
cf745554fb | ||
|
|
e909eac98e | ||
|
|
5e42efc3ec | ||
|
|
020937d4f6 | ||
|
|
ff58fb8617 | ||
|
|
98b24dff36 | ||
|
|
ed9197e6ef | ||
|
|
eb1d56f439 | ||
|
|
438b84d4ee | ||
|
|
afc8354523 | ||
|
|
23fab1bab0 | ||
|
|
644e1ac4f6 | ||
|
|
4c88dbd9ac | ||
|
|
11d2e62e67 | ||
|
|
58b27a9daa | ||
|
|
caf939bf58 | ||
|
|
8c217fdac9 | ||
|
|
6b80bbeaa2 | ||
|
|
134736dce5 | ||
|
|
8b26b34f96 | ||
|
|
4b870bcf1e | ||
|
|
c20cedf266 | ||
|
|
2165bfe8c2 | ||
|
|
dd8a4a0082 | ||
|
|
cfd0b80225 | ||
|
|
865aaf0191 | ||
|
|
c1f335fedb | ||
|
|
22a4a2fc5e | ||
|
|
bb2c9dedeb | ||
|
|
6946bb54ed | ||
|
|
b4e623d175 | ||
|
|
891dd31290 | ||
|
|
6a4217e87b | ||
|
|
34cac2ac1b | ||
|
|
1dd25be6ef | ||
|
|
f4e04477db | ||
|
|
7bfa7fa9f4 | ||
|
|
c2607c4223 | ||
|
|
66953be67d | ||
|
|
ce355e311c | ||
|
|
0a5411d411 | ||
|
|
7598b9e29a | ||
|
|
edd96d46f9 | ||
|
|
00581b82f5 | ||
|
|
bab14e1489 | ||
|
|
65a0aa7abb | ||
|
|
261a44764d | ||
|
|
b3ef9333af | ||
|
|
8c8ab9988c | ||
|
|
fe4aa9dcd7 | ||
|
|
1fb0911710 | ||
|
|
a71a5f53f7 | ||
|
|
867cc6ce7e | ||
|
|
b348e0ff27 | ||
|
|
4646a05c7a | ||
|
|
84e35e27b8 | ||
|
|
c5527c106c | ||
|
|
5eac1b8730 | ||
|
|
197bad19ab | ||
|
|
603934bac1 | ||
|
|
0de15d040f | ||
|
|
41d032eee6 | ||
|
|
c4ae94fd4c | ||
|
|
afaf72573b | ||
|
|
32ed40e733 | ||
|
|
fa5644064a | ||
|
|
dd3f77a397 | ||
|
|
909fb4c75b | ||
|
|
ddd41d2ba7 | ||
|
|
1e8818984e | ||
|
|
a023f73509 | ||
|
|
6f0e1a7f47 | ||
|
|
0ef9bb1a47 | ||
|
|
71ceedc4bb | ||
|
|
73c3c1249f | ||
|
|
88a10dba28 | ||
|
|
001e222f67 | ||
|
|
af8bcdc242 | ||
|
|
f4c7afc148 | ||
|
|
b19c73a36e | ||
|
|
5fe0d0b94f | ||
|
|
f8d435d5f3 | ||
|
|
f15ef36fd1 | ||
|
|
64b25c4daa | ||
|
|
c0f0e354dd | ||
|
|
d6f58d04f1 | ||
|
|
cc88ee1de0 | ||
|
|
8008e4d840 | ||
|
|
a4390fbb57 | ||
|
|
f4170ade8a | ||
|
|
e81587470d | ||
|
|
fc6df1063c | ||
|
|
5a37bc338a | ||
|
|
f4a027b474 | ||
|
|
d0ba0d193b | ||
|
|
1d9a9bcb28 | ||
|
|
178ba60973 | ||
|
|
8e6e2432d3 | ||
|
|
83ec19dfca | ||
|
|
6e619f2c35 | ||
|
|
163ba41e8d | ||
|
|
ec143c43db | ||
|
|
cacf0004a5 | ||
|
|
cb39541e2a | ||
|
|
b9ddadeb44 | ||
|
|
dabd2a3f4d | ||
|
|
11e811cc4b | ||
|
|
c47f224566 | ||
|
|
8fcd454445 | ||
|
|
e422adb0d0 | ||
|
|
b03e28efb9 | ||
|
|
ba9d5f0c4b | ||
|
|
e02d9f3f0e | ||
|
|
e831d66b76 | ||
|
|
0efc978004 | ||
|
|
6fa2d47780 | ||
|
|
e691454339 | ||
|
|
b026e5ab87 | ||
|
|
1e75154cc7 | ||
|
|
e4a04de9f4 | ||
|
|
645e612e8b | ||
|
|
26a78a10bc | ||
|
|
8ba28adf68 | ||
|
|
1feb5e6ad6 | ||
|
|
34a26d33a1 | ||
|
|
b0a55e057b | ||
|
|
5ac9b05b2d | ||
|
|
63e582a07f | ||
|
|
470076daa2 | ||
|
|
f6d92a189b | ||
|
|
42599eae64 | ||
|
|
c788c93542 | ||
|
|
b75f29b8d7 | ||
|
|
81d285a143 | ||
|
|
ad4442ae78 | ||
|
|
1db4b9a12e | ||
|
|
a773f0d8a2 | ||
|
|
29c6b73d93 | ||
|
|
5b52ca4776 | ||
|
|
8ddaf1b731 | ||
|
|
964ce44577 | ||
|
|
cea6c557ce | ||
|
|
f4ecdf116a | ||
|
|
8d4545f008 | ||
|
|
5854c199d0 | ||
|
|
a937d08655 | ||
|
|
4bdf84bf6c | ||
|
|
9ce88699ca | ||
|
|
f9e8bf88c8 | ||
|
|
bddf7a11c8 | ||
|
|
ee7fb15fc2 | ||
|
|
8473df0f0c | ||
|
|
7eab003f3c | ||
|
|
d437e2d662 | ||
|
|
99819527db | ||
|
|
965273009c | ||
|
|
7bee616b1b | ||
|
|
f5302133d9 | ||
|
|
bf2aba1b06 | ||
|
|
60c372f535 | ||
|
|
22f9d16743 | ||
|
|
3d3d94e837 | ||
|
|
3bec96abe1 | ||
|
|
44c18395b3 | ||
|
|
09ff052c1f | ||
|
|
9ef5fd70c0 | ||
|
|
3b599a40f3 | ||
|
|
1495ab992f | ||
|
|
d89dc1aa4d | ||
|
|
1fac2ae787 | ||
|
|
ff7d1f2d6a | ||
|
|
91d4da85e1 | ||
|
|
dcd52d6919 | ||
|
|
9a75ca2c21 | ||
|
|
812606db78 | ||
|
|
c17ceb1702 | ||
|
|
ce01dad875 | ||
|
|
c04beea38c | ||
|
|
9ca106d889 | ||
|
|
5f575d524a | ||
|
|
93d88cca37 | ||
|
|
0997750816 | ||
|
|
427c8b0794 | ||
|
|
06008ed8eb | ||
|
|
374b71c017 | ||
|
|
fba78e7d9b | ||
|
|
551abc861e | ||
|
|
16142bd979 | ||
|
|
c1e2ba8abc | ||
|
|
cc2efe432e | ||
|
|
e879ae2f11 | ||
|
|
8bcd2ce571 | ||
|
|
d301562ffe | ||
|
|
caed22ea8d | ||
|
|
a90a483756 | ||
|
|
78f51d40f6 | ||
|
|
8bfc54e6b4 |
Binary file not shown.
|
After Width: | Height: | Size: 983 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
65
.doc/itop-version-history.md
Normal file
65
.doc/itop-version-history.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# iTop version history
|
||||
|
||||
```mermaid
|
||||
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true,'mainBranchName': 'develop','rotateCommitLabel': true}} }%%
|
||||
gitGraph
|
||||
commit id: "2016-07-06" tag: "2.3.0" type: HIGHLIGHT
|
||||
branch support/2.3 order: 900
|
||||
commit id: "2016-07-08" tag: "2.3.1"
|
||||
commit id: "2016-12-22" tag: "2.3.3"
|
||||
commit id: "2017-04-14" tag: "2.3.4"
|
||||
checkout develop
|
||||
commit id: "2017-07-12" tag: "2.4.0-beta" type: REVERSE
|
||||
commit id: "2017-11-16" tag: "2.4.0" type: HIGHLIGHT
|
||||
branch support/2.4 order: 890
|
||||
commit id: "2018-02-14" tag: "2.4.1"
|
||||
checkout develop
|
||||
commit id: "2018-04-25" tag: "2.5.0-beta" type: REVERSE
|
||||
checkout support/2.4
|
||||
commit id: "2018-06-14" tag: "2.4.2"
|
||||
checkout develop
|
||||
commit id: "2018-06-27" tag: "2.5.0" type: HIGHLIGHT
|
||||
branch support/2.5 order: 880
|
||||
checkout develop
|
||||
commit id: "2019-01-09" tag: "2.6.0" type: HIGHLIGHT
|
||||
branch support/2.6 order: 870
|
||||
commit id: "2019-03-28" tag: "2.6.1"
|
||||
checkout develop
|
||||
commit id: "2019-12-18" tag: "2.7.0-beta" type: REVERSE
|
||||
checkout support/2.5
|
||||
commit id: "2020-01-22" tag: "2.5.4"
|
||||
checkout support/2.6
|
||||
commit id: "2020-01-23" tag: "2.6.3"
|
||||
checkout develop
|
||||
commit id: "2020-01-29" tag: "2.7.0-beta2" type: REVERSE
|
||||
commit id: "2020-04-01" tag: "2.7.0-1" type: HIGHLIGHT
|
||||
checkout support/2.6
|
||||
commit id: "2020-04-22" tag: "2.6.4"
|
||||
checkout develop
|
||||
branch support/2.7 order: 860
|
||||
commit id: "2020-06-26" tag: "2.7.1"
|
||||
checkout support/2.7
|
||||
commit id: "2020-12-09" tag: "2.7.3"
|
||||
commit id: "2021-03-31" tag: "2.7.4"
|
||||
checkout develop
|
||||
commit id: "2021-04-06" tag: "3.0.0-beta" type: REVERSE
|
||||
checkout support/2.7
|
||||
commit id: "2021-07-05" tag: "2.7.5"
|
||||
checkout develop
|
||||
commit id: "2021-07-05." tag: "3.0.0-beta2" type: REVERSE
|
||||
checkout support/2.7
|
||||
commit id: "2021-12-17" tag: "2.7.6"
|
||||
checkout develop
|
||||
commit id: "2022-01-04" tag: "3.0.0" type: HIGHLIGHT
|
||||
branch support/3.0 order: 850
|
||||
commit id: "2022-04-08" tag: "3.0.1"
|
||||
checkout support/2.7
|
||||
commit id: "2022-07-11" tag: "2.7.7"
|
||||
checkout support/3.0
|
||||
commit id: "2022-09-12" tag: "3.0.2-1"
|
||||
checkout develop
|
||||
checkout support/2.7
|
||||
commit id: "2022-12-28" tag: "2.7.8"
|
||||
```
|
||||
|
||||
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).
|
||||
48
.gitattributes
vendored
Normal file
48
.gitattributes
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.bash text eol=lf
|
||||
*.bat text eol=lf
|
||||
*.cmd text eol=lf
|
||||
*.css text eol=lf
|
||||
*.scss text eol=lf
|
||||
*.dist text eol=lf
|
||||
.editorconfig text eol=lf
|
||||
.env* text eol=lf
|
||||
.gitignore text eol=lf
|
||||
.htaccess text eol=lf
|
||||
*.htm text eol=lf
|
||||
*.html text eol=lf
|
||||
*.ini text eol=lf
|
||||
*.js text eol=lf
|
||||
*.json text eol=lf
|
||||
*.lock text eol=lf
|
||||
*.md text eol=lf
|
||||
*.php text eol=lf
|
||||
*.php_cs text eol=lf
|
||||
*.php8 text eol=lf
|
||||
*.plex text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.svg text eol=lf
|
||||
*.ts text eol=lf
|
||||
*.twig text eol=lf
|
||||
*.txt text eol=lf
|
||||
*.xml text eol=lf
|
||||
*.xsd text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.yml text eol=lf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
*.jpeg binary
|
||||
*.jpg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.pdf binary
|
||||
*.swf binary
|
||||
*.zip binary
|
||||
*.ttf binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -19,7 +19,7 @@
|
||||
|
||||
# composer reserver directory, from sources, populate/update using "composer install"
|
||||
vendor/*
|
||||
test/vendor/*
|
||||
tests/*/vendor/*
|
||||
|
||||
# all conf but listing prevention
|
||||
/conf/**
|
||||
@@ -45,9 +45,13 @@ test/vendor/*
|
||||
!/log/index.php
|
||||
!/log/web.config
|
||||
|
||||
# PHPUnit cache file
|
||||
/tests/php-unit-tests/.phpunit.result.cache
|
||||
|
||||
|
||||
# Jetbrains
|
||||
/.idea/**
|
||||
!/.idea/IntelliLang.xml
|
||||
|
||||
# doc. generation
|
||||
/.doc/vendor
|
||||
@@ -142,3 +146,10 @@ local.properties
|
||||
.cache-main
|
||||
.scala_dependencies
|
||||
.worksheet
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
|
||||
|
||||
15
.idea/IntelliLang.xml
generated
Normal file
15
.idea/IntelliLang.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="LanguageInjectionConfiguration">
|
||||
<injection language="InjectablePHP" injector-id="xml">
|
||||
<display-name>iTop - Class method code</display-name>
|
||||
<place><![CDATA[xmlTag().withLocalName(string().equalTo("code"))]]></place>
|
||||
<xpath-condition>name(..) = 'method' and count(/itop_design) = 1</xpath-condition>
|
||||
</injection>
|
||||
<injection language="InjectablePHP" injector-id="xml">
|
||||
<display-name>iTop - Snippet code</display-name>
|
||||
<place><![CDATA[xmlTag().withLocalName(string().equalTo("snippet"))]]></place>
|
||||
<xpath-condition>name(..) = 'snippets' and count(/itop_design) = 1</xpath-condition>
|
||||
</injection>
|
||||
</component>
|
||||
</project>
|
||||
@@ -19,16 +19,23 @@
|
||||
*
|
||||
*/
|
||||
|
||||
$iTopFolder = __DIR__ . "/../../" ;
|
||||
/**
|
||||
* Alias for `composer show -loD`
|
||||
* You can also use `composer outdated -D`
|
||||
*
|
||||
* @link https://getcomposer.org/doc/03-cli.md#show
|
||||
*/
|
||||
|
||||
require_once ("$iTopFolder/approot.inc.php");
|
||||
$iTopFolder = __DIR__."/../../";
|
||||
|
||||
require_once("$iTopFolder/approot.inc.php");
|
||||
$sApproot = APPROOT;
|
||||
$aTrace = array();
|
||||
|
||||
$aParamsConfig = array(
|
||||
'composer-path' => array(
|
||||
'default' => 'composer.phar',
|
||||
)
|
||||
'default' => 'composer',
|
||||
),
|
||||
);
|
||||
$aParamsConfigNotFound = array_flip(array_keys($aParamsConfig));
|
||||
$aGivenArgs = $argv;
|
||||
|
||||
@@ -50,14 +50,24 @@ foreach ($aDeniedButStillPresent as $sDir)
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
SetupUtils::rrmdir($sDir);
|
||||
echo "OK Remove denied test dir: '$sDir'\n";
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
catch (\Exception $e) {
|
||||
echo "\nFAILED to remove denied test dir: '$sDir'\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$aAllowedAndDeniedDirs = array_merge(
|
||||
$oiTopComposer->ListAllowedTestDir(),
|
||||
$oiTopComposer->ListDeniedTestDir()
|
||||
);
|
||||
$aExistingDirs = $oiTopComposer->ListAllTestDir();
|
||||
$aMissing = array_diff($aExistingDirs, $aAllowedAndDeniedDirs);
|
||||
if (false === empty($aMissing)) {
|
||||
echo "Some new tests dirs exists !\n"
|
||||
.' They must be declared either in the allowed or denied list in '.iTopComposer::class." (see N°2651).\n"
|
||||
.' List of dirs:'."\n".var_export($aMissing, true);
|
||||
}
|
||||
@@ -6,11 +6,19 @@
|
||||
* Will update version in the following files :
|
||||
*
|
||||
* datamodels/2.x/.../datamodel.*.xml
|
||||
* application/*.xml
|
||||
* core/*.xml
|
||||
*
|
||||
* Usage :
|
||||
* `php .make\release\update-xml.php "1.7"`
|
||||
* `php .make\release\update-xml.php`
|
||||
*
|
||||
* @since 2.7.0
|
||||
* If no parameter provided then the current XML version will be used as target version
|
||||
*
|
||||
* @since 2.7.0 simple version change using regexp (not doing conversion)
|
||||
* @since 3.1.0 N°5405 now does a real conversion
|
||||
* @since 3.1.0 N°5633 allow to use without parameter
|
||||
* @since 3.1.0 N°5633 add /application and /core XML files
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
@@ -22,10 +30,12 @@ require_once (__DIR__.DIRECTORY_SEPARATOR.'update.classes.inc.php');
|
||||
|
||||
if (count($argv) === 1)
|
||||
{
|
||||
echo '/!\ You must pass the new version as parameter';
|
||||
exit(1);
|
||||
echo '/!\ No version passed: assuming target XML version is current XML version ('.ITOP_DESIGN_LATEST_VERSION.")\n";
|
||||
$sVersionLabel = ITOP_DESIGN_LATEST_VERSION;
|
||||
} else {
|
||||
$sVersionLabel = $argv[1];
|
||||
}
|
||||
$sVersionLabel = $argv[1];
|
||||
|
||||
if (empty($sVersionLabel))
|
||||
{
|
||||
echo 'Version passed as parameter is empty !';
|
||||
|
||||
@@ -125,16 +125,31 @@ class iTopVersionFileUpdater extends AbstractSingleFileVersionUpdater
|
||||
|
||||
abstract class AbstractGlobFileVersionUpdater extends FileVersionUpdater
|
||||
{
|
||||
protected $sGlobPattern;
|
||||
/** @var array|string glob patterns to seek for files to modify */
|
||||
protected $globPattern;
|
||||
|
||||
public function __construct($sGlobPattern)
|
||||
public function __construct($globPattern)
|
||||
{
|
||||
$this->sGlobPattern = $sGlobPattern;
|
||||
$this->globPattern = $globPattern;
|
||||
}
|
||||
|
||||
public function GetFiles()
|
||||
{
|
||||
return glob($this->sGlobPattern);
|
||||
$aGlobPatterns = (is_array($this->globPattern))
|
||||
? $this->globPattern
|
||||
: [$this->globPattern];
|
||||
|
||||
$aFiles = [];
|
||||
foreach ($aGlobPatterns as $sGlobPattern) {
|
||||
$result = glob($sGlobPattern);
|
||||
if (false === $result) {
|
||||
continue;
|
||||
}
|
||||
/** @noinspection SlowArrayOperationsInLoopInspection */
|
||||
$aFiles = array_merge($aFiles, $result);
|
||||
}
|
||||
|
||||
return $aFiles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +181,11 @@ class DatamodelsXmlFiles extends AbstractGlobFileVersionUpdater
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(APPROOT.'datamodels/2.x/*/datamodel.*.xml');
|
||||
parent::__construct([
|
||||
APPROOT.'datamodels/2.x/*/datamodel.*.xml',
|
||||
APPROOT.'application/*.xml',
|
||||
APPROOT.'core/*.xml',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,10 +193,40 @@ class DatamodelsXmlFiles extends AbstractGlobFileVersionUpdater
|
||||
*/
|
||||
public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath)
|
||||
{
|
||||
return preg_replace(
|
||||
'/(<itop_design .* version=")[^"]+(">)/',
|
||||
'${1}'.$sVersionLabel.'${2}',
|
||||
$sFileContent
|
||||
);
|
||||
require_once APPROOT.'setup/itopdesignformat.class.inc.php';
|
||||
$oFileXml = new DOMDocument();
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
libxml_clear_errors();
|
||||
$oFileXml->formatOutput = true;
|
||||
$oFileXml->preserveWhiteSpace = false;
|
||||
$oFileXml->loadXML($sFileContent);
|
||||
|
||||
$oFileItopFormat = new iTopDesignFormat($oFileXml);
|
||||
|
||||
$sDesignVersionToSet = static::GetDesignVersionToSet($oFileItopFormat->GetVersion());
|
||||
if (false === is_null($sDesignVersionToSet)) {
|
||||
// N°5779 if same as target version, we will try to convert from version below
|
||||
$oFileItopFormat->GetITopDesignNode()->setAttribute('version', $sDesignVersionToSet);
|
||||
}
|
||||
|
||||
$bConversionResult = $oFileItopFormat->Convert($sVersionLabel);
|
||||
|
||||
if (false === $bConversionResult) {
|
||||
throw new Exception("Error when converting $sFileFullPath");
|
||||
}
|
||||
|
||||
return $oFileItopFormat->GetXmlAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?string version to use : if file version is same as current version then return previous version, else return null
|
||||
* @since 3.1.0 N°5779
|
||||
*/
|
||||
protected static function GetDesignVersionToSet($sFileDesignVersion):?string {
|
||||
if ($sFileDesignVersion !== ITOP_DESIGN_LATEST_VERSION) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return iTopDesignFormat::GetPreviousDesignVersion(ITOP_DESIGN_LATEST_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,14 @@ If you have an idea you're sure would benefit to all of iTop users, you may
|
||||
[create a corresponding ticket](https://sourceforge.net/p/itop/tickets/new/) to submit it, but be warned that there are lots of good
|
||||
reasons to refuse such changes.
|
||||
|
||||
### 📄 License
|
||||
iTop is distributed under the AGPL-3.0 license (see the [license.txt] file),
|
||||
your code must comply with this license.
|
||||
### 📄 License and copyright
|
||||
iTop is distributed under the AGPL-3.0 license (see the [license.txt] file).
|
||||
|
||||
If you want to use another license, you may [create an extension][wiki new ext].
|
||||
The iTop repository is divided in three parts: iTop (mainly PHP/JS/XML sources and dictionaries), images, and third-party libraries.
|
||||
Combodo has the copyright on most of the source files in the iTop part of the repository: please do not modify the existing file copyrights.
|
||||
Anyhow, you are encouraged to signal your contribution by the mean of `@author` annotations.
|
||||
|
||||
If you want to use another license or keep the code ownership (copyright), you may [create an extension][wiki new ext].
|
||||
|
||||
[license.txt]: https://github.com/Combodo/iTop/blob/develop/license.txt
|
||||
[wiki new ext]: https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Astart#by_writing_your_own_extension
|
||||
@@ -52,26 +55,26 @@ Here are the branches we use and their meaning :
|
||||
|
||||
For example, if no version is currently prepared for shipping we could have:
|
||||
|
||||
- `develop` containing future 3.0.0 version
|
||||
- `develop` containing future 3.1.0 version
|
||||
- `support/3.0`: 3.0.x maintenance version
|
||||
- `support/2.7`: 2.7.x maintenance version
|
||||
- `support/2.6`: 2.6.x maintenance version
|
||||
- `support/2.5`: 2.5.x maintenance version
|
||||
|
||||
In this example, when 3.0.0-beta is shipped that will become:
|
||||
In this example, when 3.1.0-beta is shipped that will become:
|
||||
|
||||
- `develop`: future 3.1.0 version
|
||||
- `release/3.0.0`: 3.0.0-beta
|
||||
- `develop`: future 3.2.0 version
|
||||
- `release/3.1.0`: 3.1.0-beta
|
||||
- `support/3.0`: 3.0.x maintenance version
|
||||
- `support/2.7`: 2.7.x maintenance version
|
||||
- `support/2.6`: 2.6.x maintenance version
|
||||
- `support/2.5`: 2.5.x maintenance version
|
||||
|
||||
And when 3.0.0 final will be out:
|
||||
And when 3.1.0 final will be out:
|
||||
|
||||
- `develop`: future 3.1.0 version
|
||||
- `support/3.0`: 3.0.x maintenance version (will host developments for 3.0.1)
|
||||
- `develop`: future 3.2.0 version
|
||||
- `support/3.1`: 3.1.x maintenance version (will host developments for 3.1.1)
|
||||
- `support/3.0`: 3.0.x maintenance version
|
||||
- `support/2.7`: 2.7.x maintenance version
|
||||
- `support/2.6`: 2.6.x maintenance version
|
||||
- `support/2.5`: 2.5.x maintenance version
|
||||
|
||||
Also note that we have a "micro-version" concept : each of those versions have a very small amount of modifications. They are made from
|
||||
`support/*` branches as well. For example 2.6.2-1 and 2.6.2-2 were made from the `support/2.6.2` branch.
|
||||
@@ -111,9 +114,9 @@ Our tests are located in the `test/` directory, containing a PHPUnit config file
|
||||
* Use the present tense ("Add feature" not "Added feature")
|
||||
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
|
||||
* Limit the first line to 72 characters or less
|
||||
* Please start the commit message with an applicable emoji code (following the [Gitmoji guide](https://gitmoji.carloscuesta.me/)).
|
||||
Beware to use the code (for example `:bug:`) and not the character (🐛) as Unicode support in git clients is very poor for now...
|
||||
Emoji examples :
|
||||
* Please start the commit message with an applicable emoji code (following the [Gitmoji guide](https://gitmoji.dev/)).
|
||||
Beware to use the code (for example `:bug:`) and not the character (🐛) as Unicode support in git clients is very poor for now...
|
||||
Emoji examples :
|
||||
* 🌐 `:globe_with_meridians:` for translations
|
||||
* 🎨 `:art:` when improving the format/structure of the code
|
||||
* ⚡️ `:zap:` when improving performance
|
||||
@@ -131,21 +134,20 @@ Our tests are located in the `test/` directory, containing a PHPUnit config file
|
||||
|
||||
When your code is working, please:
|
||||
|
||||
* stash as much as possible your commits,
|
||||
* rebase your branch on our repo last commit,
|
||||
* create a pull request.
|
||||
* Squash as much as possible your commits,
|
||||
* Rebase your branch on our repo last commit,
|
||||
* 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/)_.
|
||||
* Pull request description: mind to add all the information useful to understand why you're suggesting this modification and anything necessary to dive into your work. Especially:
|
||||
- Bugfixes: exact steps to reproduce the bug (given/when/then), description of the bug cause and what solution is implemented
|
||||
- Enhancements: use cases, implementation details if needed
|
||||
* Mind to check the "[Allow edits from maintainers](https://docs.github.com/en/github-ae@latest/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)" option !
|
||||
|
||||
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.
|
||||
## 🙏 We are thankful
|
||||
|
||||
[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 for all your contributions to the iTop universe! As a thank you gift, we will send stickers to every iTop (& extensions) contributors!
|
||||
|
||||
### 🙏 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:
|
||||
We have one sticker per contribution type. You might get multiple stickers with one contribution though :)
|
||||
|
||||
* Bug hunter: Fix a bug
|
||||
* Translator: Add/update translations
|
||||
@@ -157,4 +159,6 @@ Stickers' design might change from one year to another. For the first year we wa
|
||||
* Beta tester: Test and give feedback on beta releases
|
||||
* Extension developer: Develop and publish an extension
|
||||
|
||||

|
||||
Here is the design of each stickers for year 2022:
|
||||
|
||||

|
||||
|
||||
8
Jenkinsfile
vendored
8
Jenkinsfile
vendored
@@ -1,6 +1,14 @@
|
||||
def infra
|
||||
|
||||
node(){
|
||||
properties([
|
||||
buildDiscarder(
|
||||
logRotator(
|
||||
daysToKeepStr: "28",
|
||||
numToKeepStr: "500")
|
||||
)
|
||||
])
|
||||
|
||||
checkout scm
|
||||
|
||||
infra = load '/var/lib/jenkins/workspace/itop-test-infra_master/src/Infra.groovy'
|
||||
|
||||
18
README.md
18
README.md
@@ -1,13 +1,11 @@
|
||||
<p align="center"><a href="https://www.combodo.com/itop-193" target="_blank">
|
||||
<img src="https://www.combodo.com/logos/logo-itop.svg">
|
||||
<img src="https://www.combodo.com/logos/logo-itop-baseline.svg" width=350>
|
||||
</a></p>
|
||||
|
||||
|
||||
# iTop - ITSM & CMDB
|
||||
|
||||
iTop stands for *IT Operations Portal*.
|
||||
It is a complete open source, ITIL, web based service management tool including a fully customizable CMDB, a helpdesk system and a document management tool.
|
||||
iTop also offers mass import tools and web services to integrate with your IT
|
||||
iTop stands for IT Operations Portal. It is a complete open source and web-based IT service management platform, including a fully customizable CMDB, a helpdesk system, and a document management tool. It is ITIL compliant and easily customizable and extensible thanks to a high number of add-ons and web services to integrate with your IT.
|
||||
|
||||
iTop also offers mass import tools to help you become even more efficient.
|
||||
|
||||
## Features
|
||||
- Fully configurable [Configuration Management (CMDB)][10]
|
||||
@@ -42,6 +40,7 @@ iTop also offers mass import tools and web services to integrate with your IT
|
||||
- [Software requirements][4]
|
||||
- [Documentation][5] covering both iTop and its official extensions
|
||||
- [iTop Hub][6] : discover and install extensions !
|
||||
- [iTop versions history][7]
|
||||
|
||||
|
||||
[1]: https://sourceforge.net/p/itop/discussion/
|
||||
@@ -50,6 +49,7 @@ iTop also offers mass import tools and web services to integrate with your IT
|
||||
[4]: https://www.itophub.io/wiki/page?id=latest:install:upgrading_itop
|
||||
[5]: https://www.itophub.io/wiki
|
||||
[6]: https://store.itophub.io/en_US/
|
||||
[7]: .doc/itop-version-history.md
|
||||
|
||||
[10]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#configuration_management_cmdb
|
||||
[11]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#ticketing
|
||||
@@ -65,7 +65,7 @@ iTop also offers mass import tools and web services to integrate with your IT
|
||||
|
||||
## About Us
|
||||
|
||||
iTop development is sponsored, led and supported by [Combodo][0].
|
||||
iTop development is sponsored, led, and supported by [Combodo][0].
|
||||
|
||||
[0]: https://www.combodo.com
|
||||
|
||||
@@ -100,8 +100,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](https://www.github.com/rokam))
|
||||
- Mozart de Oliveira, Eduardo (a.k.a [@eduardomozart](https://github.com/eduardomozart))
|
||||
- Raenker, Martin
|
||||
- Roháč, Richard (a.k.a [@RohacRichard](https://github.com/RohacRichard))
|
||||
- Rosenke, Stephan
|
||||
- Rudner, Björn (a.k.a [@rudnerbjoern](https://github.com/rudnerbjoern))
|
||||
- Seki, Shoji
|
||||
- Shilov, Vladimir
|
||||
- Stukalov, Ilya (a.k.a [@ilya](https://www.github.com/ilya)-stukalov)
|
||||
@@ -115,6 +118,7 @@ We would like to give a special thank you 🤗 to the people from the community
|
||||
- DudekArtur
|
||||
- Karkoff1212
|
||||
- Laura
|
||||
- nv35
|
||||
- Purple Grape
|
||||
- Schlobinux
|
||||
- theBigOne
|
||||
|
||||
12
SECURITY.md
12
SECURITY.md
@@ -18,8 +18,7 @@ to [itop-security@combodo.com](mailto:itop-security@combodo.com).
|
||||
|
||||
|
||||
|
||||
## 📆 Disclosure Policy
|
||||
|
||||
## 🔍 Combodo acknowledgment and investigation
|
||||
Report sent to us will be acknowledged within the week.
|
||||
|
||||
Then, a Combodo developer will be assigned to the reported issue and will:
|
||||
@@ -34,3 +33,12 @@ Then, a Combodo developer will be assigned to the reported issue and will:
|
||||
Security issues always take precedence over bug fixes and feature work.
|
||||
|
||||
The assignee will keep you informed of the resolution progress, and may ask you for additional information or guidance.
|
||||
|
||||
|
||||
## 📆 Disclosure Policy
|
||||
Once the fix is done and acknowledged by every stakeholder, it will be included in the next iTop version.
|
||||
Mind we have at least 2 active branches (LTS and STS, see [iTop Community Releases [iTop Documentation]](https://www.itophub.io/wiki/page?id=latest:release:start))
|
||||
|
||||
The release communications will include the information of the vulnerability fix.
|
||||
|
||||
Corresponding GitHub advisories and CVE will be published 3 months after the iTop version release date so that iTop instances can be updated.
|
||||
|
||||
@@ -121,7 +121,6 @@ class UserRightsMatrix extends UserRightsAddOnAPI
|
||||
public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
|
||||
{
|
||||
// Maybe we should check that no other user with userid == 0 exists
|
||||
CMDBObject::SetTrackInfo('Initialization');
|
||||
$oUser = new UserLocal();
|
||||
$oUser->Set('login', $sAdminUser);
|
||||
$oUser->Set('password', $sAdminPwd);
|
||||
|
||||
@@ -124,7 +124,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
$bGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode);
|
||||
if ($bGrant === true)
|
||||
{
|
||||
$aStimuli[] = '<span title="'.$sStimulusCode.': '.htmlentities($oStimulus->GetDescription(), ENT_QUOTES, 'UTF-8').'">'.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').'</span>';
|
||||
$aStimuli[] = '<span title="'.$sStimulusCode.': '.utils::EscapeHtml($oStimulus->GetDescription()).'">'.utils::EscapeHtml($oStimulus->GetLabel()).'</span>';
|
||||
}
|
||||
}
|
||||
$sStimuli = implode(', ', $aStimuli);
|
||||
@@ -435,20 +435,18 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
// Installation: create the very first user
|
||||
public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
|
||||
{
|
||||
CMDBObject::SetTrackInfo('Initialization');
|
||||
CMDBObject::SetCurrentChangeFromParams('Initialization create administrator');
|
||||
|
||||
$iContactId = 0;
|
||||
// Support drastic data model changes: no organization class (or not writable)!
|
||||
if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization'))
|
||||
{
|
||||
if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) {
|
||||
$oOrg = MetaModel::NewObject('Organization');
|
||||
$oOrg->Set('name', 'My Company/Department');
|
||||
$oOrg->Set('code', 'SOMECODE');
|
||||
$iOrgId = $oOrg->DBInsertNoReload();
|
||||
|
||||
// Support drastic data model changes: no Person class (or not writable)!
|
||||
if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person'))
|
||||
{
|
||||
if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) {
|
||||
$oContact = MetaModel::NewObject('Person');
|
||||
$oContact->Set('name', 'My last name');
|
||||
$oContact->Set('first_name', 'My first name');
|
||||
|
||||
@@ -278,8 +278,8 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
{
|
||||
$oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode);
|
||||
if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes'))
|
||||
{
|
||||
$aStimuli[] = '<span title="'.$sStimulusCode.': '.htmlentities($oStimulus->GetDescription(), ENT_QUOTES, 'UTF-8').'">'.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').'</span>';
|
||||
{
|
||||
$aStimuli[] = '<span title="'.$sStimulusCode.': '.utils::EscapeHtml($oStimulus->GetDescription()).'">'.utils::EscapeHtml($oStimulus->GetLabel()).'</span>';
|
||||
}
|
||||
}
|
||||
$sStimuli = implode(', ', $aStimuli);
|
||||
@@ -508,24 +508,18 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
|
||||
{
|
||||
// Create a change to record the history of the User object
|
||||
/** @var \CMDBChange $oChange */
|
||||
$oChange = MetaModel::NewObject("CMDBChange");
|
||||
$oChange->Set("date", time());
|
||||
$oChange->Set("userinfo", "Initialization");
|
||||
CMDBObject::SetCurrentChangeFromParams('Initialization : create first user admin profile');
|
||||
|
||||
$iContactId = 0;
|
||||
// Support drastic data model changes: no organization class (or not writable)!
|
||||
if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization'))
|
||||
{
|
||||
if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) {
|
||||
$oOrg = MetaModel::NewObject('Organization');
|
||||
$oOrg->Set('name', 'My Company/Department');
|
||||
$oOrg->Set('code', 'SOMECODE');
|
||||
$oOrg::SetCurrentChange($oChange);
|
||||
$iOrgId = $oOrg->DBInsertNoReload();
|
||||
|
||||
// Support drastic data model changes: no Person class (or not writable)!
|
||||
if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person'))
|
||||
{
|
||||
if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) {
|
||||
$oContact = MetaModel::NewObject('Person');
|
||||
$oContact->Set('name', 'My last name');
|
||||
$oContact->Set('first_name', 'My first name');
|
||||
@@ -534,7 +528,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
$oContact->Set('org_id', $iOrgId);
|
||||
}
|
||||
$oContact->Set('email', 'my.email@foo.org');
|
||||
$oContact::SetCurrentChange($oChange);
|
||||
$iContactId = $oContact->DBInsertNoReload();
|
||||
}
|
||||
}
|
||||
@@ -543,24 +536,22 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
$oUser = new UserLocal();
|
||||
$oUser->Set('login', $sAdminUser);
|
||||
$oUser->Set('password', $sAdminPwd);
|
||||
if (MetaModel::IsValidAttCode('UserLocal', 'contactid') && ($iContactId != 0))
|
||||
{
|
||||
if (MetaModel::IsValidAttCode('UserLocal', 'contactid') && ($iContactId != 0)) {
|
||||
$oUser->Set('contactid', $iContactId);
|
||||
}
|
||||
$oUser->Set('language', $sLanguage); // Language was chosen during the installation
|
||||
|
||||
// Add this user to the very specific 'admin' profile
|
||||
$oAdminProfile = MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => ADMIN_PROFILE_NAME), true /*all data*/);
|
||||
if (is_object($oAdminProfile))
|
||||
{
|
||||
if (is_object($oAdminProfile)) {
|
||||
$oUserProfile = new URP_UserProfile();
|
||||
$oUserProfile->Set('profileid', $oAdminProfile->GetKey());
|
||||
$oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile');
|
||||
$oSet = DBObjectSet::FromObject($oUserProfile);
|
||||
$oUser->Set('profile_list', $oSet);
|
||||
}
|
||||
$oUser::SetCurrentChange($oChange);
|
||||
$iUserId = $oUser->DBInsertNoReload();
|
||||
$oUser->DBInsertNoReload();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,8 +110,8 @@ class URP_Profiles extends UserRightsBaseClass
|
||||
{
|
||||
$oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode);
|
||||
if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes'))
|
||||
{
|
||||
$aStimuli[] = '<span title="'.$sStimulusCode.': '.htmlentities($oStimulus->GetDescription(), ENT_QUOTES, 'UTF-8').'">'.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').'</span>';
|
||||
{
|
||||
$aStimuli[] = '<span title="'.$sStimulusCode.': '.utils::EscapeHtml($oStimulus->GetDescription()).'">'.utils::EscapeHtml($oStimulus->GetLabel()).'</span>';
|
||||
}
|
||||
}
|
||||
$sStimuli = implode(', ', $aStimuli);
|
||||
@@ -568,14 +568,11 @@ class UserRightsProjection extends UserRightsAddOnAPI
|
||||
public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
|
||||
{
|
||||
// Create a change to record the history of the User object
|
||||
$oChange = MetaModel::NewObject("CMDBChange");
|
||||
$oChange->Set("date", time());
|
||||
$oChange->Set("userinfo", "Initialization");
|
||||
CMDBObject::SetCurrentChangeFromParams('Initialization : create first user admin');
|
||||
|
||||
$oOrg = new Organization();
|
||||
$oOrg->Set('name', 'My Company/Department');
|
||||
$oOrg->Set('code', 'SOMECODE');
|
||||
$oOrg::SetCurrentChange($oChange);
|
||||
$iOrgId = $oOrg->DBInsertNoReload();
|
||||
|
||||
$oContact = new Person();
|
||||
@@ -584,7 +581,6 @@ class UserRightsProjection extends UserRightsAddOnAPI
|
||||
//$oContact->Set('status', 'available');
|
||||
$oContact->Set('org_id', $iOrgId);
|
||||
$oContact->Set('email', 'my.email@foo.org');
|
||||
$oContact::SetCurrentChange($oChange);
|
||||
$iContactId = $oContact->DBInsertNoReload();
|
||||
|
||||
$oUser = new UserLocal();
|
||||
@@ -592,7 +588,6 @@ class UserRightsProjection extends UserRightsAddOnAPI
|
||||
$oUser->Set('password', $sAdminPwd);
|
||||
$oUser->Set('contactid', $iContactId);
|
||||
$oUser->Set('language', $sLanguage); // Language was chosen during the installation
|
||||
$oUser::SetCurrentChange($oChange);
|
||||
$iUserId = $oUser->DBInsertNoReload();
|
||||
|
||||
// Add this user to the very specific 'admin' profile
|
||||
@@ -600,7 +595,6 @@ class UserRightsProjection extends UserRightsAddOnAPI
|
||||
$oUserProfile->Set('userid', $iUserId);
|
||||
$oUserProfile->Set('profileid', ADMIN_PROFILE_ID);
|
||||
$oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile');
|
||||
$oUserProfile::SetCurrentChange($oChange);
|
||||
$oUserProfile->DBInsertNoReload();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/AjaxPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
// cannot notify depreciation for now as this is still load in autoloader
|
||||
//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
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to AjaxPage
|
||||
*/
|
||||
class ajax_page extends AjaxPage
|
||||
{
|
||||
|
||||
function __construct($s_title)
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('ajax_page is deprecated. Please use AjaxPage instead');
|
||||
parent::__construct($s_title);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ require_once(APPROOT.'/application/applicationcontext.class.inc.php');
|
||||
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
|
||||
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.category.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.domain.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.rule.class.inc.php');
|
||||
require_once(APPROOT.'/application/query.class.inc.php');
|
||||
require_once(APPROOT.'/setup/moduleinstallation.class.inc.php');
|
||||
|
||||
@@ -224,7 +224,7 @@ class ApplicationContext
|
||||
{
|
||||
$sContext = "";
|
||||
foreach ($this->aValues as $sName => $sValue) {
|
||||
$sContext .= "<input type=\"hidden\" name=\"c[$sName]\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n";
|
||||
$sContext .= "<input type=\"hidden\" name=\"c[$sName]\" value=\"".utils::EscapeHtml($sValue)."\" />\n";
|
||||
}
|
||||
return $sContext;
|
||||
}
|
||||
@@ -238,7 +238,7 @@ class ApplicationContext
|
||||
{
|
||||
$aContextInputBlocks = [];
|
||||
foreach ($this->aValues as $sName => $sValue) {
|
||||
$aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", htmlentities($sValue, ENT_QUOTES, 'UTF-8'));
|
||||
$aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", utils::EscapeHtml($sValue));
|
||||
}
|
||||
return $aContextInputBlocks;
|
||||
}
|
||||
@@ -376,26 +376,19 @@ class ApplicationContext
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
|
||||
if (is_null($sUrlMakerClass))
|
||||
{
|
||||
$sUrlMakerClass = self::GetUrlMakerClass();
|
||||
}
|
||||
if (is_null($sUrlMakerClass)) {
|
||||
$sUrlMakerClass = self::GetUrlMakerClass();
|
||||
}
|
||||
$sUrl = call_user_func(array($sUrlMakerClass, 'MakeObjectUrl'), $sObjClass, $sObjKey);
|
||||
if (strlen($sUrl) > 0)
|
||||
{
|
||||
if ($bWithNavigationContext)
|
||||
{
|
||||
return $sUrl."&".$oAppContext->GetForLink();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sUrl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
if (utils::StrLen($sUrl) > 0) {
|
||||
if ($bWithNavigationContext) {
|
||||
return $sUrl."&".$oAppContext->GetForLink();
|
||||
} else {
|
||||
return $sUrl;
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* This class manages the audit "categories". Each category defines a set of objects
|
||||
* to check and is linked to a set of rules that determine the valid or invalid objects
|
||||
* inside the set
|
||||
* inside the set
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
@@ -48,13 +48,39 @@ class AuditCategory extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeOQL("definition_set", array("allowed_values"=>null, "sql"=>"definition_set", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", array("linked_class"=>"AuditRule", "ext_key_to_me"=>"category_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(), "edit_mode" => LINKSET_EDITMODE_INPLACE, "tracking_level" => LINKSET_TRACKING_ALL)));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("ok_error_tolerance", array("allowed_values"=>null, "sql"=>"ok_error_tolerance", "default_value"=>5, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("warning_error_tolerance", array("allowed_values"=>null, "sql"=>"warning_error_tolerance", "default_value"=>25, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("domains_list", array("linked_class" => "lnkAuditCategoryToAuditDomain", "ext_key_to_me" => "category_id", "ext_key_to_remote" => "domain_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('name', 'description', 'definition_set', 'rules_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('name', 'description', 'definition_set', 'ok_error_tolerance', 'warning_error_tolerance', 'rules_list', 'domains_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('description', )); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description', 'definition_set')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $iTotal
|
||||
* @param int $iErrors
|
||||
*
|
||||
* @return string A semantic color name (eg. red, green, orange, success, failure, ... {@see css/backoffice/utils/variables/colors/_semantic-palette.scss}) to use for this category depending on its error count and tolerance
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public function GetReportColor($iTotal, $iErrors)
|
||||
{
|
||||
$sResult = 'red';
|
||||
if ( ($iTotal == 0) || ($iErrors / $iTotal) <= ($this->Get('ok_error_tolerance') / 100) )
|
||||
{
|
||||
$sResult = 'green';
|
||||
}
|
||||
else if ( ($iErrors / $iTotal) <= ($this->Get('warning_error_tolerance') / 100) )
|
||||
{
|
||||
$sResult = 'orange';
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
99
application/audit.domain.class.inc.php
Normal file
99
application/audit.domain.class.inc.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?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/>
|
||||
|
||||
|
||||
/**
|
||||
* This class manages the audit "categories". Each category defines a set of objects
|
||||
* to check and is linked to a set of rules that determine the valid or invalid objects
|
||||
* inside the set
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @since 3.1.0
|
||||
*/
|
||||
class AuditDomain extends cmdbAbstractObject
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "application, grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array('name'),
|
||||
"db_table" => "priv_auditdomain",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeImage("icon", array("is_null_allowed"=>true, "depends_on"=>array(), "display_max_width"=>96, "display_max_height"=>96, "storage_max_width"=>256, "storage_max_height"=>256, "default_image"=>null, "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("categories_list", array("linked_class" => "lnkAuditCategoryToAuditDomain", "ext_key_to_me" => "domain_id", "ext_key_to_remote" => "category_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('name', 'description', 'icon', 'categories_list')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('description', )); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('description')); // Criteria of the std search form
|
||||
MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0
|
||||
*/
|
||||
class lnkAuditCategoryToAuditDomain extends cmdbAbstractObject
|
||||
{
|
||||
/**
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "application, grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array('category_id', 'domain_id'),
|
||||
"db_table" => "priv_link_audit_category_domain",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
"is_link" => true,
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", array("targetclass" => "AuditCategory", "jointype" => '', "allowed_values" => null, "sql" => "category_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", array("allowed_values" => null, "extkey_attcode" => 'category_id', "target_attcode" => "name")));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("domain_id", array("targetclass" => "AuditDomain", "jointype" => '', "allowed_values" => null, "sql" => "domain_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("domain_name", array("allowed_values" => null, "extkey_attcode" => 'domain_id', "target_attcode" => "name")));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('category_id', 'domain_id'));
|
||||
MetaModel::Init_SetZListItems('list', array('category_id', 'domain_id'));
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search', array('category_id', 'domain_id'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CaptureWebPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/CaptureWebPage.php, now loadable using autoloader');
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CLIPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/CLIPage.php, now loadable using autoloader');
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CSVPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/CSVPage.php, now loadable using autoloader');
|
||||
@@ -1549,6 +1549,29 @@ JS
|
||||
return $this->sDefinitionFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sDashboardFileRelative can also be an absolute path (compatibility with old URL)
|
||||
*
|
||||
* @return string full path to the Dashboard file
|
||||
* @throws \SecurityException if path isn't under approot
|
||||
* @uses utils::RealPath()
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°4449 remove FPD
|
||||
*/
|
||||
public static function GetDashboardFileFromRelativePath($sDashboardFileRelative)
|
||||
{
|
||||
if (utils::RealPath($sDashboardFileRelative, APPROOT)) {
|
||||
// compatibility with old URL containing absolute path !
|
||||
return $sDashboardFileRelative;
|
||||
}
|
||||
|
||||
$sDashboardFile = APPROOT.$sDashboardFileRelative;
|
||||
if (false === utils::RealPath($sDashboardFile, APPROOT)) {
|
||||
throw new SecurityException('Invalid dashboard file !');
|
||||
}
|
||||
|
||||
return $sDashboardFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sDefinitionFile
|
||||
*/
|
||||
|
||||
@@ -262,7 +262,7 @@ abstract class Dashlet
|
||||
}
|
||||
} catch (OqlException $e) {
|
||||
$oDashletContainer->AddCSSClass("dashlet-content");
|
||||
$oDashletContainer->AddHtml('<p>'.$e->GetUserFriendlyDescription().'</p>');
|
||||
$oDashletContainer->AddHtml('<p>'.utils::HtmlEntities($e->GetUserFriendlyDescription()).'</p>');
|
||||
} catch (Exception $e) {
|
||||
$oDashletContainer->AddCSSClass("dashlet-content");
|
||||
$oDashletContainer->AddHtml('<p>'.$e->getMessage().'</p>');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.1">
|
||||
<classes>
|
||||
<class id="AbstractResource" _delta="define">
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
@@ -55,9 +55,9 @@
|
||||
<menus>
|
||||
<menu id="WelcomeMenu" xsi:type="MenuGroup" _delta="define">
|
||||
<rank>10</rank>
|
||||
<style>
|
||||
<decoration_classes>fas fa-home</decoration_classes>
|
||||
</style>
|
||||
<style>
|
||||
<decoration_classes>fas fa-home</decoration_classes>
|
||||
</style>
|
||||
</menu>
|
||||
<menu id="WelcomeMenuPage" xsi:type="DashboardMenuNode" _delta="define">
|
||||
<rank>10</rank>
|
||||
@@ -151,9 +151,9 @@
|
||||
</menu>
|
||||
<menu id="ConfigurationTools" xsi:type="MenuGroup" _delta="define_if_not_exists">
|
||||
<rank>90</rank>
|
||||
<style>
|
||||
<decoration_classes>fas fa-cog</decoration_classes>
|
||||
</style>
|
||||
<style>
|
||||
<decoration_classes>fas fa-cog</decoration_classes>
|
||||
</style>
|
||||
</menu>
|
||||
<menu id="DataSources" xsi:type="OQLMenuNode" _delta="define">
|
||||
<rank>20</rank>
|
||||
@@ -172,19 +172,356 @@
|
||||
</menu>
|
||||
<menu id="AdminTools" xsi:type="MenuGroup" _delta="define">
|
||||
<rank>80</rank>
|
||||
<style>
|
||||
<decoration_classes>fas fa-tools</decoration_classes>
|
||||
</style>
|
||||
<style>
|
||||
<decoration_classes>fas fa-tools</decoration_classes>
|
||||
</style>
|
||||
</menu>
|
||||
<menu id="SystemTools" xsi:type="MenuGroup" _delta="define">
|
||||
<rank>100</rank>
|
||||
<enable_class>ResourceSystemMenu</enable_class>
|
||||
<enable_action>UR_ACTION_MODIFY</enable_action>
|
||||
<style>
|
||||
<decoration_classes>fas fa-terminal</decoration_classes>
|
||||
</style>
|
||||
<style>
|
||||
<decoration_classes>fas fa-terminal</decoration_classes>
|
||||
</style>
|
||||
</menu>
|
||||
</menus>
|
||||
<events>
|
||||
<event id="EVENT_DB_CHECK_TO_WRITE" _delta="define">
|
||||
<description>Check an object before it is written into the database (no change possible). Call DBObject::AddCheckIssue() to signal an issue</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>cmdbAbstractObject::DoCheckToWrite</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object to check</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_CREATE_DONE" _delta="define">
|
||||
<description>An object has been created into the database. The modifications can be propagated to other objects.</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::AfterInsert</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object inserted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_UPDATE_DONE" _delta="define">
|
||||
<description>An object has been updated into the database and reloaded.</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::AfterUpdate</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object updated</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_CHECK_TO_DELETE" _delta="define">
|
||||
<description>Check an object before it is deleted from the database. Call DBObject::AddDeleteIssue() to signal an issue</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>cmdbAbstractObject::DoCheckToDelete</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object to check</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_DELETE_DONE" _delta="define">
|
||||
<description>An object has been deleted into the database</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::AfterDelete</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object deleted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_BEFORE_APPLY_STIMULUS" _delta="define">
|
||||
<description>A stimulus is about to be applied to an object</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object where the stimulus is targeted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="stimulus">
|
||||
<description>Current stimulus applied</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="previous_state">
|
||||
<description>Object previous state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="new_state">
|
||||
<description>Object new state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="save_object">
|
||||
<description>The object must be saved in the database</description>
|
||||
<type>boolean</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_AFTER_APPLY_STIMULUS" _delta="define">
|
||||
<description>A stimulus has been applied to an object</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object where the stimulus is targeted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="stimulus">
|
||||
<description>Current stimulus applied</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="previous_state">
|
||||
<description>Object previous state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="new_state">
|
||||
<description>Object new state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="save_object">
|
||||
<description>The object is asked to be saved in the database</description>
|
||||
<type>boolean</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_APPLY_STIMULUS_FAILED" _delta="define">
|
||||
<description>A stimulus has failed</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="action">
|
||||
<description>The action that failed to apply the stimulus</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="object">
|
||||
<description>The object where the stimulus is targeted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="stimulus">
|
||||
<description>Current stimulus applied</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="previous_state">
|
||||
<description>Object previous state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="new_state">
|
||||
<description>Object new state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="save_object">
|
||||
<description>The object must be saved in the database</description>
|
||||
<type>boolean</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_LINKS_CHANGED" _delta="define">
|
||||
<description>At least one link class was changed</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object where the link is or was pointing to</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_OBJECT_RELOAD" _delta="define">
|
||||
<description>An object has been re-loaded from the database</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object re-loaded</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_COMPUTE_VALUES" _delta="define">
|
||||
<description>An object needs to be recomputed after changes</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::ComputeValues</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object inserted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_ARCHIVE" _delta="define">
|
||||
<description>An object has been archived</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object archived</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_UNARCHIVE" _delta="define">
|
||||
<description>An object has been unarchived</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object unarchived</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_SET_ATTRIBUTES_FLAGS" _delta="define">
|
||||
<description>Set object attributes flags. Call cmdbAbstractObject::AddAttributeFlags() for all the attributes to be set for this target state.</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The current object</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="target_state">
|
||||
<description>The target state in which to evaluate the flags</description>
|
||||
<type>array</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DB_SET_INITIAL_ATTRIBUTES_FLAGS" _delta="define">
|
||||
<description>Set object initial attributes flags. Call cmdbAbstractObject::AddInitialAttributeFlags() for all the initial attributes to be set initially.</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The current object</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_DOWNLOAD_DOCUMENT" _delta="define">
|
||||
<description>A document has been downloaded from the GUI</description>
|
||||
<sources>
|
||||
<source id="Document">Document</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object containing the document</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="document">
|
||||
<description>The document downloaded</description>
|
||||
<type>ormDocument</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_LOGIN" _delta="define">
|
||||
<description>Inform the listeners about the connection states</description>
|
||||
<event_data>
|
||||
<event_datum id="code">
|
||||
<description>The login step result code (LoginWebPage::EXIT_CODE_...) </description>
|
||||
<type>integer</type>
|
||||
</event_datum>
|
||||
<event_datum id="state">
|
||||
<description>Current login state (LoginWebPage::LOGIN_STATE_CONNECTED...)</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
</events>
|
||||
<meta>
|
||||
<classes>
|
||||
<class id="cmdbAbstractObject" _delta="define">
|
||||
|
||||
@@ -19,7 +19,7 @@ use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*
|
||||
* @deprecated since 3.0.0 use Combodo\iTop\Application\UI\Base\Component\DataTable\Datatable
|
||||
* @deprecated 3.0.0 use Combodo\iTop\Application\UI\Base\Component\DataTable\Datatable
|
||||
*/
|
||||
|
||||
class DataTable
|
||||
|
||||
@@ -272,6 +272,8 @@ class DisplayBlock
|
||||
'panel_title',
|
||||
/** string true if panel title should be displayed as html */
|
||||
'panel_title_is_html',
|
||||
/** string Description of the panel content, displayed as a hint on the title */
|
||||
'panel_title_tooltip',
|
||||
/** string class for panel block style */
|
||||
'panel_class',
|
||||
/** string class for panel block style */
|
||||
@@ -476,7 +478,7 @@ class DisplayBlock
|
||||
$oExceptionAlert = AlertUIBlockFactory::MakeForFailure('Cannot display results', $sExceptionContent);
|
||||
$oHtml->AddSubBlock($oExceptionAlert);
|
||||
}
|
||||
IssueLog::Error('Exception during GetDisplay: '.$e->getMessage());
|
||||
ExceptionLog::LogException($e);
|
||||
}
|
||||
} else {
|
||||
// render it as an Ajax (asynchronous) call
|
||||
@@ -566,7 +568,7 @@ class DisplayBlock
|
||||
if (($this->m_sStyle != 'links') && ($this->m_sStyle != 'search') && ($this->m_sStyle != 'list_search')) {
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sClass = $this->m_oFilter->GetClass();
|
||||
$aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($sClass));
|
||||
$aFilterCodes = MetaModel::GetFiltersList($sClass);
|
||||
$aCallSpec = array($sClass, 'MapContextParam');
|
||||
if (is_callable($aCallSpec)) {
|
||||
foreach ($oAppContext->GetNames() as $sContextParam) {
|
||||
@@ -1045,9 +1047,18 @@ JS
|
||||
$aCount = $aCounts[$sStateValue];
|
||||
$sHyperlink = $aCount['link'];
|
||||
$sCountLabel = $aCount['label'];
|
||||
$oPill = PillFactory::MakeForState($sClass, $sStateValue)
|
||||
->SetTooltip($sStateLabel)
|
||||
->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>");
|
||||
|
||||
$oPill = PillFactory::MakeForState($sClass, $sStateValue);
|
||||
// N°5849 - Unencode label for ExternalKey attribute because friendlyname is already html encoded thanks to DBObject::GetName() in AttributeExternalKey::GetAllowedValues(). (A fix in this function may have too much impact).
|
||||
if ($oAttDef instanceof AttributeExternalKey) {
|
||||
$sPillTooltip = htmlspecialchars_decode($sStateLabel, ENT_QUOTES | ENT_DISALLOWED | ENT_HTML5);
|
||||
$sPillLabel = $sStateLabel;
|
||||
} else {
|
||||
$sPillTooltip = $sStateLabel;
|
||||
$sPillLabel = utils::HtmlEntities($sStateLabel);
|
||||
}
|
||||
$oPill->SetTooltip($sPillTooltip)
|
||||
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--count\">$sCountLabel</span><span class=\"ibo-dashlet-header-dynamic--label ibo-text-truncated-with-ellipsis\">".$sPillLabel."</span>");
|
||||
if ($sHyperlink != '-') {
|
||||
$oPill->SetUrl($sHyperlink);
|
||||
}
|
||||
@@ -1207,6 +1218,7 @@ JS
|
||||
$sTitle = Dict::Format($sFormat, $iTotalCount);
|
||||
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
|
||||
$oBlock->AddSubTitleBlock(new Html($sTitle));
|
||||
$oBlock->AddCSSClass('ibo-datatable-panel');
|
||||
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
|
||||
$oBlock->SetIcon($aExtraParams["panel_icon"]);
|
||||
}
|
||||
@@ -1303,17 +1315,12 @@ JS
|
||||
}
|
||||
}
|
||||
if (count($aAuthorizedClasses) > 0) {
|
||||
if ($this->m_oSet->CountWithLimit(1) > 0) {
|
||||
if (empty($aExtraParams['currentId'])) {
|
||||
$iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !!
|
||||
} else {
|
||||
$iListId = $aExtraParams['currentId'];
|
||||
}
|
||||
$oBlock->AddSubBlock(DataTableUIBlockFactory::MakeForObject($oPage, $iListId, $this->m_oSet, $aExtraParams));
|
||||
if (empty($aExtraParams['currentId'])) {
|
||||
$iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !!
|
||||
} else {
|
||||
// Empty set
|
||||
$oBlock->bEmptySet = true;
|
||||
$iListId = $aExtraParams['currentId'];
|
||||
}
|
||||
$oBlock->AddSubBlock(DataTableUIBlockFactory::MakeForObject($oPage, $iListId, $this->m_oSet, $aExtraParams));
|
||||
} else {
|
||||
// Not authorized
|
||||
$oBlock->bNotAuthorized = true;
|
||||
@@ -1336,43 +1343,13 @@ JS
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) {
|
||||
$oBlock->AddSubBlock(cmdbAbstractObject::GetDisplaySetBlock($oPage, $this->m_oSet, $aExtraParams));
|
||||
} else {
|
||||
$oBlock->bEmptySet = true;
|
||||
$oBlock->sClass = $this->m_oFilter->GetClass();
|
||||
$oBlock->sClassLabel = MetaModel::GetName($oBlock->sClass);
|
||||
$bDisplayMenu = isset($aExtraParams['menu']) ? ($aExtraParams['menu'] == true) : true;
|
||||
if ($bDisplayMenu) {
|
||||
if ((UserRights::IsActionAllowed($oBlock->sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) {
|
||||
$oBlock->sLinkTarget = '';
|
||||
$oAppContext = new ApplicationContext();
|
||||
$oBlock->sParams = $oAppContext->GetForLink();
|
||||
// 1:n links, populate the target object as a default value when creating a new linked object
|
||||
if (isset($aExtraParams['target_attr'])) {
|
||||
$oBlock->sLinkTarget = ' target="_blank" ';
|
||||
$aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id'];
|
||||
}
|
||||
if (!empty($aExtraParams['default'])) {
|
||||
foreach ($aExtraParams['default'] as $sKey => $sValue) {
|
||||
$oBlock->sDefault .= "&default[$sKey]=$sValue";
|
||||
}
|
||||
}
|
||||
$oBlock->bCreateNew = true;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -1470,6 +1447,7 @@ JS
|
||||
} else {
|
||||
$iListId = $aExtraParams['currentId'];
|
||||
}
|
||||
|
||||
$oBlock = DataTableUIBlockFactory::MakeForRendering($iListId, $oSet, $aExtraParams);
|
||||
$oHtml->AddHtml("<tr><td>");
|
||||
$oContentBlock->AddSubBlock($oBlock);
|
||||
@@ -1705,162 +1683,6 @@ JS
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to manage 'blocks' of HTML pieces that are parts of a page and contain some list of cmdb objects
|
||||
*
|
||||
* Each block is actually rendered as a <div></div> tag that can be rendered synchronously
|
||||
* or as a piece of Javascript/JQuery/Ajax that will get its content from another page (ajax.render.php).
|
||||
* The list of cmdbObjects to be displayed into the block is defined by a filter
|
||||
* Right now the type of display is either: list, count or details
|
||||
* - list produces a table listing the objects
|
||||
* - count produces a paragraphs with a sentence saying 'cont' objects found
|
||||
* - details display (as table) the details of each object found (best if only one)
|
||||
*
|
||||
* @deprecated 3.0.0 will be removed in 3.1, see N°3824
|
||||
*/
|
||||
class HistoryBlock extends DisplayBlock
|
||||
{
|
||||
protected $iLimitCount;
|
||||
protected $iLimitStart;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function SetLimit($iCount, $iStart = 0)
|
||||
{
|
||||
$this->iLimitStart = $iStart;
|
||||
$this->iLimitCount = $iCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WebPage $oPage
|
||||
* @param array $aExtraParams
|
||||
* @param string $sId
|
||||
*
|
||||
* @return string
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aExtraParams and add type hinting for PHP 8.0 compatibility
|
||||
* (var is unused, and all calls were already made using a default value)
|
||||
*/
|
||||
public function GetRenderContent(WebPage $oPage, array $aExtraParams, string $sId)
|
||||
{
|
||||
$sHtml = '';
|
||||
$bTruncated = false;
|
||||
$oSet = new CMDBObjectSet($this->m_oFilter, array('date' => false));
|
||||
if (!$oPage->IsPrintableVersion()) {
|
||||
if (($this->iLimitStart > 0) || ($this->iLimitCount > 0)) {
|
||||
$oSet->SetLimit($this->iLimitCount, $this->iLimitStart);
|
||||
if (($this->iLimitCount - $this->iLimitStart) < $oSet->Count()) {
|
||||
$bTruncated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$sHtml .= "<!-- filter: ".($this->m_oFilter->ToOQL())."-->\n";
|
||||
switch ($this->m_sStyle) {
|
||||
case 'toggle':
|
||||
// First the latest change that the user is allowed to see
|
||||
do {
|
||||
$oLatestChangeOp = $oSet->Fetch();
|
||||
} while (is_object($oLatestChangeOp) && ($oLatestChangeOp->GetDescription() == ''));
|
||||
|
||||
if (is_object($oLatestChangeOp)) {
|
||||
// There is one change in the list... only when the object has been created !
|
||||
$sDate = $oLatestChangeOp->GetAsHTML('date');
|
||||
$oChange = MetaModel::GetObject('CMDBChange', $oLatestChangeOp->Get('change'));
|
||||
$sUserInfo = $oChange->GetAsHTML('userinfo');
|
||||
$sHtml .= $oPage->GetStartCollapsibleSection(Dict::Format('UI:History:LastModified_On_By', $sDate, $sUserInfo));
|
||||
$sHtml .= $this->GetHistoryTable($oPage, $oSet);
|
||||
$sHtml .= $oPage->GetEndCollapsibleSection();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'table':
|
||||
default:
|
||||
if ($bTruncated)
|
||||
{
|
||||
$sFilter = htmlentities($this->m_oFilter->serialize(), ENT_QUOTES, 'UTF-8');
|
||||
$sHtml .= '<div id="history_container"><p>';
|
||||
$sHtml .= Dict::Format('UI:TruncatedResults', $this->iLimitCount, $oSet->Count());
|
||||
$sHtml .= ' ';
|
||||
$sHtml .= '<a href="#" onclick="DisplayHistory(\'#history_container\', \''.$sFilter.'\', 0, 0); return false;">'.Dict::S('UI:DisplayAll').'</a>';
|
||||
$sHtml .= $this->GetHistoryTable($oPage, $oSet);
|
||||
$sHtml .= '</p></div>';
|
||||
$oPage->add_ready_script("$('#{$sId} table.listResults tr:last td').addClass('truncated');");
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHtml .= $this->GetHistoryTable($oPage, $oSet);
|
||||
}
|
||||
$oPage->add_ready_script(InlineImage::FixImagesWidth());
|
||||
|
||||
$oPage->add_ready_script("$('.case-log-history-entry-toggle').on('click', function () { $(this).closest('.case-log-history-entry').toggleClass('expanded');});");
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('.history_entry').each(function() {
|
||||
var jMe = $(this);
|
||||
var oContent = $(this).find('.history_html_content');
|
||||
if (jMe.height() < oContent.height())
|
||||
{
|
||||
jMe.prepend('<span class="history_truncated_toggler"></span>');
|
||||
jMe.find('.history_truncated_toggler').on('click', function() {
|
||||
jMe.toggleClass('history_entry_truncated');
|
||||
});
|
||||
}
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
return new Html($sHtml);
|
||||
}
|
||||
|
||||
protected function GetHistoryTable(WebPage $oPage, DBObjectSet $oSet)
|
||||
{
|
||||
$sHtml = '';
|
||||
// First the latest change that the user is allowed to see
|
||||
$oSet->Rewind(); // Reset the pointer to the beginning of the set
|
||||
$aChanges = array();
|
||||
while($oChangeOp = $oSet->Fetch())
|
||||
{
|
||||
$sChangeDescription = $oChangeOp->GetDescription();
|
||||
if ($sChangeDescription != '')
|
||||
{
|
||||
// The change is visible for the current user
|
||||
$changeId = $oChangeOp->Get('change');
|
||||
$aChanges[$changeId]['date'] = $oChangeOp->Get('date');
|
||||
$aChanges[$changeId]['userinfo'] = $oChangeOp->Get('userinfo');
|
||||
if (!isset($aChanges[$changeId]['log']))
|
||||
{
|
||||
$aChanges[$changeId]['log'] = array();
|
||||
}
|
||||
$aChanges[$changeId]['log'][] = $sChangeDescription;
|
||||
}
|
||||
}
|
||||
$aAttribs = array('date' => array('label' => Dict::S('UI:History:Date'), 'description' => Dict::S('UI:History:Date+')),
|
||||
'userinfo' => array('label' => Dict::S('UI:History:User'), 'description' => Dict::S('UI:History:User+')),
|
||||
'log' => array('label' => Dict::S('UI:History:Changes') , 'description' => Dict::S('UI:History:Changes+')),
|
||||
);
|
||||
$aValues = array();
|
||||
foreach($aChanges as $aChange)
|
||||
{
|
||||
$aValues[] = array('date' => AttributeDateTime::GetFormat()->Format($aChange['date']), 'userinfo' => htmlentities($aChange['userinfo'], ENT_QUOTES, 'UTF-8'), 'log' => "<ul><li>".implode('</li><li>', $aChange['log'])."</li></ul>");
|
||||
}
|
||||
$sHtml .= $oPage->GetTable($aAttribs, $aValues);
|
||||
return $sHtml;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the 'Actions' menu for a given (list of) object(s)
|
||||
* The 'style' of the list (see constructor of DisplayBlock) can be either 'list' or 'details'
|
||||
@@ -1907,9 +1729,14 @@ class MenuBlock extends DisplayBlock
|
||||
$this->m_sStyle = 'list';
|
||||
}
|
||||
|
||||
$sClass = $this->m_oFilter->GetClass();
|
||||
$oSet = new CMDBObjectSet($this->m_oFilter);
|
||||
$sClass = $this->GetFilter()->GetClass();
|
||||
$aSelectedClasses = $this->GetFilter()->GetSelectedClasses();
|
||||
$bIsForLinkset = isset($aExtraParams['target_attr']);
|
||||
$oSet = new CMDBObjectSet($this->GetFilter());
|
||||
$iSetCount = $oSet->Count();
|
||||
/** @var string $sRefreshAction JS snippet to run when clicking on the refresh button of the menu */
|
||||
$sRefreshAction = $aExtraParams['refresh_action'] ?? '';
|
||||
$bIsCreationInModalAllowed = isset($aExtraParams['creation_in_modal_is_allowed']) && $aExtraParams['creation_in_modal_is_allowed'] === true;
|
||||
|
||||
/** @var array $aRegularActions Any action other than a transition */
|
||||
$aRegularActions = [];
|
||||
@@ -1917,275 +1744,240 @@ class MenuBlock extends DisplayBlock
|
||||
$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') {
|
||||
|
||||
if (!isset($aExtraParams['selection_mode']) || ($aExtraParams['selection_mode'] == "")) {
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
if (!empty($sContext)) {
|
||||
if (utils::IsNotNullOrEmptyString($sContext)) {
|
||||
$sContext = '&'.$sContext;
|
||||
}
|
||||
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
$sFilter = $this->m_oFilter->serialize();
|
||||
$sFilter = $this->GetFilter()->serialize();
|
||||
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
|
||||
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
|
||||
// Common params that will be applied to actions
|
||||
$aActionParams = array();
|
||||
if (isset($aExtraParams['menu_actions_target'])) {
|
||||
$aActionParams['target'] = $aExtraParams['menu_actions_target'];
|
||||
}
|
||||
$aActionParams = $this->GetDefaultParamsForMenuAction();
|
||||
|
||||
// 1:n links, populate the target object as a default value when creating a new linked object
|
||||
if (isset($aExtraParams['target_attr'])) {
|
||||
if ($bIsForLinkset) {
|
||||
$aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id'];
|
||||
}
|
||||
$sDefault = '';
|
||||
/** @var string $sDefaultValuesAsUrlParams Default values for the object to create, already formatted as URL params (eg. "&default[org_id]=3&default[title]=Foo") */
|
||||
$sDefaultValuesAsUrlParams = '';
|
||||
if (!empty($aExtraParams['default'])) {
|
||||
foreach ($aExtraParams['default'] as $sKey => $sValue) {
|
||||
$sDefault .= "&default[$sKey]=$sValue";
|
||||
$sDefaultValuesAsUrlParams .= "&default[$sKey]=$sValue";
|
||||
}
|
||||
}
|
||||
$bIsCreationAllowed = (UserRights::IsActionAllowed($sClass,
|
||||
UR_ACTION_CREATE) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
|
||||
switch ($oSet->Count()) {
|
||||
case 0:
|
||||
// No object in the set, the only possible action is "new"
|
||||
if ($bIsCreationAllowed) {
|
||||
$aRegularActions['UI:Menu:New'] = array(
|
||||
'label' => Dict::S('UI:Menu:New'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}",
|
||||
) + $aActionParams;
|
||||
|
||||
// Check rights
|
||||
$bIsCreationAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_CREATE) === UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
|
||||
$bIsModifyAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) === UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
|
||||
|
||||
// Create in new tab
|
||||
if ($bIsCreationAllowed && !$bIsCreationInModalAllowed) {
|
||||
$this->AddNewObjectMenuAction($aRegularActions, $sClass, $sDefaultValuesAsUrlParams);
|
||||
}
|
||||
|
||||
// Any style actions
|
||||
// - Bulk actions on objects set
|
||||
if ($iSetCount > 1) {
|
||||
// Bulk actions for each selected classes (eg. "link" and "remote" on n:n relations)
|
||||
foreach ($aSelectedClasses as $sSelectedAlias => $sSelectedClass) {
|
||||
$sSelectedClassName = MetaModel::GetName($sSelectedClass);
|
||||
|
||||
// Check rights on class
|
||||
$bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sSelectedClass)) && UserRights::IsActionAllowed($sSelectedClass, UR_ACTION_BULK_MODIFY, $oSet) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
|
||||
$bIsBulkDeleteAllowed = (bool) UserRights::IsActionAllowed($sSelectedClass, UR_ACTION_BULK_DELETE, $sSelectedClass);
|
||||
|
||||
// Refine filter on selected class so bullk actions occur on the right class
|
||||
$oSelectedClassFilter = $this->GetFilter()->DeepClone();
|
||||
$oSelectedClassFilter->SetSelectedClasses([$sSelectedAlias]);
|
||||
|
||||
// Action identifier is using the alias on purpose so they can be used as "shortcut actions" easily for "Link" or "Remote" aliases on linksets.
|
||||
// Action label dict code has a specific suffix for "Link" / "Remote" aliases to allow dedicated labels in linksets.
|
||||
$sActionLabelCodeSuffix = in_array($sSelectedAlias, ['Link', 'Remote']) ? $sSelectedAlias : 'Class';
|
||||
if ($bIsBulkModifyAllowed) {
|
||||
$this->AddBulkModifyObjectsMenuAction($aRegularActions, $sSelectedClass, $oSelectedClassFilter->serialize(), 'UI:Menu:ModifyAll:'.$sSelectedAlias, Dict::Format('UI:Menu:ModifyAll_'.$sActionLabelCodeSuffix, $sSelectedClassName));
|
||||
}
|
||||
break;
|
||||
if ($bIsBulkDeleteAllowed) {
|
||||
$this->AddBulkDeleteObjectsMenuAction($aRegularActions, $sSelectedClass, $oSelectedClassFilter->serialize(), 'UI:Menu:BulkDelete:'.$sSelectedAlias, Dict::Format('UI:Menu:BulkDelete_'.$sActionLabelCodeSuffix, $sSelectedClassName));
|
||||
}
|
||||
}
|
||||
|
||||
case 1:
|
||||
$oObj = $oSet->Fetch();
|
||||
if (is_null($oObj)) {
|
||||
if (!isset($aExtraParams['link_attr'])) {
|
||||
if ($bIsCreationAllowed) {
|
||||
$aRegularActions['UI:Menu:New'] = array(
|
||||
'label' => Dict::S('UI:Menu:New'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$id = $oObj->GetKey();
|
||||
if (empty($sRefreshAction) && utils::ReadParam('operation') == 'details') {
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
|
||||
$sRefreshAction = "window.location.reload();";
|
||||
} else {
|
||||
$sRefreshAction = "window.location.href='".ApplicationContext::MakeObjectUrl(get_class($oObj), $id)."';";
|
||||
}
|
||||
}
|
||||
// Stimuli
|
||||
$aStates = MetaModel::EnumStates($sClass);
|
||||
// Do not perform time-consuming computations if there are too many objects in the list
|
||||
$iLimit = MetaModel::GetConfig()->Get('complex_actions_limit');
|
||||
|
||||
$bLocked = false;
|
||||
if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) {
|
||||
$aLockInfo = iTopOwnershipLock::IsLocked(get_class($oObj), $id);
|
||||
if ($aLockInfo['locked']) {
|
||||
$bLocked = true;
|
||||
//$this->AddMenuSeparator($aActions);
|
||||
//$aActions['concurrent_lock_unlock'] = array ('label' => Dict::S('UI:Menu:ReleaseConcurrentLock'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}");
|
||||
}
|
||||
}
|
||||
$bRawModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
|
||||
$bIsModifyAllowed = !$bLocked && $bRawModifiedAllowed;
|
||||
$bIsDeleteAllowed = !$bLocked && UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
|
||||
// Just one object in the set, possible actions are "new / clone / modify and delete"
|
||||
if (!isset($aExtraParams['link_attr'])) {
|
||||
if ($bIsModifyAllowed) {
|
||||
$aRegularActions['UI:Menu:Modify'] = array(
|
||||
'label' => Dict::S('UI:Menu:Modify'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=modify&class=$sClass&id=$id{$sContext}#",
|
||||
) + $aActionParams;
|
||||
}
|
||||
if ($bIsCreationAllowed) {
|
||||
$aRegularActions['UI:Menu:New'] = array(
|
||||
'label' => Dict::S('UI:Menu:New'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
if ($bIsDeleteAllowed) {
|
||||
$aRegularActions['UI:Menu:Delete'] = array(
|
||||
'label' => Dict::S('UI:Menu:Delete'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->CountWithLimit($iLimit + 1) < $iLimit))) {
|
||||
// Life cycle actions may be available... if all objects are in the same state
|
||||
//
|
||||
// Group by <state>
|
||||
$oGroupByExp = new FieldExpression(MetaModel::GetStateAttributeCode($sClass), $this->m_oFilter->GetClassAlias());
|
||||
$aGroupBy = array('__state__' => $oGroupByExp);
|
||||
$aQueryParams = array();
|
||||
if (isset($aExtraParams['query_params'])) {
|
||||
$aQueryParams = $aExtraParams['query_params'];
|
||||
}
|
||||
|
||||
// Transitions / Stimuli
|
||||
if (!$bLocked) {
|
||||
$aTransitions = $oObj->EnumTransitions();
|
||||
if (count($aTransitions)) {
|
||||
$aStimuli = Metamodel::EnumStimuli(get_class($oObj));
|
||||
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
|
||||
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
|
||||
$sStimulusCode, $oSet) : UR_ALLOWED_NO;
|
||||
switch ($iActionAllowed) {
|
||||
case UR_ALLOWED_YES:
|
||||
$aTransitionActions[$sStimulusCode] = array(
|
||||
'label' => $aStimuli[$sStimulusCode]->GetLabel(),
|
||||
'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}",
|
||||
) + $aActionParams;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Relations...
|
||||
$aRelations = MetaModel::EnumRelationsEx($sClass);
|
||||
if (count($aRelations)) {
|
||||
$this->AddMenuSeparator($aRegularActions);
|
||||
foreach ($aRelations as $sRelationCode => $aRelationInfo) {
|
||||
if (array_key_exists('down', $aRelationInfo)) {
|
||||
$aRegularActions[$sRelationCode.'_down'] = array(
|
||||
'label' => $aRelationInfo['down'],
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}",
|
||||
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy);
|
||||
$aRes = CMDBSource::QueryToArray($sSql);
|
||||
if (count($aRes) == 1) {
|
||||
// All objects are in the same state...
|
||||
$sState = $aRes[0]['__state__'];
|
||||
$aTransitions = Metamodel::EnumTransitions($sClass, $sState);
|
||||
if (count($aTransitions)) {
|
||||
$aStimuli = Metamodel::EnumStimuli($sClass);
|
||||
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
|
||||
$oSet->Rewind();
|
||||
// As soon as the user rights implementation will browse the object set,
|
||||
// then we might consider using OptimizeColumnLoad() here
|
||||
$iActionAllowed = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet);
|
||||
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? $iActionAllowed : UR_ALLOWED_NO;
|
||||
switch ($iActionAllowed) {
|
||||
case UR_ALLOWED_YES:
|
||||
case UR_ALLOWED_DEPENDS:
|
||||
$aTransitionActions[$sStimulusCode] = array(
|
||||
'label' => $aStimuli[$sStimulusCode]->GetLabel(),
|
||||
'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=".urlencode($sFilter)."{$sContext}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
if (array_key_exists('up', $aRelationInfo)) {
|
||||
$aRegularActions[$sRelationCode.'_up'] = array(
|
||||
'label' => $aRelationInfo['up'],
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a special menu to kill the lock, but only to allowed users who can also modify this object
|
||||
if ($bLocked && $bRawModifiedAllowed) {
|
||||
/** @var array $aAllowedProfiles */
|
||||
$aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles');
|
||||
$bCanKill = false;
|
||||
|
||||
$oUser = UserRights::GetUserObject();
|
||||
$aUserProfiles = array();
|
||||
if (!is_null($oUser)) {
|
||||
$oProfileSet = $oUser->Get('profile_list');
|
||||
while ($oProfile = $oProfileSet->Fetch()) {
|
||||
$aUserProfiles[$oProfile->Get('profile')] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($aAllowedProfiles as $sProfile) {
|
||||
if (array_key_exists($sProfile, $aUserProfiles)) {
|
||||
$bCanKill = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bCanKill) {
|
||||
$this->AddMenuSeparator($aRegularActions);
|
||||
$aRegularActions['concurrent_lock_unlock'] = array(
|
||||
'label' => Dict::S('UI:Menu:KillConcurrentLock'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}",
|
||||
);
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->AddMenuSeparator($aRegularActions);
|
||||
|
||||
$this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) {
|
||||
$aRegularActions[$sLabel] = array('label' => $sLabel, 'url' => $data) + $aActionParams;
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// Check rights
|
||||
// New / Modify
|
||||
$bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY,
|
||||
$oSet) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
|
||||
$bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY,
|
||||
$oSet) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
|
||||
$bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet);
|
||||
if (isset($aExtraParams['link_attr'])) {
|
||||
$id = $aExtraParams['object_id'];
|
||||
$sTargetAttr = $aExtraParams['target_attr'];
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr);
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
if ($bIsModifyAllowed) {
|
||||
$aRegularActions['UI:Menu:Add'] = array(
|
||||
'label' => Dict::S('UI:Menu:Add'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true{$sContext}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
if ($bIsBulkModifyAllowed) {
|
||||
$aRegularActions['UI:Menu:Manage'] = array(
|
||||
'label' => Dict::S('UI:Menu:Manage'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id{$sContext}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
//if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#") + $aActionParams; }
|
||||
} else {
|
||||
// many objects in the set, possible actions are: new / modify all / delete all
|
||||
if ($bIsCreationAllowed) {
|
||||
$aRegularActions['UI:Menu:New'] = array(
|
||||
'label' => Dict::S('UI:Menu:New'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
if ($bIsBulkModifyAllowed) {
|
||||
$aRegularActions['UI:Menu:ModifyAll'] = array(
|
||||
'label' => Dict::S('UI:Menu:ModifyAll'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=".urlencode($sFilter)."{$sContext}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
if ($bIsBulkDeleteAllowed) {
|
||||
$aRegularActions['UI:Menu:BulkDelete'] = array(
|
||||
'label' => Dict::S('UI:Menu:BulkDelete'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_deletion&filter=".urlencode($sFilter)."{$sContext}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
|
||||
// Stimuli
|
||||
$aStates = MetaModel::EnumStates($sClass);
|
||||
// Do not perform time consuming computations if there are too may objects in the list
|
||||
$iLimit = MetaModel::GetConfig()->Get('complex_actions_limit');
|
||||
|
||||
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->CountWithLimit($iLimit + 1) < $iLimit))) {
|
||||
// Life cycle actions may be available... if all objects are in the same state
|
||||
//
|
||||
// Group by <state>
|
||||
$oGroupByExp = new FieldExpression(MetaModel::GetStateAttributeCode($sClass), $this->m_oFilter->GetClassAlias());
|
||||
$aGroupBy = array('__state__' => $oGroupByExp);
|
||||
$aQueryParams = array();
|
||||
if (isset($aExtraParams['query_params'])) {
|
||||
$aQueryParams = $aExtraParams['query_params'];
|
||||
// NOT "listInObject" style actions
|
||||
if ($this->m_sStyle !== 'listInObject') {
|
||||
switch ($iSetCount) {
|
||||
case 1:
|
||||
$oObj = $oSet->Fetch();
|
||||
if (false === is_null($oObj)) {
|
||||
$id = $oObj->GetKey();
|
||||
if (empty($sRefreshAction) && utils::ReadParam('operation') == 'details') {
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
|
||||
$sRefreshAction = "window.location.reload();";
|
||||
} else {
|
||||
$sRefreshAction = "window.location.href='".ApplicationContext::MakeObjectUrl(get_class($oObj), $id)."';";
|
||||
}
|
||||
}
|
||||
|
||||
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy);
|
||||
$aRes = CMDBSource::QueryToArray($sSql);
|
||||
if (count($aRes) == 1) {
|
||||
// All objects are in the same state...
|
||||
$sState = $aRes[0]['__state__'];
|
||||
$aTransitions = Metamodel::EnumTransitions($sClass, $sState);
|
||||
if (count($aTransitions)) {
|
||||
$aStimuli = Metamodel::EnumStimuli($sClass);
|
||||
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
|
||||
$oSet->Rewind();
|
||||
// As soon as the user rights implementation will browse the object set,
|
||||
// then we might consider using OptimizeColumnLoad() here
|
||||
$iActionAllowed = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet);
|
||||
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? $iActionAllowed : UR_ALLOWED_NO;
|
||||
switch ($iActionAllowed) {
|
||||
case UR_ALLOWED_YES:
|
||||
case UR_ALLOWED_DEPENDS:
|
||||
$aTransitionActions[$sStimulusCode] = array(
|
||||
'label' => $aStimuli[$sStimulusCode]->GetLabel(),
|
||||
'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=".urlencode($sFilter)."{$sContext}",
|
||||
) + $aActionParams;
|
||||
break;
|
||||
$bLocked = false;
|
||||
if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) {
|
||||
$aLockInfo = iTopOwnershipLock::IsLocked(get_class($oObj), $id);
|
||||
if ($aLockInfo['locked']) {
|
||||
$bLocked = true;
|
||||
}
|
||||
}
|
||||
$bRawModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
|
||||
$bIsModifyAllowed = !$bLocked && $bRawModifiedAllowed;
|
||||
$bIsDeleteAllowed = !$bLocked && UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
|
||||
// Just one object in the set, possible actions are "new / clone / modify and delete"
|
||||
if (!isset($aExtraParams['link_attr'])) {
|
||||
if ($bIsModifyAllowed) {
|
||||
$aRegularActions['UI:Menu:Modify'] = array(
|
||||
'label' => Dict::S('UI:Menu:Modify'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?route=object.modify&class=$sClass&id=$id{$sContext}#",
|
||||
) + $aActionParams;
|
||||
}
|
||||
if ($bIsDeleteAllowed) {
|
||||
$aRegularActions['UI:Menu:Delete'] = array(
|
||||
'label' => Dict::S('UI:Menu:Delete'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
// Transitions / Stimuli
|
||||
if (!$bLocked) {
|
||||
$aTransitions = $oObj->EnumTransitions();
|
||||
if (count($aTransitions)) {
|
||||
$aStimuli = Metamodel::EnumStimuli(get_class($oObj));
|
||||
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
|
||||
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
|
||||
$sStimulusCode, $oSet) : UR_ALLOWED_NO;
|
||||
switch ($iActionAllowed) {
|
||||
case UR_ALLOWED_YES:
|
||||
$aTransitionActions[$sStimulusCode] = array(
|
||||
'label' => $aStimuli[$sStimulusCode]->GetLabel(),
|
||||
'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}",
|
||||
) + $aActionParams;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Relations...
|
||||
$aRelations = MetaModel::EnumRelationsEx($sClass);
|
||||
if (count($aRelations)) {
|
||||
$this->AddMenuSeparator($aRegularActions);
|
||||
foreach ($aRelations as $sRelationCode => $aRelationInfo) {
|
||||
if (array_key_exists('down', $aRelationInfo)) {
|
||||
$aRegularActions[$sRelationCode.'_down'] = array(
|
||||
'label' => $aRelationInfo['down'],
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
if (array_key_exists('up', $aRelationInfo)) {
|
||||
$aRegularActions[$sRelationCode.'_up'] = array(
|
||||
'label' => $aRelationInfo['up'],
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}",
|
||||
) + $aActionParams;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a special menu to kill the lock, but only to allowed users who can also modify this object
|
||||
if ($bLocked && $bRawModifiedAllowed) {
|
||||
/** @var array $aAllowedProfiles */
|
||||
$aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles');
|
||||
$bCanKill = false;
|
||||
|
||||
$oUser = UserRights::GetUserObject();
|
||||
$aUserProfiles = array();
|
||||
if (!is_null($oUser)) {
|
||||
$oProfileSet = $oUser->Get('profile_list');
|
||||
while ($oProfile = $oProfileSet->Fetch()) {
|
||||
$aUserProfiles[$oProfile->Get('profile')] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($aAllowedProfiles as $sProfile) {
|
||||
if (array_key_exists($sProfile, $aUserProfiles)) {
|
||||
$bCanKill = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bCanKill) {
|
||||
$this->AddMenuSeparator($aRegularActions);
|
||||
$aRegularActions['concurrent_lock_unlock'] = array(
|
||||
'label' => Dict::S('UI:Menu:KillConcurrentLock'),
|
||||
'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->AddMenuSeparator($aRegularActions);
|
||||
|
||||
$this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) {
|
||||
$aRegularActions[$sLabel] = array('label' => $sLabel, 'url' => $data) + $aActionParams;
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->AddMenuSeparator($aRegularActions);
|
||||
@@ -2210,8 +2002,9 @@ class MenuBlock extends DisplayBlock
|
||||
$sRefreshAction = "window.location.reload();";
|
||||
}
|
||||
} else {
|
||||
//it's easier just display configure this list and MENU_OBJLIST_TOOLKIT
|
||||
// It's easier just display configure this list and MENU_OBJLIST_TOOLKIT
|
||||
}
|
||||
|
||||
$param = null;
|
||||
if (is_null($sId)) {
|
||||
$sId = uniqid();
|
||||
@@ -2224,14 +2017,7 @@ class MenuBlock extends DisplayBlock
|
||||
case 'listInObject':
|
||||
$oSet->Rewind();
|
||||
$param = $oSet;
|
||||
$bToolkitMenu = true;
|
||||
if (isset($aExtraParams['toolkit_menu'])) {
|
||||
$bToolkitMenu = (bool)$aExtraParams['toolkit_menu'];
|
||||
}
|
||||
if ($bToolkitMenu) {
|
||||
$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;
|
||||
@@ -2243,6 +2029,7 @@ class MenuBlock extends DisplayBlock
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if ($oPopupMenuItemsBlock->HasSubBlocks()) {
|
||||
$oRenderBlock->AddSubBlock($oPopupMenuItemsBlock);
|
||||
} else {
|
||||
@@ -2253,6 +2040,7 @@ class MenuBlock extends DisplayBlock
|
||||
$oRenderBlock->AddCssFileRelPath($sCssPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract favorite actions from their menus
|
||||
$aFavoriteRegularActions = [];
|
||||
$aFavoriteTransitionActions = [];
|
||||
@@ -2338,12 +2126,16 @@ class MenuBlock extends DisplayBlock
|
||||
break;
|
||||
|
||||
case 'UI:Menu:ModifyAll':
|
||||
case 'UI:Menu:ModifyAll:Link': // Link class on linkset
|
||||
case 'UI:Menu:ModifyAll:Remote': // Remote class on linkset
|
||||
case 'UI:Menu:Modify':
|
||||
$sIconClass = 'fas fa-pen';
|
||||
$sLabel = '';
|
||||
break;
|
||||
|
||||
case 'UI:Menu:BulkDelete':
|
||||
case 'UI:Menu:BulkDelete:Link': // Link class on linkset
|
||||
case 'UI:Menu:BulkDelete:Remote': // Remote class on linkset
|
||||
case 'UI:Menu:Delete':
|
||||
$sIconClass = 'fas fa-trash';
|
||||
$sLabel = '';
|
||||
@@ -2376,8 +2168,28 @@ class MenuBlock extends DisplayBlock
|
||||
$oActionsToolbar->AddSubBlock($oActionButton);
|
||||
}
|
||||
|
||||
// - Creation in modal
|
||||
if ($bIsCreationInModalAllowed === true) {
|
||||
$oAddLinkActionButton = ButtonUIBlockFactory::MakeIconAction(
|
||||
'fas fa-plus',
|
||||
Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass)),
|
||||
'UI:Links:New',
|
||||
'',
|
||||
false
|
||||
);
|
||||
|
||||
// - If we are used in a Datatable, 'datatable_' will be prefixed to our $sId, so we do the same here
|
||||
$sRealId = $sId;
|
||||
if(in_array($this->m_sStyle, ['list', 'links', 'listInObject'])){
|
||||
$sRealId = 'datatable_' . $sId;
|
||||
}
|
||||
$oAddLinkActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button'])
|
||||
->SetOnClickJsCode("$('#$sRealId').trigger('open_creation_modal.object.itop');");
|
||||
$oActionsToolbar->AddSubBlock($oAddLinkActionButton);
|
||||
}
|
||||
|
||||
// - Refresh
|
||||
if ($sRefreshAction != '') {
|
||||
if (utils::IsNotNullOrEmptyString($sRefreshAction)) {
|
||||
$oActionButton = ButtonUIBlockFactory::MakeAlternativeNeutral('', 'UI:Button:Refresh');
|
||||
$oActionButton->SetIconClass('fas fa-sync-alt')
|
||||
->SetOnClickJsCode($sRefreshAction)
|
||||
@@ -2387,7 +2199,7 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
|
||||
// - Search
|
||||
if ($this->m_sStyle == 'details') {
|
||||
if ($this->m_sStyle === 'details') {
|
||||
$oActionButton = ButtonUIBlockFactory::MakeIconLink('fas fa-search', Dict::Format('UI:SearchFor_Class', MetaModel::GetName($sClass)), "{$sRootUrl}pages/UI.php?operation=search_form&do_search=0&class=$sClass{$sContext}", '', 'UI:SearchFor_Class');
|
||||
$oActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
|
||||
$oActionsToolbar->AddSubBlock($oActionButton);
|
||||
@@ -2412,6 +2224,12 @@ class MenuBlock extends DisplayBlock
|
||||
|
||||
// Toolkit actions
|
||||
if (!empty($aToolkitActions)) {
|
||||
// Add separator if necessary
|
||||
if (count($aRegularActions) > 0) {
|
||||
$oRegularActionsMenu->AddItem('separator-regular-actions-toolkit-actions', PopoverMenuItemFactory::MakeSeparator());
|
||||
}
|
||||
|
||||
// Add actions
|
||||
foreach ($aToolkitActions as $sActionId => $aActionData) {
|
||||
$oRegularActionsMenu->AddItem('toolkit-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItemData($sActionId, $aActionData));
|
||||
}
|
||||
@@ -2492,5 +2310,106 @@ class MenuBlock extends DisplayBlock
|
||||
$aActions['sep_'.(count($aActions)-1)] = array('label' => $sSeparator, 'url' => '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a create action (to $aActions) for an $sClass object
|
||||
*
|
||||
* @param array &$aActions Pointer to the array in which the action will be added
|
||||
* @param string $sClass Datamodel class concerned by the action
|
||||
* @param string $sDefaultValuesAsUrlParams Default values for the new object form,
|
||||
*
|
||||
* @return void
|
||||
* @since 3.1.0
|
||||
* @internal
|
||||
*/
|
||||
protected function AddNewObjectMenuAction(array &$aActions, string $sClass, string $sDefaultValuesAsUrlParams = ''): void
|
||||
{
|
||||
$aActions['UI:Menu:New'] = [
|
||||
'label' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass)),
|
||||
'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=new&class=$sClass{$sDefaultValuesAsUrlParams}"),
|
||||
] + $this->GetDefaultParamsForMenuAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bulk modify action (to $aActions) on objects defined by $sFilter
|
||||
*
|
||||
* @param array &$aActions Pointer to the array in which the action will be added
|
||||
* @param string $sClass Datamodel class concerned by the action
|
||||
* @param string $sFilter OQL of the objects to propose for bulk modify
|
||||
* @param string $sActionIdentifier Unique identifier for the action. Default if 'UI:Menu:ModifyAll'
|
||||
* @param string $sActionLabel Label for the action, can be either a dict code to translate or an hardcoded label. Default is 'UI:Menu:ModifyAll'.
|
||||
*
|
||||
* @return void
|
||||
* @since 3.1.0
|
||||
* @internal
|
||||
*/
|
||||
protected function AddBulkModifyObjectsMenuAction(array &$aActions, string $sClass, string $sFilter, string $sActionIdentifier = 'UI:Menu:ModifyAll', $sActionLabel = 'UI:Menu:ModifyAll'): void
|
||||
{
|
||||
$aActions[$sActionIdentifier] = [
|
||||
'label' => Dict::S($sActionLabel),
|
||||
'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=select_for_modify_all&class=$sClass&filter=".urlencode($sFilter)),
|
||||
] + $this->GetDefaultParamsForMenuAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bulk delete action (to $aActions) on objects defined by $sFilter
|
||||
*
|
||||
* @param array &$aActions Pointer to the array in which the action will be added
|
||||
* @param string $sClass Datamodel class concerned by the action
|
||||
* @param string $sFilter OQL of the objects to propose for bulk deletion
|
||||
* @param string $sActionIdentifier Unique identifier for the action. Default if 'UI:Menu:BulkDelete'
|
||||
* @param string $sActionLabel Label for the action, can be either a dict code to translate or an hardcoded label. Default is 'UI:Menu:BulkDelete'.
|
||||
*
|
||||
* @return void
|
||||
* @since 3.1.0
|
||||
* @internal
|
||||
*/
|
||||
protected function AddBulkDeleteObjectsMenuAction(array &$aActions, string $sClass, string $sFilter, string $sActionIdentifier = 'UI:Menu:BulkDelete', $sActionLabel = 'UI:Menu:BulkDelete')
|
||||
{
|
||||
$aActions[$sActionIdentifier] = array(
|
||||
'label' => Dict::S($sActionLabel),
|
||||
'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=select_for_deletion&filter=".urlencode($sFilter)),
|
||||
) + $this->GetDefaultParamsForMenuAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array Default parameters of a menu action
|
||||
* @since 3.1.0
|
||||
* @internal
|
||||
*/
|
||||
private function GetDefaultParamsForMenuAction(): array
|
||||
{
|
||||
$aDefaultParams = [];
|
||||
|
||||
if (isset($aExtraParams['menu_actions_target'])) {
|
||||
$aDefaultParams['target'] = $aExtraParams['menu_actions_target'];
|
||||
}
|
||||
|
||||
return $aDefaultParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass Datamodel class for which the URL is prepared
|
||||
* @param string $sUrlParams URL parameters to add to the URL, must already be concatenated as a string (eg. "foo=bar&some=thing&third=param")
|
||||
*
|
||||
* @return string An absolute URL for a menu action on the $sClass class with $sUrlParams
|
||||
* @throws \Exception
|
||||
* @internal
|
||||
*/
|
||||
private function PrepareUrlForStandardMenuAction(string $sClass, string $sUrlParams)
|
||||
{
|
||||
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
|
||||
|
||||
$sUrl = "{$sRootUrl}pages/{$sUIPage}?{$sUrlParams}";
|
||||
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
if (utils::IsNotNullOrEmptyString($sContext)) {
|
||||
$sUrl .= '&'.$sContext;
|
||||
}
|
||||
|
||||
return $sUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/ErrorPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/ErrorPage.php, now loadable using autoloader');
|
||||
@@ -44,15 +44,15 @@ class CoreCannotSaveObjectException extends CoreException
|
||||
public function getHtmlMessage()
|
||||
{
|
||||
$sTitle = Dict::S('UI:Error:SaveFailed');
|
||||
$sContent = "<span><strong>{$sTitle}</strong></span>";
|
||||
$sContent = "<span><strong>".utils::HtmlEntities($sTitle)."</strong></span>";
|
||||
|
||||
if (count($this->aIssues) == 1) {
|
||||
$sIssue = reset($this->aIssues);
|
||||
$sContent .= " <span>{$sIssue}</span>";
|
||||
$sContent .= " <span>".utils::HtmlEntities($sIssue)."</span>";
|
||||
} else {
|
||||
$sContent .= '<ul>';
|
||||
foreach ($this->aIssues as $sError) {
|
||||
$sContent .= "<li>$sError</li>";
|
||||
$sContent .= "<li>".utils::HtmlEntities($sError)."</li>";
|
||||
}
|
||||
$sContent .= '</ul>';
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
class CoreException extends Exception
|
||||
{
|
||||
protected $m_sIssue;
|
||||
protected $m_sImpact;
|
||||
protected $m_aContextData;
|
||||
|
||||
/**
|
||||
* CoreException constructor.
|
||||
*
|
||||
|
||||
10
application/exceptions/iTopXmlException.php
Normal file
10
application/exceptions/iTopXmlException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2022 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class iTopXmlException extends CoreException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°5538
|
||||
*/
|
||||
class MySQLTransactionNotClosedException extends MySQLException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -838,7 +838,8 @@ class DesignerFormField
|
||||
{
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
return array('label' => $this->sLabel, 'value' => "<input type=\"text\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">");
|
||||
|
||||
return array('label' => $this->sLabel, 'value' => "<input type=\"text\" id=\"$sId\" name=\"$sName\" value=\"".utils::EscapeHtml($this->defaultValue)."\">");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1012,9 +1013,8 @@ class DesignerTextField extends DesignerFormField
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
if ($this->IsReadOnly())
|
||||
{
|
||||
$sHtmlValue = "<span>".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\"/></span>";
|
||||
if ($this->IsReadOnly()) {
|
||||
$sHtmlValue = "<span>".utils::EscapeHtml($this->defaultValue)."<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".utils::EscapeHtml($this->defaultValue)."\"/></span>";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1038,11 +1038,10 @@ $('#$sId').on('change keyup validate', function() { ValidateWithPattern('$sId',
|
||||
EOF
|
||||
);
|
||||
$sCSSClasses = '';
|
||||
if (count($this->aCSSClasses) > 0)
|
||||
{
|
||||
if (count($this->aCSSClasses) > 0) {
|
||||
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
|
||||
}
|
||||
$sHtmlValue = "<input type=\"text\" $sCSSClasses id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">";
|
||||
$sHtmlValue = "<input type=\"text\" $sCSSClasses id=\"$sId\" name=\"$sName\" value=\"".utils::EscapeHtml($this->defaultValue)."\">";
|
||||
}
|
||||
return array('label' => $this->sLabel, 'value' => $sHtmlValue);
|
||||
}
|
||||
@@ -1101,10 +1100,9 @@ class DesignerLongTextField extends DesignerTextField
|
||||
{
|
||||
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
|
||||
}
|
||||
if (!$this->IsReadOnly())
|
||||
{
|
||||
if (!$this->IsReadOnly()) {
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
<<<EOF
|
||||
$('#$sId').on('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues); } );
|
||||
{
|
||||
var myTimer = null;
|
||||
@@ -1112,11 +1110,10 @@ $('#$sId').on('change keyup validate', function() { ValidateWithPattern('$sId',
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$sValue = "<textarea $sCSSClasses id=\"$sId\" name=\"$sName\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</textarea>";
|
||||
$sValue = "<textarea $sCSSClasses id=\"$sId\" name=\"$sName\">".utils::EscapeHtml($this->defaultValue)."</textarea>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = "<div $sCSSClasses id=\"$sId\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</div>";
|
||||
else {
|
||||
$sValue = "<div $sCSSClasses id=\"$sId\">".utils::EscapeHtml($this->defaultValue)."</div>";
|
||||
}
|
||||
return array('label' => $this->sLabel, 'value' => $sValue);
|
||||
}
|
||||
@@ -1145,9 +1142,8 @@ class DesignerIntegerField extends DesignerFormField
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
if ($this->IsReadOnly())
|
||||
{
|
||||
$sHtmlValue = "<span>".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\"/></span>";
|
||||
if ($this->IsReadOnly()) {
|
||||
$sHtmlValue = "<span>".utils::EscapeHtml($this->defaultValue)."<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".utils::EscapeHtml($this->defaultValue)."\"/></span>";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1164,11 +1160,10 @@ $('#$sId').on('change keyup validate', function() { ValidateInteger('$sId', $sMa
|
||||
EOF
|
||||
);
|
||||
$sCSSClasses = '';
|
||||
if (count($this->aCSSClasses) > 0)
|
||||
{
|
||||
if (count($this->aCSSClasses) > 0) {
|
||||
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
|
||||
}
|
||||
$sHtmlValue = "<input type=\"text\" $sCSSClasses id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">";
|
||||
$sHtmlValue = "<input type=\"text\" $sCSSClasses id=\"$sId\" name=\"$sName\" value=\"".utils::EscapeHtml($this->defaultValue)."\">";
|
||||
}
|
||||
return array('label' => $this->sLabel, 'value' => $sHtmlValue);
|
||||
}
|
||||
@@ -1272,7 +1267,7 @@ class DesignerComboField extends DesignerFormField
|
||||
$sChecked = $this->defaultValue ? 'checked' : '';
|
||||
$sMandatory = $this->bMandatory ? 'true' : 'false';
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
|
||||
if ($this->IsSorted())
|
||||
if ($this->IsSorted() )
|
||||
{
|
||||
asort($this->aAllowedValues);
|
||||
}
|
||||
@@ -1289,22 +1284,18 @@ class DesignerComboField extends DesignerFormField
|
||||
{
|
||||
if ($this->bMultipleSelection)
|
||||
{
|
||||
if(in_array($sKey, $this->defaultValue))
|
||||
{
|
||||
if(in_array($sKey, $this->defaultValue)) {
|
||||
$aSelected[] = $sDisplayValue;
|
||||
$aHiddenValues[] = "<input type=\"hidden\" name=\"{$sName}[]\" value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\"/>";
|
||||
$aHiddenValues[] = "<input type=\"hidden\" name=\"{$sName}[]\" value=\"".utils::EscapeHtml($sKey)."\"/>";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($sKey == $this->defaultValue)
|
||||
{
|
||||
} else {
|
||||
if ($sKey == $this->defaultValue) {
|
||||
$aSelected[] = $sDisplayValue;
|
||||
$aHiddenValues[] = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\"/>";
|
||||
$aHiddenValues[] = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".utils::EscapeHtml($sKey)."\"/>";
|
||||
}
|
||||
}
|
||||
}
|
||||
$sHtml = "<span $sCSSClasses>".htmlentities(implode(', ', $aSelected), ENT_QUOTES, 'UTF-8').implode($aHiddenValues)."</span>";
|
||||
$sHtml = "<span $sCSSClasses>".utils::EscapeHtml(implode(', ', $aSelected)).implode($aHiddenValues)."</span>";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1320,19 +1311,15 @@ class DesignerComboField extends DesignerFormField
|
||||
$sHtml .= "<option value=\"\">".$this->sNullLabel."</option>";
|
||||
}
|
||||
}
|
||||
foreach($this->aAllowedValues as $sKey => $sDisplayValue)
|
||||
{
|
||||
if ($this->bMultipleSelection)
|
||||
{
|
||||
foreach ($this->aAllowedValues as $sKey => $sDisplayValue) {
|
||||
if ($this->bMultipleSelection) {
|
||||
$sSelected = in_array($sKey, $this->defaultValue) ? 'selected' : '';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sSelected = ($sKey == $this->defaultValue) ? 'selected' : '';
|
||||
}
|
||||
// Quick and dirty: display the menu parents as a tree
|
||||
$sHtmlValue = str_replace(' ', ' ', htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8'));
|
||||
$sHtml .= "<option value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\" $sSelected>$sHtmlValue</option>";
|
||||
$sHtmlValue = str_replace(' ', ' ', $sDisplayValue);
|
||||
$sHtml .= "<option value=\"".utils::EscapeHtml($sKey)."\" $sSelected>$sHtmlValue</option>";
|
||||
}
|
||||
$sHtml .= "</select></span>";
|
||||
if ($this->bOtherChoices)
|
||||
@@ -1383,10 +1370,9 @@ class DesignerBooleanField extends DesignerFormField
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
$sChecked = $this->defaultValue ? 'checked' : '';
|
||||
if ($this->IsReadOnly())
|
||||
{
|
||||
if ($this->IsReadOnly()) {
|
||||
$sLabel = $this->defaultValue ? Dict::S('UI:UserManagement:ActionAllowed:Yes') : Dict::S('UI:UserManagement:ActionAllowed:No'); //TODO use our own yes/no translations
|
||||
$sHtmlValue = "<span>".htmlentities($sLabel)."<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\"/></span>";
|
||||
$sHtmlValue = "<span>".utils::EscapeHtml($sLabel)."<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".utils::EscapeHtml($this->defaultValue)."\"/></span>";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1454,8 +1440,8 @@ class DesignerHiddenField extends DesignerFormField
|
||||
{
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
$sChecked = $this->defaultValue ? 'checked' : '';
|
||||
return array('label' =>'', 'value' => "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">");
|
||||
|
||||
return array('label' => '', 'value' => "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".utils::EscapeHtml($this->defaultValue)."\">");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1522,7 +1508,7 @@ class DesignerIconSelectionField extends DesignerFormField
|
||||
EOF
|
||||
);
|
||||
} 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>';
|
||||
$sValue = '<span style="display:inline-block;line-height:48px;height:48px;"><span><img style="vertical-align:middle" src="'.$this->aAllowedValues[$idx]['icon'].'" /> '.utils::EscapeHtml($this->aAllowedValues[$idx]['label']).'</span></span>';
|
||||
}
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled' : '';
|
||||
return array('label' => $this->sLabel, 'value' => $sValue);
|
||||
@@ -1669,14 +1655,14 @@ class DesignerSortableField extends DesignerFormField
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
$sName = $this->oForm->GetFieldName($this->sCode);
|
||||
$sReadOnly = $this->IsReadOnly() ? 'readonly="readonly"' : '';
|
||||
$aResult = array('label' => $this->sLabel, 'value' => "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" $sReadOnly value=\"".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."\">");
|
||||
|
||||
$aResult = array('label' => $this->sLabel, 'value' => "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" $sReadOnly value=\"".utils::EscapeHtml($this->defaultValue)."\">");
|
||||
|
||||
|
||||
$sJSFields = json_encode(array_keys($this->aAllowedValues));
|
||||
$oP->add_ready_script(
|
||||
"$('#$sId').sortable_field({aAvailableFields: $sJSFields});"
|
||||
);
|
||||
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
}
|
||||
@@ -1765,8 +1751,8 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
foreach ($this->aSubForms as $iKey => $aFormData) {
|
||||
if ($iKey == $this->defaultValue) // Default value is actually the index
|
||||
{
|
||||
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
|
||||
$sHiddenValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($iKey, ENT_QUOTES, 'UTF-8')."\"/>";
|
||||
$sDisplayValue = utils::EscapeHtml($aFormData['label']);
|
||||
$sHiddenValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".utils::EscapeHtml($iKey)."\"/>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1774,8 +1760,8 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
} 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');
|
||||
$sDisplayValue = utils::EscapeHtml($aFormData['label']);
|
||||
$sValue = utils::EscapeHtml($aFormData['value']);
|
||||
$sSelected = ($iKey == $this->defaultValue) ? 'selected' : '';
|
||||
$sHtml .= "<option data-value=\"$sValue\" value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/iTopWebPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/iTopWebPage.php, now loadable using autoloader');
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/iTopWizardWebPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/iTopWizardWebPage.php, now loadable using autoloader');
|
||||
@@ -62,6 +62,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
Session::Set('auth_user', $sAuthUser);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -70,8 +71,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
{
|
||||
if (Session::Get('login_mode') == 'basic')
|
||||
{
|
||||
list($sAuthUser) = $this->GetAuthUserAndPassword();
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
|
||||
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
Session::Set('auth_user', $sAuthUser);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -53,8 +54,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
{
|
||||
if (Session::Get('login_mode') == 'external')
|
||||
{
|
||||
$sAuthUser = $this->GetAuthUser();
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', Session::Get('login_mode'));
|
||||
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'external', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -88,4 +88,4 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
/** @var string $sAuthUser */
|
||||
return $sAuthUser; // Retrieve the value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
Session::Set('auth_user', $sAuthUser);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -82,17 +83,8 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
{
|
||||
if (Session::Get('login_mode') == 'form')
|
||||
{
|
||||
if (Session::IsSet('auth_user'))
|
||||
{
|
||||
// If FSM reenter this state (example 2FA) then the auth_user is not resubmitted
|
||||
$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::Get('login_mode'));
|
||||
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
|
||||
|
||||
use Combodo\iTop\Application\Branding;
|
||||
use Combodo\iTop\TwigExtension;
|
||||
use Combodo\iTop\Application\TwigBase\Twig\Extension;
|
||||
use Twig\Environment;
|
||||
use Twig\Loader\ChainLoader;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
|
||||
/**
|
||||
* Twig context for modules extending the login screen
|
||||
@@ -217,14 +220,14 @@ class LoginTwigRenderer
|
||||
$sTwigLoaderPath = $oLoginContext->GetTwigLoaderPath();
|
||||
if ($sTwigLoaderPath != null)
|
||||
{
|
||||
$oExtensionLoader = new Twig_Loader_Filesystem();
|
||||
$oExtensionLoader = new FilesystemLoader();
|
||||
$oExtensionLoader->setPaths($sTwigLoaderPath);
|
||||
$aTwigLoaders[] = $oExtensionLoader;
|
||||
}
|
||||
$this->aPostedVars = array_merge($this->aPostedVars, $oLoginContext->GetPostedVars());
|
||||
}
|
||||
|
||||
$oCoreLoader = new Twig_Loader_Filesystem(array(), APPROOT.'templates');
|
||||
$oCoreLoader = new FilesystemLoader(array(), APPROOT.'templates');
|
||||
$aCoreTemplatesPaths = array('pages/login', 'pages/login/password');
|
||||
// Having this path declared after the plugins let the plugins replace the core templates
|
||||
$oCoreLoader->setPaths($aCoreTemplatesPaths);
|
||||
@@ -232,9 +235,9 @@ class LoginTwigRenderer
|
||||
$oCoreLoader->setPaths($aCoreTemplatesPaths, 'ItopCore');
|
||||
$aTwigLoaders[] = $oCoreLoader;
|
||||
|
||||
$oLoader = new Twig_Loader_Chain($aTwigLoaders);
|
||||
$this->oTwig = new Twig_Environment($oLoader);
|
||||
TwigExtension::RegisterTwigExtensions($this->oTwig);
|
||||
$oLoader = new ChainLoader($aTwigLoaders);
|
||||
$this->oTwig = new Environment($oLoader);
|
||||
Extension::RegisterTwigExtensions($this->oTwig);
|
||||
}
|
||||
|
||||
public function GetDefaultVars()
|
||||
@@ -306,7 +309,7 @@ class LoginTwigRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Twig_Environment
|
||||
* @return \Twig\Environment
|
||||
*/
|
||||
public function GetTwig()
|
||||
{
|
||||
|
||||
@@ -60,6 +60,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
Session::Set('auth_user', $sAuthUser);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -68,8 +69,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
{
|
||||
if (Session::Get('login_mode') == 'url')
|
||||
{
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
|
||||
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -92,4 +92,4 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
use Combodo\iTop\Application\Branding;
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
|
||||
/**
|
||||
* Web page used for displaying the login form
|
||||
@@ -112,7 +114,7 @@ class LoginWebPage extends NiceWebPage
|
||||
*/
|
||||
public static function SynchronizeProfiles(&$oUser, array $aProfiles, $sOrigin)
|
||||
{
|
||||
$oProfilesSet = $oUser->Get(‘profile_list’);
|
||||
$oProfilesSet = $oUser->Get('profile_list');
|
||||
//delete old profiles
|
||||
$aExistingProfiles = [];
|
||||
while ($oProfile = $oProfilesSet->Fetch())
|
||||
@@ -131,10 +133,6 @@ class LoginWebPage extends NiceWebPage
|
||||
//add profiles not already linked with user
|
||||
foreach ($aProfiles as $iProfileId)
|
||||
{
|
||||
$oLink = new URP_UserProfile();
|
||||
$oLink->Set('profileid', $iProfileId);
|
||||
$oLink->Set('reason', $sOrigin);
|
||||
|
||||
$oProfilesSet->AddItem(MetaModel::NewObject('URP_UserProfile', array('profileid' => $iProfileId, 'reason' => $sOrigin)));
|
||||
}
|
||||
$oUser->Set('profile_list', $oProfilesSet);
|
||||
@@ -241,7 +239,7 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
|
||||
// This token allows the user to change the password without knowing the previous one
|
||||
$sToken = substr(md5(APPROOT.uniqid()), 0, 16);
|
||||
$sToken = bin2hex(random_bytes(32));
|
||||
$oUser->Set('reset_pwd_token', $sToken);
|
||||
CMDBObject::SetTrackInfo('Reset password');
|
||||
$oUser->AllowWrite(true);
|
||||
@@ -483,11 +481,13 @@ class LoginWebPage extends NiceWebPage
|
||||
$iResponse = $oLoginFSMExtensionInstance->LoginAction($sLoginState, $iErrorCode);
|
||||
if ($iResponse == self::LOGIN_FSM_RETURN)
|
||||
{
|
||||
EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState]));
|
||||
Session::WriteClose();
|
||||
return $iErrorCode; // Asked to exit FSM, generally login OK
|
||||
}
|
||||
if ($iResponse == self::LOGIN_FSM_ERROR)
|
||||
{
|
||||
EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState]));
|
||||
$sLoginState = self::LOGIN_STATE_SET_ERROR; // Next state will be error
|
||||
// An error was detected, skip the other plugins turn
|
||||
break;
|
||||
@@ -501,6 +501,7 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['state' => $_SESSION['login_state']]));
|
||||
IssueLog::Error($e->getTraceAsString());
|
||||
static::ResetSession();
|
||||
die($e->getMessage());
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Helper\WebResourcesHelper;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\Title;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
require_once(APPROOT.'/application/template.class.inc.php');
|
||||
@@ -267,6 +265,14 @@ class ApplicationMenu
|
||||
/** @var \MenuGroup $oMenuNode */
|
||||
$oMenuNode = static::GetMenuNode($sMenuGroupIdx);
|
||||
|
||||
if (!($oMenuNode instanceof MenuGroup)) {
|
||||
IssueLog::Error('Menu node was not displayed as a menu group as it is actually not a menu group', LogChannels::CONSOLE, [
|
||||
'menu_node_class' => get_class($oMenuNode),
|
||||
'menu_node_label' => $oMenuNode->GetLabel(),
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$aMenuGroups[] = [
|
||||
'sId' => $oMenuNode->GetMenuID(),
|
||||
'sIconCssClasses' => $oMenuNode->GetDecorationClasses(),
|
||||
@@ -285,7 +291,17 @@ class ApplicationMenu
|
||||
* @param string $sMenuGroupIdx
|
||||
* @param array $aExtraParams
|
||||
*
|
||||
* @return array
|
||||
* @return array{
|
||||
* array{
|
||||
* sId: string,
|
||||
* sTitle: string,
|
||||
* sLabel: string,
|
||||
* bHasCount: boolean,
|
||||
* sUrl: string,
|
||||
* bOpenInNewWindow: boolean,
|
||||
* aSubMenuNodes: array
|
||||
* }
|
||||
* } The aSubMenuNodes key contains the same structure recursively
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \Exception
|
||||
* @since 3.0.0
|
||||
@@ -314,12 +330,13 @@ class ApplicationMenu
|
||||
}
|
||||
|
||||
$aSubMenuNodes[] = [
|
||||
'sId' => $oSubMenuNode->GetMenuId(),
|
||||
'sTitle' => $oSubMenuNode->GetTitle(),
|
||||
'bHasCount' => $oSubMenuNode->HasCount(),
|
||||
'sUrl' => $oSubMenuNode->GetHyperlink($aExtraParams),
|
||||
'sId' => $oSubMenuNode->GetMenuId(),
|
||||
'sTitle' => $oSubMenuNode->GetTitle(),
|
||||
'sLabel' => $oSubMenuNode->GetLabel(),
|
||||
'bHasCount' => $oSubMenuNode->HasCount(),
|
||||
'sUrl' => $oSubMenuNode->GetHyperlink($aExtraParams),
|
||||
'bOpenInNewWindow' => $oSubMenuNode->IsHyperLinkInNewWindow(),
|
||||
'aSubMenuNodes' => static::GetSubMenuNodes($sSubMenuItemIdx, $aExtraParams),
|
||||
'aSubMenuNodes' => static::GetSubMenuNodes($sSubMenuItemIdx, $aExtraParams),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -655,8 +672,7 @@ abstract class MenuNode
|
||||
$this->sMenuId = $sMenuId;
|
||||
$this->iParentIndex = $iParentIndex;
|
||||
$this->aReflectionProperties = array();
|
||||
if (strlen($sEnableClass) > 0)
|
||||
{
|
||||
if (utils::IsNotNullOrEmptyString($sEnableClass)) {
|
||||
$this->aReflectionProperties['enable_class'] = $sEnableClass;
|
||||
$this->aReflectionProperties['enable_action'] = $iActionCode;
|
||||
$this->aReflectionProperties['enable_permission'] = $iAllowedResults;
|
||||
@@ -741,7 +757,7 @@ abstract class MenuNode
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return string The "+" dictionary entry for this menu if exists, otherwise the Title (if we have a parent title, will output parentTitle / currentTitle)
|
||||
*/
|
||||
public function GetLabel()
|
||||
{
|
||||
@@ -753,7 +769,6 @@ abstract class MenuNode
|
||||
} else {
|
||||
$sRet = $this->GetTitle();
|
||||
}
|
||||
//$sRet = $this->GetTitle();
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
@@ -1096,6 +1111,7 @@ class OQLMenuNode extends MenuNode
|
||||
*/
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
ContextTag::AddContext(ContextTag::TAG_OBJECT_SEARCH);
|
||||
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
|
||||
OQLMenuNode::RenderOQLSearch
|
||||
(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/NiceWebPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/NiceWebPage.php, now loadable using autoloader');
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/PDFPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/PDFPage.php, now loadable using autoloader');
|
||||
@@ -18,8 +18,7 @@
|
||||
*/
|
||||
|
||||
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\FieldSet\FieldSetUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Input\TextArea;
|
||||
|
||||
@@ -51,6 +50,7 @@ abstract class Query extends cmdbAbstractObject
|
||||
"is_null_allowed" => false,
|
||||
"depends_on" => array(),
|
||||
)));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeText("description", array(
|
||||
"allowed_values" => null,
|
||||
"sql" => "description",
|
||||
@@ -68,6 +68,41 @@ abstract class Query extends cmdbAbstractObject
|
||||
'display_style' => 'radio_horizontal',
|
||||
)));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("export_count", array(
|
||||
"allowed_values" => null,
|
||||
"sql" => "export_count",
|
||||
"default_value" => 0,
|
||||
"is_null_allowed" => false,
|
||||
"depends_on" => array(),
|
||||
)));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("export_last_date", array(
|
||||
"allowed_values" => null,
|
||||
"sql" => "export_last_date",
|
||||
"default_value" => null,
|
||||
"is_null_allowed" => true,
|
||||
"depends_on" => array(),
|
||||
)));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("export_last_user_id",
|
||||
array(
|
||||
"targetclass"=>'User',
|
||||
"allowed_values"=>null,
|
||||
"sql"=>'user_id',
|
||||
"is_null_allowed"=>true,
|
||||
"depends_on"=>array(),
|
||||
"display_style"=>'select',
|
||||
"always_load_in_tables"=>false,
|
||||
"on_target_delete"=>DEL_SILENT
|
||||
)));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("export_last_user_contact",
|
||||
array(
|
||||
"allowed_values"=>null,
|
||||
"extkey_attcode"=> "export_last_user_id",
|
||||
"target_attcode"=>"contactid"
|
||||
)));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details',
|
||||
array('name', 'is_template', 'description')); // Attributes to be displayed for the complete details
|
||||
@@ -78,6 +113,55 @@ abstract class Query extends cmdbAbstractObject
|
||||
array('name', 'description', 'is_template')); // Criteria of the default search form
|
||||
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '')
|
||||
{
|
||||
// read only attribute
|
||||
if (in_array($sAttCode, ['export_count', 'export_last_date', 'export_last_user_id'])){
|
||||
return OPT_ATT_READONLY;
|
||||
}
|
||||
|
||||
return parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return export url.
|
||||
*
|
||||
* @param array|null $aValues optional values for the query
|
||||
*
|
||||
* @return string|null
|
||||
* @since 3.1.0
|
||||
*/
|
||||
abstract public function GetExportUrl(array $aValues = null) : ?string;
|
||||
|
||||
/**
|
||||
* Update last export information.
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public function UpdateLastExportInformation() : void
|
||||
{
|
||||
// last export information
|
||||
$this->Set('export_last_date', date(AttributeDateTime::GetSQLFormat()));
|
||||
$this->Set('export_last_user_id', UserRights::GetUserObject());
|
||||
$this->AllowWrite(true);
|
||||
$this->DBUpdate();
|
||||
|
||||
// increment usage counter
|
||||
$this->DBIncrement('export_count');
|
||||
}
|
||||
}
|
||||
|
||||
class QueryOQL extends Query
|
||||
@@ -116,13 +200,51 @@ class QueryOQL extends Query
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details',
|
||||
array('name', 'is_template', 'description', 'oql', 'fields')); // Attributes to be displayed for the complete details
|
||||
array(
|
||||
'col:col1' => array('fieldset:Query:baseinfo' => array('name', 'is_template', 'description', 'oql', 'fields')),
|
||||
'col:col2' => array('fieldset:Query:exportInfo' => array('export_count', 'export_last_date', 'export_last_user_id', 'export_last_user_contact'))
|
||||
)
|
||||
); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
MetaModel::Init_SetZListItems('standard_search',
|
||||
array('name', 'description', 'is_template', 'fields', 'oql')); // Criteria of the std search form
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function GetExportUrl(array $aValues = null) : ?string
|
||||
{
|
||||
try{
|
||||
// retrieve attributes
|
||||
$sFields = trim($this->Get('fields'));
|
||||
$sOql = $this->Get('oql');
|
||||
|
||||
// construct base url depending on version
|
||||
$bExportV1Recommended = ($sFields == '');
|
||||
if ($bExportV1Recommended) {
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?format=spreadsheet&login_mode=basic&query='.$this->GetKey();
|
||||
}
|
||||
else{
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php?format=spreadsheet&login_mode=basic&date_format='.urlencode((string)AttributeDateTime::GetFormat()).'&query='.$this->GetKey();
|
||||
}
|
||||
|
||||
// search object from OQL
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql);
|
||||
|
||||
// inject parameters
|
||||
$aParameters = $oSearch->GetQueryParams();
|
||||
foreach ($aParameters as $sParam => $val) {
|
||||
$paramValue = ($aValues === null || $aValues[$sParam] === null) ? $sParam : $aValues[$sParam];
|
||||
$sUrl .= '&arg_' . $sParam . '=' . $paramValue;
|
||||
}
|
||||
|
||||
return $sUrl;
|
||||
}
|
||||
catch(Exception $e){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
|
||||
{
|
||||
$aFieldsMap = parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams);
|
||||
@@ -152,9 +274,11 @@ class QueryOQL extends Query
|
||||
$sUrl .= '&arg_'.$sParam.'=["'.$sParam.'"]';
|
||||
}
|
||||
|
||||
// add text area inside field set
|
||||
$oFieldSet = FieldSetUIBlockFactory::MakeStandard(Dict::S('UI:Query:UrlForExcel'));
|
||||
$oTextArea = new TextArea("", $sUrl, null, 80, 3);
|
||||
$oFieldUrl = FieldUIBlockFactory::MakeFromObject(Dict::S('UI:Query:UrlForExcel'), $oTextArea, Field::ENUM_FIELD_LAYOUT_LARGE);
|
||||
$oPage->AddSubBlock($oFieldUrl);
|
||||
$oFieldSet->AddSubBlock($oTextArea);
|
||||
$oPage->AddSubBlock($oFieldSet);
|
||||
|
||||
if (count($aParameters) == 0) {
|
||||
$oBlock = new DisplayBlock($oSearch, 'list');
|
||||
@@ -178,6 +302,7 @@ class QueryOQL extends Query
|
||||
return $aFieldsMap;
|
||||
}
|
||||
|
||||
|
||||
// Rolled back until 'fields' can be properly managed by AttributeQueryAttCodeSet
|
||||
//
|
||||
// public function ComputeValues()
|
||||
|
||||
@@ -15,12 +15,16 @@
|
||||
//
|
||||
// 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\EventRegister\ApplicationEvents;
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
|
||||
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');
|
||||
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');
|
||||
require_once(APPROOT.'setup/setuputils.class.inc.php');
|
||||
|
||||
|
||||
/**
|
||||
@@ -99,3 +103,6 @@ else
|
||||
}
|
||||
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
|
||||
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface
|
||||
EventService::InitService();
|
||||
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));
|
||||
|
||||
@@ -229,12 +229,10 @@ class DisplayTemplate
|
||||
static public function UnitTest()
|
||||
{
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
require_once(APPROOT."/application/itopwebpage.class.inc.php");
|
||||
|
||||
$sTemplate = '<div class="page_header">
|
||||
<div class="actions_details"><a href="#"><span>Actions</span></a></div>
|
||||
<h1>$class$: <span class="hilite">$name$</span></h1>
|
||||
<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOp WHERE objkey = $id$ AND objclass = \'$class$\'</itopblock>
|
||||
</div>
|
||||
<img src="../../images/connect_to_network.png" style="margin-top:-10px; margin-right:10px; float:right">
|
||||
<itoptabs>
|
||||
@@ -353,7 +351,7 @@ class ObjectDetailsTemplate extends DisplayTemplate
|
||||
$sTip = '';
|
||||
foreach($aReasons as $aRow)
|
||||
{
|
||||
$sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8');
|
||||
$sDescription = utils::EscapeHtml($aRow['description']);
|
||||
$sDescription = str_replace(array("\r\n", "\n"), "<br/>", $sDescription);
|
||||
$sTip .= "<div class='synchro-source'>";
|
||||
$sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>";
|
||||
@@ -361,10 +359,10 @@ class ObjectDetailsTemplate extends DisplayTemplate
|
||||
}
|
||||
$oPage->add_ready_script("$('#synchro_$iInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
}
|
||||
|
||||
|
||||
// Attribute is read-only
|
||||
$sHTMLValue = "<span id=\"field_{$iInputId}\">".$this->m_oObj->GetAsHTML($sAttCode);
|
||||
$sHTMLValue .= '<input type="hidden" id="'.$iInputId.'" name="attr_'.$sAttCode.'" value="'.htmlentities($this->m_oObj->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/></span>';
|
||||
$sHTMLValue .= '<input type="hidden" id="'.$iInputId.'" name="attr_'.$sAttCode.'" value="'.utils::EscapeHtml($this->m_oObj->Get($sAttCode)).'"/></span>';
|
||||
$aFieldsMap[$sAttCode] = $iInputId;
|
||||
$aParams['this->comments('.$sAttCode.')'] = $sSynchroIcon;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<div class="page_header">
|
||||
<itopblock blockclass="MenuBlock" type="popup" encoding="text/oql" label="Actions">SELECT $class$ WHERE id = $id$</itopblock>
|
||||
<h1>$class$: <span class="hilite">$name$</span></h1>
|
||||
<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOp WHERE objkey = $id$ AND objclass = '$class$'</itopblock>
|
||||
</div>
|
||||
<img src="../../images/clean.png" style="margin-top:-20px; margin-right:10px; float:right">
|
||||
<itopblock blockclass="DisplayBlock" asynchronous="false" type="bare_details" encoding="text/oql">SELECT $class$ WHERE id = $id$</itopblock>
|
||||
|
||||
@@ -4,14 +4,17 @@ namespace Combodo\iTop;
|
||||
|
||||
use AttributeDate;
|
||||
use AttributeDateTime;
|
||||
use DeprecatedCallsLog;
|
||||
use Dict;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
use Twig_Environment;
|
||||
use Twig_SimpleFilter;
|
||||
use Twig_SimpleFunction;
|
||||
use Twig\Environment;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use utils;
|
||||
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('instead use sources/Application/TwigBase/Twig/Extension.php, which is loaded by the autoloader');
|
||||
|
||||
/**
|
||||
* Class TwigExtension
|
||||
*
|
||||
@@ -25,13 +28,13 @@ class TwigExtension
|
||||
* Registers Twig extensions such as filters or functions.
|
||||
* It allows us to access some stuff directly in twig.
|
||||
*
|
||||
* @param \Twig_Environment $oTwigEnv
|
||||
* @param Environment $oTwigEnv
|
||||
*/
|
||||
public static function RegisterTwigExtensions(Twig_Environment &$oTwigEnv)
|
||||
public static function RegisterTwigExtensions(Environment &$oTwigEnv)
|
||||
{
|
||||
// Filter to translate a string via the Dict::S function
|
||||
// Usage in twig: {{ 'String:ToTranslate'|dict_s }}
|
||||
$oTwigEnv->addFilter(new Twig_SimpleFilter('dict_s',
|
||||
$oTwigEnv->addFilter(new TwigFilter('dict_s',
|
||||
function ($sStringCode, $sDefault = null, $bUserLanguageOnly = false) {
|
||||
return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly);
|
||||
})
|
||||
@@ -39,7 +42,7 @@ class TwigExtension
|
||||
|
||||
// Filter to format a string via the Dict::Format function
|
||||
// Usage in twig: {{ 'String:ToTranslate'|dict_format() }}
|
||||
$oTwigEnv->addFilter(new Twig_SimpleFilter('dict_format',
|
||||
$oTwigEnv->addFilter(new TwigFilter('dict_format',
|
||||
function ($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null) {
|
||||
return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04);
|
||||
})
|
||||
@@ -48,16 +51,13 @@ class TwigExtension
|
||||
// Filter to format output
|
||||
// example a DateTime is converted to user format
|
||||
// Usage in twig: {{ 'String:ToFormat'|output_format }}
|
||||
$oTwigEnv->addFilter(new Twig_SimpleFilter('date_format',
|
||||
$oTwigEnv->addFilter(new TwigFilter('date_format',
|
||||
function ($sDate) {
|
||||
try
|
||||
{
|
||||
if (preg_match('@^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$@', trim($sDate)))
|
||||
{
|
||||
try {
|
||||
if (preg_match('@^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$@', trim($sDate))) {
|
||||
return AttributeDateTime::GetFormat()->Format($sDate);
|
||||
}
|
||||
if (preg_match('@^\d\d\d\d-\d\d-\d\d$@', trim($sDate)))
|
||||
{
|
||||
if (preg_match('@^\d\d\d\d-\d\d-\d\d$@', trim($sDate))) {
|
||||
return AttributeDate::GetFormat()->Format($sDate);
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ class TwigExtension
|
||||
// Filter to format output
|
||||
// example a DateTime is converted to user format
|
||||
// Usage in twig: {{ 'String:ToFormat'|output_format }}
|
||||
$oTwigEnv->addFilter(new Twig_SimpleFilter('size_format',
|
||||
$oTwigEnv->addFilter(new TwigFilter('size_format',
|
||||
function ($sSize) {
|
||||
return utils::BytesToFriendlyFormat($sSize);
|
||||
})
|
||||
@@ -80,24 +80,25 @@ class TwigExtension
|
||||
|
||||
// Filter to enable base64 encode/decode
|
||||
// Usage in twig: {{ 'String to encode'|base64_encode }}
|
||||
$oTwigEnv->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode'));
|
||||
$oTwigEnv->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode'));
|
||||
$oTwigEnv->addFilter(new TwigFilter('base64_encode', 'base64_encode'));
|
||||
$oTwigEnv->addFilter(new TwigFilter('base64_decode', 'base64_decode'));
|
||||
|
||||
// Filter to enable json decode (encode already exists)
|
||||
// Usage in twig: {{ aSomeArray|json_decode }}
|
||||
$oTwigEnv->addFilter(new Twig_SimpleFilter('json_decode', function ($sJsonString, $bAssoc = false) {
|
||||
$oTwigEnv->addFilter(new TwigFilter('json_decode', function ($sJsonString, $bAssoc = false) {
|
||||
return json_decode($sJsonString, $bAssoc);
|
||||
})
|
||||
);
|
||||
|
||||
// Filter to add itopversion to an url
|
||||
$oTwigEnv->addFilter(new Twig_SimpleFilter('add_itop_version', function ($sUrl) {
|
||||
$oTwigEnv->addFilter(new TwigFilter('add_itop_version', function ($sUrl) {
|
||||
$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) {
|
||||
$oTwigEnv->addFilter(new TwigFilter('add_module_version', function ($sUrl, $sModuleName) {
|
||||
$sModuleVersion = utils::GetCompiledModuleVersion($sModuleName);
|
||||
$sUrl = utils::AddParameterToUrl($sUrl, 'moduleversion', $sModuleVersion);
|
||||
|
||||
@@ -106,38 +107,19 @@ class TwigExtension
|
||||
|
||||
// Function to check our current environment
|
||||
// Usage in twig: {% if is_development_environment() %}
|
||||
$oTwigEnv->addFunction(new Twig_SimpleFunction('is_development_environment', function()
|
||||
{
|
||||
$oTwigEnv->addFunction(new TwigFunction('is_development_environment', function () {
|
||||
return utils::IsDevelopmentEnvironment();
|
||||
}));
|
||||
|
||||
// Function to get configuration parameter
|
||||
// Usage in twig: {{ get_config_parameter('foo') }}
|
||||
$oTwigEnv->addFunction(new Twig_SimpleFunction('get_config_parameter', function($sParamName)
|
||||
{
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
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)
|
||||
{
|
||||
$oTwigEnv->addFunction(new TwigFunction('get_static_page_module_url', function ($sModuleName, $sPage) {
|
||||
return utils::GetAbsoluteUrlModulesRoot().$sModuleName.'/'.$sPage;
|
||||
}));
|
||||
|
||||
// Function to get the URL of a php page in a module
|
||||
// Usage in twig: {{ get_page_module_url('itop-my-module', 'path-to-my-my-page.php') }}
|
||||
$oTwigEnv->addFunction(new Twig_SimpleFunction('get_page_module_url', function($sModuleName, $sPage)
|
||||
{
|
||||
$oTwigEnv->addFunction(new TwigFunction('get_page_module_url', function ($sModuleName, $sPage) {
|
||||
return utils::GetAbsoluteUrlModulePage($sModuleName, $sPage);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -211,14 +211,23 @@ class UIExtKeyWidget
|
||||
$sClassAllowed = $oAllowedValues->GetClass();
|
||||
$bAddingValue = false;
|
||||
|
||||
// N°4792 - load only the required fields
|
||||
$aFieldsToLoad = [];
|
||||
|
||||
$aComplementAttributeSpec = MetaModel::GetNameSpec($oAllowedValues->GetClass(), FriendlyNameType::COMPLEMENTARY);
|
||||
$sFormatAdditionalField = $aComplementAttributeSpec[0];
|
||||
$aAdditionalField = $aComplementAttributeSpec[1];
|
||||
|
||||
if (count($aAdditionalField) > 0) {
|
||||
$bAddingValue = true;
|
||||
$aFieldsToLoad[$sClassAllowed] = $aAdditionalField;
|
||||
}
|
||||
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sClassAllowed);
|
||||
if (!empty($sObjectImageAttCode)) {
|
||||
$aFieldsToLoad[$sClassAllowed][] = $sObjectImageAttCode;
|
||||
}
|
||||
$aFieldsToLoad[$sClassAllowed][] = 'friendlyname';
|
||||
$oAllowedValues->OptimizeColumnLoad($aFieldsToLoad);
|
||||
$bInitValue = false;
|
||||
while ($oObj = $oAllowedValues->Fetch()) {
|
||||
$aOption = [];
|
||||
@@ -298,7 +307,7 @@ EOF
|
||||
$sHTMLValue .= "<input class=\"field_autocomplete ibo-input ibo-input-select ibo-input-select-autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\" placeholder='...'/>";
|
||||
|
||||
// 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";
|
||||
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".utils::HtmlEntities($value)."\" />\n";
|
||||
|
||||
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
|
||||
// Scripts to start the autocomplete and bind some events to it
|
||||
@@ -610,7 +619,7 @@ EOF
|
||||
$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";
|
||||
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".utils::EscapeHtml($value)."\" />\n";
|
||||
|
||||
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
|
||||
// Scripts to start the autocomplete and bind some events to it
|
||||
@@ -676,15 +685,15 @@ JS
|
||||
}
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
|
||||
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, $this->iId,
|
||||
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, 'dtc_'.$this->iId,
|
||||
array(
|
||||
'menu' => false,
|
||||
'currentId' => $this->iId,
|
||||
'table_id' => "dr_{$this->iId}",
|
||||
'menu' => false,
|
||||
'currentId' => $this->iId,
|
||||
'table_id' => "dr_{$this->iId}",
|
||||
'table_inner_id' => "{$this->iId}_results",
|
||||
'selection_mode' => true,
|
||||
'selection_type' => 'single',
|
||||
'cssCount' => '#count_'.$this->iId.'_results',
|
||||
'cssCount' => '#count_'.$this->iId.'_results',
|
||||
)
|
||||
));
|
||||
$sCancel = Dict::S('UI:Button:Cancel');
|
||||
@@ -769,16 +778,14 @@ JS
|
||||
* @param DBObject $oObj The current object for the OQL context
|
||||
* @param string $sContains The text of the autocomplete to filter the results
|
||||
* @param string $sOutputFormat
|
||||
* @param null $sOperation for the values @see ValueSetObjects->LoadValues()
|
||||
* @param null $sOperation for the values @see ValueSetObjects->LoadValues() not used since 3.0.0
|
||||
*
|
||||
* @throws CoreException
|
||||
* @throws OQLException
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $oObj for PHP 8.0 compatibility
|
||||
*/
|
||||
public function AutoComplete(
|
||||
WebPage $oP, $sFilter, $oObj, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null
|
||||
)
|
||||
public function AutoComplete(WebPage $oP, $sFilter, $oObj, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null )
|
||||
{
|
||||
if (is_null($sFilter)) {
|
||||
throw new Exception('Implementation: null value for allowed values definition');
|
||||
@@ -792,13 +799,13 @@ JS
|
||||
$oValuesSet->SetSort(false);
|
||||
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oValuesSet->SetLimit($iMax);
|
||||
$aValuesContains = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'start_with');
|
||||
asort($aValuesContains);
|
||||
$aValues = $aValuesContains;
|
||||
$aValuesStartWith = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'start_with');
|
||||
asort($aValuesStartWith);
|
||||
$aValues = $aValuesStartWith;
|
||||
if (sizeof($aValues) < $iMax) {
|
||||
$aValuesContains = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
|
||||
asort($aValuesContains);
|
||||
$iSize = sizeof($aValuesContains);
|
||||
$iSize = sizeof($aValues);
|
||||
foreach ($aValuesContains as $sKey => $sFriendlyName)
|
||||
{
|
||||
if (!isset($aValues[$sKey]))
|
||||
@@ -814,7 +821,9 @@ JS
|
||||
elseif (!in_array($sContains, $aValues))
|
||||
{
|
||||
$aValuesEquals = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals');
|
||||
$aValues = array_merge($aValuesEquals, $aValues);
|
||||
// Note: Here we cannot use array_merge as it would reindex the numeric keys starting from 0 when keys are actually the objects ID.
|
||||
// As a workaround we use array_replace as it does preserve numeric keys. It's ok if some values from $aValuesEquals are replaced with values from $aValues as they contain the same data.
|
||||
$aValues = array_replace($aValuesEquals, $aValues);
|
||||
}
|
||||
|
||||
switch($sOutputFormat)
|
||||
@@ -962,7 +971,7 @@ HTML
|
||||
foreach (MetaModel::ListAttributeDefs($this->sTargetClass) as $sAttCode => $oAttDef) {
|
||||
if (($oAttDef instanceof AttributeBlob) || (false)) {
|
||||
$aFieldsFlags[$sAttCode] = OPT_ATT_READONLY;
|
||||
$aFieldsComments[$sAttCode] = ' <img src="../images/transp-lock.png" style="vertical-align:middle" title="'.htmlentities(Dict::S('UI:UploadNotSupportedInThisMode')).'"/>';
|
||||
$aFieldsComments[$sAttCode] = ' <img src="../images/transp-lock.png" style="vertical-align:middle" title="'.utils::EscapeHtml(Dict::S('UI:UploadNotSupportedInThisMode')).'"/>';
|
||||
}
|
||||
}
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
|
||||
@@ -973,9 +982,9 @@ HTML
|
||||
);
|
||||
|
||||
$oPage->add_ready_script(<<<JS
|
||||
$('#ac_create_{$this->iId}').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true});
|
||||
$('#ac_create_{$this->iId}').dialog({ width: $(window).width() * 0.6, height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true});
|
||||
$('#dcr_{$this->iId} form').removeAttr('onsubmit');
|
||||
$('#dcr_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoCreateObject);
|
||||
$('#dcr_{$this->iId} form').find('button[type="submit"]').on('click', oACWidget_{$this->iId}.DoCreateObject);
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Links\Direct\BlockDirectLinksEditTable;
|
||||
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
|
||||
|
||||
/**
|
||||
* Class UILinksWidgetDirect
|
||||
@@ -19,10 +19,12 @@ class UILinksWidgetDirect
|
||||
protected $sAttCode;
|
||||
protected $sInputid;
|
||||
protected $sNameSuffix;
|
||||
protected $aZlist;
|
||||
protected $sLinkedClass;
|
||||
|
||||
/**
|
||||
* UILinksWidgetDirect constructor.
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param string $sInputId
|
||||
@@ -80,97 +82,10 @@ class UILinksWidgetDirect
|
||||
*/
|
||||
public function Display(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj)
|
||||
{
|
||||
if (empty($aArgs)) {
|
||||
$aArgs = [];
|
||||
}
|
||||
$oBlock = new BlockDirectLinksEditTable($this, $this->sInputid);
|
||||
$oBlock->InitTable($oPage, $oValue, $sFormPrefix);
|
||||
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
switch($oLinksetDef->GetEditMode())
|
||||
{
|
||||
case LINKSET_EDITMODE_NONE: // The linkset is read-only
|
||||
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/);
|
||||
break;
|
||||
|
||||
case LINKSET_EDITMODE_ADDONLY: // The only possible action is to open (in a new window) the form to create a new object
|
||||
if ($oCurrentObj && !$oCurrentObj->IsNew())
|
||||
{
|
||||
$sTargetClass = $oLinksetDef->GetLinkedClass();
|
||||
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
|
||||
$sDefault = "default[$sExtKeyToMe]=".$oCurrentObj->GetKey();
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sParams = $oAppContext->GetForLink();
|
||||
$oPage->p("<a target=\"_blank\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sTargetClass&$sParams&{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sTargetClass))."</a>\n");
|
||||
}
|
||||
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/);
|
||||
break;
|
||||
|
||||
case LINKSET_EDITMODE_INPLACE: // The whole linkset can be edited 'in-place'
|
||||
$this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj);
|
||||
break;
|
||||
|
||||
case LINKSET_EDITMODE_ADDREMOVE: // The whole linkset can be edited 'in-place'
|
||||
$sTargetClass = $oLinksetDef->GetLinkedClass();
|
||||
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
|
||||
$oExtKeyDef = MetaModel::GetAttributeDef($sTargetClass, $sExtKeyToMe);
|
||||
$aButtons = array('add');
|
||||
if ($oExtKeyDef->IsNullAllowed())
|
||||
{
|
||||
$aButtons = array('add', 'remove');
|
||||
}
|
||||
$this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aButtons);
|
||||
break;
|
||||
|
||||
case LINKSET_EDITMODE_ACTIONS:
|
||||
default:
|
||||
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, true /* bDisplayMenu*/);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param bool $bDisplayMenu
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (protected method, always called with default value)
|
||||
*/
|
||||
protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
{
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$sTargetClass = $oLinksetDef->GetLinkedClass();
|
||||
if ($oCurrentObj && $oCurrentObj->IsNew() && $bDisplayMenu)
|
||||
{
|
||||
$oPage->p(Dict::Format('UI:BeforeAdding_Class_ObjectsSaveThisObject', MetaModel::GetName($sTargetClass)));
|
||||
}
|
||||
else
|
||||
{
|
||||
$oFilter = new DBObjectSearch($sTargetClass);
|
||||
$oFilter->AddCondition($oLinksetDef->GetExtKeyToMe(), $oCurrentObj->GetKey(),'=');
|
||||
|
||||
$aDefaults = array($oLinksetDef->GetExtKeyToMe() => $oCurrentObj->GetKey());
|
||||
$oAppContext = new ApplicationContext();
|
||||
foreach($oAppContext->GetNames() as $sKey)
|
||||
{
|
||||
// The linked object inherits the parent's value for the context
|
||||
if (MetaModel::IsValidAttCode($this->sClass, $sKey) && $oCurrentObj)
|
||||
{
|
||||
$aDefaults[$sKey] = $oCurrentObj->Get($sKey);
|
||||
}
|
||||
}
|
||||
$aParams = array(
|
||||
'target_attr' => $oLinksetDef->GetExtKeyToMe(),
|
||||
'object_id' => $oCurrentObj ? $oCurrentObj->GetKey() : null,
|
||||
'menu' => $bDisplayMenu,
|
||||
'menu_actions_target' => '_blank',
|
||||
'default' => $aDefaults,
|
||||
'table_id' => $this->sClass.'_'.$this->sAttCode,
|
||||
);
|
||||
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false);
|
||||
$oBlock->Display($oPage, $this->sInputid, $aParams);
|
||||
}
|
||||
return ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,55 +144,6 @@ class UILinksWidgetDirect
|
||||
$oPage->add('</div></div>');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param array $aButtons
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (protected method, caller already handles it)
|
||||
*/
|
||||
protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
{
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
$oValue->Rewind();
|
||||
$aData = array();
|
||||
while ($oLinkObj = $oValue->Fetch()) {
|
||||
$aRow = array();
|
||||
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.$oLinkObj->GetKey().'"/>';
|
||||
foreach ($this->aZlist as $sLinkedAttCode) {
|
||||
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
|
||||
}
|
||||
$aData[] = $aRow;
|
||||
}
|
||||
$oDiv = UIContentBlockUIBlockFactory::MakeStandard($this->sInputid, ['listContainer']);
|
||||
$oPage->AddSubBlock($oDiv);
|
||||
$oDatatable = DataTableUIBlockFactory::MakeForForm($this->sInputid, $aAttribs, $aData);
|
||||
$oDatatable->SetOptions(['select_mode' => 'custom']);
|
||||
$oDiv->AddSubBlock($oDatatable);
|
||||
$sInputName = $sFormPrefix.'attr_'.$this->sAttCode;
|
||||
$aLabels = array(
|
||||
'delete' => Dict::S('UI:Button:Delete'),
|
||||
// 'modify' => 'Modify...' ,
|
||||
'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)),
|
||||
'remove' => Dict::S('UI:Button:Remove'),
|
||||
'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
);
|
||||
$oContext = new ApplicationContext();
|
||||
$sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
|
||||
$sJSONLabels = json_encode($aLabels);
|
||||
$sJSONButtons = json_encode($aButtons);
|
||||
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
|
||||
// Don't automatically launch the search if the table is huge
|
||||
$bDoSearch = !utils::IsHighCardinality($this->sLinkedClass);
|
||||
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
|
||||
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper, do_search: $sJSDoSearch});");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject $oCurrentObj
|
||||
@@ -433,18 +299,21 @@ HTML
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected function GetTableConfig()
|
||||
|
||||
public function GetTableConfig()
|
||||
{
|
||||
$aAttribs = array();
|
||||
$aAttribs['form::select'] = array('label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->sInputid}:not(:disabled)', this.checked);\" class=\"checkAll\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
|
||||
$aAttribs['form::select'] = array(
|
||||
'label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->sInputid}:not(:disabled)', this.checked);oWidget".$this->sInputid.".directlinks('instance')._onSelectChange();\" class=\"checkAll\"></input>",
|
||||
'description' => Dict::S('UI:SelectAllToggle+'),
|
||||
);
|
||||
|
||||
foreach($this->aZlist as $sLinkedAttCode)
|
||||
{
|
||||
foreach ($this->aZlist as $sLinkedAttCode) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->sLinkedClass, $sLinkedAttCode);
|
||||
$aAttribs[$sLinkedAttCode] = array('label' => MetaModel::GetLabel($this->sLinkedClass, $sLinkedAttCode), 'description' => $oAttDef->GetOrderByHint());
|
||||
}
|
||||
return $aAttribs;
|
||||
|
||||
return $aAttribs;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -543,12 +412,43 @@ HTML
|
||||
{
|
||||
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
|
||||
}
|
||||
|
||||
if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue))
|
||||
{
|
||||
|
||||
if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue)) {
|
||||
$oSearch->AddCondition($sAttCode, $defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function GetClass(): string
|
||||
{
|
||||
return $this->sClass;
|
||||
}
|
||||
|
||||
public function GetLinkedClass(): string
|
||||
{
|
||||
return $this->sLinkedClass;
|
||||
}
|
||||
|
||||
public function GetAttCode(): string
|
||||
{
|
||||
return $this->sAttCode;
|
||||
}
|
||||
|
||||
public function GetInputId(): string
|
||||
{
|
||||
return $this->sInputid;
|
||||
}
|
||||
|
||||
public function GetNameSuffix(): string
|
||||
{
|
||||
return $this->sNameSuffix;
|
||||
}
|
||||
|
||||
public function GetZList(): array
|
||||
{
|
||||
return $this->aZlist;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\FormTableRow\FormTableRow;
|
||||
use Combodo\iTop\Application\UI\Links\Indirect\BlockIndirectLinksEdit\BlockIndirectLinksEdit;
|
||||
use Combodo\iTop\Application\UI\Links\Indirect\BlockObjectPickerDialog\BlockObjectPickerDialog;
|
||||
use Combodo\iTop\Application\UI\Links\Indirect\BlockIndirectLinksEditTable;
|
||||
use Combodo\iTop\Application\UI\Links\Indirect\BlockObjectPickerDialog;
|
||||
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
|
||||
|
||||
require_once(APPROOT.'application/displayblock.class.inc.php');
|
||||
|
||||
class UILinksWidget
|
||||
class UILinksWidget
|
||||
{
|
||||
protected $m_sClass;
|
||||
protected $m_sAttCode;
|
||||
protected $m_sNameSuffix;
|
||||
protected $m_iInputId;
|
||||
protected $m_sInputId;
|
||||
protected $m_aAttributes;
|
||||
protected $m_sExtKeyToRemote;
|
||||
protected $m_sExtKeyToMe;
|
||||
@@ -33,7 +33,7 @@ class UILinksWidget
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode AttributeLinkedSetIndirect attcode
|
||||
* @param int $iInputId
|
||||
* @param string $sInputId
|
||||
* @param string $sNameSuffix
|
||||
* @param bool $bDuplicatesAllowed
|
||||
*
|
||||
@@ -41,13 +41,14 @@ class UILinksWidget
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($sClass, $sAttCode, $iInputId, $sNameSuffix = '', $bDuplicatesAllowed = false)
|
||||
public function __construct($sClass, $sAttCode, $sInputId, $sNameSuffix = '', $bDuplicatesAllowed = false)
|
||||
{
|
||||
$this->m_sClass = $sClass;
|
||||
$this->m_sAttCode = $sAttCode;
|
||||
$this->m_sInputId = $sInputId;
|
||||
$this->m_sNameSuffix = $sNameSuffix;
|
||||
$this->m_iInputId = $iInputId;
|
||||
$this->m_bDuplicatesAllowed = $bDuplicatesAllowed;
|
||||
|
||||
$this->m_aEditableFields = array();
|
||||
|
||||
/** @var AttributeLinkedSetIndirect $oAttDef */
|
||||
@@ -63,7 +64,7 @@ class UILinksWidget
|
||||
$this->m_aEditableFields = array();
|
||||
$this->m_aTableConfig = array();
|
||||
$this->m_aTableConfig['form::checkbox'] = array(
|
||||
'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_iInputId.".OnSelectChange();\">",
|
||||
'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_sInputId.".OnSelectChange();\">",
|
||||
'description' => Dict::S('UI:SelectAllToggle+'),
|
||||
);
|
||||
|
||||
@@ -91,234 +92,13 @@ class UILinksWidget
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A one-row form for editing a link record
|
||||
*
|
||||
* @param WebPage $oP Web page used for the ouput
|
||||
* @param DBObject $oLinkedObj Remote object
|
||||
* @param DBObject|int $linkObjOrId Either the lnk object or a unique number for new link records to add
|
||||
* @param array $aArgs Extra context arguments
|
||||
* @param DBObject $oCurrentObj The object to which all the elements of the linked set refer to
|
||||
* @param int $iUniqueId A unique identifier of new links
|
||||
* @param boolean $bReadOnly Display link as editable or read-only. Default is false (editable)
|
||||
*
|
||||
* @return array The HTML fragment of the one-row form
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false)
|
||||
{
|
||||
$sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}";
|
||||
$aRow = array();
|
||||
$aFieldsMap = array();
|
||||
$iKey = 0;
|
||||
|
||||
if (is_object($linkObjOrId) && (!$linkObjOrId->IsNew()))
|
||||
{
|
||||
$iKey = $linkObjOrId->GetKey();
|
||||
$iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote);
|
||||
$sPrefix .= "[$iKey][";
|
||||
$sNameSuffix = "]"; // To make a tabular form
|
||||
$aArgs['prefix'] = $sPrefix;
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$iKey}";
|
||||
$aArgs['this'] = $linkObjOrId;
|
||||
|
||||
if ($bReadOnly)
|
||||
{
|
||||
$aRow['form::checkbox'] = "";
|
||||
foreach ($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
|
||||
$aRow[$sFieldCode] = $sDisplayValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"$iKey\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$iKey\">";
|
||||
foreach ($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sSafeFieldId = $this->GetFieldId($linkObjOrId->GetKey(), $sFieldCode);
|
||||
$this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $linkObjOrId, $oP, $sNameSuffix, $sSafeFieldId);
|
||||
$aFieldsMap[$sFieldCode] = $sSafeFieldId;
|
||||
}
|
||||
}
|
||||
|
||||
$sState = $linkObjOrId->GetState();
|
||||
$sRemoteKeySafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $this->m_sExtKeyToRemote);;
|
||||
}
|
||||
else
|
||||
{
|
||||
// form for creating a new record
|
||||
if (is_object($linkObjOrId))
|
||||
{
|
||||
// New link existing only in memory
|
||||
$oNewLinkObj = $linkObjOrId;
|
||||
$iRemoteObjKey = $oNewLinkObj->Get($this->m_sExtKeyToRemote);
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToMe,
|
||||
$oCurrentObj); // Setting the extkey with the object also fills the related external fields
|
||||
}
|
||||
else
|
||||
{
|
||||
$iRemoteObjKey = $linkObjOrId;
|
||||
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
|
||||
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToRemote,
|
||||
$oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToMe,
|
||||
$oCurrentObj); // Setting the extkey with the object also fills the related external fields
|
||||
}
|
||||
$sPrefix .= "[-$iUniqueId][";
|
||||
$sNameSuffix = "]"; // To make a tabular form
|
||||
$aArgs['prefix'] = $sPrefix;
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".($iUniqueId < 0 ? -$iUniqueId : $iUniqueId);
|
||||
$aArgs['this'] = $oNewLinkObj;
|
||||
$sInputValue = $iUniqueId > 0 ? "-$iUniqueId" : "$iUniqueId";
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"0\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$sInputValue\">";
|
||||
|
||||
if ($iUniqueId > 0)
|
||||
{
|
||||
// Rows created with ajax call need OnLinkAdded call.
|
||||
//
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
PrepareWidgets();
|
||||
oWidget{$this->m_iInputId}.OnLinkAdded($iUniqueId, $iRemoteObjKey);
|
||||
EOF
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rows added before loading the form don't have to call OnLinkAdded.
|
||||
// Listeners are already present and DOM is not recreated
|
||||
$iPositiveUniqueId = -$iUniqueId;
|
||||
$oP->add_ready_script(<<<EOF
|
||||
oWidget{$this->m_iInputId}.AddLink($iPositiveUniqueId, $iRemoteObjKey);
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sSafeFieldId = $this->GetFieldId($iUniqueId, $sFieldCode);
|
||||
$this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $oNewLinkObj, $oP, $sNameSuffix, $sSafeFieldId);
|
||||
$aFieldsMap[$sFieldCode] = $sSafeFieldId;
|
||||
|
||||
$sValue = $oNewLinkObj->Get($sFieldCode);
|
||||
$oP->add_ready_script(
|
||||
<<<JS
|
||||
oWidget{$this->m_iInputId}.OnValueChange($iKey, $iUniqueId, '$sFieldCode', '$sValue');
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
$sState = '';
|
||||
$sRemoteKeySafeFieldId = $this->GetFieldId($iUniqueId, $this->m_sExtKeyToRemote);
|
||||
}
|
||||
|
||||
if (!$bReadOnly)
|
||||
{
|
||||
$sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe);
|
||||
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
|
||||
|
||||
$sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote);
|
||||
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
|
||||
}
|
||||
|
||||
// Adding fields from remote class
|
||||
// all fields are embedded in a span + added to $aFieldsMap array so that we can refresh them after extkey change
|
||||
$aRemoteFieldsMap = [];
|
||||
foreach (MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode)
|
||||
{
|
||||
$sSafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $sFieldCode);
|
||||
$aRow['static::'.$sFieldCode] = "<span id='field_$sSafeFieldId'>".$oLinkedObj->GetAsHTML($sFieldCode).'</span>';
|
||||
$aRemoteFieldsMap[$sFieldCode] = $sSafeFieldId;
|
||||
}
|
||||
// id field is needed so that remote object could be load server side
|
||||
$aRemoteFieldsMap['id'] = $sRemoteKeySafeFieldId;
|
||||
|
||||
// Generate WizardHelper to update dependant fields
|
||||
$this->AddWizardHelperInit($oP, $aArgs['wizHelper'], $this->m_sLinkedClass, $sState, $aFieldsMap);
|
||||
//instantiate specific WizarHelper instance for remote class fields refresh
|
||||
$bHasExtKeyUpdatingRemoteClassFields = (
|
||||
array_key_exists('replaceDependenciesByRemoteClassFields', $aArgs)
|
||||
&& ($aArgs['replaceDependenciesByRemoteClassFields'])
|
||||
);
|
||||
if ($bHasExtKeyUpdatingRemoteClassFields)
|
||||
{
|
||||
$this->AddWizardHelperInit($oP, $aArgs['wizHelperRemote'], $this->m_sRemoteClass, $sState, $aRemoteFieldsMap);
|
||||
}
|
||||
|
||||
return $aRow;
|
||||
}
|
||||
|
||||
private function AddRowForFieldCode(&$aRow, $sFieldCode, &$aArgs, $oLnk, $oP, $sNameSuffix, $sSafeFieldId): void
|
||||
{
|
||||
if (($sFieldCode === $this->m_sExtKeyToRemote))
|
||||
{
|
||||
// current field is the lnk extkey to the remote class
|
||||
$aArgs['replaceDependenciesByRemoteClassFields'] = true;
|
||||
$sRowFieldCode = 'static::key';
|
||||
$aArgs['wizHelperRemote'] = $aArgs['wizHelper'].'_remote';
|
||||
$aRemoteAttDefs = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($this->m_sRemoteClass);
|
||||
$aRemoteCodes = array_map(
|
||||
function ($value) {
|
||||
return $value->GetCode();
|
||||
},
|
||||
$aRemoteAttDefs
|
||||
);
|
||||
$aArgs['remoteCodes'] = $aRemoteCodes;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aArgs['replaceDependenciesByRemoteClassFields'] = false;
|
||||
$sRowFieldCode = $sFieldCode;
|
||||
}
|
||||
$sValue = $oLnk->Get($sFieldCode);
|
||||
$sDisplayValue = $oLnk->GetEditValue($sFieldCode);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
|
||||
|
||||
$aRow[$sRowFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'
|
||||
.cmdbAbstractObject::GetFormElementForField(
|
||||
$oP,
|
||||
$this->m_sLinkedClass,
|
||||
$sFieldCode,
|
||||
$oAttDef,
|
||||
$sValue,
|
||||
$sDisplayValue,
|
||||
$sSafeFieldId,
|
||||
$sNameSuffix,
|
||||
0,
|
||||
$aArgs
|
||||
)
|
||||
.'</div></div></div>';
|
||||
}
|
||||
|
||||
private function GetFieldId($iLnkId, $sFieldCode, $bSafe = true)
|
||||
{
|
||||
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$iLnkId.']';
|
||||
$sFieldId = $this->m_sInputId.'_'.$sFieldCode.'['.$iLnkId.']';
|
||||
|
||||
return ($bSafe) ? utils::GetSafeId($sFieldId) : $sFieldId;
|
||||
}
|
||||
|
||||
private function AddWizardHelperInit($oP, $sWizardHelperVarName, $sWizardHelperClass, $sState, $aFieldsMap): void
|
||||
{
|
||||
$iFieldsCount = count($aFieldsMap);
|
||||
$sJsonFieldsMap = json_encode($aFieldsMap);
|
||||
|
||||
$oP->add_script(
|
||||
<<<JS
|
||||
var $sWizardHelperVarName = new WizardHelper('$sWizardHelperClass', '', '$sState');
|
||||
$sWizardHelperVarName.SetFieldsMap($sJsonFieldsMap);
|
||||
$sWizardHelperVarName.SetFieldsCount($iFieldsCount);
|
||||
$sWizardHelperVarName.SetReturnNotEditableFields(true);
|
||||
$sWizardHelperVarName.SetWizHelperJsVarName('$sWizardHelperVarName');
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the table with the form for editing all the links at once
|
||||
@@ -356,91 +136,12 @@ JS
|
||||
*/
|
||||
public function Display(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj): string
|
||||
{
|
||||
$sLinkedSetId = "{$this->m_sAttCode}{$this->m_sNameSuffix}";
|
||||
|
||||
$oBlock = new BlockIndirectLinksEdit("linkedset_{$sLinkedSetId}", ["ibo-block-indirect-links--edit"]);
|
||||
|
||||
$oBlock->sLinkedSetId = $sLinkedSetId;
|
||||
$oBlock->sClass = $this->m_sClass;
|
||||
$oBlock->sAttCode = $this->m_sAttCode;
|
||||
$oBlock->iInputId = $this->m_iInputId;
|
||||
$oBlock->sNameSuffix = $this->m_sNameSuffix;
|
||||
$oBlock->bDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false';
|
||||
$oBlock->oWizHelper = 'oWizardHelper'.$sFormPrefix;
|
||||
$oBlock->sExtKeyToRemote = $this->m_sExtKeyToRemote;
|
||||
// Don't automatically launch the search if the table is huge
|
||||
$oBlock->bJSDoSearch = utils::IsHighCardinality($this->m_sRemoteClass) ? 'false' : 'true';
|
||||
$oBlock->sFormPrefix = $sFormPrefix;
|
||||
$oBlock->sRemoteClass = $this->m_sRemoteClass;
|
||||
|
||||
$oValue->Rewind();
|
||||
$aForm = array();
|
||||
$iMaxAddedId = 0;
|
||||
$iAddedId = -1; // Unique id for new links
|
||||
while ($oCurrentLink = $oValue->Fetch())
|
||||
{
|
||||
// We try to retrieve the remote object as usual
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote),
|
||||
false /* Must not be found */);
|
||||
// If successful, it means that we can edit its link
|
||||
if ($oLinkedObj !== null) {
|
||||
$bReadOnly = false;
|
||||
} // Else we retrieve it without restrictions (silos) and will display its link as readonly
|
||||
else {
|
||||
$bReadOnly = true;
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */, true);
|
||||
}
|
||||
|
||||
if ($oCurrentLink->IsNew()) {
|
||||
$key = $iAddedId--;
|
||||
} else {
|
||||
$key = $oCurrentLink->GetKey();
|
||||
}
|
||||
|
||||
$iMaxAddedId = max($iMaxAddedId, $key);
|
||||
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly);
|
||||
}
|
||||
$oBlock->iMaxAddedId = (int) $iMaxAddedId;
|
||||
|
||||
$oDataTable = DataTableUIBlockFactory::MakeForForm("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aForm);
|
||||
$oDataTable->SetOptions(['select_mode' => 'custom']);
|
||||
$oBlock->AddSubBlock($oDataTable);
|
||||
|
||||
$oBlock->AddControls();
|
||||
$oBlock = new BlockIndirectLinksEditTable($this);
|
||||
$oBlock->InitTable($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $this->m_aTableConfig);
|
||||
|
||||
return ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected static function GetTargetClass($sClass, $sAttCode)
|
||||
{
|
||||
/** @var AttributeLinkedSet $oAttDef */
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$sLinkedClass = $oAttDef->GetLinkedClass();
|
||||
$sTargetClass = '';
|
||||
switch(get_class($oAttDef))
|
||||
{
|
||||
case 'AttributeLinkedSetIndirect':
|
||||
/** @var AttributeExternalKey $oLinkingAttDef */
|
||||
/** @var AttributeLinkedSetIndirect $oAttDef */
|
||||
$oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote());
|
||||
$sTargetClass = $oLinkingAttDef->GetTargetClass();
|
||||
break;
|
||||
|
||||
case 'AttributeLinkedSet':
|
||||
$sTargetClass = $sLinkedClass;
|
||||
break;
|
||||
}
|
||||
|
||||
return $sTargetClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject $oCurrentObj
|
||||
@@ -470,15 +171,10 @@ JS
|
||||
$oCurrentObj->PrefillForm('search', $aPrefillFormParam);
|
||||
}
|
||||
|
||||
$sLinkedSetId = "{$this->m_sAttCode}{$this->m_sNameSuffix}";
|
||||
|
||||
$oBlock = new BlockObjectPickerDialog();
|
||||
$oBlock = new BlockObjectPickerDialog($this);
|
||||
$oPage->AddUiBlock($oBlock);
|
||||
|
||||
$oBlock->sLinkedSetId = $sLinkedSetId;
|
||||
$oBlock->iInputId = $this->m_iInputId;
|
||||
$oBlock->sLinkedClassName = MetaModel::GetName($this->m_sLinkedClass);
|
||||
$oBlock->sClassName = MetaModel::GetName($this->m_sClass);
|
||||
$sLinkedSetId = $oBlock->oUILinksWidget->GetLinkedSetId();
|
||||
|
||||
$oDisplayBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$oBlock->AddSubBlock($oDisplayBlock->GetDisplay($oPage, "SearchFormToAdd_{$sLinkedSetId}",
|
||||
@@ -547,7 +243,8 @@ JS
|
||||
foreach ($aLinkedObjectIds as $iObjectId) {
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false);
|
||||
if (is_object($oLinkedObj)) {
|
||||
$aRow = $this->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
|
||||
$oBlock = new BlockIndirectLinksEditTable($this);
|
||||
$aRow = $oBlock->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
|
||||
$oRow = new FormTableRow("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aRow, -$iAdditionId);
|
||||
$oP->AddUiBlock($oRow);
|
||||
$iAdditionId++;
|
||||
@@ -574,7 +271,8 @@ JS
|
||||
foreach ($aLinkedObjectIds as $iObjectId) {
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false);
|
||||
if (is_object($oLinkedObj)) {
|
||||
$aRow = $this->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
|
||||
$oBlock = new BlockIndirectLinksEditTable($this);
|
||||
$aRow = $oBlock->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
|
||||
$aData = [];
|
||||
foreach ($aRow as $item) {
|
||||
$aData[] = $item;
|
||||
@@ -635,24 +333,77 @@ JS
|
||||
/** @var AttributeExternalKey $oAttDef */
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass);
|
||||
if ($sHierarchicalKeyCode !== false)
|
||||
{
|
||||
if ($sHierarchicalKeyCode !== false) {
|
||||
$oFilter = new DBObjectSearch($sTargetClass);
|
||||
$oFilter->AddCondition('id', $defaultValue);
|
||||
$oHKFilter = new DBObjectSearch($sTargetClass);
|
||||
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW);
|
||||
$oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode);
|
||||
}
|
||||
} catch (Exception $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
catch (Exception $e) {
|
||||
}
|
||||
} else {
|
||||
$oSearch->AddCondition($sAttCode, $defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function GetLinkedSetId(): string
|
||||
{
|
||||
return "{$this->m_sAttCode}{$this->m_sNameSuffix}";
|
||||
}
|
||||
|
||||
public function GetClass(): string
|
||||
{
|
||||
return $this->m_sClass;
|
||||
}
|
||||
|
||||
public function GetLinkedClass(): string
|
||||
{
|
||||
return $this->m_sLinkedClass;
|
||||
}
|
||||
|
||||
public function GetAttCode(): string
|
||||
{
|
||||
return $this->m_sAttCode;
|
||||
}
|
||||
|
||||
public function GetInputId(): string
|
||||
{
|
||||
return $this->m_sInputId;
|
||||
}
|
||||
|
||||
public function GetNameSuffix(): string
|
||||
{
|
||||
return $this->m_sNameSuffix;
|
||||
}
|
||||
|
||||
public function IsDuplicatesAllowed(): bool
|
||||
{
|
||||
return $this->m_bDuplicatesAllowed;
|
||||
}
|
||||
|
||||
public function GetExternalKeyToRemote(): string
|
||||
{
|
||||
return $this->m_sExtKeyToRemote;
|
||||
}
|
||||
|
||||
public function GetExternalKeyToMe(): string
|
||||
{
|
||||
return $this->m_sExtKeyToMe;
|
||||
}
|
||||
|
||||
public function GetRemoteClass(): string
|
||||
{
|
||||
return $this->m_sRemoteClass;
|
||||
}
|
||||
|
||||
public function GetEditableFields(): array
|
||||
{
|
||||
return $this->m_aEditableFields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ class UIPasswordWidget
|
||||
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
|
||||
$sHtmlValue = '';
|
||||
$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 .= '<input class="ibo-input" type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.utils::EscapeHtml($sPasswordValue).'"/>';
|
||||
$sHtmlValue .= '<div class="ibo-input-wrapper ibo-input-wrapper--with-buttons"><input class="ibo-input" type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.utils::EscapeHtml($sConfirmPasswordValue).'" 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>';
|
||||
|
||||
@@ -21,6 +21,8 @@ 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;
|
||||
use ScssPhp\ScssPhp\OutputStyle;
|
||||
use ScssPhp\ScssPhp\ValueConverter;
|
||||
|
||||
|
||||
/**
|
||||
@@ -67,6 +69,16 @@ class utils
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_CONTEXT_PARAM = 'context_param';
|
||||
/**
|
||||
* @var string To filter routes passed to back-end router before being redirected to corresponding controller / method
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_ROUTE = 'route';
|
||||
/**
|
||||
* @var string To filter operation codes passed to back-end router before being redirected to corresponding controller (/ business logic in case of legacy operations)
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_OPERATION = 'operation';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
@@ -97,6 +109,11 @@ class utils
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_RAW_DATA = 'raw_data';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.2, 3.1.0 N°4899
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_URL = 'url';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
@@ -377,6 +394,7 @@ class utils
|
||||
* @since 2.5.2 2.6.0 new 'transaction_id' filter
|
||||
* @since 2.7.0 new 'element_identifier' filter
|
||||
* @since 3.0.0 new utils::ENUM_SANITIZATION_* const
|
||||
* @since 2.7.7, 3.0.2, 3.1.0 N°4899 - new 'url' filter
|
||||
*/
|
||||
protected static function Sanitize_Internal($value, $sSanitizationFilter)
|
||||
{
|
||||
@@ -398,6 +416,8 @@ class utils
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
|
||||
case static::ENUM_SANITIZATION_FILTER_ROUTE:
|
||||
case static::ENUM_SANITIZATION_FILTER_OPERATION:
|
||||
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
|
||||
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
|
||||
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
|
||||
@@ -419,27 +439,31 @@ class utils
|
||||
switch ($sSanitizationFilter)
|
||||
{
|
||||
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
|
||||
// same as parameter type but keep the dot character
|
||||
// see N°1835 : when using file transaction_id on Windows you get *.tmp tokens
|
||||
// it must be included at the regexp beginning otherwise you'll get an invalid character error
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
|
||||
array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/')));
|
||||
// Same as parameter type but keep the dot character
|
||||
// transaction_id, the dot is mostly for Windows servers when using file storage as the tokens are named *.tmp
|
||||
// - See N°1835
|
||||
// - Note: It must be included at the regexp beginning otherwise you'll get an invalid character error
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/')));
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_ROUTE:
|
||||
case static::ENUM_SANITIZATION_FILTER_OPERATION:
|
||||
// - Routes should be of the "controller_namespace_code.controller_method_name" form
|
||||
// - Operations should be allowed to be namespaced as well even though then don't have dedicated controller yet
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\.A-Za-z0-9_-]*$/')));
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
|
||||
array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F'
|
||||
// characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F'
|
||||
// Characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
|
||||
array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
|
||||
array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/')));
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/')));
|
||||
break;
|
||||
|
||||
}
|
||||
@@ -454,6 +478,11 @@ class utils
|
||||
$retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value);
|
||||
break;
|
||||
|
||||
// For URL
|
||||
case static::ENUM_SANITIZATION_FILTER_URL:
|
||||
$retValue = filter_var($value, FILTER_SANITIZE_URL);
|
||||
break;
|
||||
|
||||
default:
|
||||
case static::ENUM_SANITIZATION_FILTER_RAW_DATA:
|
||||
$retValue = $value;
|
||||
@@ -800,21 +829,20 @@ class utils
|
||||
*/
|
||||
public static function StringToTime($sDate, $sFormat)
|
||||
{
|
||||
// Source: http://php.net/manual/fr/function.strftime.php
|
||||
// Source: http://php.net/manual/fr/function.strftime.php
|
||||
// (alternative: http://www.php.net/manual/fr/datetime.formats.date.php)
|
||||
static $aDateTokens = null;
|
||||
static $aDateRegexps = null;
|
||||
if (is_null($aDateTokens))
|
||||
{
|
||||
$aSpec = array(
|
||||
'%d' =>'(?<day>[0-9]{2})',
|
||||
if (is_null($aDateTokens)) {
|
||||
$aSpec = array(
|
||||
'%d' => '(?<day>[0-9]{2})',
|
||||
'%m' => '(?<month>[0-9]{2})',
|
||||
'%y' => '(?<year>[0-9]{2})',
|
||||
'%Y' => '(?<year>[0-9]{4})',
|
||||
'%H' => '(?<hour>[0-2][0-9])',
|
||||
'%i' => '(?<minute>[0-5][0-9])',
|
||||
'%s' => '(?<second>[0-5][0-9])',
|
||||
);
|
||||
);
|
||||
$aDateTokens = array_keys($aSpec);
|
||||
$aDateRegexps = array_values($aSpec);
|
||||
}
|
||||
@@ -846,11 +874,50 @@ class utils
|
||||
*/
|
||||
public static function DateTimeFormatToPHP($sOldDateTimeFormat)
|
||||
{
|
||||
$aSearch = array('%d', '%m', '%y', '%Y', '%H', '%i', '%s');
|
||||
$aReplacement = array('d', 'm', 'y', 'Y', 'H', 'i', 's');
|
||||
$aSearch = ['%d', '%m', '%y', '%Y', '%H', '%i', '%s'];
|
||||
$aReplacement = ['d', 'm', 'y', 'Y', 'H', 'i', 's'];
|
||||
return str_replace($aSearch, $aReplacement, $sOldDateTimeFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an old strtime() date/time format specification {@link https://www.php.net/manual/fr/function.strftime.php}
|
||||
* to a format compatible with \DateTime::format {@link https://www.php.net/manual/fr/datetime.format.php}
|
||||
*
|
||||
* Example: '%Y-%m-%d %H:%M:%S' => 'Y-m-d H:i:s'
|
||||
*
|
||||
* Note: Not all strftime() formats can be converted, in which case they will be present in the returned string (eg. '%U' or '%W')
|
||||
*
|
||||
* @param string $sOldStrftimeFormat
|
||||
*
|
||||
* @return string
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static function StrftimeFormatToDateTimeFormat(string $sOldStrftimeFormat): string
|
||||
{
|
||||
$aSearch = [
|
||||
'%d', '%m', '%y', '%Y', '%H', '%M', '%S', // Most popular formats
|
||||
'%a', '%A', '%e', '%j', '%u', '%w', // Day formats
|
||||
'%U', '%V', '%W', // Week formats
|
||||
'%b', '%B', '%h', // Month formats
|
||||
'%C', '%g', '%G', // Year formats
|
||||
'%k', '%I', '%l', '%p', '%P', '%r', '%R', '%T', '%X', '%z', '%Z', // Time formats
|
||||
'%c', '%D', '%F', '%s', '%x', // Datetime formats
|
||||
'%n', '%t', '%%', // Misc. formats
|
||||
];
|
||||
$aReplacement = [
|
||||
'd', 'm', 'y', 'Y', 'H', 'i', 's',
|
||||
'D', 'l', 'j', 'z', 'N', 'w',
|
||||
'%U', 'W', '%W',
|
||||
'M', 'F', 'M',
|
||||
'%C', 'y', 'Y',
|
||||
'G', 'h', 'g', 'A', 'a', 'h:i:s A', 'H:i', 'H:i:s', '%X', 'O', 'T',
|
||||
'%c', 'm/d/y', 'Y-m-d', 'U', '%x',
|
||||
'%n', '%t', '%',
|
||||
];
|
||||
|
||||
return str_replace($aSearch, $aReplacement, $sOldStrftimeFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to set cached config. Useful when running with {@link Parameters} for example.
|
||||
* @param \Config $oConfig
|
||||
@@ -1382,71 +1449,83 @@ class utils
|
||||
*/
|
||||
public static function GetPopupMenuItemsBlock(iUIBlock &$oContainerBlock, $iMenuId, $param, &$aActions, $sDataTableId = null)
|
||||
{
|
||||
$aResult = [];
|
||||
|
||||
// 1st - add standard built-in menu items
|
||||
//
|
||||
switch($iMenuId)
|
||||
{
|
||||
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
|
||||
// $param is a DBObjectSet
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
$sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
|
||||
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($param->GetFilter()->GetClass());
|
||||
$sOQL = addslashes($param->GetFilter()->ToOQL(true));
|
||||
$sFilter = urlencode($param->GetFilter()->serialize());
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
|
||||
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
|
||||
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
|
||||
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
|
||||
case iPopupMenuExtension::MENU_OBJLIST_ACTIONS:
|
||||
// No native action there yet
|
||||
break;
|
||||
|
||||
$aResult = array();
|
||||
if (strlen($sUrl) < SERVER_MAX_URL_LENGTH)
|
||||
{
|
||||
$aResult[] = new SeparatorPopupMenuItem();
|
||||
// Static menus: Email this page, CSV Export & Add to Dashboard
|
||||
$aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'),
|
||||
"mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook
|
||||
);
|
||||
}
|
||||
|
||||
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO)
|
||||
{
|
||||
// Bulk export actions
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")");
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");
|
||||
if (extension_loaded('gd'))
|
||||
{
|
||||
// PDF export requires GD
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
|
||||
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
|
||||
/** @var \DBObjectSet $param */
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
$sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
|
||||
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($param->GetFilter()->GetClass());
|
||||
$sOQL = addslashes($param->GetFilter()->ToOQL(true));
|
||||
$sFilter = urlencode($param->GetFilter()->serialize());
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
|
||||
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
|
||||
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
|
||||
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
|
||||
|
||||
// Configure this list on datatables
|
||||
if (utils::IsNotNullOrEmptyString($sDataTableId)) {
|
||||
$aResult[] = new JSPopupMenuItem(
|
||||
'iTop::ConfigureList',
|
||||
Dict::S('UI:ConfigureThisList'),
|
||||
"$('#datatable_dlg_datatable_{$sDataTableId}').dialog('open'); return false;"
|
||||
);
|
||||
$aResult[] = new SeparatorPopupMenuItem();
|
||||
}
|
||||
}
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
|
||||
|
||||
break;
|
||||
|
||||
if (strlen($sUrl) < SERVER_MAX_URL_LENGTH) {
|
||||
// Static menus: Email this page, CSV Export & Add to Dashboard
|
||||
$aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'),
|
||||
"mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook
|
||||
);
|
||||
}
|
||||
|
||||
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO) {
|
||||
// Bulk export actions
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")");
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");
|
||||
if (extension_loaded('gd'))
|
||||
{
|
||||
// PDF export requires GD
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
|
||||
}
|
||||
}
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
|
||||
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
|
||||
|
||||
break;
|
||||
|
||||
case iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS:
|
||||
// $param is a DBObject
|
||||
$oObj = $param;
|
||||
$sOQL = "SELECT ".get_class($oObj)." WHERE id=".$oObj->GetKey();
|
||||
$sUrl = ApplicationContext::MakeObjectUrl(get_class($oObj), $oObj->GetKey());
|
||||
$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(),
|
||||
// Static menus: Email this page & CSV Export
|
||||
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
|
||||
new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")"),
|
||||
new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")"),
|
||||
new SeparatorPopupMenuItem(),
|
||||
new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $sUrl.'&printable=1', '_blank'),
|
||||
);
|
||||
break;
|
||||
/** @var \DBObject $param */
|
||||
$oObj = $param;
|
||||
$sOQL = "SELECT ".get_class($oObj)." WHERE id=".$oObj->GetKey();
|
||||
$sUrl = ApplicationContext::MakeObjectUrl(get_class($oObj), $oObj->GetKey());
|
||||
$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(),
|
||||
// Static menus: Email this page & CSV Export
|
||||
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
|
||||
new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")"),
|
||||
new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")"),
|
||||
new SeparatorPopupMenuItem(),
|
||||
new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $sUrl.'&printable=1', '_blank'),
|
||||
);
|
||||
break;
|
||||
|
||||
case iPopupMenuExtension::MENU_DASHBOARD_ACTIONS:
|
||||
// $param is a Dashboard
|
||||
@@ -1454,19 +1533,19 @@ class utils
|
||||
$oDashboard = $param;
|
||||
$sDashboardId = $oDashboard->GetId();
|
||||
$sDashboardFile = $oDashboard->GetDefinitionFile();
|
||||
$sDashboardFileRelative = utils::LocalPath($sDashboardFile);
|
||||
$sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle'));
|
||||
$sDlgText = addslashes(Dict::S('UI:ImportDashboardText'));
|
||||
$sCloseBtn = addslashes(Dict::S('UI:Button:Cancel'));
|
||||
$sDashboardFileJS = addslashes($sDashboardFile);
|
||||
$sDashboardFileURL = urlencode($sDashboardFile);
|
||||
$sDashboardFileJS = addslashes($sDashboardFileRelative);
|
||||
$sDashboardFileURL = urlencode($sDashboardFileRelative);
|
||||
$sUploadDashboardTransactId = utils::GetNewTransactionId();
|
||||
$aResult = array(
|
||||
new SeparatorPopupMenuItem(),
|
||||
new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sDashboardId.'&file='.$sDashboardFileURL),
|
||||
new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sDashboardId', file: '$sDashboardFileJS', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn', transaction: '$sUploadDashboardTransactId' })"),
|
||||
);
|
||||
if ($oDashboard->GetReloadURL())
|
||||
{
|
||||
if ($oDashboard->GetReloadURL()) {
|
||||
$aResult[] = new SeparatorPopupMenuItem();
|
||||
$aResult[] = new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $oDashboard->GetReloadURL().'&printable=1', '_blank');
|
||||
}
|
||||
@@ -1475,34 +1554,25 @@ class utils
|
||||
|
||||
default:
|
||||
// Unknown type of menu, do nothing
|
||||
$aResult = array();
|
||||
}
|
||||
foreach ($aResult as $oMenuItem)
|
||||
{
|
||||
foreach ($aResult as $oMenuItem) {
|
||||
$aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
|
||||
}
|
||||
|
||||
// Invoke the plugins
|
||||
//
|
||||
/** @var \iPopupMenuExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
|
||||
{
|
||||
if (is_object($param) && !($param instanceof DBObject))
|
||||
{
|
||||
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance) {
|
||||
if (is_object($param) && !($param instanceof DBObject)) {
|
||||
$tmpParam = clone $param; // In case the parameter is an DBObjectSet, clone it to prevent alterations
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$tmpParam = $param;
|
||||
}
|
||||
foreach($oExtensionInstance->EnumItems($iMenuId, $tmpParam) as $oMenuItem)
|
||||
{
|
||||
if (is_object($oMenuItem))
|
||||
{
|
||||
foreach ($oExtensionInstance->EnumItems($iMenuId, $tmpParam) as $oMenuItem) {
|
||||
if (is_object($oMenuItem)) {
|
||||
$aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
|
||||
|
||||
foreach($oMenuItem->GetLinkedScripts() as $sLinkedScript)
|
||||
{
|
||||
|
||||
foreach ($oMenuItem->GetLinkedScripts() as $sLinkedScript) {
|
||||
$oContainerBlock->AddJsFileRelPath($sLinkedScript);
|
||||
}
|
||||
}
|
||||
@@ -1510,6 +1580,136 @@ class utils
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We cannot use iMenuId (corresponding values in {@see \iPopupMenuExtension} constants) as value is always {@see \iPopupMenuExtension::MENU_OBJLIST_TOOLKIT}
|
||||
* whenever we are in a datatable, whereas it is included in a object tab, a dashlet or a search.
|
||||
*
|
||||
* So a {@see \ContextTag} is set on the corresponding calls.
|
||||
*
|
||||
* @return bool true if we are in a search page context, either directly or by the datatable ajax call
|
||||
*
|
||||
* @since 3.1.0 N°3200
|
||||
*
|
||||
* @uses \ContextTag::TAG_OBJECT_SEARCH
|
||||
*/
|
||||
public static function IsCurrentPageASearch(): bool
|
||||
{
|
||||
if (ContextTag::Check(ContextTag::TAG_OBJECT_SEARCH)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBObjectSearch $oFilter object list
|
||||
* @param array $aExtraParams
|
||||
*
|
||||
* @return string|null null if we are already in a search, otherwise the URL to open this list in a search
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @uses utils::IsCurrentPageASearch()
|
||||
*
|
||||
* @since 3.1.0 N°3200
|
||||
*/
|
||||
public static function GetDataTableSearchUrl(DBSearch $oFilter, array $aExtraParams): ?string
|
||||
{
|
||||
if (static::IsCurrentPageASearch()) {
|
||||
// we don't want to add the link when already in a search page !
|
||||
return null;
|
||||
}
|
||||
|
||||
$bIsObjectRelation = isset($aExtraParams['object_id'], $aExtraParams['target_attr']);
|
||||
if ($bIsObjectRelation) {
|
||||
[$oDataTableSearchFilter, $aParams] = static::GetDataTableSearchForRelations($oFilter, $aExtraParams);
|
||||
} else {
|
||||
$oDataTableSearchFilter = $oFilter;
|
||||
$aParams = [];
|
||||
}
|
||||
|
||||
if (isset($aExtraParams['table_id'])) {
|
||||
$aParams['table_id'] = $aExtraParams['table_id'];
|
||||
}
|
||||
$sParams = json_encode($aParams);
|
||||
|
||||
$sAppRootUrl = static::GetAbsoluteUrlAppRoot();
|
||||
$oAppContext = new ApplicationContext();
|
||||
|
||||
$sUrl = $sAppRootUrl
|
||||
.'pages/UI.php?operation=search&'
|
||||
.$oAppContext->GetForLink()
|
||||
.'&filter='.rawurlencode($oDataTableSearchFilter->serialize());
|
||||
$sUrl .= '&aParams='.rawurlencode($sParams); // Not working... yet, cause not handled by UI.php
|
||||
|
||||
return $sUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites filter for object relations, so that in the search page we will have the correct criteria and will be able to use "configure this list"
|
||||
*
|
||||
* @param \DBSearch $oFilter object list
|
||||
* @param array{link_attr: string, target_attr: string, object_id: string} $aExtraParams
|
||||
*
|
||||
* @return array{\DBObjectSearch, string[]}
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*
|
||||
* @since 3.1.0 N°3200
|
||||
*/
|
||||
private static function GetDataTableSearchForRelations(DBSearch $oFilter, array $aExtraParams): array
|
||||
{
|
||||
$sObjectId = $aExtraParams['object_id'];
|
||||
$bIsLinkedSetIndirect = isset($aExtraParams['link_attr']);
|
||||
if ($bIsLinkedSetIndirect) {
|
||||
//--- AttributeLinkedSetIndirect (n,n => lnk class)
|
||||
$sLnkClass = $oFilter->GetClass();
|
||||
$sExtKeyToObjectClass = $aExtraParams['link_attr'];
|
||||
$sExtKeyToRemoteClass = $aExtraParams['target_attr'];
|
||||
|
||||
/** @var \AttributeExternalKey $oLnkExtKeyToRemote */
|
||||
$oLnkExtKeyToRemote = MetaModel::GetAttributeDef($sLnkClass, $sExtKeyToRemoteClass);
|
||||
$sRemoteClass = $oLnkExtKeyToRemote->GetTargetClass();
|
||||
|
||||
/** @var \AttributeExternalKey $oLnkExtKeyToRemote */
|
||||
$oLnkExtKeyToObject = MetaModel::GetAttributeDef($sLnkClass, $sExtKeyToObjectClass);
|
||||
$sObjectClass = $oLnkExtKeyToObject->GetTargetClass();
|
||||
|
||||
/** @var \AttributeExternalKey $oLnkExtKeyToRemote */
|
||||
$oObjectExtKeyToLnk = $oLnkExtKeyToObject->GetMirrorLinkAttribute();
|
||||
$sObjectExtKeyToLnkClass = $oObjectExtKeyToLnk->GetCode();
|
||||
|
||||
$sRemoteClassAliasName = ormLinkSet::REMOTE_ALIAS;
|
||||
$sLnkClassAliasName = ormLinkSet::LINK_ALIAS;
|
||||
$sOql = <<<SQL
|
||||
SELECT {$sRemoteClassAliasName},{$sLnkClassAliasName}
|
||||
FROM {$sRemoteClass} AS {$sRemoteClassAliasName}
|
||||
JOIN {$sLnkClass} AS {$sLnkClassAliasName} ON {$sLnkClassAliasName}.$sExtKeyToRemoteClass = {$sRemoteClassAliasName}.id
|
||||
WHERE {$sLnkClassAliasName}.$sExtKeyToObjectClass = $sObjectId
|
||||
SQL;
|
||||
|
||||
$aAttCodesToDisplay = MetaModel::GetAttributeLinkedSetIndirectDatatableAttCodesToDisplay($sObjectClass, $sObjectExtKeyToLnkClass, $sRemoteClass, $sExtKeyToRemoteClass);
|
||||
/** @noinspection PhpUnnecessaryLocalVariableInspection */
|
||||
$sAttCodesToDisplay = implode(',', $aAttCodesToDisplay);
|
||||
$aParams = [
|
||||
'zlist' => false,
|
||||
'extra_fields' => $sAttCodesToDisplay,
|
||||
];
|
||||
} else {
|
||||
//--- AttributeLinkedSet (1,n => AttributeExternalKey)
|
||||
$sClass = $oFilter->GetClass();
|
||||
$sExtKeyCode = $aExtraParams['target_attr'];
|
||||
|
||||
$sOql = "SELECT $sClass WHERE $sExtKeyCode = $sObjectId";
|
||||
|
||||
$aParams = [];
|
||||
}
|
||||
|
||||
$oDataTableSearchFilter = DBSearch::FromOQL($sOql);
|
||||
|
||||
return [$oDataTableSearchFilter, $aParams];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sEnvironment
|
||||
*
|
||||
@@ -1517,10 +1717,10 @@ class utils
|
||||
*/
|
||||
public static function GetConfigFilePath($sEnvironment = null)
|
||||
{
|
||||
if (is_null($sEnvironment))
|
||||
{
|
||||
if (is_null($sEnvironment)) {
|
||||
$sEnvironment = self::GetCurrentEnvironment();
|
||||
}
|
||||
|
||||
return APPCONF.$sEnvironment.'/'.ITOP_CONFIG_FILE;
|
||||
}
|
||||
|
||||
@@ -1814,7 +2014,7 @@ class utils
|
||||
*/
|
||||
public static function HtmlEntities($sValue)
|
||||
{
|
||||
return htmlentities($sValue, ENT_QUOTES, 'UTF-8');
|
||||
return htmlentities($sValue ?? '', ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1830,7 +2030,7 @@ class utils
|
||||
public static function EscapeHtml($sValue)
|
||||
{
|
||||
return htmlspecialchars(
|
||||
$sValue,
|
||||
$sValue ?? '',
|
||||
ENT_QUOTES | ENT_DISALLOWED | ENT_HTML5,
|
||||
WebPage::PAGES_CHARSET,
|
||||
false
|
||||
@@ -1879,7 +2079,8 @@ class utils
|
||||
{
|
||||
$sText = str_replace("\r\n", "\n", $sText);
|
||||
$sText = str_replace("\r", "\n", $sText);
|
||||
return str_replace("\n", '<br/>', htmlentities($sText, ENT_QUOTES, 'UTF-8'));
|
||||
|
||||
return str_replace("\n", '<br/>', utils::EscapeHtml($sText));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1930,42 +2131,36 @@ class utils
|
||||
public static function CompileCSSFromSASS($sSassContent, $aImportPaths = array(), $aVariables = array())
|
||||
{
|
||||
$oSass = new Compiler();
|
||||
$oSass->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Compressed');
|
||||
$oSass->setOutputStyle(OutputStyle::COMPRESSED);
|
||||
// Setting our variables
|
||||
$oSass->setVariables($aVariables);
|
||||
$aScssVariables = [];
|
||||
foreach ($aVariables as $entry => $value) {
|
||||
$aScssVariables[$entry] = ValueConverter::parseValue($value);
|
||||
}
|
||||
$oSass->addVariables($aScssVariables);
|
||||
// Setting our imports paths
|
||||
$oSass->setImportPaths($aImportPaths);
|
||||
// Temporary disabling max exec time while compiling
|
||||
$iCurrentMaxExecTime = (int) ini_get('max_execution_time');
|
||||
set_time_limit(0);
|
||||
// Compiling SASS
|
||||
$sCss = $oSass->compile($sSassContent);
|
||||
$oCompilationRes = $oSass->compileString($sSassContent);
|
||||
set_time_limit(intval($iCurrentMaxExecTime));
|
||||
|
||||
return $sCss;
|
||||
return $oCompilationRes->getCss();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the size of an image from a string.
|
||||
*
|
||||
* @see \getimagesizefromstring()
|
||||
* @param $sImageData string The image data, as a string.
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public static function GetImageSize($sImageData)
|
||||
{
|
||||
if (function_exists('getimagesizefromstring')) // PHP 5.4.0 or higher
|
||||
{
|
||||
$aRet = @getimagesizefromstring($sImageData);
|
||||
}
|
||||
else if(ini_get('allow_url_fopen'))
|
||||
{
|
||||
// work around to avoid creating a tmp file
|
||||
$sUri = 'data://application/octet-stream;base64,'.base64_encode($sImageData);
|
||||
$aRet = @getimagesize($sUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Damned, need to create a tmp file
|
||||
$sTempFile = tempnam(SetupUtils::GetTmpDir(), 'img-');
|
||||
@file_put_contents($sTempFile, $sImageData);
|
||||
$aRet = @getimagesize($sTempFile);
|
||||
@unlink($sTempFile);
|
||||
}
|
||||
return $aRet;
|
||||
return @getimagesizefromstring($sImageData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2711,10 +2906,24 @@ HTML;
|
||||
$aAutoloadClassMaps = array_merge($aAutoloadClassMaps, glob(APPROOT.'env-'.utils::GetCurrentEnvironment().'/*/vendor/composer/autoload_classmap.php'));
|
||||
|
||||
$aClassMap = [];
|
||||
$aAutoloaderErrors = [];
|
||||
foreach ($aAutoloadClassMaps as $sAutoloadFile) {
|
||||
if (false === static::RealPath($sAutoloadFile, APPROOT)) {
|
||||
// can happen when we still have the autoloader symlink in env-*, but it points to a file that no longer exists
|
||||
$aAutoloaderErrors[] = $sAutoloadFile;
|
||||
continue;
|
||||
}
|
||||
$aTmpClassMap = include $sAutoloadFile;
|
||||
/** @noinspection SlowArrayOperationsInLoopInspection we are getting an associative array so the documented workarounds cannot be used */
|
||||
$aClassMap = array_merge($aClassMap, $aTmpClassMap);
|
||||
}
|
||||
if (count($aAutoloaderErrors) > 0) {
|
||||
IssueLog::Debug(
|
||||
"\utils::GetClassesForInterface cannot load some of the autoloader files",
|
||||
LogChannels::CORE,
|
||||
['autoloader_errors' => $aAutoloaderErrors]
|
||||
);
|
||||
}
|
||||
|
||||
// Add already loaded classes
|
||||
$aCurrentClasses = array_fill_keys(get_declared_classes(), '');
|
||||
@@ -2722,18 +2931,24 @@ HTML;
|
||||
|
||||
foreach ($aClassMap as $sPHPClass => $sPHPFile) {
|
||||
$bSkipped = false;
|
||||
|
||||
|
||||
// Check if our class matches name filter, or is in an excluded path
|
||||
if ($sClassNameFilter !== '' && strpos($sPHPClass, $sClassNameFilter) === false) {
|
||||
$bSkipped = true;
|
||||
}
|
||||
else {
|
||||
foreach ($aExcludedPath as $sExcludedPath) {
|
||||
// Note: We use '#' as delimiters as usual '/' is often used in paths.
|
||||
if ($sExcludedPath !== '' && preg_match('#'.$sExcludedPath.'#', $sPHPFile) === 1) {
|
||||
$bSkipped = true;
|
||||
break;
|
||||
$sPHPFile = self::LocalPath($sPHPFile);
|
||||
if ($sPHPFile !== false) {
|
||||
$sPHPFile = '/'.$sPHPFile; // for regex
|
||||
foreach ($aExcludedPath as $sExcludedPath) {
|
||||
// Note: We use '#' as delimiters as usual '/' is often used in paths.
|
||||
if ($sExcludedPath !== '' && preg_match('#'.$sExcludedPath.'#', $sPHPFile) === 1) {
|
||||
$bSkipped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$bSkipped = true; // file not found
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2771,7 +2986,7 @@ HTML;
|
||||
$aResultPref = [];
|
||||
$aShortcutPrefs = appUserPreferences::GetPref('keyboard_shortcuts', []);
|
||||
// Note: Mind the 4 blackslashes, see utils::GetClassesForInterface()
|
||||
$aShortcutClasses = utils::GetClassesForInterface('iKeyboardShortcut', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]'));
|
||||
$aShortcutClasses = utils::GetClassesForInterface('iKeyboardShortcut', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]'));
|
||||
|
||||
foreach ($aShortcutClasses as $cShortcutPlugin) {
|
||||
$sTriggeredElement = $cShortcutPlugin::GetShortcutTriggeredElementSelector();
|
||||
@@ -2819,6 +3034,54 @@ HTML;
|
||||
return $aPrefs[$sShortcutId];
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
// PHP function helpers
|
||||
//----------------------------------------------
|
||||
|
||||
/**
|
||||
* Helper around the native strlen() PHP method to keep allowing usage of null value when computing the length of a string as null value is no longer allowed with PHP 8.1+
|
||||
* @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated"
|
||||
*
|
||||
* @param string|null $sString
|
||||
*
|
||||
* @return int Length of $sString, 0 if null
|
||||
* @since 3.0.2 N°5172
|
||||
*/
|
||||
public static function StrLen(?string $sString): int
|
||||
{
|
||||
return strlen($sString ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper around the native strlen() PHP method to test a string for null or empty value
|
||||
*
|
||||
* @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated"
|
||||
*
|
||||
* @param string|null $sString
|
||||
*
|
||||
* @return bool if string null or empty
|
||||
* @since 3.0.2 N°5302
|
||||
*/
|
||||
public static function IsNullOrEmptyString(?string $sString): bool
|
||||
{
|
||||
return $sString === null || strlen($sString) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper around the native strlen() PHP method to test a string not null or empty value
|
||||
*
|
||||
* @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated"
|
||||
*
|
||||
* @param string|null $sString
|
||||
*
|
||||
* @return bool if string is not null and not empty
|
||||
* @since 3.0.2 N°5302
|
||||
*/
|
||||
public static function IsNotNullOrEmptyString(?string $sString): bool
|
||||
{
|
||||
return !static::IsNullOrEmptyString($sString);
|
||||
}
|
||||
|
||||
//----------------------------------------------
|
||||
// Environment helpers
|
||||
//----------------------------------------------
|
||||
@@ -2950,18 +3213,35 @@ HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a snake_case $sInput into a CamelCase string
|
||||
*
|
||||
* @param string $sInput
|
||||
*
|
||||
* @return string
|
||||
* @return string Camel case representation of $sInput (eg. "something_new" becomes "SomethingNew")
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public static function ToCamelCase($sInput)
|
||||
public static function ToCamelCase($sInput): string
|
||||
{
|
||||
return str_replace(' ', '', ucwords(strtr($sInput, '_-', ' ')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sInput
|
||||
*
|
||||
* @return string Snake case representation of $sInput (eg. "SomethingNew" becomes "something_new")
|
||||
* @since 3.1.0
|
||||
* @link https://stackoverflow.com/a/19533226/2710325
|
||||
*/
|
||||
public static function ToSnakeCase(string $sInput): string
|
||||
{
|
||||
// Remove special chars to join words
|
||||
$sOutput = preg_replace('/(\W)/', '_', $sInput);
|
||||
// Transform camel case words to snake case
|
||||
$sOutput = preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '_$0', $sOutput);
|
||||
// Lowercase everything
|
||||
$sOutput = mb_strtolower($sOutput);
|
||||
// Trim outer underscores
|
||||
return trim($sOutput, '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sInput
|
||||
*
|
||||
@@ -3074,15 +3354,50 @@ HTML;
|
||||
*/
|
||||
public static function AddParameterToUrl(string $sUrl, string $sParamName, string $sParamValue): string
|
||||
{
|
||||
if (strpos($sUrl, '?') === false)
|
||||
{
|
||||
if (strpos($sUrl, '?') === false) {
|
||||
$sUrl = $sUrl.'?'.urlencode($sParamName).'='.urlencode($sParamValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sUrl = $sUrl.'&'.urlencode($sParamName).'='.urlencode($sParamValue);
|
||||
}
|
||||
|
||||
return $sUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return traits array used by a class and by parent classes hierarchy.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.class-uses.php#110752
|
||||
*
|
||||
* @param string $sClass Class to scan
|
||||
* @param bool $bAutoload Autoload flag
|
||||
*
|
||||
* @return array traits used
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static function TraitsUsedByClass(string $sClass, bool $bAutoload = true): array
|
||||
{
|
||||
$aTraits = [];
|
||||
do {
|
||||
$aTraits = array_merge(class_uses($sClass, $bAutoload), $aTraits);
|
||||
} while ($sClass = get_parent_class($sClass));
|
||||
foreach ($aTraits as $sTrait => $same) {
|
||||
$aTraits = array_merge(class_uses($sTrait, $bAutoload), $aTraits);
|
||||
}
|
||||
|
||||
return array_unique($aTraits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trait usage by a class or by parent classes hierarchy.
|
||||
*
|
||||
* @param string $sTrait Trait to search for
|
||||
* @param string $sClass Class to check
|
||||
*
|
||||
* @return bool
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static function IsTraitUsedByClass(string $sTrait, string $sClass): bool
|
||||
{
|
||||
return in_array($sTrait, self::TraitsUsedByClass($sClass, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/WebPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/WebPage.php, now loadable using autoloader');
|
||||
@@ -60,16 +60,20 @@ class WizardHelper
|
||||
// special handling for lists
|
||||
// assumes this is handled as an array of objects
|
||||
// thus encoded in json like: [ { name:'link1', 'id': 123}, { name:'link2', 'id': 124}...]
|
||||
$aData = json_decode($value, true); // true means decode as a hash array (not an object)
|
||||
if (!is_array($value)) {
|
||||
$aData = json_decode($value, true); // true means decode as a hash array (not an object)
|
||||
} else {
|
||||
$aData = $value;
|
||||
}
|
||||
|
||||
// Check what are the meaningful attributes
|
||||
$aFields = $this->GetLinkedWizardStructure($oAttDef);
|
||||
$sLinkedClass = $oAttDef->GetLinkedClass();
|
||||
$aLinkedObjectsArray = array();
|
||||
if (!is_array($aData))
|
||||
{
|
||||
echo ("aData: '$aData' (value: '$value')\n");
|
||||
if (!is_array($aData)) {
|
||||
echo("aData: '$aData' (value: '$value')\n");
|
||||
}
|
||||
foreach($aData as $aLinkedObject)
|
||||
foreach ($aData as $aLinkedObject)
|
||||
{
|
||||
$oLinkedObj = MetaModel::NewObject($sLinkedClass);
|
||||
foreach($aFields as $sLinkedAttCode)
|
||||
@@ -359,6 +363,10 @@ class WizardHelper
|
||||
JS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function with an old pattern of code
|
||||
* @deprecated 3.1.0
|
||||
*/
|
||||
static function ParseJsonSet($oMe, $sLinkClass, $sExtKeyToMe, $sJsonSet)
|
||||
{
|
||||
$aSet = json_decode($sJsonSet, true); // true means hash array instead of object
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/XMLPage.php, now loadable using autoloader
|
||||
* @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');
|
||||
DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/XMLPage.php, now loadable using autoloader');
|
||||
@@ -11,7 +11,7 @@ define('APPCONF', APPROOT.'conf/');
|
||||
*
|
||||
* @see ITOP_CORE_VERSION to get full iTop core version
|
||||
*/
|
||||
define('ITOP_DESIGN_LATEST_VERSION', '3.0');
|
||||
define('ITOP_DESIGN_LATEST_VERSION', '3.1');
|
||||
|
||||
/**
|
||||
* Constant containing the iTop core version, whatever application was built
|
||||
@@ -23,6 +23,6 @@ define('ITOP_DESIGN_LATEST_VERSION', '3.0');
|
||||
* @used-by utils::GetItopVersionWikiSyntax()
|
||||
* @used-by iTopModulesPhpVersionIntegrationTest
|
||||
*/
|
||||
define('ITOP_CORE_VERSION', '3.0.1');
|
||||
define('ITOP_CORE_VERSION', '3.1.0');
|
||||
|
||||
require_once APPROOT.'bootstrap.inc.php';
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"name": "combodo/itop",
|
||||
"description": "IT Operations Portal",
|
||||
"type": "project",
|
||||
"license": "AGPLv3",
|
||||
"license": "AGPL-3.0-only",
|
||||
"require": {
|
||||
"php": ">=7.1.3 <8.0.0",
|
||||
"php": ">=7.4.0 <8.2.0",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
@@ -10,22 +12,26 @@
|
||||
"ext-json": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-soap": "*",
|
||||
"combodo/tcpdf": "6.3.5",
|
||||
"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.47",
|
||||
"symfony/dotenv": "~3.4.47",
|
||||
"symfony/framework-bundle": "~3.4.47",
|
||||
"symfony/polyfill-php70": "1.*",
|
||||
"symfony/twig-bundle": "~3.4.47",
|
||||
"symfony/yaml": "~3.4.47"
|
||||
"apereo/phpcas" : "~1.6.0",
|
||||
"combodo/tcpdf": "~6.4.4",
|
||||
"guzzlehttp/guzzle": "^7.4.5",
|
||||
"laminas/laminas-mail": "^2.11",
|
||||
"laminas/laminas-servicemanager": "^3.5",
|
||||
"league/oauth2-google": "^3.0",
|
||||
"nikic/php-parser": "~4.14.0",
|
||||
"pear/archive_tar": "~1.4.14",
|
||||
"pelago/emogrifier": "^6.0.0",
|
||||
"scssphp/scssphp": "^1.10.3",
|
||||
"symfony/console": "5.4.*",
|
||||
"symfony/dotenv": "5.4.*",
|
||||
"symfony/framework-bundle": "5.4.*",
|
||||
"symfony/twig-bundle": "5.4.*",
|
||||
"symfony/yaml": "5.4.*",
|
||||
"thenetworg/oauth2-azure": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/stopwatch": "~3.4.47",
|
||||
"symfony/web-profiler-bundle": "~3.4.47"
|
||||
"symfony/stopwatch": "5.4.*",
|
||||
"symfony/web-profiler-bundle": "5.4.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Required to use the AttributeEncryptedString.",
|
||||
@@ -37,7 +43,7 @@
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.1.3"
|
||||
"php": "7.4.0"
|
||||
},
|
||||
"vendor-dir": "lib",
|
||||
"preferred-install": {
|
||||
@@ -54,12 +60,7 @@
|
||||
"sources"
|
||||
],
|
||||
"exclude-from-classmap": [
|
||||
"core/dbobjectsearch.class.php",
|
||||
"core/legacy/dbobjectsearchlegacy.class.php",
|
||||
"core/querybuildercontext.class.inc.php",
|
||||
"core/legacy/querybuildercontextlegacy.class.inc.php",
|
||||
"core/querybuilderexpressions.class.inc.php",
|
||||
"core/legacy/querybuilderexpressionslegacy.class.inc.php",
|
||||
"application/twigextension.class.inc.php",
|
||||
"core/oql/build/PHP/",
|
||||
"core/apc-emulation.php",
|
||||
"application/startup.inc.php",
|
||||
|
||||
4147
composer.lock
generated
4147
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -470,9 +470,9 @@ class Str
|
||||
public static function pure2html($pure, $maxLength = false)
|
||||
{
|
||||
// Check for HTML entities, but be careful the DB is in UTF-8
|
||||
return $maxLength
|
||||
? htmlentities(substr($pure, 0, $maxLength), ENT_QUOTES, 'UTF-8')
|
||||
: htmlentities($pure, ENT_QUOTES, 'UTF-8');
|
||||
return $maxLength
|
||||
? utils::EscapeHtml(substr($pure, 0, $maxLength))
|
||||
: utils::EscapeHtml($pure);
|
||||
}
|
||||
public static function pure2sql($pure, $maxLength = false)
|
||||
{
|
||||
|
||||
@@ -43,22 +43,24 @@ abstract class Action extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "grant_by_profile,core/cmdb",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array('name'),
|
||||
"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'),
|
||||
"category" => "grant_by_profile,core/cmdb",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"complementary_name_attcode" => array('finalclass', 'description'),
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array('name'),
|
||||
"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();
|
||||
MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum(array('test'=>'Being tested' ,'enabled'=>'In production', 'disabled'=>'Inactive')), "sql"=>"status", "default_value"=>"test", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("trigger_list", array("linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"action_id", "ext_key_to_remote"=>"trigger_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values" => new ValueSetEnum(array('test' => 'Being tested', 'enabled' => 'In production', 'disabled' => 'Inactive')), "sql" => "status", "default_value" => "test", "is_null_allowed" => false, "depends_on" => array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("trigger_list",
|
||||
array("linked_class" => "lnkTriggerAction", "ext_key_to_me" => "action_id", "ext_key_to_remote" => "trigger_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => array(), "display_style" => 'property')));
|
||||
|
||||
// Display lists
|
||||
// - Attributes to be displayed for the complete details
|
||||
@@ -277,7 +279,7 @@ class ActionEmail extends ActionNotification
|
||||
protected function FindRecipients($sRecipAttCode, $aArgs)
|
||||
{
|
||||
$sOQL = $this->Get($sRecipAttCode);
|
||||
if (strlen($sOQL) == '') return '';
|
||||
if (strlen($sOQL) === 0) return '';
|
||||
|
||||
try
|
||||
{
|
||||
@@ -566,7 +568,7 @@ class ActionEmail extends ActionNotification
|
||||
// Prefix
|
||||
$sPrefix = sprintf('%s_%s_%d', $sAppName, $sObjClass, $sObjId);
|
||||
if ($sHeaderName === static::ENUM_HEADER_NAME_MESSAGE_ID) {
|
||||
$sPrefix .= sprintf('_%f', microtime(true /* get as float*/));
|
||||
$sPrefix .= sprintf('_%F', microtime(true /* get as float*/));
|
||||
}
|
||||
// Suffix
|
||||
$sSuffix = sprintf('@%s.openitop.org', MetaModel::GetEnvironmentId());
|
||||
|
||||
@@ -181,7 +181,7 @@ abstract class AsyncTask extends DBObject
|
||||
if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
|
||||
{
|
||||
$aConfig = $aRetries[get_class($this)];
|
||||
$bExponential = (bool)$aConfig['exponential_delay'] ?? $bExponential;
|
||||
$bExponential = (bool) ($aConfig['exponential_delay'] ?? $bExponential);
|
||||
}
|
||||
return $bExponential;
|
||||
}
|
||||
@@ -293,7 +293,7 @@ abstract class AsyncTask extends DBObject
|
||||
$this->Set('remaining_retries', $this->GetMaxRetries($iErrorCode));
|
||||
}
|
||||
|
||||
$this->Set('last_error', $sErrorMessage);
|
||||
$this->SetTrim('last_error', $sErrorMessage);
|
||||
$this->Set('last_error_code', $iErrorCode); // Note: can be ZERO !!!
|
||||
$this->Set('last_attempt', time());
|
||||
|
||||
@@ -409,8 +409,6 @@ class AsyncSendEmail extends AsyncTask
|
||||
$oNew->Set('to', $oEMail->GetRecipientTO(true /* string */));
|
||||
$oNew->Set('subject', $oEMail->GetSubject());
|
||||
|
||||
// $oNew->Set('version', 1);
|
||||
// $sMessage = serialize($oEMail);
|
||||
$oNew->Set('version', 2);
|
||||
$sMessage = $oEMail->SerializeV2();
|
||||
$oNew->Set('message', $sMessage);
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\FieldBadge\FieldBadgeUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Links\Indirect\BlockLinksSetDisplayAsProperty;
|
||||
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;
|
||||
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
|
||||
use Combodo\iTop\Service\Links\LinkSetModel;
|
||||
|
||||
require_once('MyHelpers.class.inc.php');
|
||||
require_once('ormdocument.class.inc.php');
|
||||
@@ -89,8 +92,15 @@ define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu
|
||||
define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place
|
||||
define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place
|
||||
|
||||
define('LINKSET_RELATIONTYPE_PROPERTY', 'property');
|
||||
define('LINKSET_RELATIONTYPE_LINK', 'link');
|
||||
|
||||
define('LINKSET_DISPLAY_STYLE_PROPERTY', 'property');
|
||||
define('LINKSET_DISPLAY_STYLE_TAB', 'tab');
|
||||
|
||||
/**
|
||||
* Attributes implementing this interface won't be accepted as `group by` field
|
||||
*
|
||||
* @since 2.7.4 N°3473
|
||||
*/
|
||||
interface iAttributeNoGroupBy
|
||||
@@ -136,6 +146,15 @@ abstract class AttributeDefinition
|
||||
|
||||
abstract public function GetEditClass();
|
||||
|
||||
/**
|
||||
* @return array Css classes
|
||||
* @since 3.1.0 N°3190
|
||||
*/
|
||||
public function GetCssClasses(): array
|
||||
{
|
||||
return $this->aCSSClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the search widget type corresponding to this attribute
|
||||
*
|
||||
@@ -357,6 +376,18 @@ abstract class AttributeDefinition
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the attribute can be used in bulk modify.
|
||||
*
|
||||
* @return bool
|
||||
* @since 3.1.0 N°3190
|
||||
*
|
||||
*/
|
||||
public static function IsBulkModifyCompatible(): bool
|
||||
{
|
||||
return static::IsScalar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the attribute value is a set of related objects (1-N or N-N)
|
||||
*
|
||||
@@ -620,6 +651,16 @@ abstract class AttributeDefinition
|
||||
return $sLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if the attribute has a description {@see \AttributeDefinition::GetDescription()}
|
||||
* @throws \Exception
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public function HasDescription(): bool
|
||||
{
|
||||
return utils::IsNotNullOrEmptyString($this->GetDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $sDefault
|
||||
*
|
||||
@@ -854,6 +895,11 @@ abstract class AttributeDefinition
|
||||
//abstract protected GetBasicFilterHTMLInput();
|
||||
abstract public function GetBasicFilterSQLExpr($sOpCode, $value);
|
||||
|
||||
/**
|
||||
* since 3.1.0 return has changed (N°4690 - Deprecate "FilterCodes")
|
||||
*
|
||||
* @return array filtercode => attributecode
|
||||
*/
|
||||
public function GetFilterDefinitions()
|
||||
{
|
||||
return array();
|
||||
@@ -1408,6 +1454,7 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
public function __construct($sCode, $aParams)
|
||||
{
|
||||
parent::__construct($sCode, $aParams);
|
||||
$this->aCSSClasses[] = 'attribute-set';
|
||||
}
|
||||
|
||||
public static function ListExpectedParams()
|
||||
@@ -1421,6 +1468,12 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
return "LinkedSet";
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function IsBulkModifyCompatible(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function IsWritable()
|
||||
{
|
||||
return true;
|
||||
@@ -1438,7 +1491,26 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
|
||||
public function GetValuesDef()
|
||||
{
|
||||
return $this->Get("allowed_values");
|
||||
$oValSetDef = $this->Get("allowed_values");
|
||||
if (!$oValSetDef) {
|
||||
// Let's propose every existing value
|
||||
$oValSetDef = new ValueSetObjects('SELECT '.LinkSetModel::GetTargetClass($this));
|
||||
}
|
||||
|
||||
return $oValSetDef;
|
||||
}
|
||||
|
||||
public function GetEditValue($value, $oHostObj = null)
|
||||
{
|
||||
/** @var ormLinkSet $value * */
|
||||
if ($value->Count() === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/** Return linked objects key as string **/
|
||||
$aValues = $value->GetValues();
|
||||
|
||||
return implode(' ', $aValues);
|
||||
}
|
||||
|
||||
public function GetPrerequisiteAttributes($sClass = null)
|
||||
@@ -1505,11 +1577,42 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_default'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string see LINKSET_EDITMODE_* constants
|
||||
* @since 3.1.0 N°5563 relations are edited using new attributes in details mode, but as nothing changed in edit mode we are still using edit_mode attribute
|
||||
*/
|
||||
public function GetEditMode()
|
||||
{
|
||||
return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string see LINKSET_RELATIONTYPE_* constants
|
||||
* @since 3.1.0 N°5563
|
||||
*/
|
||||
public function GetRelationType()
|
||||
{
|
||||
return $this->GetOptional('relation_type', LINKSET_RELATIONTYPE_LINK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string see LINKSET_DISPLAY_STYLE_* constants
|
||||
* @since 3.1.0 N°3190
|
||||
*/
|
||||
public function GetDisplayStyle()
|
||||
{
|
||||
return $this->GetOptional('display_style', LINKSET_DISPLAY_STYLE_TAB);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
* @since 3.1.0 N°5563
|
||||
*/
|
||||
public function GetReadOnly()
|
||||
{
|
||||
return $this->GetOptional('read_only', false);
|
||||
}
|
||||
|
||||
public function GetLinkedClass()
|
||||
{
|
||||
return $this->Get('linked_class');
|
||||
@@ -1535,49 +1638,30 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sValue
|
||||
* @param \DBObject $oHostObject
|
||||
* @param bool $bLocalize
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
|
||||
/** @inheritDoc * */
|
||||
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true): string
|
||||
{
|
||||
if (is_object($sValue) && ($sValue instanceof ormLinkSet))
|
||||
{
|
||||
$sValue->Rewind();
|
||||
$aItems = array();
|
||||
while ($oObj = $sValue->Fetch())
|
||||
{
|
||||
// Show only relevant information (hide the external key to the current object)
|
||||
$aAttributes = array();
|
||||
foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if ($sAttCode == $this->GetExtKeyToMe())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ($oAttDef->IsExternalField())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$sAttValue = $oObj->GetAsHTML($sAttCode);
|
||||
if (strlen($sAttValue) > 0)
|
||||
{
|
||||
$aAttributes[] = $sAttValue;
|
||||
}
|
||||
}
|
||||
$sAttributes = implode(', ', $aAttributes);
|
||||
$aItems[] = $sAttributes;
|
||||
try {
|
||||
|
||||
/** @var ormLinkSet $sValue */
|
||||
if ($sValue->Count() === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return implode('<br/>', $aItems);
|
||||
}
|
||||
$oLinkSetBlock = new BlockLinksSetDisplayAsProperty($this->GetCode(), $this, $sValue);
|
||||
|
||||
return null;
|
||||
return ConsoleBlockRenderer::RenderBlockTemplates($oLinkSetBlock);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$sMessage = "Error while displaying attribute {$this->GetCode()}";
|
||||
IssueLog::Error($sMessage, IssueLog::CHANNEL_DEFAULT, [
|
||||
'host_object_class' => $this->GetHostClass(),
|
||||
'host_object_key' => $oHostObject->GetKey(),
|
||||
'attribute' => $this->GetCode(),
|
||||
]);
|
||||
|
||||
return $sMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1685,7 +1769,7 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
{
|
||||
if ($sObjClass == $this->GetLinkedClass())
|
||||
{
|
||||
// Simplify the output if the exact class could be determined implicitely
|
||||
// Simplify the output if the exact class could be determined implicitely
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2007,7 +2091,7 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
{
|
||||
if ($sObjClass == $this->GetLinkedClass())
|
||||
{
|
||||
// Simplify the output if the exact class could be determined implicitely
|
||||
// Simplify the output if the exact class could be determined implicitely
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2284,6 +2368,15 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
|
||||
return $this->GetOptional("duplicates", false);
|
||||
} // The same object may be linked several times... or not...
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
* @since 3.1.0 N°5563
|
||||
*/
|
||||
public function GetReadOnly()
|
||||
{
|
||||
return $this->GetOptional('read_only', false);
|
||||
}
|
||||
|
||||
public function GetTrackingLevel()
|
||||
{
|
||||
return $this->GetOptional('tracking_level',
|
||||
@@ -2321,6 +2414,13 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
|
||||
|
||||
return $oRet;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function IsBulkModifyCompatible(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2412,7 +2512,7 @@ class AttributeDBFieldVoid extends AttributeDefinition
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
protected function ScalarToSQL($value)
|
||||
{
|
||||
return $value;
|
||||
@@ -2452,7 +2552,7 @@ class AttributeDBFieldVoid extends AttributeDefinition
|
||||
|
||||
public function GetFilterDefinitions()
|
||||
{
|
||||
return array($this->GetCode() => new FilterFromAttribute($this));
|
||||
return array($this->GetCode() => $this->GetCode());
|
||||
}
|
||||
|
||||
public function GetBasicFilterOperators()
|
||||
@@ -4174,20 +4274,21 @@ class AttributeText extends AttributeString
|
||||
|
||||
public function GetEditValue($sValue, $oHostObj = null)
|
||||
{
|
||||
if ($this->GetFormat() == 'text')
|
||||
{
|
||||
if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER))
|
||||
{
|
||||
foreach($aAllMatches as $iPos => $aMatches)
|
||||
{
|
||||
// N°4517 - PHP 8.1 compatibility: str_replace call with null cause deprecated message
|
||||
if ($sValue == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($this->GetFormat() == 'text') {
|
||||
if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) {
|
||||
foreach ($aAllMatches as $iPos => $aMatches) {
|
||||
$sClass = trim($aMatches[1]);
|
||||
$sName = trim($aMatches[2]);
|
||||
$sLabel = (!empty($aMatches[4])) ? trim($aMatches[4]) : null;
|
||||
|
||||
if (MetaModel::IsValidClass($sClass))
|
||||
{
|
||||
if (MetaModel::IsValidClass($sClass)) {
|
||||
$sClassLabel = MetaModel::GetName($sClass);
|
||||
$sReplacement = "[[$sClassLabel:$sName" . (!empty($sLabel) ? " | $sLabel" : "") . "]]";
|
||||
$sReplacement = "[[$sClassLabel:$sName".(!empty($sLabel) ? " | $sLabel" : "")."]]";
|
||||
$sValue = str_replace($aMatches[0], $sReplacement, $sValue);
|
||||
}
|
||||
}
|
||||
@@ -4224,31 +4325,31 @@ class AttributeText extends AttributeString
|
||||
public function MakeRealValue($proposedValue, $oHostObj)
|
||||
{
|
||||
$sValue = $proposedValue;
|
||||
switch ($this->GetFormat())
|
||||
{
|
||||
|
||||
// N°4517 - PHP 8.1 compatibility: str_replace call with null cause deprecated message
|
||||
if ($sValue == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch ($this->GetFormat()) {
|
||||
case 'html':
|
||||
if (($sValue !== null) && ($sValue !== ''))
|
||||
{
|
||||
if (($sValue !== null) && ($sValue !== '')) {
|
||||
$sValue = HTMLSanitizer::Sanitize($sValue);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
default:
|
||||
if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER))
|
||||
{
|
||||
foreach($aAllMatches as $iPos => $aMatches)
|
||||
{
|
||||
if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) {
|
||||
foreach ($aAllMatches as $iPos => $aMatches) {
|
||||
$sClassLabel = trim($aMatches[1]);
|
||||
$sName = trim($aMatches[2]);
|
||||
$sLabel = (!empty($aMatches[4])) ? trim($aMatches[4]) : null;
|
||||
$sLabel = (!empty($aMatches[4])) ? trim($aMatches[4]) : null;
|
||||
|
||||
if (!MetaModel::IsValidClass($sClassLabel))
|
||||
{
|
||||
if (!MetaModel::IsValidClass($sClassLabel)) {
|
||||
$sClass = MetaModel::GetClassFromLabel($sClassLabel);
|
||||
if ($sClass)
|
||||
{
|
||||
$sReplacement = "[[$sClassLabel:$sName" . (!empty($sLabel) ? " | $sLabel" : "") . "]]";
|
||||
if ($sClass) {
|
||||
$sReplacement = "[[$sClassLabel:$sName".(!empty($sLabel) ? " | $sLabel" : "")."]]";
|
||||
$sValue = str_replace($aMatches[0], $sReplacement, $sValue);
|
||||
}
|
||||
}
|
||||
@@ -4587,7 +4688,13 @@ class AttributeCaseLog extends AttributeLongText
|
||||
{
|
||||
if (strlen($proposedValue) > 0)
|
||||
{
|
||||
$oCaseLog->AddLogEntry($proposedValue);
|
||||
//N°5135 - add impersonation information in caselog
|
||||
if (UserRights::IsImpersonated()){
|
||||
$sOnBehalfOf = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUserFriendlyName(), UserRights::GetUserFriendlyName());
|
||||
$oCaseLog->AddLogEntry($proposedValue, $sOnBehalfOf, UserRights::GetConnectedUserId());
|
||||
} else {
|
||||
$oCaseLog->AddLogEntry($proposedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
$ret = $oCaseLog;
|
||||
@@ -5394,7 +5501,7 @@ class AttributeEnum extends AttributeString
|
||||
{
|
||||
if (is_null($sValue))
|
||||
{
|
||||
// Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
|
||||
// Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
|
||||
$sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue,
|
||||
Dict::S('Enum:Undefined'));
|
||||
}
|
||||
@@ -5416,7 +5523,7 @@ class AttributeEnum extends AttributeString
|
||||
{
|
||||
if (is_null($sValue))
|
||||
{
|
||||
// Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
|
||||
// Unless a specific label is defined for the null value of this enum, use a generic "undefined" label
|
||||
$sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+',
|
||||
Dict::S('Enum:Undefined'));
|
||||
}
|
||||
@@ -6010,7 +6117,9 @@ class AttributeDateTime extends AttributeDBField
|
||||
|
||||
public function GetDefaultValue(DBObject $oHostObject = null)
|
||||
{
|
||||
// null value will be replaced by the current date, if not already set, in DoComputeValues
|
||||
if (!$this->IsNullAllowed()) {
|
||||
return date($this->GetInternalFormat());
|
||||
}
|
||||
return $this->GetNullValue();
|
||||
}
|
||||
|
||||
@@ -7213,7 +7322,7 @@ class AttributeExternalField extends AttributeDefinition
|
||||
|
||||
protected function GetSQLCol($bFullSpec = false)
|
||||
{
|
||||
// throw new CoreException("external attribute: does it make any sense to request its type ?");
|
||||
// throw new CoreException("external attribute: does it make any sense to request its type ?");
|
||||
$oExtAttDef = $this->GetExtAttDef();
|
||||
|
||||
return $oExtAttDef->GetSQLCol($bFullSpec);
|
||||
@@ -7486,7 +7595,7 @@ class AttributeExternalField extends AttributeDefinition
|
||||
|
||||
public function GetFilterDefinitions()
|
||||
{
|
||||
return array($this->GetCode() => new FilterFromAttribute($this));
|
||||
return array($this->GetCode() => $this->GetCode());
|
||||
}
|
||||
|
||||
public function GetBasicFilterOperators()
|
||||
@@ -7800,7 +7909,7 @@ class AttributeBlob extends AttributeDefinition
|
||||
|
||||
public function GetDefaultValue(DBObject $oHostObject = null)
|
||||
{
|
||||
return "";
|
||||
return new ormDocument('', '', '');
|
||||
}
|
||||
|
||||
public function IsNullAllowed(DBObject $oHostObject = null)
|
||||
@@ -8149,6 +8258,11 @@ class AttributeImage extends AttributeBlob
|
||||
return $oDoc;
|
||||
}
|
||||
|
||||
public function GetDefaultValue(DBObject $oHostObject = null)
|
||||
{
|
||||
return new ormDocument('', '', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the supplied ormDocument actually contains an image
|
||||
* {@inheritDoc}
|
||||
@@ -8523,18 +8637,17 @@ class AttributeStopWatch extends AttributeDefinition
|
||||
public function GetFilterDefinitions()
|
||||
{
|
||||
$aRes = array(
|
||||
$this->GetCode() => new FilterFromAttribute($this),
|
||||
$this->GetCode().'_started' => new FilterFromAttribute($this, '_started'),
|
||||
$this->GetCode().'_laststart' => new FilterFromAttribute($this, '_laststart'),
|
||||
$this->GetCode().'_stopped' => new FilterFromAttribute($this, '_stopped')
|
||||
$this->GetCode() => $this->GetCode(),
|
||||
$this->GetCode().'_started' => $this->GetCode(),
|
||||
$this->GetCode().'_laststart' => $this->GetCode(),
|
||||
$this->GetCode().'_stopped' => $this->GetCode(),
|
||||
);
|
||||
foreach($this->ListThresholds() as $iThreshold => $aFoo)
|
||||
{
|
||||
foreach ($this->ListThresholds() as $iThreshold => $aFoo) {
|
||||
$sPrefix = $this->GetCode().'_'.$iThreshold;
|
||||
$aRes[$sPrefix.'_deadline'] = new FilterFromAttribute($this, '_deadline');
|
||||
$aRes[$sPrefix.'_passed'] = new FilterFromAttribute($this, '_passed');
|
||||
$aRes[$sPrefix.'_triggered'] = new FilterFromAttribute($this, '_triggered');
|
||||
$aRes[$sPrefix.'_overrun'] = new FilterFromAttribute($this, '_overrun');
|
||||
$aRes[$sPrefix.'_deadline'] = $this->GetCode();
|
||||
$aRes[$sPrefix.'_passed'] = $this->GetCode();
|
||||
$aRes[$sPrefix.'_triggered'] = $this->GetCode();
|
||||
$aRes[$sPrefix.'_overrun'] = $this->GetCode();
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
@@ -9262,7 +9375,7 @@ class AttributeSubItem extends AttributeDefinition
|
||||
return $res;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside)
|
||||
|
||||
public function FromSQLToValue($aCols, $sPrefix = '')
|
||||
@@ -9276,7 +9389,7 @@ class AttributeSubItem extends AttributeDefinition
|
||||
|
||||
public function GetFilterDefinitions()
|
||||
{
|
||||
return array($this->GetCode() => new FilterFromAttribute($this));
|
||||
return array($this->GetCode() => $this->GetCode());
|
||||
}
|
||||
|
||||
public function GetBasicFilterOperators()
|
||||
@@ -11341,6 +11454,13 @@ class AttributeTagSet extends AttributeSet
|
||||
return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
|
||||
}
|
||||
|
||||
public function GetDefaultValue(DBObject $oHostObject = null)
|
||||
{
|
||||
$oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
|
||||
$oTagSet->SetValues([]);
|
||||
return $oTagSet;
|
||||
}
|
||||
|
||||
public function IsNull($proposedValue)
|
||||
{
|
||||
if (is_null($proposedValue))
|
||||
@@ -11935,7 +12055,7 @@ class AttributeFriendlyName extends AttributeDefinition
|
||||
|
||||
public function GetFilterDefinitions()
|
||||
{
|
||||
return array($this->GetCode() => new FilterFromAttribute($this));
|
||||
return array($this->GetCode() => $this->GetCode());
|
||||
}
|
||||
|
||||
public function GetBasicFilterOperators()
|
||||
@@ -12772,7 +12892,7 @@ class AttributeCustomFields extends AttributeDefinition
|
||||
$sRet = $value->GetAsHTML($bLocalize);
|
||||
} catch (Exception $e)
|
||||
{
|
||||
$sRet = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8');
|
||||
$sRet = 'Custom field error: '.utils::EscapeHtml($e->getMessage());
|
||||
}
|
||||
|
||||
return $sRet;
|
||||
@@ -13072,7 +13192,7 @@ class AttributeObsolescenceFlag extends AttributeBoolean
|
||||
|
||||
public function GetDefaultValue(DBObject $oHostObject = null)
|
||||
{
|
||||
return $this->MakeRealValue("", $oHostObject);
|
||||
return $this->MakeRealValue(false, $oHostObject);
|
||||
}
|
||||
|
||||
public function IsNullAllowed()
|
||||
|
||||
@@ -21,6 +21,7 @@ MetaModel::IncludeModule('application/menunode.class.inc.php');
|
||||
MetaModel::IncludeModule('application/user.preferences.class.inc.php');
|
||||
MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.domain.class.inc.php');
|
||||
MetaModel::IncludeModule('application/query.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ define('UTF8_BOM', chr(239).chr(187).chr(191)); // 0xEF, 0xBB, 0xBF
|
||||
|
||||
/**
|
||||
* CellChangeSpec
|
||||
* A series of classes, keeping the information about a given cell: could it be changed or not (and why)?
|
||||
* A series of classes, keeping the information about a given cell: could it be changed or not (and why)?
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
@@ -42,6 +42,17 @@ abstract class CellChangeSpec
|
||||
return $this->m_sOql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
*/
|
||||
public function GetDisplayableValueAndDescription(): string
|
||||
{
|
||||
return sprintf("%s%s",
|
||||
$this->GetDisplayableValue(),
|
||||
$this->GetDescription()
|
||||
);
|
||||
}
|
||||
|
||||
abstract public function GetDescription();
|
||||
}
|
||||
|
||||
@@ -86,26 +97,90 @@ class CellStatus_Issue extends CellStatus_Modify
|
||||
parent::__construct($proposedValue, $previousValue);
|
||||
}
|
||||
|
||||
public function GetDescription()
|
||||
public function GetDisplayableValue()
|
||||
{
|
||||
if (is_null($this->m_proposedValue))
|
||||
{
|
||||
return Dict::Format('UI:CSVReport-Value-SetIssue', $this->m_sReason);
|
||||
return Dict::Format('UI:CSVReport-Value-SetIssue');
|
||||
}
|
||||
return Dict::Format('UI:CSVReport-Value-ChangeIssue', $this->m_proposedValue, $this->m_sReason);
|
||||
return Dict::Format('UI:CSVReport-Value-ChangeIssue', \utils::EscapeHtml($this->m_proposedValue));
|
||||
}
|
||||
|
||||
public function GetDescription()
|
||||
{
|
||||
return $this->m_sReason;
|
||||
}
|
||||
/*
|
||||
* @since 3.1.0 N°5305
|
||||
*/
|
||||
public function GetDisplayableValueAndDescription(): string
|
||||
{
|
||||
return sprintf("%s. %s",
|
||||
$this->GetDisplayableValue(),
|
||||
$this->GetDescription()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CellStatus_SearchIssue extends CellStatus_Issue
|
||||
{
|
||||
public function __construct()
|
||||
/** @var string|null $m_sAllowedValues */
|
||||
private $m_sAllowedValues;
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
* @var string $sSerializedSearch
|
||||
*/
|
||||
private $sSerializedSearch;
|
||||
|
||||
/** @var string|null $m_sTargetClass */
|
||||
private $m_sTargetClass;
|
||||
|
||||
/**
|
||||
* CellStatus_SearchIssue constructor.
|
||||
* @since 3.1.0 N°5305
|
||||
*
|
||||
* @param string $sOql : main message
|
||||
* @param string $sReason : main message
|
||||
* @param null $sClass : used for additional message that provides allowed values for current class $sClass
|
||||
* @param null $sAllowedValues : used for additional message that provides allowed values $sAllowedValues for current class
|
||||
*/
|
||||
public function __construct($sSerializedSearch, $sReason, $sClass=null, $sAllowedValues=null)
|
||||
{
|
||||
parent::__construct(null, null, null);
|
||||
parent::__construct(null, null, $sReason);
|
||||
$this->sSerializedSearch = $sSerializedSearch;
|
||||
$this->m_sAllowedValues = $sAllowedValues;
|
||||
$this->m_sTargetClass = $sClass;
|
||||
}
|
||||
|
||||
public function GetDisplayableValue()
|
||||
{
|
||||
if (null === $this->m_sReason) {
|
||||
return Dict::Format('UI:CSVReport-Value-NoMatch', '');
|
||||
}
|
||||
|
||||
return $this->m_sReason;
|
||||
}
|
||||
|
||||
public function GetDescription()
|
||||
{
|
||||
return Dict::S('UI:CSVReport-Value-NoMatch');
|
||||
if (\utils::IsNullOrEmptyString($this->m_sAllowedValues) ||
|
||||
\utils::IsNullOrEmptyString($this->m_sTargetClass)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Dict::Format('UI:CSVReport-Value-NoMatch-PossibleValues', $this->m_sTargetClass, $this->m_sAllowedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
* @return string
|
||||
*/
|
||||
public function GetSearchLinkUrl()
|
||||
{
|
||||
return sprintf("UI.php?operation=search&filter=%s",
|
||||
rawurlencode($this->sSerializedSearch)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,11 +201,24 @@ class CellStatus_NullIssue extends CellStatus_Issue
|
||||
class CellStatus_Ambiguous extends CellStatus_Issue
|
||||
{
|
||||
protected $m_iCount;
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
* @var string
|
||||
*/
|
||||
protected $sSerializedSearch;
|
||||
|
||||
public function __construct($previousValue, $iCount, $sOql)
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
*
|
||||
* @param $previousValue
|
||||
* @param int $iCount
|
||||
* @param string $sSerializedSearch
|
||||
*
|
||||
*/
|
||||
public function __construct($previousValue, $iCount, $sSerializedSearch)
|
||||
{
|
||||
$this->m_iCount = $iCount;
|
||||
$this->m_sQuery = $sOql;
|
||||
$this->sSerializedSearch = $sSerializedSearch;
|
||||
parent::__construct(null, $previousValue, '');
|
||||
}
|
||||
|
||||
@@ -139,12 +227,23 @@ class CellStatus_Ambiguous extends CellStatus_Issue
|
||||
$sCount = $this->m_iCount;
|
||||
return Dict::Format('UI:CSVReport-Value-Ambiguous', $sCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
* @return string
|
||||
*/
|
||||
public function GetSearchLinkUrl()
|
||||
{
|
||||
return sprintf("UI.php?operation=search&filter=%s",
|
||||
rawurlencode($this->sSerializedSearch)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* RowStatus
|
||||
* A series of classes, keeping the information about a given row: could it be changed or not (and why)?
|
||||
* A series of classes, keeping the information about a given row: could it be changed or not (and why)?
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
@@ -211,6 +310,26 @@ class RowStatus_Issue extends RowStatus
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* class dedicated to testability
|
||||
* not used/ignored in csv imports UI/CLI
|
||||
* @since 3.1.0 N°5305
|
||||
*/
|
||||
class RowStatus_Error extends RowStatus
|
||||
{
|
||||
/** @var string */
|
||||
protected $m_sError;
|
||||
|
||||
public function __construct($sError)
|
||||
{
|
||||
$this->m_sError = $sError;
|
||||
}
|
||||
|
||||
public function GetDescription()
|
||||
{
|
||||
return $this->m_sError;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BulkChange
|
||||
@@ -220,17 +339,35 @@ class RowStatus_Issue extends RowStatus
|
||||
*/
|
||||
class BulkChange
|
||||
{
|
||||
protected $m_sClass;
|
||||
/** @var string */
|
||||
protected $m_sClass;
|
||||
protected $m_aData; // Note: hereafter, iCol maybe actually be any acceptable key (string)
|
||||
// #@# todo: rename the variables to sColIndex
|
||||
protected $m_aAttList; // attcode => iCol
|
||||
protected $m_aExtKeys; // aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol;
|
||||
protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey)
|
||||
protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported
|
||||
protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined)
|
||||
protected $m_sDateFormat; // Date format specification, see DateTime::createFromFormat
|
||||
protected $m_bLocalizedValues; // Values in the data set are localized (see AttributeEnum)
|
||||
protected $m_aExtKeysMappingCache; // Cache for resolving external keys based on the given search criterias
|
||||
/** @var array<string, string> attcode as key, iCol as value */
|
||||
protected $m_aAttList;
|
||||
/** @var array<string, array<string, string>> sExtKeyAttCode as key, array of sExtReconcKeyAttCode/iCol as value */
|
||||
protected $m_aExtKeys;
|
||||
/** @var string[] list of attcode (attcode = 'id' for the pkey) */
|
||||
protected $m_aReconcilKeys;
|
||||
/** @var string OQL - if specified, then the missing items will be reported */
|
||||
protected $m_sSynchroScope;
|
||||
/**
|
||||
* @var array<string, mixed> attcode as key, attvalue as value. Values to be set when an object gets out of scope
|
||||
* (ignored if no scope has been defined)
|
||||
*/
|
||||
protected $m_aOnDisappear;
|
||||
/**
|
||||
* @see DateTime::createFromFormat
|
||||
* @var string Date format specification
|
||||
*/
|
||||
protected $m_sDateFormat;
|
||||
/**
|
||||
* @see AttributeEnum
|
||||
* @var boolean true if Values in the data set are localized
|
||||
*/
|
||||
protected $m_bLocalizedValues;
|
||||
/** @var array Cache for resolving external keys based on the given search criterias */
|
||||
protected $m_aExtKeysMappingCache;
|
||||
|
||||
public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys, $sSynchroScope = null, $aOnDisappear = null, $sDateFormat = null, $bLocalize = false)
|
||||
{
|
||||
@@ -261,30 +398,30 @@ class BulkChange
|
||||
$this->m_sReportCsvSep = $sSeparator;
|
||||
$this->m_sReportCsvDelimiter = $sDelimiter;
|
||||
}
|
||||
|
||||
|
||||
protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults)
|
||||
{
|
||||
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||
foreach ($this->m_aExtKeys[$sAttCode] as $sForeignAttCode => $iCol)
|
||||
foreach ($this->m_aExtKeys[$sAttCode] as $sReconKeyAttCode => $iCol)
|
||||
{
|
||||
if ($sForeignAttCode == 'id')
|
||||
if ($sReconKeyAttCode == 'id')
|
||||
{
|
||||
$value = (int) $aRowData[$iCol];
|
||||
}
|
||||
else
|
||||
{
|
||||
// The foreign attribute is one of our reconciliation key
|
||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
|
||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sReconKeyAttCode);
|
||||
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
}
|
||||
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
|
||||
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
|
||||
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
|
||||
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
||||
$aKeys = $oExtObjects->ToArray();
|
||||
return array($oReconFilter->ToOql(), $aKeys);
|
||||
return array($oReconFilter, $aKeys);
|
||||
}
|
||||
|
||||
// Returns true if the CSV data specifies that the external key must be left undefined
|
||||
@@ -318,10 +455,10 @@ class BulkChange
|
||||
{
|
||||
$aResults = array();
|
||||
$aErrors = array();
|
||||
|
||||
|
||||
// External keys reconciliation
|
||||
//
|
||||
foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig)
|
||||
foreach($this->m_aExtKeys as $sAttCode => $aReconKeys)
|
||||
{
|
||||
// Skip external keys used for the reconciliation process
|
||||
// if (!array_key_exists($sAttCode, $this->m_aAttList)) continue;
|
||||
@@ -330,7 +467,7 @@ class BulkChange
|
||||
|
||||
if ($this->IsNullExternalKeySpec($aRowData, $sAttCode))
|
||||
{
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||
{
|
||||
// Default reporting
|
||||
// $aRowData[$iCol] is always null
|
||||
@@ -352,25 +489,24 @@ class BulkChange
|
||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||
|
||||
$aCacheKeys = array();
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||
{
|
||||
// The foreign attribute is one of our reconciliation key
|
||||
if ($sForeignAttCode == 'id')
|
||||
if ($sReconKeyAttCode == 'id')
|
||||
{
|
||||
$value = $aRowData[$iCol];
|
||||
}
|
||||
else
|
||||
{
|
||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
|
||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sReconKeyAttCode);
|
||||
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
}
|
||||
$aCacheKeys[] = $value;
|
||||
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
|
||||
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
|
||||
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
|
||||
$iForeignKey = null;
|
||||
$sOQL = '';
|
||||
// TODO: check if *too long* keys can lead to collisions... and skip the cache in such a case...
|
||||
if (!array_key_exists($sAttCode, $this->m_aExtKeysMappingCache))
|
||||
{
|
||||
@@ -379,9 +515,8 @@ class BulkChange
|
||||
if (array_key_exists($sCacheKey, $this->m_aExtKeysMappingCache[$sAttCode]))
|
||||
{
|
||||
// Cache hit
|
||||
$iCount = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['c'];
|
||||
$iObjectFoundCount = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['c'];
|
||||
$iForeignKey = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['k'];
|
||||
$sOQL = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['oql'];
|
||||
// Record the hit
|
||||
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['h']++;
|
||||
}
|
||||
@@ -389,34 +524,35 @@ class BulkChange
|
||||
{
|
||||
// Cache miss, let's initialize it
|
||||
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
||||
$iCount = $oExtObjects->Count();
|
||||
if ($iCount == 1)
|
||||
$iObjectFoundCount = $oExtObjects->Count();
|
||||
if ($iObjectFoundCount == 1)
|
||||
{
|
||||
$oForeignObj = $oExtObjects->Fetch();
|
||||
$iForeignKey = $oForeignObj->GetKey();
|
||||
}
|
||||
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey] = array(
|
||||
'c' => $iCount,
|
||||
'c' => $iObjectFoundCount,
|
||||
'k' => $iForeignKey,
|
||||
'oql' => $oReconFilter->ToOql(),
|
||||
'h' => 0, // number of hits on this cache entry
|
||||
);
|
||||
}
|
||||
switch($iCount)
|
||||
switch($iObjectFoundCount)
|
||||
{
|
||||
case 0:
|
||||
$aErrors[$sAttCode] = Dict::S('UI:CSVReport-Value-Issue-NotFound');
|
||||
$aResults[$sAttCode]= new CellStatus_SearchIssue();
|
||||
break;
|
||||
|
||||
$oCellStatus_SearchIssue = $this->GetCellSearchIssue($oReconFilter);
|
||||
$aResults[$sAttCode] = $oCellStatus_SearchIssue;
|
||||
$aErrors[$sAttCode] = Dict::S('UI:CSVReport-Value-Issue-NotFound');
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Do change the external key attribute
|
||||
$oTargetObj->Set($sAttCode, $iForeignKey);
|
||||
break;
|
||||
|
||||
// Do change the external key attribute
|
||||
$oTargetObj->Set($sAttCode, $iForeignKey);
|
||||
break;
|
||||
|
||||
default:
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $iCount);
|
||||
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $iCount, $sOQL);
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $iObjectFoundCount);
|
||||
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $iObjectFoundCount, $oReconFilter->serialize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +569,7 @@ class BulkChange
|
||||
else
|
||||
{
|
||||
$aResults[$sAttCode]= new CellStatus_Modify($iForeignObj, $oTargetObj->GetOriginal($sAttCode));
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||
{
|
||||
// Report the change on reconciliation values as well
|
||||
$aResults[$iCol] = new CellStatus_Modify(utils::HtmlEntities($aRowData[$iCol]));
|
||||
@@ -446,7 +582,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set the object attributes
|
||||
//
|
||||
foreach ($this->m_aAttList as $sAttCode => $iCol)
|
||||
@@ -487,7 +623,13 @@ class BulkChange
|
||||
$value = $oAttDef->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
if (is_null($value) && (strlen($aRowData[$iCol]) > 0))
|
||||
{
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-NoMatch', $sAttCode);
|
||||
if ($oAttDef instanceof AttributeEnum || $oAttDef instanceof AttributeTagSet){
|
||||
/** @var AttributeDefinition $oAttributeDefinition */
|
||||
$oAttributeDefinition = $oAttDef;
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-AllowedValues', $sAttCode, implode(',', $oAttributeDefinition->GetAllowedValues()));
|
||||
} else {
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-NoMatch', $sAttCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -504,7 +646,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reporting on fields
|
||||
//
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
@@ -556,7 +698,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Checks
|
||||
//
|
||||
$res = $oTargetObj->CheckConsistency();
|
||||
@@ -567,12 +709,101 @@ class BulkChange
|
||||
}
|
||||
return $aResults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* search with current permissions did not match
|
||||
* let's search why and give some more feedbacks to the user through proper labels
|
||||
*
|
||||
* @param DBObjectSearch $oDbSearchWithConditions search used to find external key
|
||||
*
|
||||
* @return \CellStatus_SearchIssue
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*
|
||||
* @since 3.1.0 N°5305
|
||||
*/
|
||||
protected function GetCellSearchIssue($oDbSearchWithConditions) : CellStatus_SearchIssue {
|
||||
//current search with current permissions did not match
|
||||
//let's search why and give some more feedback to the user
|
||||
|
||||
$sSerializedSearch = $oDbSearchWithConditions->serialize();
|
||||
|
||||
// Count all objects with all permissions without any condition
|
||||
$oDbSearchWithoutAnyCondition = new DBObjectSearch($oDbSearchWithConditions->GetClass());
|
||||
$oDbSearchWithoutAnyCondition->AllowAllData(true);
|
||||
$oExtObjectSet = new CMDBObjectSet($oDbSearchWithoutAnyCondition);
|
||||
$iAllowAllDataObjectCount = $oExtObjectSet->Count();
|
||||
|
||||
if ($iAllowAllDataObjectCount === 0) {
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-NoObject', $oDbSearchWithConditions->GetClass());
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason);
|
||||
}
|
||||
|
||||
// Count all objects with current user permissions
|
||||
$oDbSearchWithoutAnyCondition->AllowAllData(false);
|
||||
$oExtObjectSetWithCurrentUserPermissions = new CMDBObjectSet($oDbSearchWithoutAnyCondition);
|
||||
$iCurrentUserRightsObjectCount = $oExtObjectSetWithCurrentUserPermissions->Count();
|
||||
|
||||
if ($iCurrentUserRightsObjectCount === 0){
|
||||
// No objects visible by current user
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser', $oDbSearchWithConditions->GetClass());
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason);
|
||||
}
|
||||
|
||||
try{
|
||||
$aDisplayedAllowedValues = [];
|
||||
// Possibles values are displayed to UI user. we have to limit the amount of displayed values
|
||||
$oExtObjectSetWithCurrentUserPermissions->SetLimit(4);
|
||||
for($i = 0; $i < 3; $i++){
|
||||
/** @var \DBObject $oVisibleObject */
|
||||
$oVisibleObject = $oExtObjectSetWithCurrentUserPermissions->Fetch();
|
||||
if (is_null($oVisibleObject)){
|
||||
break;
|
||||
}
|
||||
|
||||
$aCurrentAllowedValueFields = [];
|
||||
foreach ($oDbSearchWithConditions->GetInternalParams() as $sForeignAttCode => $sValue){
|
||||
$aCurrentAllowedValueFields[] = $oVisibleObject->Get($sForeignAttCode);
|
||||
}
|
||||
$aDisplayedAllowedValues[] = implode(" ", $aCurrentAllowedValueFields);
|
||||
|
||||
}
|
||||
$allowedValues = implode(", ", $aDisplayedAllowedValues);
|
||||
if ($oExtObjectSetWithCurrentUserPermissions->Count() > 3){
|
||||
$allowedValues .= "...";
|
||||
}
|
||||
} catch(Exception $e) {
|
||||
IssueLog::Error("failure during CSV import when fetching few visible objects: ", null,
|
||||
[ 'target_class' => $oDbSearchWithConditions->GetClass(), 'criteria' => $oDbSearchWithConditions->GetCriteria(), 'message' => $e->getMessage()]
|
||||
);
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser', $oDbSearchWithConditions->GetClass());
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason);
|
||||
}
|
||||
|
||||
if ($iAllowAllDataObjectCount != $iCurrentUserRightsObjectCount) {
|
||||
// No match and some objects NOT visible by current user. including current search maybe...
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser', $oDbSearchWithConditions->GetClass());
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues);
|
||||
}
|
||||
|
||||
// No match. This is not linked to any right issue
|
||||
// Possible values: DD,DD
|
||||
$aCurrentValueFields = [];
|
||||
foreach ($oDbSearchWithConditions->GetInternalParams() as $sValue){
|
||||
$aCurrentValueFields[] = $sValue;
|
||||
}
|
||||
$value =implode(" ", $aCurrentValueFields);
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch', $value);
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues);
|
||||
}
|
||||
|
||||
protected function PrepareMissingObject(&$oTargetObj, &$aErrors)
|
||||
{
|
||||
$aResults = array();
|
||||
$aErrors = array();
|
||||
|
||||
|
||||
// External keys
|
||||
//
|
||||
foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig)
|
||||
@@ -585,7 +816,7 @@ class BulkChange
|
||||
$aResults[$iCol] = new CellStatus_Void('?');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update attributes
|
||||
//
|
||||
foreach($this->m_aOnDisappear as $sAttCode => $value)
|
||||
@@ -596,7 +827,7 @@ class BulkChange
|
||||
}
|
||||
$oTargetObj->Set($sAttCode, $value);
|
||||
}
|
||||
|
||||
|
||||
// Reporting on fields
|
||||
//
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
@@ -616,7 +847,7 @@ class BulkChange
|
||||
$aResults[$iCol]= new CellStatus_Void($oTargetObj->Get($sAttCode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Checks
|
||||
//
|
||||
$res = $oTargetObj->CheckConsistency();
|
||||
@@ -674,14 +905,16 @@ class BulkChange
|
||||
}
|
||||
|
||||
$aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors);
|
||||
|
||||
|
||||
if (count($aErrors) > 0)
|
||||
{
|
||||
$sErrors = implode(', ', $aErrors);
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
||||
//__ERRORS__ used by tests only
|
||||
$aResult[$iRow]["__ERRORS__"] = new RowStatus_Error($sErrors);
|
||||
return $oTargetObj;
|
||||
}
|
||||
|
||||
|
||||
// Check that any external key will have a value proposed
|
||||
$aMissingKeys = array();
|
||||
foreach (MetaModel::GetExternalKeys($this->m_sClass) as $sExtKeyAttCode => $oExtKey)
|
||||
@@ -689,7 +922,7 @@ class BulkChange
|
||||
if (!$oExtKey->IsNullAllowed())
|
||||
{
|
||||
if (!array_key_exists($sExtKeyAttCode, $this->m_aExtKeys) && !array_key_exists($sExtKeyAttCode, $this->m_aAttList))
|
||||
{
|
||||
{
|
||||
$aMissingKeys[] = $oExtKey->GetLabel();
|
||||
}
|
||||
}
|
||||
@@ -745,14 +978,16 @@ class BulkChange
|
||||
{
|
||||
$sErrors = implode(', ', $aErrors);
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
||||
//__ERRORS__ used by tests only
|
||||
$aResult[$iRow]["__ERRORS__"] = new RowStatus_Error($sErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
if (count($aChangedFields) > 0)
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Modify(count($aChangedFields));
|
||||
|
||||
|
||||
// Optionaly record the results
|
||||
//
|
||||
if ($oChange)
|
||||
@@ -794,9 +1029,11 @@ class BulkChange
|
||||
{
|
||||
$sErrors = implode(', ', $aErrors);
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
||||
//__ERRORS__ used by tests only
|
||||
$aResult[$iRow]["__ERRORS__"] = new RowStatus_Error($sErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
if (count($aChangedFields) > 0)
|
||||
{
|
||||
@@ -821,7 +1058,7 @@ class BulkChange
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Disappeared(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function Process(CMDBChange $oChange = null)
|
||||
{
|
||||
if ($oChange)
|
||||
@@ -866,7 +1103,7 @@ class BulkChange
|
||||
foreach ($this->m_aAttList as $sAttCode => $iCol)
|
||||
{
|
||||
if ($sAttCode == 'id') continue;
|
||||
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
@@ -881,14 +1118,18 @@ class BulkChange
|
||||
$sFormat = $sDateFormat;
|
||||
}
|
||||
$oFormat = new DateTimeFormat($sFormat);
|
||||
$sDateExample = $oFormat->Format(new DateTime('2022-10-23 16:25:33'));
|
||||
$sRegExp = $oFormat->ToRegExpr('/');
|
||||
if (!preg_match($sRegExp, $this->m_aData[$iRow][$iCol]))
|
||||
$sErrorMsg = Dict::Format('UI:CSVReport-Row-Issue-ExpectedDateFormat', $sDateExample);
|
||||
if (!preg_match($sRegExp, $sValue))
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
$aResult[$iRow][$iCol] = new CellStatus_Issue(utils::HtmlEntities($sValue), null, $sErrorMsg);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$oDate = DateTime::createFromFormat($sFormat, $this->m_aData[$iRow][$iCol]);
|
||||
$oDate = DateTime::createFromFormat($sFormat, $sValue);
|
||||
if ($oDate !== false)
|
||||
{
|
||||
$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
|
||||
@@ -898,7 +1139,7 @@ class BulkChange
|
||||
{
|
||||
// Leave the cell unchanged
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, utils::HtmlEntities($this->m_aData[$iRow][$iCol]), Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
$aResult[$iRow][$iCol] = new CellStatus_Issue($sValue, null, $sErrorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -919,133 +1160,112 @@ class BulkChange
|
||||
}
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
|
||||
foreach($this->m_aData as $iRow => $aRowData)
|
||||
{
|
||||
set_time_limit(intval($iLoopTimeLimit));
|
||||
if (isset($aResult[$iRow]["__STATUS__"]))
|
||||
{
|
||||
// An issue at the earlier steps - skip the rest
|
||||
continue;
|
||||
|
||||
// Avoid too many events
|
||||
cmdbAbstractObject::SetEventDBLinksChangedBlocked(true);
|
||||
try {
|
||||
foreach ($this->m_aData as $iRow => $aRowData) {
|
||||
set_time_limit(intval($iLoopTimeLimit));
|
||||
if (isset($aResult[$iRow]["__STATUS__"])) {
|
||||
// An issue at the earlier steps - skip the rest
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$oReconciliationFilter = new DBObjectSearch($this->m_sClass);
|
||||
$bSkipQuery = false;
|
||||
foreach ($this->m_aReconcilKeys as $sAttCode) {
|
||||
$valuecondition = null;
|
||||
if (array_key_exists($sAttCode, $this->m_aExtKeys)) {
|
||||
if ($this->IsNullExternalKeySpec($aRowData, $sAttCode)) {
|
||||
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oExtKey->IsNullAllowed()) {
|
||||
$valuecondition = $oExtKey->GetNullValue();
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Void($oExtKey->GetNullValue());
|
||||
} else {
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_NullIssue();
|
||||
}
|
||||
} else {
|
||||
// The value has to be found or verified
|
||||
|
||||
/** var DBObjectSearch $oReconFilter */
|
||||
list($oReconFilter, $aMatches) = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]);
|
||||
|
||||
if (count($aMatches) == 1) {
|
||||
$oRemoteObj = reset($aMatches); // first item
|
||||
$valuecondition = $oRemoteObj->GetKey();
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Void($oRemoteObj->GetKey());
|
||||
} elseif (count($aMatches) == 0) {
|
||||
$oCellStatus_SearchIssue = $this->GetCellSearchIssue($oReconFilter);
|
||||
$aResult[$iRow][$sAttCode] = $oCellStatus_SearchIssue;
|
||||
} else {
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $oReconFilter->serialize());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The value is given in the data row
|
||||
$iCol = $this->m_aAttList[$sAttCode];
|
||||
if ($sAttCode == 'id') {
|
||||
$valuecondition = $aRowData[$iCol];
|
||||
} else {
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
$valuecondition = $oAttDef->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
}
|
||||
}
|
||||
if (is_null($valuecondition)) {
|
||||
$bSkipQuery = true;
|
||||
} else {
|
||||
$oReconciliationFilter->AddCondition($sAttCode, $valuecondition, '=');
|
||||
}
|
||||
}
|
||||
if ($bSkipQuery) {
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Reconciliation'));
|
||||
} else {
|
||||
$oReconciliationSet = new CMDBObjectSet($oReconciliationFilter);
|
||||
switch ($oReconciliationSet->Count()) {
|
||||
case 0:
|
||||
$oTargetObj = $this->CreateObject($aResult, $iRow, $aRowData, $oChange);
|
||||
// $aResult[$iRow]["__STATUS__"]=> set in CreateObject
|
||||
$aVisited[] = $oTargetObj->GetKey();
|
||||
break;
|
||||
case 1:
|
||||
$oTargetObj = $oReconciliationSet->Fetch();
|
||||
$this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange);
|
||||
// $aResult[$iRow]["__STATUS__"]=> set in UpdateObject
|
||||
if (!is_null($this->m_sSynchroScope)) {
|
||||
$aVisited[] = $oTargetObj->GetKey();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Found several matches, ambiguous
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Ambiguous'));
|
||||
$aResult[$iRow]["id"] = new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->serialize());
|
||||
$aResult[$iRow]["finalclass"] = 'n/a';
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::Format('UI:CSVReport-Row-Issue-Internal', get_class($e), $e->getMessage()));
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
$oReconciliationFilter = new DBObjectSearch($this->m_sClass);
|
||||
$bSkipQuery = false;
|
||||
foreach($this->m_aReconcilKeys as $sAttCode)
|
||||
{
|
||||
$valuecondition = null;
|
||||
if (array_key_exists($sAttCode, $this->m_aExtKeys))
|
||||
{
|
||||
if ($this->IsNullExternalKeySpec($aRowData, $sAttCode))
|
||||
{
|
||||
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oExtKey->IsNullAllowed())
|
||||
{
|
||||
$valuecondition = $oExtKey->GetNullValue();
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Void($oExtKey->GetNullValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_NullIssue();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The value has to be found or verified
|
||||
list($sQuery, $aMatches) = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]);
|
||||
|
||||
if (count($aMatches) == 1)
|
||||
{
|
||||
$oRemoteObj = reset($aMatches); // first item
|
||||
$valuecondition = $oRemoteObj->GetKey();
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Void($oRemoteObj->GetKey());
|
||||
}
|
||||
elseif (count($aMatches) == 0)
|
||||
{
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_SearchIssue();
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $sQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The value is given in the data row
|
||||
$iCol = $this->m_aAttList[$sAttCode];
|
||||
if ($sAttCode == 'id')
|
||||
{
|
||||
$valuecondition = $aRowData[$iCol];
|
||||
}
|
||||
else
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
$valuecondition = $oAttDef->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
}
|
||||
}
|
||||
if (is_null($valuecondition))
|
||||
{
|
||||
$bSkipQuery = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oReconciliationFilter->AddCondition($sAttCode, $valuecondition, '=');
|
||||
}
|
||||
}
|
||||
if ($bSkipQuery)
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Reconciliation'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$oReconciliationSet = new CMDBObjectSet($oReconciliationFilter);
|
||||
switch($oReconciliationSet->Count())
|
||||
{
|
||||
case 0:
|
||||
$oTargetObj = $this->CreateObject($aResult, $iRow, $aRowData, $oChange);
|
||||
// $aResult[$iRow]["__STATUS__"]=> set in CreateObject
|
||||
$aVisited[] = $oTargetObj->GetKey();
|
||||
break;
|
||||
case 1:
|
||||
$oTargetObj = $oReconciliationSet->Fetch();
|
||||
$this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange);
|
||||
// $aResult[$iRow]["__STATUS__"]=> set in UpdateObject
|
||||
if (!is_null($this->m_sSynchroScope))
|
||||
{
|
||||
$aVisited[] = $oTargetObj->GetKey();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Found several matches, ambiguous
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Ambiguous'));
|
||||
$aResult[$iRow]["id"]= new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->ToOql());
|
||||
$aResult[$iRow]["finalclass"]= 'n/a';
|
||||
|
||||
if (!is_null($this->m_sSynchroScope)) {
|
||||
// Compute the delta between the scope and visited objects
|
||||
$oScopeSearch = DBObjectSearch::FromOQL($this->m_sSynchroScope);
|
||||
$oScopeSet = new DBObjectSet($oScopeSearch);
|
||||
while ($oObj = $oScopeSet->Fetch()) {
|
||||
$iObj = $oObj->GetKey();
|
||||
if (!in_array($iObj, $aVisited)) {
|
||||
set_time_limit(intval($iLoopTimeLimit));
|
||||
$iRow++;
|
||||
$this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::Format('UI:CSVReport-Row-Issue-Internal', get_class($e), $e->getMessage()));
|
||||
}
|
||||
} finally {
|
||||
// Send all the retained events for further computations
|
||||
cmdbAbstractObject::SetEventDBLinksChangedBlocked(false);
|
||||
cmdbAbstractObject::FireEventDbLinksChangedForAllObjects();
|
||||
}
|
||||
|
||||
if (!is_null($this->m_sSynchroScope))
|
||||
{
|
||||
// Compute the delta between the scope and visited objects
|
||||
$oScopeSearch = DBObjectSearch::FromOQL($this->m_sSynchroScope);
|
||||
$oScopeSet = new DBObjectSet($oScopeSearch);
|
||||
while ($oObj = $oScopeSet->Fetch())
|
||||
{
|
||||
$iObj = $oObj->GetKey();
|
||||
if (!in_array($iObj, $aVisited))
|
||||
{
|
||||
set_time_limit(intval($iLoopTimeLimit));
|
||||
$iRow++;
|
||||
$this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
set_time_limit(intval($iPreviousTimeLimit));
|
||||
|
||||
// Fill in the blanks - the result matrix is expected to be 100% complete
|
||||
@@ -1110,7 +1330,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
$oBulkChanges->Seek(0);
|
||||
|
||||
|
||||
$aDetails = array();
|
||||
while ($oChange = $oBulkChanges->Fetch())
|
||||
{
|
||||
@@ -1274,7 +1494,7 @@ EOF
|
||||
$oOldTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('oldvalue'));
|
||||
$sOldValue = $oOldTarget->GetHyperlink();
|
||||
}
|
||||
|
||||
|
||||
$sNewValue = Dict::S('UI:UndefinedObject');
|
||||
if ($oOperation->Get('newvalue') != 0)
|
||||
{
|
||||
@@ -1300,11 +1520,11 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAttributes[$sAttCode] = 1;
|
||||
$aAttributes[$sAttCode] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$aDetails = array();
|
||||
foreach($aObjects as $iUId => $aObjData)
|
||||
{
|
||||
@@ -1356,6 +1576,6 @@ EOF
|
||||
$aConfig[$sAttCode] = array('label' => MetaModel::GetLabel($sClass, $sAttCode), 'description' => MetaModel::GetDescription($sClass, $sAttCode));
|
||||
}
|
||||
$oPage->table($aConfig, $aDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,22 +79,30 @@ class CMDBChangeOp extends DBObject implements iCMDBChangeOp
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
*/
|
||||
public function GetDescription()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Safety net: in case the change is not given, let's guarantee that it will
|
||||
* be set to the current ongoing change (or create a new one)
|
||||
*/
|
||||
* Safety net:
|
||||
* * if change isn't persisted yet, use the current change and persist it if needed
|
||||
* * in case the change is not given, let's guarantee that it will be set to the current ongoing change (or create a new one)
|
||||
*
|
||||
* @since 2.7.7 3.0.2 3.1.0 N°3717 do persist the current change if needed
|
||||
*/
|
||||
protected function OnInsert()
|
||||
{
|
||||
if ($this->Get('change') <= 0)
|
||||
{
|
||||
$this->Set('change', CMDBObject::GetCurrentChange());
|
||||
$iChange = $this->Get('change');
|
||||
if (($iChange <= 0) || (is_null($iChange))) {
|
||||
$oChange = CMDBObject::GetCurrentChange();
|
||||
if ($oChange->IsNew()) {
|
||||
$oChange->DBWrite();
|
||||
}
|
||||
$this->Set('change', $oChange);
|
||||
}
|
||||
|
||||
parent::OnInsert();
|
||||
}
|
||||
}
|
||||
@@ -342,20 +350,30 @@ class CMDBChangeOpSetAttributeURL extends CMDBChangeOpSetAttribute
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"key_type" => "",
|
||||
"name_attcode" => "change",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_changeop_setatt_url",
|
||||
"db_key_field" => "id",
|
||||
"category" => "core/cmdb",
|
||||
"key_type" => "",
|
||||
"name_attcode" => "change",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_changeop_setatt_url",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeURL("oldvalue", array("allowed_values"=>null, "sql"=>"oldvalue", "target" => '_blank', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeURL("newvalue", array("allowed_values"=>null, "sql"=>"newvalue", "target" => '_blank', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
|
||||
// N°4910 (oldvalue), N°5423 (newvalue)
|
||||
// We cannot have validation here, as AttributeUrl validation is field dependant.
|
||||
// The validation will be done when editing the iTop object, it isn't the history API responsibility
|
||||
//
|
||||
// Pattern is retrieved using this order :
|
||||
// 1. try to get the pattern from the field definition (datamodel)
|
||||
// 2. from the iTop config
|
||||
// 3. config parameter default value
|
||||
// see \AttributeURL::GetValidationPattern
|
||||
MetaModel::Init_AddAttribute(new AttributeURL("oldvalue", array("allowed_values" => null, "sql" => "oldvalue", "target" => '_blank', "default_value" => null, "is_null_allowed" => true, "depends_on" => array(), "validation_pattern" => '.*')));
|
||||
MetaModel::Init_AddAttribute(new AttributeURL("newvalue", array("allowed_values" => null, "sql" => "newvalue", "target" => '_blank', "default_value" => null, "is_null_allowed" => true, "depends_on" => array(), "validation_pattern" => '.*')));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list
|
||||
@@ -454,7 +472,7 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
|
||||
$sDisplayLabel = Dict::S('UI:OpenDocumentInNewWindow_');
|
||||
$sDisplayUrl = $oPrevDoc->GetDisplayURL(get_class($this), $this->GetKey(), 'prevdata');
|
||||
|
||||
$sDownloadLabel = Dict::Format('UI:DownloadDocument_');
|
||||
$sDownloadLabel = Dict::S('UI:DownloadDocument_');
|
||||
$sDownloadUrl = $oPrevDoc->GetDownloadURL(get_class($this), $this->GetKey(), 'prevdata');
|
||||
|
||||
$sDocView = <<<HTML
|
||||
@@ -867,7 +885,7 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
|
||||
*/
|
||||
protected function ToHtml($sRawText)
|
||||
{
|
||||
return str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sRawText, ENT_QUOTES, 'UTF-8'));
|
||||
return str_replace(array("\r\n", "\n", "\r"), "<br/>", utils::EscapeHtml($sRawText));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1159,9 +1177,8 @@ class CMDBChangeOpSetAttributeCustomFields extends CMDBChangeOpSetAttribute
|
||||
$oHandler = $oAttDef->GetHandler($aValues);
|
||||
$sValueDesc = $oHandler->GetAsHTML($aValues);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$sValueDesc = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8');
|
||||
catch (Exception $e) {
|
||||
$sValueDesc = 'Custom field error: '.utils::EscapeHtml($e->getMessage());
|
||||
}
|
||||
$sTextView = '<div>'.$sValueDesc.'</div>';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
@@ -113,6 +113,26 @@ abstract class CMDBObject extends DBObject
|
||||
self::$m_oCurrChange = $oChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sUserInfo
|
||||
* @param string $sOrigin
|
||||
* @param \DateTime $oDate
|
||||
*
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @since 2.7.7 3.0.2 3.1.0 N°3717 new method to reset current change
|
||||
*/
|
||||
public static function SetCurrentChangeFromParams($sUserInfo, $sOrigin = null, $oDate = null)
|
||||
{
|
||||
static::SetTrackInfo($sUserInfo);
|
||||
static::SetTrackOrigin($sOrigin);
|
||||
static::CreateChange();
|
||||
|
||||
if (!is_null($oDate)) {
|
||||
static::$m_oCurrChange->Set("date", $oDate);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Todo: simplify the APIs and do not pass the current change as an argument anymore
|
||||
// SetTrackInfo to be invoked in very few cases (UI.php, CSV import, Data synchro)
|
||||
@@ -144,6 +164,8 @@ abstract class CMDBObject extends DBObject
|
||||
* $oMyChange->Set("userinfo", 'this is done by ... for ...');
|
||||
* $iChangeId = $oMyChange->DBInsert();
|
||||
*
|
||||
* **warning** : this will do nothing if current change already exists !
|
||||
*
|
||||
* @see SetCurrentChange to specify a CMDBObject instance instead
|
||||
*
|
||||
* @param string $sInfo
|
||||
@@ -171,6 +193,8 @@ abstract class CMDBObject extends DBObject
|
||||
/**
|
||||
* Provides information about the origin of the change
|
||||
*
|
||||
* **warning** : this will do nothing if current change already exists !
|
||||
*
|
||||
* @see SetTrackInfo
|
||||
* @see SetCurrentChange to specify a CMDBObject instance instead
|
||||
*
|
||||
@@ -181,19 +205,21 @@ abstract class CMDBObject extends DBObject
|
||||
{
|
||||
self::$m_sOrigin = $sOrigin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the additional information (defaulting to user name)
|
||||
*/
|
||||
protected static function GetTrackInfo()
|
||||
*/
|
||||
public static function GetTrackInfo()
|
||||
{
|
||||
if (is_null(self::$m_sInfo))
|
||||
{
|
||||
if (is_null(self::$m_sInfo)) {
|
||||
return CMDBChange::GetCurrentUserName();
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::$m_sInfo;
|
||||
} else {
|
||||
//N°5135 - add impersonation information in activity log/current cmdb change
|
||||
if (UserRights::IsImpersonated()){
|
||||
return sprintf("%s (%s)", CMDBChange::GetCurrentUserName(), self::$m_sInfo);
|
||||
} else {
|
||||
return self::$m_sInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +232,10 @@ abstract class CMDBObject extends DBObject
|
||||
*/
|
||||
protected static function GetTrackUserId()
|
||||
{
|
||||
if (is_null(self::$m_sUserId))
|
||||
if (is_null(self::$m_sUserId)
|
||||
//N°5135 - indicate impersonation inside changelogs
|
||||
&& (false === UserRights::IsImpersonated())
|
||||
)
|
||||
{
|
||||
return CMDBChange::GetCurrentUserId();
|
||||
}
|
||||
@@ -215,10 +244,10 @@ abstract class CMDBObject extends DBObject
|
||||
return self::$m_sUserId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the 'origin' information (defaulting to 'interactive')
|
||||
*/
|
||||
*/
|
||||
protected static function GetTrackOrigin()
|
||||
{
|
||||
if (is_null(self::$m_sOrigin))
|
||||
@@ -243,15 +272,17 @@ abstract class CMDBObject extends DBObject
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*
|
||||
* @since 2.7.7 3.0.2 3.1.0 N°3717 {@see CMDBChange} **will be persisted later** in {@see \CMDBChangeOp::OnInsert} (was done previously directly here)
|
||||
* This will avoid creating in DB CMDBChange lines without any corresponding CMDBChangeOp
|
||||
*/
|
||||
protected static function CreateChange()
|
||||
public static function CreateChange()
|
||||
{
|
||||
self::$m_oCurrChange = MetaModel::NewObject("CMDBChange");
|
||||
self::$m_oCurrChange->Set("date", time());
|
||||
self::$m_oCurrChange->Set("userinfo", self::GetTrackInfo());
|
||||
self::$m_oCurrChange->Set("user_id", self::GetTrackUserId());
|
||||
self::$m_oCurrChange->Set("origin", self::GetTrackOrigin());
|
||||
self::$m_oCurrChange->DBInsert();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,7 +499,7 @@ abstract class CMDBObject extends DBObject
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value[$sAttCode]);
|
||||
$oMyChangeOp->Set("newvalue", $value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeCustomFields)
|
||||
@@ -592,6 +623,9 @@ abstract class CMDBObject extends DBObject
|
||||
return $this->DBCloneTracked_Internal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.1.0 N°5232 not used
|
||||
*/
|
||||
public function DBCloneTracked(CMDBChange $oChange, $newKey = null)
|
||||
{
|
||||
self::SetCurrentChange($oChange);
|
||||
@@ -601,25 +635,11 @@ abstract class CMDBObject extends DBObject
|
||||
protected function DBCloneTracked_Internal($newKey = null)
|
||||
{
|
||||
$newKey = parent::DBClone($newKey);
|
||||
$oClone = MetaModel::GetObject(get_class($this), $newKey);
|
||||
$oClone = MetaModel::GetObject(get_class($this), $newKey);
|
||||
|
||||
return $newKey;
|
||||
}
|
||||
|
||||
public function DBUpdate()
|
||||
{
|
||||
// Copy the changes list before the update (the list should be reset afterwards)
|
||||
$aChanges = $this->ListChanges();
|
||||
if (count($aChanges) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$ret = parent::DBUpdate();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param null $oDeletionPlan
|
||||
*
|
||||
@@ -654,46 +674,7 @@ abstract class CMDBObject extends DBObject
|
||||
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
|
||||
{
|
||||
$ret = parent::DBDelete($oDeletionPlan);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function BulkUpdate(DBSearch $oFilter, array $aValues)
|
||||
{
|
||||
return static::BulkUpdateTracked_Internal($oFilter, $aValues);
|
||||
}
|
||||
|
||||
public static function BulkUpdateTracked(CMDBChange $oChange, DBSearch $oFilter, array $aValues)
|
||||
{
|
||||
self::SetCurrentChange($oChange);
|
||||
static::BulkUpdateTracked_Internal($oFilter, $aValues);
|
||||
}
|
||||
|
||||
protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues)
|
||||
{
|
||||
// $aValues is an array of $sAttCode => $value
|
||||
|
||||
// Get the list of objects to update (and load it before doing the change)
|
||||
$oObjSet = new CMDBObjectSet($oFilter);
|
||||
$oObjSet->Load();
|
||||
|
||||
// Keep track of the previous values (will be overwritten when the objects are synchronized with the DB)
|
||||
$aOriginalValues = array();
|
||||
$oObjSet->Rewind();
|
||||
while ($oItem = $oObjSet->Fetch())
|
||||
{
|
||||
$aOriginalValues[$oItem->GetKey()] = $oItem->m_aOrigValues;
|
||||
}
|
||||
|
||||
// Update in one single efficient query
|
||||
$ret = parent::BulkUpdate($oFilter, $aValues);
|
||||
|
||||
// Record... in many queries !!!
|
||||
$oObjSet->Rewind();
|
||||
while ($oItem = $oObjSet->Fetch())
|
||||
{
|
||||
$aChangedValues = $oItem->ListChangedValues($aValues);
|
||||
$oItem->RecordAttChanges($aChangedValues, $aOriginalValues[$oItem->GetKey()]);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
@@ -738,11 +719,11 @@ abstract class CMDBObject extends DBObject
|
||||
class CMDBObjectSet extends DBObjectSet
|
||||
{
|
||||
// this is the public interface (?)
|
||||
|
||||
|
||||
// I have to define those constructors here... :-(
|
||||
// just to get the right object class in return.
|
||||
// I have to think again to those things: maybe it will work fine if a have a constructor define here (?)
|
||||
|
||||
|
||||
static public function FromScratch($sClass)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($sClass);
|
||||
@@ -751,7 +732,7 @@ class CMDBObjectSet extends DBObjectSet
|
||||
// NOTE: THIS DOES NOT WORK IF m_bLoaded is private in the base class (and you will not get any error message)
|
||||
$oRetSet->m_bLoaded = true; // no DB load
|
||||
return $oRetSet;
|
||||
}
|
||||
}
|
||||
|
||||
// create an object set ex nihilo
|
||||
// input = array of objects
|
||||
@@ -760,7 +741,7 @@ class CMDBObjectSet extends DBObjectSet
|
||||
$oRetSet = self::FromScratch($sClass);
|
||||
$oRetSet->AddObjectArray($aObjects, $sClass);
|
||||
return $oRetSet;
|
||||
}
|
||||
}
|
||||
|
||||
static public function FromArrayAssoc($aClasses, $aObjects)
|
||||
{
|
||||
|
||||
@@ -157,7 +157,7 @@ class CMDBSource
|
||||
$iPort = null;
|
||||
self::InitServerAndPort($sDbHost, $sServer, $iPort);
|
||||
|
||||
$iFlags = null;
|
||||
$iFlags = 0;
|
||||
|
||||
// *some* errors (like connection errors) will throw mysqli_sql_exception instead of generating warnings printed to the output
|
||||
// but some other errors will still cause the query() method to return false !!!
|
||||
@@ -166,7 +166,6 @@ class CMDBSource
|
||||
try
|
||||
{
|
||||
$oMysqli = new mysqli();
|
||||
$oMysqli->init();
|
||||
|
||||
if ($bTlsEnabled)
|
||||
{
|
||||
@@ -351,19 +350,21 @@ class CMDBSource
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of the database server.
|
||||
*
|
||||
* @return string
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @uses \CMDBSource::QueryToCol() so needs a connection opened !
|
||||
* @uses \CMDBSource::QueryToScalar() so needs a connection opened !
|
||||
*/
|
||||
public static function GetDBVersion()
|
||||
{
|
||||
$aVersions = self::QueryToCol('SELECT Version() as version', 'version');
|
||||
return $aVersions[0];
|
||||
return static::QueryToScalar('SELECT VERSION()', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @deprecated Use `CMDBSource::GetDBVersion` instead.
|
||||
* @uses mysqli_get_server_info
|
||||
*/
|
||||
public static function GetServerInfo()
|
||||
{
|
||||
@@ -705,7 +706,11 @@ class CMDBSource
|
||||
private static function Commit()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if(isset($aStackTrace[2]['class']) && isset($aStackTrace[2]['function'])) {
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
} else {
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].') ';
|
||||
}
|
||||
if (!self::IsInsideTransaction()) {
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction COMMIT $sCaller", LogChannels::CMDB_SOURCE);
|
||||
@@ -739,7 +744,11 @@ class CMDBSource
|
||||
private static function Rollback()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if(isset($aStackTrace[2]['class']) && isset($aStackTrace[2]['function'])) {
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
} else {
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].') ';
|
||||
}
|
||||
if (!self::IsInsideTransaction()) {
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction ROLLBACK $sCaller", LogChannels::CMDB_SOURCE);
|
||||
@@ -1499,20 +1508,14 @@ class CMDBSource
|
||||
* Returns the value of the specified server variable
|
||||
* @param string $sVarName Name of the server variable
|
||||
* @return mixed Current value of the variable
|
||||
*/
|
||||
* @throws \MySQLQueryHasNoResultException|\MySQLException
|
||||
*/
|
||||
public static function GetServerVariable($sVarName)
|
||||
{
|
||||
$result = '';
|
||||
$sSql = "SELECT @@$sVarName as theVar";
|
||||
$aRows = self::QueryToArray($sSql);
|
||||
if (count($aRows) > 0)
|
||||
{
|
||||
$result = $aRows[0]['theVar'];
|
||||
}
|
||||
return $result;
|
||||
$sSql = 'SELECT @@'.$sVarName;
|
||||
return static::QueryToScalar($sSql, 0) ?: '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the privileges of the current user
|
||||
* @return string privileges in a raw format
|
||||
@@ -1608,4 +1611,22 @@ class CMDBSource
|
||||
|
||||
return 'ALTER DATABASE'.CMDBSource::GetSqlStringColumnDefinition().';';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check which mysql client option (--ssl or --ssl-mode) to be used for encrypted connection
|
||||
*
|
||||
* @return bool true if --ssl-mode should be used, false otherwise
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @link https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#encrypted-connection-options "Command Options for Encrypted Connections"
|
||||
*/
|
||||
public static function IsSslModeDBVersion()
|
||||
{
|
||||
if (static::GetDBVendor() === static::ENUM_DB_VENDOR_MYSQL)
|
||||
{
|
||||
//Mysql 5.7.0 and upper deprecated --ssl and uses --ssl-mode instead
|
||||
return version_compare(static::GetDBVersion(), '5.7.11', '>=');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ define('ITOP_APPLICATION_SHORT', 'iTop');
|
||||
*
|
||||
* @see ITOP_CORE_VERSION to get iTop core version
|
||||
*/
|
||||
define('ITOP_VERSION', '3.0.1-dev');
|
||||
define('ITOP_VERSION', '3.1.0-dev');
|
||||
|
||||
define('ITOP_VERSION_NAME', 'Fullmoon');
|
||||
define('ITOP_REVISION', 'svn');
|
||||
@@ -129,6 +129,22 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'event_service.debug.filter_events' => [
|
||||
'type' => 'array',
|
||||
'description' => 'List of events name to filter Event Service debug messages',
|
||||
'default' => [],
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'event_service.debug.filter_sources' => [
|
||||
'type' => 'array',
|
||||
'description' => 'List of event sources to filter Event Service debug messages',
|
||||
'default' => '',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'app_env_label' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Label displayed to describe the current application environment, defaults to the environment name (e.g. "production")',
|
||||
@@ -326,11 +342,11 @@ class Config
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'allow_menu_on_linkset' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Display Action menus in view mode on any LinkedSet with edit_mode != none',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'type' => 'bool',
|
||||
'description' => 'Display Action menus in view mode on any LinkedSet with edit_mode != none',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'allow_target_creation' => [
|
||||
@@ -530,7 +546,7 @@ class Config
|
||||
],
|
||||
'email_transport' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Mean to send emails: PHPMail (uses the function mail()) or SMTP (implements the client protocol)',
|
||||
'description' => 'Mean to send emails: PHPMail (uses the function mail()), SMTP (implements the client protocol) or SMTP_OAuth (connect to the server using OAuth 2.0)',
|
||||
'default' => "PHPMail",
|
||||
'value' => "PHPMail",
|
||||
'source_of_value' => '',
|
||||
@@ -552,7 +568,7 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'email_transport_smtp.encryption' => [
|
||||
'email_transport_smtp.encryption' => [
|
||||
'type' => 'string',
|
||||
'description' => 'tls or ssl (optional)',
|
||||
'default' => "",
|
||||
@@ -560,22 +576,38 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'email_transport_smtp.username' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Authentication user (optional)',
|
||||
'default' => "",
|
||||
'value' => "",
|
||||
'source_of_value' => '',
|
||||
'email_transport_smtp.username' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Authentication user (optional)',
|
||||
'default' => "",
|
||||
'value' => "",
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'email_transport_smtp.password' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Authentication password (optional)',
|
||||
'default' => "",
|
||||
'value' => "",
|
||||
'source_of_value' => '',
|
||||
'email_transport_smtp.password' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Authentication password (optional)',
|
||||
'default' => "",
|
||||
'value' => "",
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'email_transport_smtp.allow_self_signed' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Allow self signed peer certificates',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'email_transport_smtp.verify_peer' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Verify peer certificate',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'email_css' => [
|
||||
'type' => 'string',
|
||||
'description' => 'CSS that will override the standard stylesheet used for the notifications',
|
||||
@@ -1008,8 +1040,8 @@ class Config
|
||||
'type' => 'integer',
|
||||
'description' => 'Maximum length of the history table (in the "History" tab on each object) before it gets truncated. Latest modifications are displayed first.',
|
||||
// examples... not used
|
||||
'default' => 50,
|
||||
'value' => 50,
|
||||
'default' => 200,
|
||||
'value' => 200,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
@@ -1238,6 +1270,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'navigation_menu.show_organization_filter' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Display organization filter in menu',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'quick_create.enabled' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not the quick create is enabled',
|
||||
@@ -1456,14 +1496,6 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'use_legacy_dbsearch' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If set, DBSearch will use legacy SQL query generation',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'query_cache_enabled' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If set, DBSearch will use cache for query generation',
|
||||
@@ -1513,11 +1545,11 @@ class Config
|
||||
'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' => '',
|
||||
'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' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'behind_reverse_proxy' => [
|
||||
@@ -1552,6 +1584,22 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'setup.launch_button.enabled' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true displays in the Application Upgrade screen a button allowing to launch the setup in a single click (no more manual config file permission change needed)',
|
||||
'default' => null,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'audit.enable_selection_landing_page' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true audit categories must be selected before results are computed (use this setting in case of a lot of audit categories)',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
];
|
||||
|
||||
public function IsProperty($sPropCode)
|
||||
@@ -1869,9 +1917,9 @@ class Config
|
||||
}
|
||||
if (strlen($sNoise) > 0)
|
||||
{
|
||||
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
|
||||
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
|
||||
throw new ConfigException('Syntax error in configuration file',
|
||||
array('file' => $sConfigFile, 'error' => '<tt>'.htmlentities($sNoise, ENT_QUOTES, 'UTF-8').'</tt>'));
|
||||
array('file' => $sConfigFile, 'error' => '<tt>'.utils::EscapeHtml($sNoise, ENT_QUOTES).'</tt>'));
|
||||
}
|
||||
|
||||
if (!isset($MySettings) || !is_array($MySettings))
|
||||
@@ -2254,7 +2302,7 @@ class Config
|
||||
$oHandle = null;
|
||||
$sConfig = null;
|
||||
|
||||
if (is_file($this->m_sFile))
|
||||
if ($this->m_sFile !== null && is_file($this->m_sFile))
|
||||
{
|
||||
$oHandle = fopen($this->m_sFile, 'r');
|
||||
$index = 0;
|
||||
@@ -2429,10 +2477,15 @@ class Config
|
||||
/**
|
||||
* Helper function to initialize a configuration from the page arguments
|
||||
*
|
||||
* @see \Parameters::GetParamForConfigArray() to get aParamValues from {@see Parameters} object hierarchy in setup
|
||||
* @see \WizardController::GetParamForConfigArray() to get aParamValues from {@see \WizardController} object hierarchy in setup
|
||||
*
|
||||
* @param array $aParamValues
|
||||
* @param string|null $sModulesDir
|
||||
* @param ?string $sModulesDir
|
||||
* @param bool $bPreserveModuleSettings
|
||||
*
|
||||
* @return void The current object is modified directly
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \CoreException
|
||||
*/
|
||||
@@ -2679,7 +2732,7 @@ class ConfigPlaceholdersResolver
|
||||
}
|
||||
|
||||
$sPattern = '/\%(env|server)\((\w+)\)(?:\?:(\w*))?\%/'; //3 capturing groups, ie `%env(HTTP_PORT)?:8080%` produce: `env` `HTTP_PORT` and `8080`.
|
||||
|
||||
|
||||
if (! preg_match_all($sPattern, $rawValue, $aMatchesCollection, PREG_SET_ORDER))
|
||||
{
|
||||
return $rawValue;
|
||||
|
||||
@@ -52,17 +52,23 @@
|
||||
*/
|
||||
class ContextTag
|
||||
{
|
||||
const TAG_PORTAL = 'GUI:Portal';
|
||||
const TAG_CRON = 'CRON';
|
||||
const TAG_CONSOLE = 'GUI:Console';
|
||||
const TAG_SETUP = 'Setup';
|
||||
const TAG_SYNCHRO = 'Synchro';
|
||||
const TAG_REST = 'REST/JSON';
|
||||
public const TAG_PORTAL = 'GUI:Portal';
|
||||
public const TAG_CRON = 'CRON';
|
||||
public const TAG_CONSOLE = 'GUI:Console';
|
||||
public const TAG_SETUP = 'Setup';
|
||||
public const TAG_SYNCHRO = 'Synchro';
|
||||
public const TAG_REST = 'REST/JSON';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.1.0 N°3200
|
||||
*/
|
||||
public const TAG_OBJECT_SEARCH = 'ObjectSearch';
|
||||
|
||||
protected static $aStack = array();
|
||||
|
||||
/**
|
||||
* Store a context tag on the stack
|
||||
*
|
||||
* @param string $sTag
|
||||
*/
|
||||
public function __construct($sTag)
|
||||
|
||||
@@ -125,8 +125,8 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$sRawSeparator = utils::ReadParam('separator', ',', true, 'raw_data');
|
||||
$sCustomDateTimeFormat = utils::ReadParam('', ',', true, 'raw_data');
|
||||
$aSep = array(
|
||||
';' => Dict::S('UI:CSVImport:SeparatorSemicolon+'),
|
||||
',' => Dict::S('UI:CSVImport:SeparatorComma+'),
|
||||
';' => Dict::S('UI:CSVImport:SeparatorSemicolon+'),
|
||||
',' => Dict::S('UI:CSVImport:SeparatorComma+'),
|
||||
'tab' => Dict::S('UI:CSVImport:SeparatorTab+'),
|
||||
);
|
||||
$sOtherSeparator = '';
|
||||
@@ -134,10 +134,10 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$sOtherSeparator = $sRawSeparator;
|
||||
$sRawSeparator = 'other';
|
||||
}
|
||||
$aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' <input type="text" size="3" name="other-separator" value="'.htmlentities($sOtherSeparator, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' <input type="text" size="3" name="other-separator" value="'.utils::EscapeHtml($sOtherSeparator).'"/>';
|
||||
|
||||
foreach ($aSep as $sVal => $sLabel) {
|
||||
$oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "separator", htmlentities($sVal, ENT_QUOTES, 'UTF-8'), $sLabel, "radio");
|
||||
$oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "separator", utils::EscapeHtml($sVal), $sLabel, "radio");
|
||||
$oRadio->GetInput()->SetIsChecked(($sVal == $sRawSeparator));
|
||||
$oRadio->SetBeforeInput(false);
|
||||
$oRadio->GetInput()->AddCSSClass('ibo-input--label-right');
|
||||
@@ -152,7 +152,7 @@ class CSVBulkExport extends TabularBulkExport
|
||||
|
||||
$sRawQualifier = utils::ReadParam('text-qualifier', '"', true, 'raw_data');
|
||||
$aQualifiers = array(
|
||||
'"' => Dict::S('UI:CSVImport:QualifierDoubleQuote+'),
|
||||
'"' => Dict::S('UI:CSVImport:QualifierDoubleQuote+'),
|
||||
'\'' => Dict::S('UI:CSVImport:QualifierSimpleQuote+'),
|
||||
);
|
||||
$sOtherQualifier = '';
|
||||
@@ -160,10 +160,10 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$sOtherQualifier = $sRawQualifier;
|
||||
$sRawQualifier = 'other';
|
||||
}
|
||||
$aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' <input type="text" size="3" name="other-text-qualifier" value="'.htmlentities($sOtherQualifier, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' <input type="text" size="3" name="other-text-qualifier" value="'.utils::EscapeHtml($sOtherQualifier).'"/>';
|
||||
|
||||
foreach ($aQualifiers as $sVal => $sLabel) {
|
||||
$oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "text-qualifier", htmlentities($sVal, ENT_QUOTES, 'UTF-8'), $sLabel, "radio");
|
||||
$oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "text-qualifier", utils::EscapeHtml($sVal), $sLabel, "radio");
|
||||
$oRadio->GetInput()->SetIsChecked(($sVal == $sRawSeparator));
|
||||
$oRadio->SetBeforeInput(false);
|
||||
$oRadio->GetInput()->AddCSSClass('ibo-input--label-right');
|
||||
@@ -209,8 +209,8 @@ class CSVBulkExport extends TabularBulkExport
|
||||
|
||||
$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
|
||||
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
|
||||
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
|
||||
$sDefaultFormat = utils::EscapeHtml((string)AttributeDateTime::GetFormat());
|
||||
$sExample = utils::EscapeHtml(date((string)AttributeDateTime::GetFormat()));
|
||||
$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);
|
||||
@@ -218,7 +218,7 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$oFieldSetDate->AddSubBlock($oRadioDefault);
|
||||
$oFieldSetDate->AddSubBlock(new Html('</br>'));
|
||||
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="csv_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="csv_custom_date_time_format" title="" value="'.utils::EscapeHtml($sDateTimeFormat).'"/>';
|
||||
$oRadioCustom = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput), "csv_date_format_radio", "custom", "csv_date_time_format_custom", "radio");
|
||||
$oRadioCustom->SetDescription(Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip'));
|
||||
$oRadioCustom->GetInput()->SetIsChecked($sDateTimeFormat !== (string)AttributeDateTime::GetFormat());
|
||||
@@ -246,17 +246,18 @@ EOF
|
||||
}
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
{
|
||||
{
|
||||
if ($sAttCode != 'id') {
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.utils::EscapeHtml($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj)).'</div>';
|
||||
}
|
||||
}
|
||||
return '<div class="text-preview">'.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
|
||||
return '<div class="text-preview">'.utils::EscapeHtml($this->GetValue($oObj, $sAttCode)).'</div>';
|
||||
}
|
||||
|
||||
protected function GetValue($oObj, $sAttCode)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.1">
|
||||
<user_rights>
|
||||
<profiles>
|
||||
<profile id="1024" _delta="define">
|
||||
@@ -270,4 +270,4 @@
|
||||
</class>
|
||||
</classes>
|
||||
</meta>
|
||||
</itop_design>
|
||||
</itop_design>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,7 @@ interface iDBObjectSetIterator extends Countable
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function Count();
|
||||
public function Count(): int;
|
||||
|
||||
/**
|
||||
* Reset the cursor to the first item in the collection. Equivalent to Seek(0)
|
||||
@@ -52,7 +52,7 @@ interface iDBObjectSetIterator extends Countable
|
||||
*
|
||||
* @param int $iRow
|
||||
*/
|
||||
public function Seek($iPosition);
|
||||
public function Seek($iPosition): void;
|
||||
|
||||
/**
|
||||
* Fetch the object at the current position in the collection and move the cursor to the next position.
|
||||
|
||||
@@ -413,6 +413,10 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
|
||||
/**
|
||||
* Important: If you need to add a condition on the same $sFilterCode several times with different $value values; do not use this method as the previous $value occurences will be replaced by the last. Instead use:
|
||||
* * {@see \DBObjectSearch::AddConditionExpression()} in loops to add conditions one by one
|
||||
* * {@see \DBObjectSearch::AddConditionForInOperatorUsingParam()} for IN/NOT IN queries with lots of params at once
|
||||
*
|
||||
* @param string $sFilterCode
|
||||
* @param mixed $value
|
||||
* @param string $sOpCode operator to use : 'IN', 'NOT IN', 'Contains',' Begins with', 'Finishes with', ...
|
||||
@@ -423,22 +427,16 @@ class DBObjectSearch extends DBSearch
|
||||
* @param bool $bParseSearchString
|
||||
*
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @see AddConditionForInOperatorUsingParam for IN/NOT IN queries with lots of params
|
||||
*/
|
||||
public function AddCondition($sFilterCode, $value, $sOpCode = null, $bParseSearchString = false)
|
||||
{
|
||||
MyHelpers::CheckKeyInArray('filter code in class: '.$this->GetClass(), $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass()));
|
||||
MyHelpers::CheckKeyInArray('filter code in class: '.$this->GetClass(), $sFilterCode, MetaModel::GetFilterAttribList($this->GetClass()));
|
||||
|
||||
$oField = new FieldExpression($sFilterCode, $this->GetClassAlias());
|
||||
if (empty($sOpCode))
|
||||
{
|
||||
if ($sFilterCode == 'id')
|
||||
{
|
||||
if (empty($sOpCode)) {
|
||||
if ($sFilterCode == 'id') {
|
||||
$sOpCode = '=';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sFilterCode);
|
||||
$oNewCondition = $oAttDef->GetSmartConditionExpression($value, $oField, $this->m_aParams);
|
||||
$this->AddConditionExpression($oNewCondition);
|
||||
@@ -1900,16 +1898,13 @@ class DBObjectSearch extends DBSearch
|
||||
// Hide objects that are not visible to the current user
|
||||
//
|
||||
$oSearch = $this;
|
||||
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered())
|
||||
{
|
||||
if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered()) {
|
||||
$oVisibleObjects = UserRights::GetSelectFilter($this->GetClass(), $this->GetModifierProperties('UserRightsGetSelectFilter'));
|
||||
if ($oVisibleObjects === false)
|
||||
{
|
||||
if ($oVisibleObjects === false) {
|
||||
// Make sure this is a valid search object, saying NO for all
|
||||
$oVisibleObjects = DBObjectSearch::FromEmptySet($this->GetClass());
|
||||
}
|
||||
if (is_object($oVisibleObjects))
|
||||
{
|
||||
if (is_object($oVisibleObjects)) {
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $this->Intersect($oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
@@ -1929,63 +1924,49 @@ class DBObjectSearch extends DBSearch
|
||||
if (isset($_SERVER['REQUEST_URI']))
|
||||
{
|
||||
$aContextData['sRequestUri'] = $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
else if (isset($_SERVER['SCRIPT_NAME']))
|
||||
{
|
||||
} else if (isset($_SERVER['SCRIPT_NAME'])) {
|
||||
$aContextData['sRequestUri'] = $_SERVER['SCRIPT_NAME'];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aContextData['sRequestUri'] = '';
|
||||
}
|
||||
|
||||
// Need to identify the query
|
||||
$sOqlQuery = $oSearch->ToOql(false, null, true);
|
||||
if ((strpos($sOqlQuery, '`id` IN (') !== false) || (strpos($sOqlQuery, '`id` NOT IN (') !== false))
|
||||
{
|
||||
if ((strpos($sOqlQuery, '`id` IN (') !== false) || (strpos($sOqlQuery, '`id` NOT IN (') !== false)) {
|
||||
// Requests containing "id IN" are not worth caching
|
||||
$bCanCache = false;
|
||||
}
|
||||
|
||||
$aContextData['sOqlQuery'] = $sOqlQuery;
|
||||
|
||||
if (count($aModifierProperties))
|
||||
{
|
||||
if (count($aModifierProperties)) {
|
||||
array_multisort($aModifierProperties);
|
||||
$sModifierProperties = json_encode($aModifierProperties);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sModifierProperties = '';
|
||||
}
|
||||
$aContextData['sModifierProperties'] = $sModifierProperties;
|
||||
|
||||
$sRawId = Dict::GetUserLanguage().'-'.$sOqlQuery.$sModifierProperties;
|
||||
if (!is_null($aAttToLoad))
|
||||
{
|
||||
if (!is_null($aAttToLoad)) {
|
||||
$sRawId .= json_encode($aAttToLoad);
|
||||
}
|
||||
$aContextData['aAttToLoad'] = $aAttToLoad;
|
||||
if (!is_null($aGroupByExpr))
|
||||
{
|
||||
foreach($aGroupByExpr as $sAlias => $oExpr)
|
||||
{
|
||||
$sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render();
|
||||
if (!is_null($aGroupByExpr)) {
|
||||
foreach ($aGroupByExpr as $sAlias => $oExpr) {
|
||||
$sRawId .= 'g:'.$sAlias.'!'.$oExpr->RenderExpression();
|
||||
}
|
||||
}
|
||||
if (!is_null($aSelectExpr))
|
||||
{
|
||||
foreach($aSelectExpr as $sAlias => $oExpr)
|
||||
{
|
||||
$sRawId .= 'se:'.$sAlias.'!'.$oExpr->Render();
|
||||
if (!is_null($aSelectExpr)) {
|
||||
foreach ($aSelectExpr as $sAlias => $oExpr) {
|
||||
$sRawId .= 'se:'.$sAlias.'!'.$oExpr->RenderExpression();
|
||||
}
|
||||
}
|
||||
$aContextData['aGroupByExpr'] = $aGroupByExpr;
|
||||
$aContextData['aSelectExpr'] = $aSelectExpr;
|
||||
$sRawId .= $bGetCount;
|
||||
$aContextData['bGetCount'] = $bGetCount;
|
||||
if (is_array($aSelectedClasses))
|
||||
{
|
||||
if (is_array($aSelectedClasses)) {
|
||||
$sRawId .= implode(',', $aSelectedClasses); // Unions may alter the list of selected columns
|
||||
}
|
||||
$aContextData['aSelectedClasses'] = $aSelectedClasses;
|
||||
@@ -2096,17 +2077,13 @@ class DBObjectSearch extends DBSearch
|
||||
// 3rd step - position the attributes in the hierarchy of classes
|
||||
//
|
||||
$oSubClassExp->Browse(function($oNode) use ($sSubClass) {
|
||||
if ($oNode instanceof FieldExpression)
|
||||
{
|
||||
if ($oNode instanceof FieldExpression) {
|
||||
$sAttCode = $oNode->GetName();
|
||||
$oAttDef = MetaModel::GetAttributeDef($sSubClass, $sAttCode);
|
||||
if ($oAttDef->IsExternalField())
|
||||
{
|
||||
if ($oAttDef->IsExternalField()) {
|
||||
$sKeyAttCode = $oAttDef->GetKeyAttCode();
|
||||
$sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sKeyAttCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sAttCode);
|
||||
}
|
||||
$sParent = MetaModel::GetAttributeOrigin($sClassOfAttribute, $oNode->GetName());
|
||||
@@ -2114,12 +2091,11 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
});
|
||||
|
||||
$sSignature = $oSubClassExp->Render();
|
||||
if (!array_key_exists($sSignature, $aExpressions))
|
||||
{
|
||||
$sSignature = $oSubClassExp->RenderExpression();
|
||||
if (!array_key_exists($sSignature, $aExpressions)) {
|
||||
$aExpressions[$sSignature] = array(
|
||||
'expression' => $oSubClassExp,
|
||||
'classes' => array(),
|
||||
'classes' => array(),
|
||||
);
|
||||
}
|
||||
$aExpressions[$sSignature]['classes'][] = $sSubClass;
|
||||
|
||||
@@ -53,6 +53,18 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
* @var array
|
||||
*/
|
||||
protected $m_aAttToLoad;
|
||||
/**
|
||||
* @var null|array
|
||||
*/
|
||||
protected $m_aExtendedDataSpec;
|
||||
/**
|
||||
* @var int Maximum number of elements to retrieve
|
||||
*/
|
||||
protected $m_iLimitCount;
|
||||
/**
|
||||
* @var int Offset from which elements should be retrieved
|
||||
*/
|
||||
protected $m_iLimitStart;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@@ -141,7 +153,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
$sRet = '';
|
||||
$this->Rewind();
|
||||
$sRet .= "Set (".$this->m_oFilter->ToOQL().")<br/>\n";
|
||||
$sRet .= "Set (".$this->m_oFilter->ToOQL(true).")<br/>\n";
|
||||
$sRet .= "Query: <pre style=\"font-size: smaller; display:inline;\">".$this->m_oFilter->MakeSelectQuery().")</pre>\n";
|
||||
|
||||
$sRet .= $this->Count()." records<br/>\n";
|
||||
@@ -154,6 +166,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
}
|
||||
$sRet .= "</ul>\n";
|
||||
}
|
||||
$this->Rewind();
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
@@ -842,7 +855,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*/
|
||||
public function Count()
|
||||
public function Count(): int
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows))
|
||||
{
|
||||
@@ -1077,14 +1090,13 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
*
|
||||
* @param int $iRow
|
||||
*
|
||||
* @return int|mixed
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @since 3.1.0 N°4517 Now returns void for return type to match parent class and be compatible with PHP 8.1
|
||||
*/
|
||||
public function Seek($iRow)
|
||||
public function Seek($iRow): void
|
||||
{
|
||||
if (!$this->m_bLoaded) $this->Load();
|
||||
|
||||
@@ -1093,7 +1105,6 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
$this->m_oSQLResult->data_seek($this->m_iCurrRow);
|
||||
}
|
||||
return $this->m_iCurrRow;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,23 +18,6 @@
|
||||
*/
|
||||
|
||||
|
||||
$bUseLegacyDBSearch = utils::GetConfig()->Get('use_legacy_dbsearch');
|
||||
|
||||
if ($bUseLegacyDBSearch)
|
||||
{
|
||||
// excluded from autoload
|
||||
require_once (APPROOT.'core/legacy/querybuilderexpressionslegacy.class.inc.php');
|
||||
require_once (APPROOT.'core/legacy/querybuildercontextlegacy.class.inc.php');
|
||||
require_once(APPROOT.'core/legacy/dbobjectsearchlegacy.class.php');
|
||||
}
|
||||
else
|
||||
{
|
||||
// excluded from autoload
|
||||
require_once (APPROOT.'core/querybuilderexpressions.class.inc.php');
|
||||
require_once (APPROOT.'core/querybuildercontext.class.inc.php');
|
||||
require_once(APPROOT.'core/dbobjectsearch.class.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* An object search
|
||||
*
|
||||
@@ -790,14 +773,14 @@ abstract class DBSearch
|
||||
* @see DBSearch::ToOQL()
|
||||
*
|
||||
* @param string $sQuery The OQL to convert to a DBSearch
|
||||
* @param mixed[string] $aParams array of <mixed> params index by <string> name
|
||||
* @param array $aParams array of <mixed> params index by <string> name
|
||||
* @param ModelReflection|null $oMetaModel The MetaModel to use when checking the consistency of the OQL
|
||||
*
|
||||
* @return DBObjectSearch|DBUnionSearch
|
||||
*
|
||||
* @throws OQLException
|
||||
*/
|
||||
static public function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel=null)
|
||||
public static function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel=null)
|
||||
{
|
||||
if (empty($sQuery))
|
||||
{
|
||||
@@ -1659,7 +1642,7 @@ abstract class DBSearch
|
||||
$oSet = new DBObjectSet($this);
|
||||
if (MetaModel::IsStandaloneClass($sClass))
|
||||
{
|
||||
$oSet->OptimizeColumnLoad(array($this->GetClassAlias() => array('')));
|
||||
$oSet->OptimizeColumnLoad(array($this->GetClassAlias() => array()));
|
||||
$aIds = array($sClass => $oSet->GetColumnAsArray('id'));
|
||||
}
|
||||
else
|
||||
@@ -1724,4 +1707,16 @@ abstract class DBSearch
|
||||
{
|
||||
$this->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
}
|
||||
|
||||
/**
|
||||
* To ease the debug of filters
|
||||
* @internal
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->ToOQL(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,11 @@
|
||||
|
||||
namespace Combodo\iTop;
|
||||
|
||||
use \DOMDocument;
|
||||
use \DOMFormatException;
|
||||
use DOMDocument;
|
||||
use DOMFormatException;
|
||||
use IssueLog;
|
||||
use LogAPI;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* Class \Combodo\iTop\DesignDocument
|
||||
@@ -64,9 +67,13 @@ class DesignDocument extends DOMDocument
|
||||
* @param $filename
|
||||
* @param int $options
|
||||
*/
|
||||
public function load($filename, $options = 0)
|
||||
public function load($filename, $options = null)
|
||||
{
|
||||
parent::load($filename, LIBXML_NOBLANKS);
|
||||
libxml_clear_errors();
|
||||
if (parent::load($filename, LIBXML_NOBLANKS) === false) {
|
||||
$aErrors = libxml_get_errors();
|
||||
IssueLog::Error("Error loading $filename", LogAPI::CHANNEL_DEFAULT, $aErrors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,10 +84,12 @@ class DesignDocument extends DOMDocument
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function save($filename, $options = 0)
|
||||
// Return type union is not supported by PHP 7.4, we can remove the following PHP attribute and add the return type once iTop min PHP version is PHP 8.0+
|
||||
#[\ReturnTypeWillChange]
|
||||
public function save($filename, $options = null)
|
||||
{
|
||||
$this->documentElement->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
|
||||
return parent::save($filename, LIBXML_NOBLANKS);
|
||||
return parent::save($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,13 +100,12 @@ class DesignDocument extends DOMDocument
|
||||
public function Dump($bReturnRes = false)
|
||||
{
|
||||
$sXml = $this->saveXML();
|
||||
if ($bReturnRes)
|
||||
{
|
||||
if ($bReturnRes) {
|
||||
return $sXml;
|
||||
}
|
||||
|
||||
echo "<pre>\n";
|
||||
echo htmlentities($sXml);
|
||||
echo utils::EscapeHtml($sXml);
|
||||
echo "</pre>\n";
|
||||
|
||||
return '';
|
||||
@@ -175,6 +183,26 @@ class DesignElement extends \DOMElement
|
||||
return $this->ownerDocument->GetNodes($sXPath, $this);
|
||||
}
|
||||
|
||||
public static function ToArray(DesignElement $oNode)
|
||||
{
|
||||
$aRes = [];
|
||||
|
||||
if ($oNode->GetNodes('./*')->length == 0) {
|
||||
return $oNode->GetText('');
|
||||
}
|
||||
foreach ($oNode->GetNodes('./*') as $oSubNode) {
|
||||
/** @var \Combodo\iTop\DesignElement $oSubNode */
|
||||
$aSubArray = DesignElement::ToArray($oSubNode);
|
||||
if ($oSubNode->hasAttribute('id')) {
|
||||
$aRes[$oSubNode->getAttribute('id')] = $aSubArray;
|
||||
} else {
|
||||
$aRes[$oSubNode->tagName] = $aSubArray;
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTML representation of the DOM, for debugging purposes
|
||||
*
|
||||
@@ -190,13 +218,13 @@ class DesignElement extends \DOMElement
|
||||
$oDoc->appendChild($oClone);
|
||||
|
||||
$sXml = $oDoc->saveXML($oClone);
|
||||
if ($bReturnRes)
|
||||
{
|
||||
if ($bReturnRes) {
|
||||
return $sXml;
|
||||
}
|
||||
echo "<pre>\n";
|
||||
echo htmlentities($sXml);
|
||||
echo utils::EscapeHtml($sXml);
|
||||
echo "</pre>\n";
|
||||
|
||||
return '';
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -110,7 +110,7 @@ class Dict
|
||||
*
|
||||
* @param string $sStringCode The code identifying the dictionary entry
|
||||
* @param string $sDefault Default value if there is no match in the dictionary
|
||||
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
|
||||
* @param bool $bUserLanguageOnly False to allow the use of the default language as a fallback, true otherwise
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@@ -174,7 +174,7 @@ class Dict
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function Format($sFormatCode /*, ... arguments ....*/)
|
||||
public static function Format($sFormatCode /*, ... arguments ... */)
|
||||
{
|
||||
$sLocalizedFormat = self::S($sFormatCode);
|
||||
$aArguments = func_get_args();
|
||||
|
||||
@@ -459,11 +459,10 @@ class DisplayableNode extends GraphNode
|
||||
{
|
||||
$aContext = $aContextDefs[$key];
|
||||
$aRootCauses = array();
|
||||
foreach($aObjects as $oRootCause)
|
||||
{
|
||||
foreach ($aObjects as $oRootCause) {
|
||||
$aRootCauses[] = $oRootCause->GetHyperlink();
|
||||
}
|
||||
$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 .= '<p><img style="max-height: 24px; vertical-align:bottom;" class="ibo-class-icon ibo-is-small" src="'.utils::GetAbsoluteUrlModulesRoot().$aContext['icon'].'" title="'.utils::EscapeHtml(Dict::S($aContext['dict'])).'"> '.implode(', ', $aRootCauses).'</p>';
|
||||
}
|
||||
$sHtml .= '<hr/>';
|
||||
}
|
||||
@@ -1195,8 +1194,10 @@ class DisplayableGraph extends SimpleGraph
|
||||
* @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
|
||||
* @param float $yMax Bottom coordinate of the bounding box to display the graph
|
||||
*
|
||||
* @since 2.7.7 3.0.2 3.1.0 N°4985 $sComments param is no longer optional
|
||||
*/
|
||||
function RenderAsPDF(PDFPage $oPage, $sComments = '', $sContextKey, $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1)
|
||||
function RenderAsPDF(PDFPage $oPage, $sComments, $sContextKey, $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1)
|
||||
{
|
||||
$aContextDefs = static::GetContextDefinitions($sContextKey, false); // No need to develop the parameters
|
||||
$oPdf = $oPage->get_tcpdf();
|
||||
@@ -1333,18 +1334,17 @@ class DisplayableGraph extends SimpleGraph
|
||||
}
|
||||
$oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yMin, 'D');
|
||||
|
||||
if ($sComments != '')
|
||||
{
|
||||
if ($sComments != '') {
|
||||
// Draw the comment text (surrounded by a rectangle)
|
||||
$xPos = $xMin + $fMaxWidth + $fIconSize + 4*$fPadding;
|
||||
$w = $xMax - $xPos - 2*$fPadding;
|
||||
$xPos = $xMin + $fMaxWidth + $fIconSize + 4 * $fPadding;
|
||||
$w = $xMax - $xPos - 2 * $fPadding;
|
||||
$iNbLines = 1;
|
||||
$sText = '<p>'.str_replace("\n", '<br/>', htmlentities($sComments, ENT_QUOTES, 'UTF-8'), $iNbLines).'</p>';
|
||||
$sText = '<p>'.str_replace("\n", '<br/>', utils::EscapeHtml($sComments), $iNbLines).'</p>';
|
||||
$fLineHeight = $oPdf->getStringHeight($w, $sText);
|
||||
$h = (1+$iNbLines) * $fLineHeight;
|
||||
$yPos = $yMax - 2*$fPadding - $h;
|
||||
$h = (1 + $iNbLines) * $fLineHeight;
|
||||
$yPos = $yMax - 2 * $fPadding - $h;
|
||||
$oPdf->writeHTMLCell($w, $h, $xPos + $fPadding, $yPos + $fPadding, $sText, 0 /* border */, 1 /* ln */);
|
||||
$oPdf->Rect($xPos, $yPos, $w + 2*$fPadding, $h + 2*$fPadding, 'D');
|
||||
$oPdf->Rect($xPos, $yPos, $w + 2 * $fPadding, $h + 2 * $fPadding, 'D');
|
||||
$yMax = $yPos - $fPadding;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2021 Combodo SARL
|
||||
// Copyright (C) 2010-2022 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,46 +20,72 @@
|
||||
/**
|
||||
* Send an email (abstraction for synchronous/asynchronous modes)
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2022 Combodo SARL
|
||||
* @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');
|
||||
|
||||
use Combodo\iTop\Core\Email\EmailFactory;
|
||||
use Combodo\iTop\Core\Email\iEMail;
|
||||
|
||||
define ('EMAIL_SEND_OK', 0);
|
||||
define ('EMAIL_SEND_PENDING', 1);
|
||||
define ('EMAIL_SEND_ERROR', 2);
|
||||
|
||||
class EMail
|
||||
class EMail implements iEMail
|
||||
{
|
||||
/**
|
||||
* @see self::LoadConfig()
|
||||
* @var Config
|
||||
* @since 2.7.7 3.0.2 3.1.0 N°3169 N°5102 Move attribute to children classes
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°4947 pull up the attribute back to the Email class as config init is done here
|
||||
*/
|
||||
protected static $m_oConfig = null;
|
||||
protected $oMailer;
|
||||
|
||||
// Serialization formats
|
||||
const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object.
|
||||
// Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string
|
||||
// Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string
|
||||
const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed)
|
||||
|
||||
protected static $m_oConfig = null;
|
||||
protected $m_aData; // For storing data to serialize
|
||||
|
||||
public function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE)
|
||||
{
|
||||
if (is_null(self::$m_oConfig))
|
||||
{
|
||||
self::$m_oConfig = new Config($sConfigFile);
|
||||
}
|
||||
}
|
||||
|
||||
protected $m_oMessage;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->m_aData = array();
|
||||
$this->m_oMessage = Swift_Message::newInstance();
|
||||
$this->SetRecipientFrom(MetaModel::GetConfig()->Get('email_default_sender_address'), MetaModel::GetConfig()->Get('email_default_sender_label'));
|
||||
$this->oMailer = EmailFactory::GetMailer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@see m_oConfig} if current attribute is null
|
||||
*
|
||||
* @returns \Config the current {@see m_oConfig} value
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @uses utils::GetConfig()
|
||||
*
|
||||
* @since 2.7.7 3.0.2 3.1.0 N°3169 N°5102 Move method to children classes
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°4947 Pull up to the parent class, and remove `$sConfigFile` param
|
||||
*/
|
||||
public function LoadConfig()
|
||||
{
|
||||
if (is_null(static::$m_oConfig)) {
|
||||
static::$m_oConfig = utils::GetConfig();
|
||||
}
|
||||
|
||||
return static::$m_oConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°4947 Method creation, to factorize same code in children classes
|
||||
*/
|
||||
protected function InitRecipientFrom()
|
||||
{
|
||||
$oConfig = $this->LoadConfig();
|
||||
$this->SetRecipientFrom(
|
||||
$oConfig->Get('email_default_sender_address'),
|
||||
$oConfig->Get('email_default_sender_label')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,269 +96,42 @@ class EMail
|
||||
*/
|
||||
public function SerializeV2()
|
||||
{
|
||||
return serialize($this->m_aData);
|
||||
return $this->oMailer->SerializeV2();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Custom de-serialization method
|
||||
*
|
||||
* @param string $sSerializedMessage The serialized representation of the message
|
||||
*
|
||||
* @return \Email
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \Symfony\Component\CssSelector\Exception\SyntaxErrorException
|
||||
*/
|
||||
static public function UnSerializeV2($sSerializedMessage)
|
||||
public static function UnSerializeV2($sSerializedMessage)
|
||||
{
|
||||
$aData = unserialize($sSerializedMessage);
|
||||
$oMessage = new Email();
|
||||
|
||||
if (array_key_exists('body', $aData))
|
||||
{
|
||||
$oMessage->SetBody($aData['body']['body'], $aData['body']['mimeType']);
|
||||
}
|
||||
if (array_key_exists('message_id', $aData))
|
||||
{
|
||||
$oMessage->SetMessageId($aData['message_id']);
|
||||
}
|
||||
if (array_key_exists('bcc', $aData))
|
||||
{
|
||||
$oMessage->SetRecipientBCC($aData['bcc']);
|
||||
}
|
||||
if (array_key_exists('cc', $aData))
|
||||
{
|
||||
$oMessage->SetRecipientCC($aData['cc']);
|
||||
}
|
||||
if (array_key_exists('from', $aData))
|
||||
{
|
||||
$oMessage->SetRecipientFrom($aData['from']['address'], $aData['from']['label']);
|
||||
}
|
||||
if (array_key_exists('reply_to', $aData))
|
||||
{
|
||||
$oMessage->SetRecipientReplyTo($aData['reply_to']['address'], $aData['reply_to']['label']);
|
||||
}
|
||||
if (array_key_exists('to', $aData))
|
||||
{
|
||||
$oMessage->SetRecipientTO($aData['to']);
|
||||
}
|
||||
if (array_key_exists('subject', $aData))
|
||||
{
|
||||
$oMessage->SetSubject($aData['subject']);
|
||||
}
|
||||
|
||||
|
||||
if (array_key_exists('headers', $aData))
|
||||
{
|
||||
foreach($aData['headers'] as $sKey => $sValue)
|
||||
{
|
||||
$oMessage->AddToHeader($sKey, $sValue);
|
||||
}
|
||||
}
|
||||
if (array_key_exists('parts', $aData))
|
||||
{
|
||||
foreach($aData['parts'] as $aPart)
|
||||
{
|
||||
$oMessage->AddPart($aPart['text'], $aPart['mimeType']);
|
||||
}
|
||||
}
|
||||
if (array_key_exists('attachments', $aData))
|
||||
{
|
||||
foreach($aData['attachments'] as $aAttachment)
|
||||
{
|
||||
$oMessage->AddAttachment(base64_decode($aAttachment['data']), $aAttachment['filename'], $aAttachment['mimeType']);
|
||||
}
|
||||
}
|
||||
return $oMessage;
|
||||
}
|
||||
|
||||
protected function SendAsynchronous(&$aIssues, $oLog = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
AsyncSendEmail::AddToQueue($this, $oLog);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$aIssues = array($e->GetMessage());
|
||||
return EMAIL_SEND_ERROR;
|
||||
}
|
||||
$aIssues = array();
|
||||
return EMAIL_SEND_PENDING;
|
||||
}
|
||||
|
||||
protected function SendSynchronous(&$aIssues, $oLog = null)
|
||||
{
|
||||
// If the body of the message is in HTML, embed all images based on attachments
|
||||
$this->EmbedInlineImages();
|
||||
|
||||
$this->LoadConfig();
|
||||
|
||||
$sTransport = self::$m_oConfig->Get('email_transport');
|
||||
switch ($sTransport)
|
||||
{
|
||||
case 'SMTP':
|
||||
$sHost = self::$m_oConfig->Get('email_transport_smtp.host');
|
||||
$sPort = self::$m_oConfig->Get('email_transport_smtp.port');
|
||||
$sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption');
|
||||
$sUserName = self::$m_oConfig->Get('email_transport_smtp.username');
|
||||
$sPassword = self::$m_oConfig->Get('email_transport_smtp.password');
|
||||
|
||||
$oTransport = Swift_SmtpTransport::newInstance($sHost, $sPort, $sEncryption);
|
||||
if (strlen($sUserName) > 0)
|
||||
{
|
||||
$oTransport->setUsername($sUserName);
|
||||
$oTransport->setPassword($sPassword);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Null':
|
||||
$oTransport = Swift_NullTransport::newInstance();
|
||||
break;
|
||||
|
||||
case 'LogFile':
|
||||
$oTransport = Swift_LogFileTransport::newInstance();
|
||||
$oTransport->setLogFile(APPROOT.'log/mail.log');
|
||||
break;
|
||||
|
||||
case 'PHPMail':
|
||||
default:
|
||||
$oTransport = Swift_MailTransport::newInstance();
|
||||
}
|
||||
|
||||
$oMailer = Swift_Mailer::newInstance($oTransport);
|
||||
|
||||
$aFailedRecipients = array();
|
||||
$this->m_oMessage->setMaxLineLength(0);
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients);
|
||||
if ($iSent === 0)
|
||||
{
|
||||
// Beware: it seems that $aFailedRecipients sometimes contains the recipients that actually received the message !!!
|
||||
IssueLog::Warning('Email sending failed: Some recipients were invalid, aFailedRecipients contains: '.implode(', ', $aFailedRecipients));
|
||||
$aIssues = array('Some recipients were invalid.');
|
||||
$oKPI->ComputeStats('Email Sent', 'Error received');
|
||||
return EMAIL_SEND_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aIssues = array();
|
||||
$oKPI->ComputeStats('Email Sent', 'Succeded');
|
||||
return EMAIL_SEND_OK;
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$oKPI->ComputeStats('Email Sent', 'Error received');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reprocess the body of the message (if it is an HTML message)
|
||||
* to replace the URL of images based on attachments by a link
|
||||
* to an embedded image (i.e. cid:....)
|
||||
*/
|
||||
protected function EmbedInlineImages()
|
||||
{
|
||||
if ($this->m_aData['body']['mimeType'] == 'text/html')
|
||||
{
|
||||
$oDOMDoc = new DOMDocument();
|
||||
$oDOMDoc->preserveWhitespace = true;
|
||||
@$oDOMDoc->loadHTML('<?xml encoding="UTF-8"?>'.$this->m_aData['body']['body']); // For loading HTML chunks where the character set is not specified
|
||||
|
||||
$oXPath = new DOMXPath($oDOMDoc);
|
||||
$sXPath = '//img[@'.InlineImage::DOM_ATTR_ID.']';
|
||||
$oImagesList = $oXPath->query($sXPath);
|
||||
|
||||
if ($oImagesList->length != 0)
|
||||
{
|
||||
foreach($oImagesList as $oImg)
|
||||
{
|
||||
$iAttId = $oImg->getAttribute(InlineImage::DOM_ATTR_ID);
|
||||
$oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */);
|
||||
if ($oAttachment)
|
||||
{
|
||||
$sImageSecret = $oImg->getAttribute('data-img-secret');
|
||||
$sAttachmentSecret = $oAttachment->Get('secret');
|
||||
if ($sImageSecret !== $sAttachmentSecret)
|
||||
{
|
||||
// @see N°1921
|
||||
// If copying from another iTop we could get an IMG pointing to an InlineImage with wrong secret
|
||||
continue;
|
||||
}
|
||||
|
||||
$oDoc = $oAttachment->Get('contents');
|
||||
$oSwiftImage = new Swift_Image($oDoc->GetData(), $oDoc->GetFileName(), $oDoc->GetMimeType());
|
||||
$sCid = $this->m_oMessage->embed($oSwiftImage);
|
||||
$oImg->setAttribute('src', $sCid);
|
||||
}
|
||||
}
|
||||
}
|
||||
$sHtmlBody = $oDOMDoc->saveHTML();
|
||||
$this->m_oMessage->setBody($sHtmlBody, 'text/html', 'UTF-8');
|
||||
}
|
||||
return EmailFactory::GetMailer()::UnSerializeV2($sSerializedMessage);
|
||||
}
|
||||
|
||||
public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null)
|
||||
{
|
||||
//select a default sender if none is provided.
|
||||
if(empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])){
|
||||
$this->SetRecipientFrom($this->m_aData['to']);
|
||||
}
|
||||
|
||||
if ($bForceSynchronous)
|
||||
{
|
||||
return $this->SendSynchronous($aIssues, $oLog);
|
||||
}
|
||||
else
|
||||
{
|
||||
$bConfigASYNC = MetaModel::GetConfig()->Get('email_asynchronous');
|
||||
if ($bConfigASYNC)
|
||||
{
|
||||
return $this->SendAsynchronous($aIssues, $oLog);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->SendSynchronous($aIssues, $oLog);
|
||||
}
|
||||
}
|
||||
return $this->oMailer->Send($aIssues, $bForceSynchronous, $oLog);
|
||||
}
|
||||
|
||||
public function AddToHeader($sKey, $sValue)
|
||||
{
|
||||
if (!array_key_exists('headers', $this->m_aData))
|
||||
{
|
||||
$this->m_aData['headers'] = array();
|
||||
}
|
||||
$this->m_aData['headers'][$sKey] = $sValue;
|
||||
|
||||
if (strlen($sValue) > 0)
|
||||
{
|
||||
$oHeaders = $this->m_oMessage->getHeaders();
|
||||
switch(strtolower($sKey))
|
||||
{
|
||||
case 'return-path':
|
||||
$this->m_oMessage->setReturnPath($sValue);
|
||||
break;
|
||||
|
||||
default:
|
||||
$oHeaders->addTextHeader($sKey, $sValue);
|
||||
}
|
||||
}
|
||||
$this->oMailer->AddToHeader($sKey, $sValue);
|
||||
}
|
||||
|
||||
public function SetMessageId($sId)
|
||||
{
|
||||
$this->m_aData['message_id'] = $sId;
|
||||
|
||||
// Note: Swift will add the angle brackets for you
|
||||
// so let's remove the angle brackets if present, for historical reasons
|
||||
$sId = str_replace(array('<', '>'), '', $sId);
|
||||
|
||||
$oMsgId = $this->m_oMessage->getHeaders()->get('Message-ID');
|
||||
$oMsgId->SetId($sId);
|
||||
$this->oMailer->SetMessageId($sId);
|
||||
}
|
||||
|
||||
public function SetReferences($sReferences)
|
||||
{
|
||||
$this->AddToHeader('References', $sReferences);
|
||||
$this->oMailer->SetReferences($sReferences);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -351,222 +150,56 @@ class EMail
|
||||
|
||||
public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null)
|
||||
{
|
||||
if (($sMimeType === 'text/html') && ($sCustomStyles !== null)) {
|
||||
$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->oMailer->SetBody($sBody, $sMimeType, $sCustomStyles);
|
||||
}
|
||||
$this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType);
|
||||
$this->m_oMessage->setBody($sBody, $sMimeType);
|
||||
}
|
||||
|
||||
public function AddPart($sText, $sMimeType = 'text/html')
|
||||
{
|
||||
if (!array_key_exists('parts', $this->m_aData))
|
||||
{
|
||||
$this->m_aData['parts'] = array();
|
||||
}
|
||||
$this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType);
|
||||
$this->m_oMessage->addPart($sText, $sMimeType);
|
||||
$this->oMailer->AddPart($sText, $sMimeType);
|
||||
}
|
||||
|
||||
public function AddAttachment($data, $sFileName, $sMimeType)
|
||||
{
|
||||
if (!array_key_exists('attachments', $this->m_aData))
|
||||
{
|
||||
$this->m_aData['attachments'] = array();
|
||||
}
|
||||
$this->m_aData['attachments'][] = array('data' => base64_encode($data), 'filename' => $sFileName, 'mimeType' => $sMimeType);
|
||||
$this->m_oMessage->attach(Swift_Attachment::newInstance($data, $sFileName, $sMimeType));
|
||||
$this->oMailer->AddAttachment($data, $sFileName, $sMimeType);
|
||||
}
|
||||
|
||||
public function SetSubject($sSubject)
|
||||
{
|
||||
$this->m_aData['subject'] = $sSubject;
|
||||
$this->m_oMessage->setSubject($sSubject);
|
||||
$this->oMailer->SetSubject($sSubject);
|
||||
}
|
||||
|
||||
public function GetSubject()
|
||||
{
|
||||
return $this->m_oMessage->getSubject();
|
||||
return $this->oMailer->GetSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to transform and sanitize addresses
|
||||
* - get rid of empty addresses
|
||||
*/
|
||||
protected function AddressStringToArray($sAddressCSVList)
|
||||
{
|
||||
$aAddresses = array();
|
||||
foreach(explode(',', $sAddressCSVList) as $sAddress)
|
||||
{
|
||||
$sAddress = trim($sAddress);
|
||||
if (strlen($sAddress) > 0)
|
||||
{
|
||||
$aAddresses[] = $sAddress;
|
||||
}
|
||||
}
|
||||
return $aAddresses;
|
||||
}
|
||||
|
||||
public function SetRecipientTO($sAddress)
|
||||
{
|
||||
$this->m_aData['to'] = $sAddress;
|
||||
if (!empty($sAddress))
|
||||
{
|
||||
$aAddresses = $this->AddressStringToArray($sAddress);
|
||||
$this->m_oMessage->setTo($aAddresses);
|
||||
}
|
||||
$this->oMailer->SetRecipientTO($sAddress);
|
||||
}
|
||||
|
||||
public function GetRecipientTO($bAsString = false)
|
||||
{
|
||||
$aRes = $this->m_oMessage->getTo();
|
||||
if ($aRes === null)
|
||||
{
|
||||
// There is no "To" header field
|
||||
$aRes = array();
|
||||
}
|
||||
if ($bAsString)
|
||||
{
|
||||
$aStrings = array();
|
||||
foreach ($aRes as $sEmail => $sName)
|
||||
{
|
||||
if (is_null($sName))
|
||||
{
|
||||
$aStrings[] = $sEmail;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sName = str_replace(array('<', '>'), '', $sName);
|
||||
$aStrings[] = "$sName <$sEmail>";
|
||||
}
|
||||
}
|
||||
return implode(', ', $aStrings);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $aRes;
|
||||
}
|
||||
return $this->oMailer->GetRecipientTO($bAsString);
|
||||
}
|
||||
|
||||
public function SetRecipientCC($sAddress)
|
||||
{
|
||||
$this->m_aData['cc'] = $sAddress;
|
||||
if (!empty($sAddress))
|
||||
{
|
||||
$aAddresses = $this->AddressStringToArray($sAddress);
|
||||
$this->m_oMessage->setCc($aAddresses);
|
||||
}
|
||||
$this->oMailer->SetRecipientCC($sAddress);
|
||||
}
|
||||
|
||||
public function SetRecipientBCC($sAddress)
|
||||
{
|
||||
$this->m_aData['bcc'] = $sAddress;
|
||||
if (!empty($sAddress))
|
||||
{
|
||||
$aAddresses = $this->AddressStringToArray($sAddress);
|
||||
$this->m_oMessage->setBcc($aAddresses);
|
||||
}
|
||||
$this->oMailer->SetRecipientBCC($sAddress);
|
||||
}
|
||||
|
||||
public function SetRecipientFrom($sAddress, $sLabel = '')
|
||||
{
|
||||
$this->m_aData['from'] = array('address' => $sAddress, 'label' => $sLabel);
|
||||
if ($sLabel != '')
|
||||
{
|
||||
$this->m_oMessage->setFrom(array($sAddress => $sLabel));
|
||||
}
|
||||
else if (!empty($sAddress))
|
||||
{
|
||||
$this->m_oMessage->setFrom($sAddress);
|
||||
}
|
||||
$this->oMailer->SetRecipientFrom($sAddress, $sLabel);
|
||||
}
|
||||
|
||||
public function SetRecipientReplyTo($sAddress, $sLabel = '')
|
||||
{
|
||||
$this->m_aData['reply_to'] = array('address' => $sAddress, 'label' => $sLabel);
|
||||
if ($sLabel != '')
|
||||
{
|
||||
$this->m_oMessage->setReplyTo(array($sAddress => $sLabel));
|
||||
}
|
||||
else if (!empty($sAddress))
|
||||
{
|
||||
$this->m_oMessage->setReplyTo($sAddress);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Extension to SwiftMailer: "debug" transport that pretends messages have been sent,
|
||||
* but just log them to a file.
|
||||
*
|
||||
* @package Swift
|
||||
* @author Denis Flaven
|
||||
*/
|
||||
class Swift_Transport_LogFileTransport extends Swift_Transport_NullTransport
|
||||
{
|
||||
protected $sLogFile;
|
||||
|
||||
/**
|
||||
* Sends the given message.
|
||||
*
|
||||
* @param Swift_Mime_Message $message
|
||||
* @param string[] $failedRecipients An array of failures by-reference
|
||||
*
|
||||
* @return int The number of sent emails
|
||||
*/
|
||||
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
|
||||
{
|
||||
$hFile = @fopen($this->sLogFile, 'a');
|
||||
if ($hFile)
|
||||
{
|
||||
$sTxt = "================== ".date('Y-m-d H:i:s')." ==================\n";
|
||||
$sTxt .= $message->toString()."\n";
|
||||
|
||||
@fwrite($hFile, $sTxt);
|
||||
@fclose($hFile);
|
||||
}
|
||||
|
||||
return parent::send($message, $failedRecipients);
|
||||
}
|
||||
|
||||
public function setLogFile($sFilename)
|
||||
{
|
||||
$this->sLogFile = $sFilename;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretends messages have been sent, but just log them to a file.
|
||||
*
|
||||
* @package Swift
|
||||
* @author Denis Flaven
|
||||
*/
|
||||
class Swift_LogFileTransport extends Swift_Transport_LogFileTransport
|
||||
{
|
||||
/**
|
||||
* Create a new LogFileTransport.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
call_user_func_array(
|
||||
array($this, 'Swift_Transport_LogFileTransport::__construct'),
|
||||
Swift_DependencyContainer::getInstance()
|
||||
->createDependenciesFor('transport.null')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new LogFileTransport instance.
|
||||
*
|
||||
* @return Swift_LogFileTransport
|
||||
*/
|
||||
public static function newInstance()
|
||||
{
|
||||
return new self();
|
||||
$this->oMailer->SetRecipientReplyTo($sAddress);
|
||||
}
|
||||
}
|
||||
@@ -100,8 +100,8 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
|
||||
$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
|
||||
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
|
||||
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
|
||||
$sDefaultFormat = utils::EscapeHtml((string)AttributeDateTime::GetFormat());
|
||||
$sExample = utils::EscapeHtml(date((string)AttributeDateTime::GetFormat()));
|
||||
$oRadioDefault = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample), "excel_date_format_radio", "default", "excel_date_time_format_default", "radio");
|
||||
$oRadioDefault->GetInput()->SetIsChecked(($sDateTimeFormat == (string)AttributeDateTime::GetFormat()));
|
||||
$oRadioDefault->SetBeforeInput(false);
|
||||
@@ -109,7 +109,7 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
$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').'"/>';
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="excel_custom_date_time_format" title="" value="'.utils::EscapeHtml($sDateTimeFormat).'"/>';
|
||||
$oRadioCustom = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput), "excel_date_format_radio", "custom", "excel_date_time_format_custom", "radio");
|
||||
$oRadioCustom->SetDescription(Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip'));
|
||||
$oRadioCustom->GetInput()->SetIsChecked($sDateTimeFormat !== (string)AttributeDateTime::GetFormat());
|
||||
@@ -156,16 +156,17 @@ EOF
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
{
|
||||
if ($sAttCode != 'id') {
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.utils::EscapeHtml($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj)).'</div>';
|
||||
}
|
||||
}
|
||||
return '<div class="text-preview">'.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
|
||||
return '<div class="text-preview">'.utils::EscapeHtml($this->GetValue($oObj, $sAttCode)).'</div>';
|
||||
}
|
||||
|
||||
protected function GetValue($oObj, $sAttCode)
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<?php
|
||||
// The file has been moved in iTop 2.2.0+ (revision 3803)
|
||||
// Preserve backward compatibility with some external tools (Cf. toolkit)
|
||||
|
||||
// @deprecated 3.1.0
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use core/oql/expression.class.inc.php instead');
|
||||
|
||||
require_once(APPROOT.'core/oql/expression.class.inc.php');
|
||||
|
||||
@@ -31,8 +31,9 @@ require_once('MyHelpers.class.inc.php');
|
||||
|
||||
|
||||
/**
|
||||
* Definition of a filter (could be made out of an existing attribute, or from an expression)
|
||||
* Definition of a filter (could be made out of an existing attribute, or from an expression)
|
||||
*
|
||||
* @deprecated 3.1.0 not used N°4690 - Deprecate "FilterCodes"
|
||||
* @package iTopORM
|
||||
*/
|
||||
abstract class FilterDefinition
|
||||
@@ -46,6 +47,7 @@ abstract class FilterDefinition
|
||||
|
||||
public function __construct($sCode, $aParams = array())
|
||||
{
|
||||
DeprecatedCallsLog::NotifyDeprecatedPhpMethod("Deprecated class ".$this->GetClass().". Do not use. Will be removed in next version.");
|
||||
$this->m_sCode = $sCode;
|
||||
$this->m_aParams = $aParams;
|
||||
$this->ConsistencyCheck();
|
||||
@@ -98,8 +100,9 @@ abstract class FilterDefinition
|
||||
}
|
||||
|
||||
/**
|
||||
* Match against the object unique identifier
|
||||
* Match against the object unique identifier
|
||||
*
|
||||
* @deprecated 3.1.0 N°4690 - Deprecate "FilterCodes"
|
||||
* @package iTopORM
|
||||
*/
|
||||
class FilterPrivateKey extends FilterDefinition
|
||||
@@ -145,8 +148,9 @@ class FilterPrivateKey extends FilterDefinition
|
||||
}
|
||||
|
||||
/**
|
||||
* Match against an existing attribute (the attribute type will determine the available operators)
|
||||
* Match against an existing attribute (the attribute type will determine the available operators)
|
||||
*
|
||||
* @deprecated 3.1.0 N°4690 - Deprecate "FilterCodes"
|
||||
* @package iTopORM
|
||||
*/
|
||||
class FilterFromAttribute extends FilterDefinition
|
||||
|
||||
@@ -62,7 +62,8 @@ class HTMLBulkExport extends TabularBulkExport
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.utils::EscapeHtml($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj)).'</div>';
|
||||
}
|
||||
}
|
||||
return $this->GetValue($oObj, $sAttCode);
|
||||
|
||||
@@ -108,6 +108,18 @@ abstract class DOMSanitizer extends HTMLSanitizer
|
||||
{
|
||||
/** @var DOMDocument */
|
||||
protected $oDoc;
|
||||
/**
|
||||
* @var string Class to use for InlineImage static method calls
|
||||
* @used-by \Combodo\iTop\Test\UnitTest\Core\Sanitizer\HTMLDOMSanitizerTest::testDoSanitizeCallInlineImageProcessImageTag
|
||||
*/
|
||||
protected $sInlineImageClassName;
|
||||
|
||||
public function __construct($sInlineImageClassName = InlineImage::class)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->sInlineImageClassName = $sInlineImageClassName;
|
||||
}
|
||||
|
||||
abstract public function GetTagsWhiteList();
|
||||
|
||||
@@ -122,7 +134,7 @@ abstract class DOMSanitizer extends HTMLSanitizer
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
$this->oDoc = new DOMDocument();
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
$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)
|
||||
@@ -203,7 +215,7 @@ abstract class DOMSanitizer extends HTMLSanitizer
|
||||
// Recurse
|
||||
$this->CleanNode($oNode);
|
||||
if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img')) {
|
||||
InlineImage::ProcessImageTag($oNode);
|
||||
$this->sInlineImageClassName::ProcessImageTag($oNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,8 +286,8 @@ class HTMLDOMSanitizer extends DOMSanitizer
|
||||
'strong' => array(),
|
||||
'img' => array('src', 'style', 'alt', 'title'),
|
||||
'ul' => array('style'),
|
||||
'ol' => array('style'),
|
||||
'li' => array('style'),
|
||||
'ol' => array('reversed', 'start', 'style', 'type'),
|
||||
'li' => array('style', 'value'),
|
||||
'h1' => array('style'),
|
||||
'h2' => array('style'),
|
||||
'h3' => array('style'),
|
||||
@@ -338,6 +350,30 @@ class HTMLDOMSanitizer extends DOMSanitizer
|
||||
'white-space',
|
||||
);
|
||||
|
||||
public function __construct($sInlineImageClassName = InlineImage::class)
|
||||
{
|
||||
parent::__construct($sInlineImageClassName);
|
||||
|
||||
// Building href validation pattern from url and email validation patterns as the patterns are not used the same way in HTML content than in standard attributes value.
|
||||
// eg. "foo@bar.com" vs "mailto:foo@bar.com?subject=Title&body=Hello%20world"
|
||||
if (!array_key_exists('href', self::$aAttrsWhiteList)) {
|
||||
// Regular urls
|
||||
$sUrlPattern = utils::GetConfig()->Get('url_validation_pattern');
|
||||
|
||||
// Mailto urls
|
||||
$sMailtoPattern = '(mailto:('.utils::GetConfig()->Get('email_validation_pattern').')(?:\?(?:subject|body)=([a-zA-Z0-9+\$_.-]*)(?:&(?:subject|body)=([a-zA-Z0-9+\$_.-]*))?)?)';
|
||||
|
||||
// Notification placeholders
|
||||
// eg. $this->caller_id$, $this->hyperlink()$, $this->hyperlink(portal)$, $APP_URL$, $MODULES_URL$, ...
|
||||
// Note: Authorize both $xxx$ and %24xxx%24 as the latter one is encoded when used in HTML attributes (eg. a[href])
|
||||
$sPlaceholderPattern = '(\$|%24)[\w-]*(->[\w]*(\([\w-]*?\))?)?(\$|%24)';
|
||||
|
||||
$sPattern = $sUrlPattern.'|'.$sMailtoPattern.'|'.$sPlaceholderPattern;
|
||||
$sPattern = '/'.str_replace('/', '\/', $sPattern).'/i';
|
||||
self::$aAttrsWhiteList['href'] = $sPattern;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetTagsWhiteList()
|
||||
{
|
||||
return static::$aTagsWhiteList;
|
||||
@@ -363,35 +399,10 @@ class HTMLDOMSanitizer extends DOMSanitizer
|
||||
return static::$aStylesWhiteList;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Building href validation pattern from url and email validation patterns as the patterns are not used the same way in HTML content than in standard attributes value.
|
||||
// eg. "foo@bar.com" vs "mailto:foo@bar.com?subject=Title&body=Hello%20world"
|
||||
if (!array_key_exists('href', self::$aAttrsWhiteList))
|
||||
{
|
||||
// Regular urls
|
||||
$sUrlPattern = utils::GetConfig()->Get('url_validation_pattern');
|
||||
|
||||
// Mailto urls
|
||||
$sMailtoPattern = '(mailto:(' . utils::GetConfig()->Get('email_validation_pattern') . ')(?:\?(?:subject|body)=([a-zA-Z0-9+\$_.-]*)(?:&(?:subject|body)=([a-zA-Z0-9+\$_.-]*))?)?)';
|
||||
|
||||
// Notification placeholders
|
||||
// eg. $this->caller_id$, $this->hyperlink()$, $this->hyperlink(portal)$, $APP_URL$, $MODULES_URL$, ...
|
||||
// Note: Authorize both $xxx$ and %24xxx%24 as the latter one is encoded when used in HTML attributes (eg. a[href])
|
||||
$sPlaceholderPattern = '(\$|%24)[\w-]*(->[\w]*(\([\w-]*?\))?)?(\$|%24)';
|
||||
|
||||
$sPattern = $sUrlPattern . '|' . $sMailtoPattern . '|' . $sPlaceholderPattern;
|
||||
$sPattern = '/'.str_replace('/', '\/', $sPattern).'/i';
|
||||
self::$aAttrsWhiteList['href'] = $sPattern;
|
||||
}
|
||||
}
|
||||
|
||||
public function LoadDoc($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->oDoc->preserveWhiteSpace = true;
|
||||
}
|
||||
|
||||
public function PrintDoc()
|
||||
|
||||
@@ -295,13 +295,12 @@ class InlineImage extends DBObject
|
||||
{
|
||||
$sImgTag = $aImgInfo[0][0];
|
||||
$sSecret = '';
|
||||
if (preg_match('/data-img-secret="([0-9a-f]+)"/', $sImgTag, $aSecretMatches))
|
||||
{
|
||||
if (preg_match('/data-img-secret="([0-9a-f]+)"/', $sImgTag, $aSecretMatches)) {
|
||||
$sSecret = '&s='.$aSecretMatches[1];
|
||||
}
|
||||
$sAttId = $aImgInfo[2][0];
|
||||
|
||||
$sNewImgTag = preg_replace('/src="[^"]+"/', 'src="'.htmlentities($sUrl.$sAttId.$sSecret, ENT_QUOTES, 'UTF-8').'"', $sImgTag); // preserve other attributes, must convert & to & to be idempotent with CKEditor
|
||||
|
||||
$sNewImgTag = preg_replace('/src="[^"]+"/', 'src="'.utils::EscapeHtml($sUrl.$sAttId.$sSecret).'"', $sImgTag); // preserve other attributes, must convert & to & to be idempotent with CKEditor
|
||||
$aNeedles[] = $sImgTag;
|
||||
$aReplacements[] = $sNewImgTag;
|
||||
}
|
||||
@@ -536,8 +535,8 @@ JS
|
||||
$iObjKey = $oObject->GetKey();
|
||||
|
||||
$sAbsoluteUrlAppRoot = utils::GetAbsoluteUrlAppRoot();
|
||||
$sToggleFullScreen = htmlentities(Dict::S('UI:ToggleFullScreen'), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$sToggleFullScreen = utils::EscapeHtml(Dict::S('UI:ToggleFullScreen'));
|
||||
|
||||
return
|
||||
<<<JS
|
||||
// Hook the file upload of all CKEditor instances
|
||||
|
||||
@@ -299,7 +299,7 @@ class ExecutionKPI
|
||||
*/
|
||||
private static function Push(ExecutionKPI $oExecutionKPI)
|
||||
{
|
||||
array_push(self::$m_aExecutionStack, $oExecutionKPI);
|
||||
self::$m_aExecutionStack[] = $oExecutionKPI;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -449,4 +449,3 @@ class ExecutionKPI
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,107 +0,0 @@
|
||||
<?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/>
|
||||
|
||||
/**
|
||||
* Associated with the metamodel -> MakeQuery/MakeQuerySingleTable
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class QueryBuilderContext
|
||||
{
|
||||
protected $m_oRootFilter;
|
||||
protected $m_aClassAliases;
|
||||
protected $m_aTableAliases;
|
||||
protected $m_aModifierProperties;
|
||||
protected $m_aSelectedClasses;
|
||||
protected $m_aFilteredTables;
|
||||
|
||||
public $m_oQBExpressions;
|
||||
|
||||
public function __construct($oFilter, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
|
||||
{
|
||||
$this->m_oRootFilter = $oFilter;
|
||||
$this->m_oQBExpressions = new QueryBuilderExpressions($oFilter, $aGroupByExpr, $aSelectExpr);
|
||||
|
||||
$this->m_aClassAliases = $oFilter->GetJoinedClasses();
|
||||
$this->m_aTableAliases = array();
|
||||
$this->m_aFilteredTables = array();
|
||||
|
||||
$this->m_aModifierProperties = $aModifierProperties;
|
||||
if (is_null($aSelectedClasses))
|
||||
{
|
||||
$this->m_aSelectedClasses = $oFilter->GetSelectedClasses();
|
||||
}
|
||||
else
|
||||
{
|
||||
// For the unions, the selected classes can be upper in the hierarchy (lowest common ancestor)
|
||||
$this->m_aSelectedClasses = $aSelectedClasses;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetRootFilter()
|
||||
{
|
||||
return $this->m_oRootFilter;
|
||||
}
|
||||
|
||||
public function GenerateTableAlias($sNewName, $sRealName)
|
||||
{
|
||||
return MetaModel::GenerateUniqueAlias($this->m_aTableAliases, $sNewName, $sRealName);
|
||||
}
|
||||
|
||||
public function GenerateClassAlias($sNewName, $sRealName)
|
||||
{
|
||||
return MetaModel::GenerateUniqueAlias($this->m_aClassAliases, $sNewName, $sRealName);
|
||||
}
|
||||
|
||||
public function GetModifierProperties($sPluginClass)
|
||||
{
|
||||
if (array_key_exists($sPluginClass, $this->m_aModifierProperties))
|
||||
{
|
||||
return $this->m_aModifierProperties[$sPluginClass];
|
||||
}
|
||||
else
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
public function GetSelectedClass($sAlias)
|
||||
{
|
||||
return $this->m_aSelectedClasses[$sAlias];
|
||||
}
|
||||
|
||||
public function AddFilteredTable($sTableAlias, $oCondition)
|
||||
{
|
||||
if (array_key_exists($sTableAlias, $this->m_aFilteredTables))
|
||||
{
|
||||
$this->m_aFilteredTables[$sTableAlias][] = $oCondition;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->m_aFilteredTables[$sTableAlias] = array($oCondition);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetFilteredTables()
|
||||
{
|
||||
return $this->m_aFilteredTables;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
<?php
|
||||
|
||||
class QueryBuilderExpressions
|
||||
{
|
||||
/**
|
||||
* @var Expression
|
||||
*/
|
||||
protected $m_oConditionExpr;
|
||||
/**
|
||||
* @var Expression[]
|
||||
*/
|
||||
protected $m_aSelectExpr;
|
||||
/**
|
||||
* @var Expression[]
|
||||
*/
|
||||
protected $m_aGroupByExpr;
|
||||
/**
|
||||
* @var Expression[]
|
||||
*/
|
||||
protected $m_aJoinFields;
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $m_aClassIds;
|
||||
|
||||
public function __construct(DBObjectSearch $oSearch, $aGroupByExpr = null, $aSelectExpr = null)
|
||||
{
|
||||
$this->m_oConditionExpr = $oSearch->GetCriteria();
|
||||
if (!$oSearch->GetShowObsoleteData())
|
||||
{
|
||||
foreach ($oSearch->GetSelectedClasses() as $sAlias => $sClass)
|
||||
{
|
||||
if (MetaModel::IsObsoletable($sClass))
|
||||
{
|
||||
$oNotObsolete = new BinaryExpression(new FieldExpression('obsolescence_flag', $sAlias), '=', new ScalarExpression(0));
|
||||
$this->m_oConditionExpr = $this->m_oConditionExpr->LogAnd($oNotObsolete);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->m_aSelectExpr = is_null($aSelectExpr) ? array() : $aSelectExpr;
|
||||
$this->m_aGroupByExpr = $aGroupByExpr;
|
||||
$this->m_aJoinFields = array();
|
||||
|
||||
$this->m_aClassIds = array();
|
||||
foreach ($oSearch->GetJoinedClasses() as $sClassAlias => $sClass)
|
||||
{
|
||||
$this->m_aClassIds[$sClassAlias] = new FieldExpression('id', $sClassAlias);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetSelect()
|
||||
{
|
||||
return $this->m_aSelectExpr;
|
||||
}
|
||||
|
||||
public function GetGroupBy()
|
||||
{
|
||||
return $this->m_aGroupByExpr;
|
||||
}
|
||||
|
||||
public function GetCondition()
|
||||
{
|
||||
return $this->m_oConditionExpr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Expression|mixed
|
||||
*/
|
||||
public function PopJoinField()
|
||||
{
|
||||
return array_pop($this->m_aJoinFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sAttAlias
|
||||
* @param Expression $oExpression
|
||||
*/
|
||||
public function AddSelect($sAttAlias, Expression $oExpression)
|
||||
{
|
||||
$this->m_aSelectExpr[$sAttAlias] = $oExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Expression $oExpression
|
||||
*/
|
||||
public function AddCondition(Expression $oExpression)
|
||||
{
|
||||
$this->m_oConditionExpr = $this->m_oConditionExpr->LogAnd($oExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Expression $oExpression
|
||||
*/
|
||||
public function PushJoinField(Expression $oExpression)
|
||||
{
|
||||
array_push($this->m_aJoinFields, $oExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tables representing the queried objects
|
||||
* Could be further optimized: when the first join is an outer join, then the rest can be omitted
|
||||
*
|
||||
* @param array $aTables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetMandatoryTables(&$aTables = null)
|
||||
{
|
||||
if (is_null($aTables))
|
||||
{
|
||||
$aTables = array();
|
||||
}
|
||||
|
||||
foreach ($this->m_aClassIds as $sClass => $oExpression)
|
||||
{
|
||||
$oExpression->CollectUsedParents($aTables);
|
||||
}
|
||||
|
||||
return $aTables;
|
||||
}
|
||||
|
||||
public function GetUnresolvedFields($sAlias, &$aUnresolved)
|
||||
{
|
||||
$this->m_oConditionExpr->GetUnresolvedFields($sAlias, $aUnresolved);
|
||||
foreach ($this->m_aSelectExpr as $sColAlias => $oExpr)
|
||||
{
|
||||
$oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
|
||||
}
|
||||
if ($this->m_aGroupByExpr)
|
||||
{
|
||||
foreach ($this->m_aGroupByExpr as $sColAlias => $oExpr)
|
||||
{
|
||||
$oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
|
||||
}
|
||||
}
|
||||
foreach ($this->m_aJoinFields as $oExpression)
|
||||
{
|
||||
$oExpression->GetUnresolvedFields($sAlias, $aUnresolved);
|
||||
}
|
||||
}
|
||||
|
||||
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
|
||||
{
|
||||
$this->m_oConditionExpr = $this->m_oConditionExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
|
||||
foreach ($this->m_aSelectExpr as $sColAlias => $oExpr)
|
||||
{
|
||||
$this->m_aSelectExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
|
||||
}
|
||||
if ($this->m_aGroupByExpr)
|
||||
{
|
||||
foreach ($this->m_aGroupByExpr as $sColAlias => $oExpr)
|
||||
{
|
||||
$this->m_aGroupByExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
|
||||
}
|
||||
}
|
||||
foreach ($this->m_aJoinFields as $index => $oExpression)
|
||||
{
|
||||
$this->m_aJoinFields[$index] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
|
||||
}
|
||||
|
||||
foreach ($this->m_aClassIds as $sClass => $oExpression)
|
||||
{
|
||||
$this->m_aClassIds[$sClass] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
|
||||
}
|
||||
}
|
||||
|
||||
public function RenameParam($sOldName, $sNewName)
|
||||
{
|
||||
$this->m_oConditionExpr->RenameParam($sOldName, $sNewName);
|
||||
foreach ($this->m_aSelectExpr as $sColAlias => $oExpr)
|
||||
{
|
||||
$this->m_aSelectExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName);
|
||||
}
|
||||
if ($this->m_aGroupByExpr)
|
||||
{
|
||||
foreach ($this->m_aGroupByExpr as $sColAlias => $oExpr)
|
||||
{
|
||||
$this->m_aGroupByExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName);
|
||||
}
|
||||
}
|
||||
foreach ($this->m_aJoinFields as $index => $oExpression)
|
||||
{
|
||||
$this->m_aJoinFields[$index] = $oExpression->RenameParam($sOldName, $sNewName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -547,9 +547,19 @@ class LogChannels
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.1 N°4849
|
||||
* @since 2.7.7 N°4635
|
||||
*/
|
||||
public const NOTIFICATIONS = 'notifications';
|
||||
|
||||
/**
|
||||
* @var string Everything related to the backup / restore
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public const BACKUP = 'backup';
|
||||
|
||||
/**
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public const CLI = 'CLI';
|
||||
|
||||
/**
|
||||
@@ -559,15 +569,36 @@ class LogChannels
|
||||
*/
|
||||
public const CMDB_SOURCE = 'cmdbsource';
|
||||
|
||||
public const CONSOLE = 'console';
|
||||
/**
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public const CONSOLE = 'console';
|
||||
|
||||
public const CORE = 'core';
|
||||
public const CORE = 'core';
|
||||
|
||||
public const DEADLOCK = 'DeadLock';
|
||||
/**
|
||||
* @var string Everything related to the datatable component
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public const DATATABLE = 'Datatable';
|
||||
|
||||
public const DEADLOCK = 'DeadLock';
|
||||
|
||||
public const INLINE_IMAGE = 'InlineImage';
|
||||
|
||||
public const PORTAL = 'portal';
|
||||
|
||||
/**
|
||||
* @var string Everything related to the event service
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public const EVENT_SERVICE = 'EventService';
|
||||
|
||||
/**
|
||||
* @var string Everything related to the datamodel CRUD
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public const DM_CRUD = 'DMCRUD';
|
||||
}
|
||||
|
||||
|
||||
@@ -710,6 +741,7 @@ abstract class LogAPI
|
||||
/**
|
||||
* @throws \ConfigException if log wrongly configured
|
||||
* @uses GetMinLogLevel
|
||||
* @since 3.0.0 N°3731
|
||||
*/
|
||||
final public static function IsLogLevelEnabled(string $sLevel, string $sChannel, string $sConfigKey = self::ENUM_CONFIG_PARAM_FILE): bool
|
||||
{
|
||||
@@ -1010,10 +1042,16 @@ class DeadLockLog extends LogAPI
|
||||
class DeprecatedCallsLog extends LogAPI
|
||||
{
|
||||
public const ENUM_CHANNEL_PHP_METHOD = 'deprecated-php-method';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public const ENUM_CHANNEL_PHP_ENDPOINT = 'deprecated-php-endpoint';
|
||||
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;
|
||||
|
||||
/** @var string Warning this constant won't be used directly ! To see the real default level check {@see GetLevelDefault()} */
|
||||
public const LEVEL_DEFAULT = self::LEVEL_ERROR;
|
||||
|
||||
/** @var \FileLog we want our own instance ! */
|
||||
@@ -1088,7 +1126,12 @@ class DeprecatedCallsLog extends LogAPI
|
||||
}
|
||||
|
||||
$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
|
||||
$iStackDeprecatedMethodLevel = 2; // level 0 = current method, level 1 = @trigger_error, level 2 = method containing the `trigger_error` call (can be either 'trigger_deprecation' or the faulty method), level 3 = In some cases, method containing the 'trigger_deprecation' call
|
||||
// In case current level is actually a 'trigger_deprecation' call, try to go one level further to get the real deprecated method
|
||||
if (array_key_exists($iStackDeprecatedMethodLevel, $aStack) && ($aStack[$iStackDeprecatedMethodLevel]['function'] === 'trigger_deprecation') && array_key_exists($iStackDeprecatedMethodLevel + 1, $aStack)) {
|
||||
$iStackDeprecatedMethodLevel++;
|
||||
}
|
||||
|
||||
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
|
||||
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
|
||||
if (($sDeprecatedObject === __CLASS__) && ($sDeprecatedMethod === 'Log')) {
|
||||
@@ -1133,7 +1176,6 @@ class DeprecatedCallsLog extends LogAPI
|
||||
* - 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)
|
||||
{
|
||||
@@ -1222,6 +1264,35 @@ class DeprecatedCallsLog extends LogAPI
|
||||
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_METHOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $sAdditionalMessage
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static function NotifyDeprecatedPhpEndpoint(?string $sAdditionalMessage = null): void
|
||||
{
|
||||
try {
|
||||
if (!static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_ENDPOINT)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ConfigException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||
$iStackDeprecatedMethodLevel = 0; // level 0 = current method, level 1 = method containing the `NotifyDeprecatedPhpMethod` call
|
||||
$sDeprecatedUrl = $_SERVER['REQUEST_URI'];
|
||||
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
|
||||
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
|
||||
$sMessage = "Call to endpoint {$sDeprecatedUrl} in {$sCallerFile}#L{$sCallerLine}";
|
||||
|
||||
if (!is_null($sAdditionalMessage)) {
|
||||
$sMessage .= ' : '.$sAdditionalMessage;
|
||||
}
|
||||
|
||||
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_ENDPOINT);
|
||||
}
|
||||
|
||||
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array()): void
|
||||
{
|
||||
if (true === utils::IsDevelopmentEnvironment()) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user