mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 23:44:11 +01:00
Compare commits
1056 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5232694c04 | ||
|
|
874a5fd2ce | ||
|
|
063bb9680e | ||
|
|
8f8ac46f55 | ||
|
|
07b904ee1b | ||
|
|
affed69999 | ||
|
|
d5754fc568 | ||
|
|
44290db312 | ||
|
|
c49ceae75e | ||
|
|
8980f627e9 | ||
|
|
160bfd714b | ||
|
|
8d58372074 | ||
|
|
37fc1a5723 | ||
|
|
95aa444ee6 | ||
|
|
f5de808c7c | ||
|
|
e03033ce52 | ||
|
|
374b35f78a | ||
|
|
04bd8cc5ce | ||
|
|
8cb701bda3 | ||
|
|
1b29746806 | ||
|
|
fb9c317256 | ||
|
|
0904a21e3f | ||
|
|
82d11eeb47 | ||
|
|
142d6c8993 | ||
|
|
320922a13d | ||
|
|
f03d731b1d | ||
|
|
8be7628668 | ||
|
|
62caf16153 | ||
|
|
163a3afc0f | ||
|
|
f8b54be896 | ||
|
|
53dc452d61 | ||
|
|
ccaf2dc5b7 | ||
|
|
5d5df5ad1a | ||
|
|
32140b360f | ||
|
|
d85767a838 | ||
|
|
eeec57536b | ||
|
|
16ff6341d0 | ||
|
|
9dab8679d6 | ||
|
|
4c78488644 | ||
|
|
b65e931c4c | ||
|
|
6cb3519308 | ||
|
|
cfb9fae648 | ||
|
|
f4e791734f | ||
|
|
6653ab0668 | ||
|
|
7ab258ba03 | ||
|
|
b5af30a93f | ||
|
|
bbfa601ab1 | ||
|
|
172b1cb1ff | ||
|
|
ca356859a3 | ||
|
|
5efe294895 | ||
|
|
e0170ccc7e | ||
|
|
3b78885f38 | ||
|
|
ed562c9f73 | ||
|
|
85c576a986 | ||
|
|
5a34c76cc4 | ||
|
|
da99a250bf | ||
|
|
dbd5ba0377 | ||
|
|
5d6f293956 | ||
|
|
986c24d777 | ||
|
|
763112c179 | ||
|
|
a5efd981d8 | ||
|
|
2922b22478 | ||
|
|
2af05a437e | ||
|
|
a9f8dcc5e8 | ||
|
|
c325294e17 | ||
|
|
da490739be | ||
|
|
b867faa355 | ||
|
|
7453cc184f | ||
|
|
473cf004b6 | ||
|
|
f6fec506b1 | ||
|
|
5c12151c26 | ||
|
|
5d6c4939f6 | ||
|
|
1b3a2c8470 | ||
|
|
618d8e6468 | ||
|
|
01a955a16f | ||
|
|
87582a021b | ||
|
|
9830178a47 | ||
|
|
c140ebcb6b | ||
|
|
7a0a4e377b | ||
|
|
7fffbb60e9 | ||
|
|
2fd9523c16 | ||
|
|
a4f6f6e877 | ||
|
|
94c604a6af | ||
|
|
6995a3c641 | ||
|
|
9865bf0779 | ||
|
|
d5449cca42 | ||
|
|
5d38d22c50 | ||
|
|
99d69493d1 | ||
|
|
c9bb628c30 | ||
|
|
08e8d15d78 | ||
|
|
7b59df216b | ||
|
|
cb5eab812e | ||
|
|
c9b73a7fe2 | ||
|
|
3b2da39469 | ||
|
|
fc22d91232 | ||
|
|
b10bcb976d | ||
|
|
5a43448644 | ||
|
|
77409eed99 | ||
|
|
83a70daf68 | ||
|
|
a49a4e6c2b | ||
|
|
7419749ba6 | ||
|
|
037dfe1df6 | ||
|
|
0b26d45014 | ||
|
|
4fd8177165 | ||
|
|
a2cdf214f0 | ||
|
|
013173019f | ||
|
|
9469681a0c | ||
|
|
c72cb7e70e | ||
|
|
9df92665e0 | ||
|
|
8ecebee511 | ||
|
|
35cd965360 | ||
|
|
e5dd51f637 | ||
|
|
0a6c82dfe1 | ||
|
|
24c0f4950f | ||
|
|
d4dbbc59d4 | ||
|
|
dc0cd44c79 | ||
|
|
6c6131ce03 | ||
|
|
e76728b2bf | ||
|
|
e946fc65fc | ||
|
|
0d8ff7bbac | ||
|
|
bf768311c2 | ||
|
|
a8c689c6c0 | ||
|
|
1990ccb5d8 | ||
|
|
e107be56e4 | ||
|
|
ed6df77cbb | ||
|
|
1ad28312ec | ||
|
|
f002aa04cd | ||
|
|
b86d70623e | ||
|
|
fe3467309d | ||
|
|
851ab9c356 | ||
|
|
aef3c2e609 | ||
|
|
6d13397ba1 | ||
|
|
c0c8a13864 | ||
|
|
cd9beec313 | ||
|
|
8295eaed90 | ||
|
|
5475b9fbbe | ||
|
|
6f8e7c7002 | ||
|
|
bc7c1b4744 | ||
|
|
4d8246c4d8 | ||
|
|
5c61d725e1 | ||
|
|
2c4cad4dac | ||
|
|
da45651121 | ||
|
|
d388ce9a06 | ||
|
|
47e71d8838 | ||
|
|
2b5973ec67 | ||
|
|
78396d8e4a | ||
|
|
9afc22bd8f | ||
|
|
264a8cd70a | ||
|
|
aa1834170b | ||
|
|
f94d67ab35 | ||
|
|
3048c8c41f | ||
|
|
246e4a9f50 | ||
|
|
6d58adb6dd | ||
|
|
5a0b5364d6 | ||
|
|
76eed2eba0 | ||
|
|
1ec671ef61 | ||
|
|
72716b7ec8 | ||
|
|
4f999de844 | ||
|
|
ea49c0a87c | ||
|
|
6cc971849b | ||
|
|
2405810864 | ||
|
|
fff46d99fc | ||
|
|
3a891f707c | ||
|
|
8b6ea43ebe | ||
|
|
90cf7502e8 | ||
|
|
c596fa2967 | ||
|
|
a45177410e | ||
|
|
9e96ea2873 | ||
|
|
1172159745 | ||
|
|
fa038ded3d | ||
|
|
e7ea1b831c | ||
|
|
4aff65f98b | ||
|
|
3c94974d9d | ||
|
|
fbd72b2783 | ||
|
|
4e95ca3c7b | ||
|
|
1114ed9562 | ||
|
|
34368fe795 | ||
|
|
0f016d7511 | ||
|
|
5ee6223434 | ||
|
|
d908827787 | ||
|
|
4cea418517 | ||
|
|
97965277c7 | ||
|
|
18ed5ed526 | ||
|
|
94c4f8c929 | ||
|
|
822922df5c | ||
|
|
cac7e94a67 | ||
|
|
6d019615d0 | ||
|
|
dbd58cfeb6 | ||
|
|
f65e14397c | ||
|
|
c696a81c3a | ||
|
|
845adf43c6 | ||
|
|
5916e4ea39 | ||
|
|
fbc0a898ae | ||
|
|
36f8e58e25 | ||
|
|
6a7dbb06b0 | ||
|
|
5721a324c1 | ||
|
|
7de6c72154 | ||
|
|
c0cee02351 | ||
|
|
bb674fb873 | ||
|
|
6136eadd31 | ||
|
|
87cb73c038 | ||
|
|
11d8547cef | ||
|
|
0998c73a1a | ||
|
|
471f66649a | ||
|
|
e8bf9cf688 | ||
|
|
4f88a0e7d2 | ||
|
|
c6b0e273e6 | ||
|
|
d9539f9d01 | ||
|
|
a3e309acb5 | ||
|
|
c06cbfd4a9 | ||
|
|
1d7e4e1a42 | ||
|
|
92a36dcfdd | ||
|
|
b37e74b407 | ||
|
|
0d49c605e2 | ||
|
|
7c2f8f4d93 | ||
|
|
1f76ff940d | ||
|
|
bb26e48d38 | ||
|
|
cf433f2f80 | ||
|
|
ae94e58a43 | ||
|
|
cda017fa4f | ||
|
|
dad22f6f83 | ||
|
|
9077f7ba37 | ||
|
|
957ff40f30 | ||
|
|
aff9c7748b | ||
|
|
e518d34bc9 | ||
|
|
f0141530b9 | ||
|
|
ce5096a896 | ||
|
|
23e0ed5e56 | ||
|
|
d412a52fcc | ||
|
|
3e18ad590f | ||
|
|
22111bf667 | ||
|
|
6d0c46595d | ||
|
|
d292a6b0c3 | ||
|
|
74702c8d06 | ||
|
|
e9c91d986d | ||
|
|
70a6b276ca | ||
|
|
f77361ceb2 | ||
|
|
75f4751b82 | ||
|
|
b56f2f56f1 | ||
|
|
68d44fa981 | ||
|
|
7e5307bd96 | ||
|
|
cd010afb48 | ||
|
|
0cf8d731bb | ||
|
|
189ca3c555 | ||
|
|
1e1f1f78bf | ||
|
|
1494604740 | ||
|
|
583ab98210 | ||
|
|
88d743b1cc | ||
|
|
7ac4bc95bb | ||
|
|
d431811725 | ||
|
|
7512f721e9 | ||
|
|
bdfe3a3b35 | ||
|
|
5cf391c3bb | ||
|
|
4c1df9927d | ||
|
|
74003f12c1 | ||
|
|
3bc12b0434 | ||
|
|
02a0969b53 | ||
|
|
d78a25ee4e | ||
|
|
7a6a3d1ac0 | ||
|
|
6b32be0899 | ||
|
|
33c2168af2 | ||
|
|
ae021064a4 | ||
|
|
0a61169326 | ||
|
|
d7e5705520 | ||
|
|
35a8b501c9 | ||
|
|
8fa616f440 | ||
|
|
5829e698da | ||
|
|
df347b90e5 | ||
|
|
bb861aa262 | ||
|
|
f9ac07830e | ||
|
|
0f4c7ac90f | ||
|
|
0687f9a0a9 | ||
|
|
6e75ab2889 | ||
|
|
b90d29d448 | ||
|
|
1f78bf4119 | ||
|
|
5d25e77189 | ||
|
|
d9dabf25da | ||
|
|
73af605892 | ||
|
|
f70f95c119 | ||
|
|
53c50cf6fc | ||
|
|
f19d1472c5 | ||
|
|
eef00502cd | ||
|
|
0b1caac195 | ||
|
|
e900a44d47 | ||
|
|
a3de9fa898 | ||
|
|
8b0154cc62 | ||
|
|
1a225bf55b | ||
|
|
24d19cd8d6 | ||
|
|
c25a4a7346 | ||
|
|
20fb7b241f | ||
|
|
a0553e1195 | ||
|
|
f40141072a | ||
|
|
c759856a61 | ||
|
|
237b181eec | ||
|
|
48957fd2f0 | ||
|
|
8a99c37200 | ||
|
|
d388c3fd3d | ||
|
|
1b8e48539d | ||
|
|
104beff158 | ||
|
|
4712569a36 | ||
|
|
2392f4a902 | ||
|
|
a0f28a9098 | ||
|
|
6df622e8ed | ||
|
|
54eb9d081b | ||
|
|
9f60f27636 | ||
|
|
ba59643f52 | ||
|
|
01c02a75a8 | ||
|
|
f5b3e5f341 | ||
|
|
9b825cb529 | ||
|
|
3f326f0913 | ||
|
|
ec86bd246a | ||
|
|
aa90d5b6ab | ||
|
|
53d2129bd1 | ||
|
|
00e8c11ec2 | ||
|
|
617b6b991f | ||
|
|
b3ea1050eb | ||
|
|
ca98066d68 | ||
|
|
352f7c8675 | ||
|
|
df5d514c28 | ||
|
|
16663797b2 | ||
|
|
4099376472 | ||
|
|
6d3118d9e9 | ||
|
|
4c585614cd | ||
|
|
9674378c56 | ||
|
|
9e314ba77b | ||
|
|
cdd7dcdc5c | ||
|
|
34bed5ec4f | ||
|
|
3ea82e37d5 | ||
|
|
596c62aec8 | ||
|
|
265415030e | ||
|
|
3d26f28f9b | ||
|
|
0abec767e3 | ||
|
|
9fd10bd73e | ||
|
|
95dafc87c0 | ||
|
|
fe1790793e | ||
|
|
ddb95dc64e | ||
|
|
f6f9ee26e1 | ||
|
|
21faa92904 | ||
|
|
622f40c06c | ||
|
|
964134cb60 | ||
|
|
72f498a63b | ||
|
|
f9a1f68295 | ||
|
|
9b67b0b9d5 | ||
|
|
f798ef1d76 | ||
|
|
754946bf62 | ||
|
|
a6580e3cd8 | ||
|
|
da6621f2ff | ||
|
|
f2d42a7e56 | ||
|
|
d01e4b4a85 | ||
|
|
f57d1f1de3 | ||
|
|
a3f122184c | ||
|
|
16fcddc249 | ||
|
|
2a9c9be36a | ||
|
|
ca3aae23a1 | ||
|
|
4dd384e418 | ||
|
|
80e7313b24 | ||
|
|
183c3c1baf | ||
|
|
160c52fe81 | ||
|
|
5f0a820b4a | ||
|
|
03ef4246bf | ||
|
|
534e7cf59d | ||
|
|
e1645f6903 | ||
|
|
61a2d200b4 | ||
|
|
3d6bbe4029 | ||
|
|
44eda676a3 | ||
|
|
eac6f07823 | ||
|
|
424e2a5745 | ||
|
|
0ef4fee0b4 | ||
|
|
1d45eff9b0 | ||
|
|
8e97279401 | ||
|
|
932ef780fd | ||
|
|
59424c3126 | ||
|
|
562dd8fc21 | ||
|
|
cf745554fb | ||
|
|
e909eac98e | ||
|
|
5e42efc3ec | ||
|
|
eb1d56f439 | ||
|
|
644e1ac4f6 | ||
|
|
4c88dbd9ac | ||
|
|
11d2e62e67 | ||
|
|
58b27a9daa | ||
|
|
caf939bf58 | ||
|
|
8c217fdac9 | ||
|
|
6b80bbeaa2 | ||
|
|
134736dce5 | ||
|
|
4b870bcf1e | ||
|
|
dd8a4a0082 | ||
|
|
c2607c4223 | ||
|
|
1fb0911710 | ||
|
|
b348e0ff27 | ||
|
|
4646a05c7a | ||
|
|
c5527c106c | ||
|
|
5eac1b8730 | ||
|
|
0de15d040f | ||
|
|
c4ae94fd4c | ||
|
|
1e8818984e | ||
|
|
a023f73509 | ||
|
|
6f0e1a7f47 | ||
|
|
0ef9bb1a47 | ||
|
|
71ceedc4bb | ||
|
|
73c3c1249f | ||
|
|
88a10dba28 | ||
|
|
001e222f67 | ||
|
|
af8bcdc242 | ||
|
|
f4c7afc148 | ||
|
|
b19c73a36e | ||
|
|
5fe0d0b94f | ||
|
|
f8d435d5f3 | ||
|
|
f15ef36fd1 | ||
|
|
64b25c4daa | ||
|
|
d0ba0d193b | ||
|
|
8e6e2432d3 | ||
|
|
83ec19dfca | ||
|
|
6e619f2c35 | ||
|
|
163ba41e8d | ||
|
|
ec143c43db | ||
|
|
cacf0004a5 | ||
|
|
cb39541e2a | ||
|
|
b9ddadeb44 | ||
|
|
11e811cc4b | ||
|
|
e422adb0d0 | ||
|
|
e02d9f3f0e | ||
|
|
e831d66b76 | ||
|
|
6fa2d47780 | ||
|
|
e691454339 | ||
|
|
92997e3e57 | ||
|
|
631b38a160 | ||
|
|
7ce5712b71 | ||
|
|
61137a6f65 | ||
|
|
0080a2e733 | ||
|
|
7f4fddb378 | ||
|
|
a71cb97db3 | ||
|
|
4c99f497cc | ||
|
|
0205cdf713 | ||
|
|
39fc59a8b2 | ||
|
|
107c9adf60 | ||
|
|
d29880b1b8 | ||
|
|
2d156bd77b | ||
|
|
d122dbfdd6 | ||
|
|
46d58e6512 | ||
|
|
93a138606f | ||
|
|
70074ee1cb | ||
|
|
d28ccb264f | ||
|
|
8ab38854a8 | ||
|
|
9f27cf2b84 | ||
|
|
f78986009f | ||
|
|
809ea2eb49 | ||
|
|
968a0e5f3a | ||
|
|
83e98ef2b8 | ||
|
|
5048421bfa | ||
|
|
788caf9c50 | ||
|
|
35165568af | ||
|
|
4a67819f87 | ||
|
|
81c39c35cd | ||
|
|
4caf52f1ae | ||
|
|
0c5b845c8a | ||
|
|
cdfdb1f2ca | ||
|
|
f29a8792af | ||
|
|
b494ff2ce6 | ||
|
|
df1e19dc43 | ||
|
|
9ad341f73a | ||
|
|
03e9bcd47a | ||
|
|
55effea0a3 | ||
|
|
dfaa973359 | ||
|
|
2e45b20fc4 | ||
|
|
e89090f0ec | ||
|
|
47db04bcb7 | ||
|
|
a49c451ae4 | ||
|
|
25c3704990 | ||
|
|
3000659e86 | ||
|
|
ce36c00b83 | ||
|
|
2a3e6384d9 | ||
|
|
dd7e73e413 | ||
|
|
1709082e39 | ||
|
|
41f6e85673 | ||
|
|
3ef3166bd5 | ||
|
|
299ad7e753 | ||
|
|
84280a3b5f | ||
|
|
b4fc647845 | ||
|
|
17612f88d3 | ||
|
|
e14845728c | ||
|
|
4e80fc0f76 | ||
|
|
fcfcf85e0d | ||
|
|
f0715baf7d | ||
|
|
45b5c39af7 | ||
|
|
dbc3da7bc3 | ||
|
|
ebc9fa684a | ||
|
|
606bdc1909 | ||
|
|
7495fb9af4 | ||
|
|
75dbad7406 | ||
|
|
3381c085f4 | ||
|
|
9b6f7d94f4 | ||
|
|
64e8aa5fee | ||
|
|
477128ad53 | ||
|
|
aa66bec783 | ||
|
|
1da52a8517 | ||
|
|
cbd2181862 | ||
|
|
4180a41f27 | ||
|
|
a43adcd202 | ||
|
|
e8e170fb06 | ||
|
|
5ac5d649aa | ||
|
|
decb802df4 | ||
|
|
cacc3a3085 | ||
|
|
0fd2ea6a47 | ||
|
|
426f275c03 | ||
|
|
693a861e7d | ||
|
|
0ee6c60e94 | ||
|
|
a663e9fded | ||
|
|
b3bf516b20 | ||
|
|
c2408b74cd | ||
|
|
6855c2f83a | ||
|
|
b11bf30881 | ||
|
|
64736f1463 | ||
|
|
930b224ca2 | ||
|
|
92b61c7491 | ||
|
|
e530cbb4f2 | ||
|
|
ddb8378fe6 | ||
|
|
47db23d91c | ||
|
|
fc1f701bf6 | ||
|
|
365c7bb89e | ||
|
|
b073e4385c | ||
|
|
f9359431fe | ||
|
|
25e560fdaa | ||
|
|
6bf25f90bc | ||
|
|
3db20e8028 | ||
|
|
b190d0e385 | ||
|
|
93f273a287 | ||
|
|
04e7616c84 | ||
|
|
219b970703 | ||
|
|
76c139253e | ||
|
|
02b09c2535 | ||
|
|
10cfb373f2 | ||
|
|
69578d5d07 | ||
|
|
97d6d413bb | ||
|
|
7e0d5d64ce | ||
|
|
3f8f57fa9a | ||
|
|
eb2a615bd2 | ||
|
|
0432727ace | ||
|
|
e6d61d1ebd | ||
|
|
f916f9cde8 | ||
|
|
5e61388b65 | ||
|
|
910bbe1160 | ||
|
|
9addc4a7ca | ||
|
|
5ed71ecb96 | ||
|
|
dab0e372d0 | ||
|
|
dfd1d5fe35 | ||
|
|
d289457c0c | ||
|
|
f52b3bff0d | ||
|
|
b6b17733bf | ||
|
|
c1c2d027c3 | ||
|
|
5269096ecd | ||
|
|
e4c68936a0 | ||
|
|
4db5d4c08d | ||
|
|
3511867ba3 | ||
|
|
7934f9b9dc | ||
|
|
7f2eef4a24 | ||
|
|
8a65a592f3 | ||
|
|
7d6b019cfa | ||
|
|
5e48400cb1 | ||
|
|
252562ace9 | ||
|
|
c9c32b0de1 | ||
|
|
770ac8ffe5 | ||
|
|
dcfdb2d0a9 | ||
|
|
0cbf34ba5a | ||
|
|
f1037147a9 | ||
|
|
bea52d5fb9 | ||
|
|
b6fac4b411 | ||
|
|
d8a77c22a3 | ||
|
|
ed3c387712 | ||
|
|
312a5b246b | ||
|
|
903de43589 | ||
|
|
2d67594ccf | ||
|
|
65f9f86bcc | ||
|
|
efaf53e568 | ||
|
|
81a2a9278c | ||
|
|
e15d4bfab6 | ||
|
|
77880c3675 | ||
|
|
3559425fc1 | ||
|
|
d8c4251c03 | ||
|
|
9437e689fd | ||
|
|
a79459bc53 | ||
|
|
500bd15843 | ||
|
|
3e8dd2f4a5 | ||
|
|
d0fade9ce1 | ||
|
|
51a49dfce8 | ||
|
|
066b71686d | ||
|
|
be633001a5 | ||
|
|
84426c6634 | ||
|
|
dbaf924171 | ||
|
|
8adf743cc7 | ||
|
|
75450ded1d | ||
|
|
bcca6ac720 | ||
|
|
865f9f4f67 | ||
|
|
a7e54d4bad | ||
|
|
2beb795f9a | ||
|
|
6847d8a5c7 | ||
|
|
06985d3cf2 | ||
|
|
8c7f7abaab | ||
|
|
e8d314e1f6 | ||
|
|
e29f1825be | ||
|
|
908a48e0a1 | ||
|
|
9b854dbcc7 | ||
|
|
7757f1f2d2 | ||
|
|
a353317746 | ||
|
|
723eb90160 | ||
|
|
8ea5be4ead | ||
|
|
b3f827ed5e | ||
|
|
eaf8a187aa | ||
|
|
34f64c61f6 | ||
|
|
8154e718a1 | ||
|
|
b5369a0c03 | ||
|
|
2bc61caab1 | ||
|
|
8f0a5fcaf9 | ||
|
|
fe3512cb5f | ||
|
|
fdc987f367 | ||
|
|
ec1dcc8df6 | ||
|
|
47ed863da9 | ||
|
|
88290f9e91 | ||
|
|
cfdbc8ae62 | ||
|
|
aaa8f6d311 | ||
|
|
1c983e8093 | ||
|
|
92a9a8c65f | ||
|
|
27217815d1 | ||
|
|
2fe4265223 | ||
|
|
2b71ea108a | ||
|
|
1aa5185c93 | ||
|
|
834ac00d37 | ||
|
|
8e6379a112 | ||
|
|
da217a1cb3 | ||
|
|
a683634a05 | ||
|
|
69ad10785b | ||
|
|
9aead898e2 | ||
|
|
a48ebfefba | ||
|
|
4748717e50 | ||
|
|
d90b1a3d82 | ||
|
|
ed719e13c7 | ||
|
|
2d705c6697 | ||
|
|
c98ad106c4 | ||
|
|
3694108f42 | ||
|
|
8cf75f826f | ||
|
|
ad9726b64c | ||
|
|
e32e275f40 | ||
|
|
195056492e | ||
|
|
af338de17f | ||
|
|
a6aa183e26 | ||
|
|
b5074c4cee | ||
|
|
8b9589744b | ||
|
|
8259a79cd2 | ||
|
|
a23ea9a01f | ||
|
|
949b213f9d | ||
|
|
a940adc4ba | ||
|
|
5d994edd62 | ||
|
|
b1ca1f2630 | ||
|
|
58e315d7f6 | ||
|
|
1059befa39 | ||
|
|
0f5130611d | ||
|
|
a1271da74a | ||
|
|
0d40235791 | ||
|
|
dd63f2b817 | ||
|
|
8f84c3b84b | ||
|
|
00c58bb245 | ||
|
|
3a876d5c75 | ||
|
|
147916062b | ||
|
|
0de6f98add | ||
|
|
a076792e77 | ||
|
|
2d2a6857de | ||
|
|
373641e01d | ||
|
|
d11eceac62 | ||
|
|
3965806fa0 | ||
|
|
2625d2da80 | ||
|
|
02d32a556d | ||
|
|
71fcc6f026 | ||
|
|
7168860a0b | ||
|
|
684c88e0b8 | ||
|
|
5691ca0327 | ||
|
|
84741c19f0 | ||
|
|
86f649affc | ||
|
|
4f5c987d8b | ||
|
|
e7b5953feb | ||
|
|
e441e5e78a | ||
|
|
6be9a87c15 | ||
|
|
43daa2ef08 | ||
|
|
caa2a05bf4 | ||
|
|
fc39d8aca9 | ||
|
|
cf12578289 | ||
|
|
44952d1ea0 | ||
|
|
7f15eed9a8 | ||
|
|
c2f5cafaf3 | ||
|
|
81822efa0f | ||
|
|
923a025f1c | ||
|
|
a4b6f4e37c | ||
|
|
f0c73451a2 | ||
|
|
db6e813cba | ||
|
|
d74e3e6b42 | ||
|
|
b740cb2afd | ||
|
|
6ad3c40c42 | ||
|
|
f49c8ce188 | ||
|
|
acf828b72e | ||
|
|
bac92716f3 | ||
|
|
07257cc2d2 | ||
|
|
87ba67225a | ||
|
|
2ad3b3c27e | ||
|
|
92a640e41a | ||
|
|
842df7646b | ||
|
|
01b38d2ed6 | ||
|
|
9af4846372 | ||
|
|
91fc2d2e2b | ||
|
|
2432ff77a3 | ||
|
|
d229e08f02 | ||
|
|
b5adb2e82b | ||
|
|
386c90c601 | ||
|
|
5d0c61178b | ||
|
|
3bcae734e5 | ||
|
|
842e8f9e01 | ||
|
|
52cd4f7c5e | ||
|
|
995619af9b | ||
|
|
c842162fe2 | ||
|
|
83f99642e0 | ||
|
|
ae6a264d6d | ||
|
|
a06bf6ea7c | ||
|
|
bb8d4a92cb | ||
|
|
1429792690 | ||
|
|
1f26b59d90 | ||
|
|
7b093a6bba | ||
|
|
d4607ee815 | ||
|
|
5c0e92d51a | ||
|
|
cd4b3fdaab | ||
|
|
0030d5c2b8 | ||
|
|
95a0efedcf | ||
|
|
13a1d32f56 | ||
|
|
35155e4b7a | ||
|
|
77710f1613 | ||
|
|
2763b99142 | ||
|
|
db13c105ad | ||
|
|
2276539f24 | ||
|
|
9b7cd20d47 | ||
|
|
e1d644c33b | ||
|
|
c601082a5e | ||
|
|
5836be7131 | ||
|
|
6f40bb4c35 | ||
|
|
241bd1cdeb | ||
|
|
71c5f47cd8 | ||
|
|
74246a8278 | ||
|
|
c450c9426c | ||
|
|
46f9fe743c | ||
|
|
c31df5fff3 | ||
|
|
6e0af1a3b7 | ||
|
|
e9e18513be | ||
|
|
9d2fc883b8 | ||
|
|
913ea0cef2 | ||
|
|
82ba7f25b0 | ||
|
|
bb877a244b | ||
|
|
a12959d60e | ||
|
|
83434b5506 | ||
|
|
dcd4abe72b | ||
|
|
571520815a | ||
|
|
e9cff0920b | ||
|
|
905ee19519 | ||
|
|
0b95220d1b | ||
|
|
e1b2a767f5 | ||
|
|
3058b2eb00 | ||
|
|
38bc2d9d58 | ||
|
|
c8e8778d7b | ||
|
|
656fa3208a | ||
|
|
f647ce61c2 | ||
|
|
6b76e5a853 | ||
|
|
dbb6e43751 | ||
|
|
f07f0ba1c7 | ||
|
|
a5894c1a4c | ||
|
|
e06996a2e4 | ||
|
|
2f0e7c6d29 | ||
|
|
7115a6ae7d | ||
|
|
765560d1f5 | ||
|
|
bc024d9ed0 | ||
|
|
37a4a3eb47 | ||
|
|
54e9bd5c8e | ||
|
|
066a6d8b36 | ||
|
|
4123c6213d | ||
|
|
8265b9b034 | ||
|
|
c4756e8cec | ||
|
|
37351d6b3e | ||
|
|
57a085eec1 | ||
|
|
0019595923 | ||
|
|
4d61c14f80 | ||
|
|
cf1b613923 | ||
|
|
1304e2eb2d | ||
|
|
3cf16627c1 | ||
|
|
4aaa237bf9 | ||
|
|
cece15d10c | ||
|
|
aa15e009cb | ||
|
|
b9ca2ac13d | ||
|
|
80e1e0e61a | ||
|
|
ecebe4ecd5 | ||
|
|
8bfcb14d0c | ||
|
|
1cf1473d6b | ||
|
|
aa43425df3 | ||
|
|
35d77ff642 | ||
|
|
539fa43503 | ||
|
|
eb537f45f4 | ||
|
|
a2a4cd4e7a | ||
|
|
35215cf62f | ||
|
|
66273ebd39 | ||
|
|
eebc29d2bb | ||
|
|
512b415bd6 | ||
|
|
906c8855b0 | ||
|
|
97d322a059 | ||
|
|
ada7f30793 | ||
|
|
b065d13374 | ||
|
|
1f092f8418 | ||
|
|
65d6947e52 | ||
|
|
ba54b47f7d | ||
|
|
65e43e8d04 | ||
|
|
adb4e77c8d | ||
|
|
d3cf7176da | ||
|
|
1cfb52d220 | ||
|
|
18d5231900 | ||
|
|
e6539ccb6e | ||
|
|
96332b7885 | ||
|
|
557b9be795 | ||
|
|
75ebecddd5 | ||
|
|
5fee2438ab | ||
|
|
2d130cbba8 | ||
|
|
8b1c20cc11 | ||
|
|
0001e8ffc4 | ||
|
|
df5aacca42 | ||
|
|
06acac97ba | ||
|
|
2d6d1132c7 | ||
|
|
ed0e16494d | ||
|
|
a765eb8725 | ||
|
|
1f53757318 | ||
|
|
090119147c | ||
|
|
1551694198 | ||
|
|
5d7ae38adf | ||
|
|
2e08ae571a | ||
|
|
37522459a8 | ||
|
|
db8c26da17 | ||
|
|
93c91c4077 | ||
|
|
e4b3871947 | ||
|
|
b2474d3368 | ||
|
|
6cd0670d6b | ||
|
|
e9f81bd978 | ||
|
|
0cc5dc0471 | ||
|
|
20ce42b24b | ||
|
|
d86e904e18 | ||
|
|
bef1832ac7 | ||
|
|
5a46bb8461 | ||
|
|
05a0d61244 | ||
|
|
80b3212a19 | ||
|
|
794d4f1e0e | ||
|
|
389b61d3a8 | ||
|
|
0948e80060 | ||
|
|
9520d2794f | ||
|
|
e2c67dfcc4 | ||
|
|
4e0eed6e13 | ||
|
|
45e366745d | ||
|
|
1e634a8bba | ||
|
|
94b9a9bb75 | ||
|
|
0c90b701ea | ||
|
|
58961cd4ec | ||
|
|
1453558f3e | ||
|
|
c6df0b6d7d | ||
|
|
7c3659d5ba | ||
|
|
dd942997cb | ||
|
|
57fea03745 | ||
|
|
827b4b5bbe | ||
|
|
7de59c1977 | ||
|
|
c34c4bc09d | ||
|
|
bced819b3f | ||
|
|
23136bdf00 | ||
|
|
98c371c5cf | ||
|
|
1e0415e902 | ||
|
|
dbada2f72a | ||
|
|
9694e9848d | ||
|
|
fe87700135 | ||
|
|
7107c2f616 | ||
|
|
92e0f101d7 | ||
|
|
29624bc5c5 | ||
|
|
d09f3f4f83 | ||
|
|
f774a90b7e | ||
|
|
fef8038f70 | ||
|
|
2806a76c1d | ||
|
|
328ec52c88 | ||
|
|
70734e2b71 | ||
|
|
bb892cc180 | ||
|
|
4618f12d8a | ||
|
|
d12e2e592a | ||
|
|
6a25933744 | ||
|
|
208ccfe3ab | ||
|
|
f74c78d61c | ||
|
|
6176af089c | ||
|
|
a35b2d83b7 | ||
|
|
8902d6e532 | ||
|
|
07bd6b8539 | ||
|
|
1148449bb7 | ||
|
|
11d418fd49 | ||
|
|
ace676dc24 | ||
|
|
8122270476 | ||
|
|
1f66d53ab4 | ||
|
|
dfaeca43e4 | ||
|
|
5b04143711 | ||
|
|
bd14096d43 | ||
|
|
3b20be05cb | ||
|
|
fdec608c3e | ||
|
|
72cb3de50d | ||
|
|
c03d32b423 | ||
|
|
94f9b16c03 | ||
|
|
311aeb0b07 | ||
|
|
68fe3f01be | ||
|
|
7ce94486bd | ||
|
|
6523b34d58 | ||
|
|
be20705449 | ||
|
|
e7abaa2838 | ||
|
|
8d73eb6dff | ||
|
|
f84995a58f | ||
|
|
7f66e26b5f | ||
|
|
a6639b067f | ||
|
|
8a6d66effd | ||
|
|
6885d64124 | ||
|
|
6fa153ae8b | ||
|
|
e226222c2a | ||
|
|
aca0143e89 | ||
|
|
1968c60770 | ||
|
|
26014f410a | ||
|
|
8912618732 | ||
|
|
7bee718a13 | ||
|
|
2705543efd | ||
|
|
1e6b885301 | ||
|
|
c768e18e2b | ||
|
|
d4b93f3bf0 | ||
|
|
6354c62c2b | ||
|
|
cf8a12fe95 | ||
|
|
36804dfcf4 | ||
|
|
6966c0498a | ||
|
|
bbffc40ee0 | ||
|
|
1b7473365d | ||
|
|
0b84e809f6 | ||
|
|
a858362622 | ||
|
|
d195c2b4c9 | ||
|
|
28b75f29e5 | ||
|
|
9d8a7bf561 | ||
|
|
8064a20718 | ||
|
|
f301a283e2 | ||
|
|
e6a8f492d5 | ||
|
|
336637a7a4 | ||
|
|
0e5a501b2a | ||
|
|
59af58a173 | ||
|
|
7f922560ba | ||
|
|
d2e286345e | ||
|
|
fb120bdc7c | ||
|
|
5548997f3e | ||
|
|
156828c448 | ||
|
|
04ef2b0454 | ||
|
|
076d2e3d46 | ||
|
|
0c6ab86e54 | ||
|
|
876db3e58f | ||
|
|
5f7fe345cc | ||
|
|
cb6f78c9e3 | ||
|
|
8c86908652 | ||
|
|
7e69256cb4 | ||
|
|
83e3c089a4 | ||
|
|
0d1059a8fc | ||
|
|
e2f15ca24a | ||
|
|
7628b85b70 | ||
|
|
525f600c18 | ||
|
|
0ffa2850ea | ||
|
|
fa3610cfee | ||
|
|
898ee95a2c | ||
|
|
730570f1f8 | ||
|
|
80ce1eb125 | ||
|
|
228a945da9 | ||
|
|
79909fadc0 | ||
|
|
746b47bb0e | ||
|
|
150d3e096d | ||
|
|
23afee514d | ||
|
|
48c5698f08 | ||
|
|
1a4ee0f977 | ||
|
|
1ca39618e1 | ||
|
|
7bb1f9f423 | ||
|
|
834297e675 | ||
|
|
21c2574cd9 | ||
|
|
6d9923be68 | ||
|
|
839bbc425f | ||
|
|
70cc19768a | ||
|
|
873d109b98 | ||
|
|
a81950571a | ||
|
|
bcd9679957 | ||
|
|
2c10913fe5 | ||
|
|
0342b89481 | ||
|
|
3c9318d56a | ||
|
|
30d10b6f11 | ||
|
|
3fd55c6dd6 | ||
|
|
f8e39877b3 | ||
|
|
0a3f7d7ef7 | ||
|
|
222eb47bd2 | ||
|
|
c15b3462d1 | ||
|
|
32f05ea917 | ||
|
|
6a50b55a2a | ||
|
|
72f11c6a4d | ||
|
|
609ea47f7b | ||
|
|
74b3cfd46c | ||
|
|
f7ea6c09cd | ||
|
|
526a7f9817 | ||
|
|
5ccb1ef72a | ||
|
|
7ec7626aa0 | ||
|
|
5d7582bb6f | ||
|
|
7a40db94fb | ||
|
|
bf13f9fc8a | ||
|
|
289171b9f1 | ||
|
|
9b065ffb0a | ||
|
|
a182a37139 | ||
|
|
23c15c1b6c | ||
|
|
866e4ab995 | ||
|
|
75730eeea0 | ||
|
|
58fd8709be | ||
|
|
f18ea18a5b | ||
|
|
e1949cd3eb | ||
|
|
c5b1f02d2b | ||
|
|
f81ab4d71a | ||
|
|
0b95dbee7f | ||
|
|
db593ff85e | ||
|
|
4ee66377ce | ||
|
|
432a950f8c | ||
|
|
bbc751bee4 | ||
|
|
a77ba2fbab | ||
|
|
b88b9dabdb | ||
|
|
06b17e82db | ||
|
|
2add79a473 | ||
|
|
3103f361a4 | ||
|
|
3a37e24496 | ||
|
|
621295199c | ||
|
|
b1d703bff3 | ||
|
|
a3a34a94e7 | ||
|
|
6edc365685 | ||
|
|
4b7f736af0 | ||
|
|
016fbaed36 | ||
|
|
bfcd137e52 | ||
|
|
f9af8fc912 | ||
|
|
d5fe653e51 | ||
|
|
fc2fb235a2 | ||
|
|
d7211509bd | ||
|
|
c182b1a01f | ||
|
|
ee0d231426 | ||
|
|
3282b46c9b | ||
|
|
05649ba50f | ||
|
|
40efc4cbb1 | ||
|
|
eb41d3e2ef | ||
|
|
c6b16bb52e | ||
|
|
95adbbb58f | ||
|
|
60f5c60059 | ||
|
|
c0284ecc3b | ||
|
|
fc7b772ba3 | ||
|
|
011d742ae3 | ||
|
|
5b496f4d15 | ||
|
|
97f4c32271 | ||
|
|
c002ca7902 | ||
|
|
ff22074418 |
396
.editorconfig
396
.editorconfig
@@ -1,26 +1,30 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
max_line_length = 140
|
||||
max_line_length = 300
|
||||
tab_width = 4
|
||||
ij_continuation_indent_size = 8
|
||||
ij_formatter_off_tag = @formatter:off
|
||||
ij_formatter_on_tag = @formatter:on
|
||||
ij_formatter_tags_enabled = false
|
||||
ij_smart_tabs = false
|
||||
ij_visual_guides = 80, 120, 140
|
||||
ij_visual_guides = 300
|
||||
ij_wrap_on_typing = true
|
||||
|
||||
[*.css]
|
||||
indent_style = tab
|
||||
ij_smart_tabs = true
|
||||
ij_visual_guides = none
|
||||
ij_css_align_closing_brace_with_properties = false
|
||||
ij_css_blank_lines_around_nested_selector = 1
|
||||
ij_css_blank_lines_between_blocks = 1
|
||||
ij_css_brace_placement = 0
|
||||
ij_css_brace_placement = end_of_line
|
||||
ij_css_enforce_quotes_on_format = false
|
||||
ij_css_hex_color_long_format = false
|
||||
ij_css_hex_color_lower_case = false
|
||||
ij_css_hex_color_short_format = false
|
||||
@@ -31,59 +35,18 @@ ij_css_keep_single_line_blocks = false
|
||||
ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow
|
||||
ij_css_space_after_colon = true
|
||||
ij_css_space_before_opening_brace = true
|
||||
ij_css_value_alignment = 0
|
||||
|
||||
[*.csv]
|
||||
max_line_length = 2147483647
|
||||
ij_wrap_on_typing = false
|
||||
ij_csv_wrap_long_lines = false
|
||||
|
||||
[*.feature]
|
||||
indent_size = 2
|
||||
ij_gherkin_keep_indents_on_empty_lines = false
|
||||
|
||||
[*.less]
|
||||
indent_size = 2
|
||||
ij_less_align_closing_brace_with_properties = false
|
||||
ij_less_blank_lines_around_nested_selector = 1
|
||||
ij_less_blank_lines_between_blocks = 1
|
||||
ij_less_brace_placement = 0
|
||||
ij_less_hex_color_long_format = false
|
||||
ij_less_hex_color_lower_case = false
|
||||
ij_less_hex_color_short_format = false
|
||||
ij_less_hex_color_upper_case = false
|
||||
ij_less_keep_blank_lines_in_code = 2
|
||||
ij_less_keep_indents_on_empty_lines = false
|
||||
ij_less_keep_single_line_blocks = false
|
||||
ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow
|
||||
ij_less_space_after_colon = true
|
||||
ij_less_space_before_opening_brace = true
|
||||
ij_less_value_alignment = 0
|
||||
|
||||
[*.sass]
|
||||
indent_size = 2
|
||||
ij_sass_align_closing_brace_with_properties = false
|
||||
ij_sass_blank_lines_around_nested_selector = 1
|
||||
ij_sass_blank_lines_between_blocks = 1
|
||||
ij_sass_brace_placement = 0
|
||||
ij_sass_hex_color_long_format = false
|
||||
ij_sass_hex_color_lower_case = false
|
||||
ij_sass_hex_color_short_format = false
|
||||
ij_sass_hex_color_upper_case = false
|
||||
ij_sass_keep_blank_lines_in_code = 2
|
||||
ij_sass_keep_indents_on_empty_lines = false
|
||||
ij_sass_keep_single_line_blocks = false
|
||||
ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow
|
||||
ij_sass_space_after_colon = true
|
||||
ij_sass_space_before_opening_brace = true
|
||||
ij_sass_value_alignment = 0
|
||||
ij_css_use_double_quotes = true
|
||||
ij_css_value_alignment = do_not_align
|
||||
|
||||
[*.scss]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
ij_visual_guides = none
|
||||
ij_scss_align_closing_brace_with_properties = false
|
||||
ij_scss_blank_lines_around_nested_selector = 1
|
||||
ij_scss_blank_lines_between_blocks = 1
|
||||
ij_scss_brace_placement = 0
|
||||
ij_scss_enforce_quotes_on_format = false
|
||||
ij_scss_hex_color_long_format = false
|
||||
ij_scss_hex_color_lower_case = false
|
||||
ij_scss_hex_color_short_format = false
|
||||
@@ -94,17 +57,20 @@ ij_scss_keep_single_line_blocks = false
|
||||
ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow
|
||||
ij_scss_space_after_colon = true
|
||||
ij_scss_space_before_opening_brace = true
|
||||
ij_scss_use_double_quotes = true
|
||||
ij_scss_value_alignment = 0
|
||||
|
||||
[*.twig]
|
||||
indent_style = tab
|
||||
ij_smart_tabs = true
|
||||
ij_visual_guides = none
|
||||
ij_wrap_on_typing = false
|
||||
ij_twig_keep_indents_on_empty_lines = false
|
||||
ij_twig_spaces_inside_comments_delimiters = true
|
||||
ij_twig_spaces_inside_delimiters = true
|
||||
ij_twig_spaces_inside_variable_delimiters = true
|
||||
|
||||
[.editorconfig]
|
||||
ij_visual_guides = none
|
||||
ij_editorconfig_align_group_field_declarations = false
|
||||
ij_editorconfig_space_after_colon = false
|
||||
ij_editorconfig_space_after_comma = true
|
||||
@@ -112,10 +78,45 @@ ij_editorconfig_space_before_colon = false
|
||||
ij_editorconfig_space_before_comma = false
|
||||
ij_editorconfig_spaces_around_assignment_operators = true
|
||||
|
||||
[{*.ant, *.fxml, *.jhm, *.jnlp, *.jrxml, *.rng, *.tld, *.wsdl, *.xml, *.xsd, *.xsl, *.xslt, *.xul, phpunit.xml.dist}]
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
ij_smart_tabs = true
|
||||
ij_visual_guides = none
|
||||
ij_wrap_on_typing = false
|
||||
ij_xml_align_attributes = true
|
||||
ij_xml_align_text = false
|
||||
ij_xml_attribute_wrap = normal
|
||||
ij_xml_block_comment_at_first_column = true
|
||||
ij_xml_keep_blank_lines = 2
|
||||
ij_xml_keep_indents_on_empty_lines = false
|
||||
ij_xml_keep_line_breaks = true
|
||||
ij_xml_keep_line_breaks_in_text = true
|
||||
ij_xml_keep_whitespaces = false
|
||||
ij_xml_keep_whitespaces_around_cdata = preserve
|
||||
ij_xml_keep_whitespaces_inside_cdata = true
|
||||
ij_xml_line_comment_at_first_column = true
|
||||
ij_xml_space_after_tag_name = false
|
||||
ij_xml_space_around_equals_in_attribute = false
|
||||
ij_xml_space_inside_empty_tag = false
|
||||
ij_xml_text_wrap = off
|
||||
|
||||
[{*.bash,*.sh,*.zsh}]
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
ij_visual_guides = none
|
||||
ij_shell_binary_ops_start_line = false
|
||||
ij_shell_keep_column_alignment_padding = false
|
||||
ij_shell_minify_program = false
|
||||
ij_shell_redirect_followed_by_space = false
|
||||
ij_shell_switch_cases_indented = false
|
||||
ij_shell_use_unix_line_separator = true
|
||||
|
||||
[{*.cjs,*.js}]
|
||||
indent_style = tab
|
||||
ij_continuation_indent_size = 4
|
||||
ij_smart_tabs = true
|
||||
ij_visual_guides = none
|
||||
ij_javascript_align_imports = false
|
||||
ij_javascript_align_multiline_array_initializer_expression = false
|
||||
ij_javascript_align_multiline_binary_operation = false
|
||||
@@ -134,13 +135,13 @@ ij_javascript_array_initializer_wrap = off
|
||||
ij_javascript_assignment_wrap = off
|
||||
ij_javascript_binary_operation_sign_on_next_line = false
|
||||
ij_javascript_binary_operation_wrap = off
|
||||
ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**/*
|
||||
ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/**
|
||||
ij_javascript_blank_lines_after_imports = 1
|
||||
ij_javascript_blank_lines_around_class = 1
|
||||
ij_javascript_blank_lines_around_field = 0
|
||||
ij_javascript_blank_lines_around_function = 1
|
||||
ij_javascript_blank_lines_around_method = 1
|
||||
ij_javascript_block_brace_style = next_line
|
||||
ij_javascript_block_brace_style = end_of_line
|
||||
ij_javascript_call_parameters_new_line_after_left_paren = false
|
||||
ij_javascript_call_parameters_right_paren_on_new_line = false
|
||||
ij_javascript_call_parameters_wrap = off
|
||||
@@ -148,15 +149,15 @@ ij_javascript_catch_on_new_line = false
|
||||
ij_javascript_chained_call_dot_on_new_line = true
|
||||
ij_javascript_class_brace_style = end_of_line
|
||||
ij_javascript_comma_on_new_line = false
|
||||
ij_javascript_do_while_brace_force = never
|
||||
ij_javascript_else_on_new_line = true
|
||||
ij_javascript_do_while_brace_force = always
|
||||
ij_javascript_else_on_new_line = false
|
||||
ij_javascript_enforce_trailing_comma = keep
|
||||
ij_javascript_extends_keyword_wrap = off
|
||||
ij_javascript_extends_list_wrap = off
|
||||
ij_javascript_field_prefix = _
|
||||
ij_javascript_file_name_style = relaxed
|
||||
ij_javascript_finally_on_new_line = false
|
||||
ij_javascript_for_brace_force = never
|
||||
ij_javascript_for_brace_force = always
|
||||
ij_javascript_for_statement_new_line_after_left_paren = false
|
||||
ij_javascript_for_statement_right_paren_on_new_line = false
|
||||
ij_javascript_for_statement_wrap = off
|
||||
@@ -192,6 +193,9 @@ ij_javascript_parentheses_expression_new_line_after_left_paren = false
|
||||
ij_javascript_parentheses_expression_right_paren_on_new_line = false
|
||||
ij_javascript_place_assignment_sign_on_next_line = false
|
||||
ij_javascript_prefer_as_type_cast = false
|
||||
ij_javascript_prefer_explicit_types_function_expression_returns = false
|
||||
ij_javascript_prefer_explicit_types_function_returns = false
|
||||
ij_javascript_prefer_explicit_types_vars_fields = false
|
||||
ij_javascript_prefer_parameters_wrap = false
|
||||
ij_javascript_reformat_c_style_comments = false
|
||||
ij_javascript_space_after_colon = true
|
||||
@@ -272,11 +276,11 @@ ij_javascript_use_path_mapping = always
|
||||
ij_javascript_use_public_modifier = false
|
||||
ij_javascript_use_semicolon_after_statement = true
|
||||
ij_javascript_var_declaration_wrap = normal
|
||||
ij_javascript_while_brace_force = never
|
||||
ij_javascript_while_brace_force = always
|
||||
ij_javascript_while_on_new_line = false
|
||||
ij_javascript_wrap_comments = false
|
||||
|
||||
[{*.module,*.hphp,*.phtml,*.php5,*.php4,*.php,*.ctp,*.inc}]
|
||||
[{*.ctp, *.hphp, *.inc, *.module, *.php, *.php4, *.php5, *.phtml}]
|
||||
indent_style = tab
|
||||
ij_continuation_indent_size = 4
|
||||
ij_smart_tabs = true
|
||||
@@ -285,8 +289,8 @@ ij_php_align_assignments = false
|
||||
ij_php_align_class_constants = false
|
||||
ij_php_align_group_field_declarations = false
|
||||
ij_php_align_inline_comments = false
|
||||
ij_php_align_key_value_pairs = false
|
||||
ij_php_align_multiline_array_initializer_expression = false
|
||||
ij_php_align_key_value_pairs = true
|
||||
ij_php_align_multiline_array_initializer_expression = true
|
||||
ij_php_align_multiline_binary_operation = false
|
||||
ij_php_align_multiline_chained_methods = false
|
||||
ij_php_align_multiline_extends_list = false
|
||||
@@ -296,12 +300,14 @@ ij_php_align_multiline_parameters_in_calls = false
|
||||
ij_php_align_multiline_ternary_operation = false
|
||||
ij_php_align_phpdoc_comments = false
|
||||
ij_php_align_phpdoc_param_names = false
|
||||
ij_php_anonymous_brace_style = end_of_line
|
||||
ij_php_api_weight = 1
|
||||
ij_php_array_initializer_new_line_after_left_brace = true
|
||||
ij_php_array_initializer_right_brace_on_new_line = true
|
||||
ij_php_array_initializer_wrap = on_every_item
|
||||
ij_php_assignment_wrap = off
|
||||
ij_php_author_weight = 7
|
||||
ij_php_attributes_wrap = off
|
||||
ij_php_author_weight = 8
|
||||
ij_php_binary_operation_sign_on_next_line = false
|
||||
ij_php_binary_operation_wrap = off
|
||||
ij_php_blank_lines_after_class_header = 0
|
||||
@@ -318,7 +324,8 @@ ij_php_blank_lines_before_imports = 1
|
||||
ij_php_blank_lines_before_method_body = 0
|
||||
ij_php_blank_lines_before_package = 1
|
||||
ij_php_blank_lines_before_return_statement = 1
|
||||
ij_php_block_brace_style = next_line
|
||||
ij_php_blank_lines_between_imports = 0
|
||||
ij_php_block_brace_style = end_of_line
|
||||
ij_php_call_parameters_new_line_after_left_paren = false
|
||||
ij_php_call_parameters_right_paren_on_new_line = false
|
||||
ij_php_call_parameters_wrap = normal
|
||||
@@ -328,11 +335,11 @@ ij_php_class_brace_style = next_line
|
||||
ij_php_comma_after_last_array_element = true
|
||||
ij_php_concat_spaces = false
|
||||
ij_php_copyright_weight = 28
|
||||
ij_php_deprecated_weight = 28
|
||||
ij_php_deprecated_weight = 2
|
||||
ij_php_do_while_brace_force = always
|
||||
ij_php_else_if_style = as_is
|
||||
ij_php_else_on_new_line = true
|
||||
ij_php_example_weight = 3
|
||||
ij_php_else_on_new_line = false
|
||||
ij_php_example_weight = 4
|
||||
ij_php_extends_keyword_wrap = off
|
||||
ij_php_extends_list_wrap = off
|
||||
ij_php_fields_default_visibility = private
|
||||
@@ -343,6 +350,8 @@ ij_php_for_statement_new_line_after_left_paren = false
|
||||
ij_php_for_statement_right_paren_on_new_line = false
|
||||
ij_php_for_statement_wrap = off
|
||||
ij_php_force_short_declaration_array_style = false
|
||||
ij_php_getters_setters_naming_style = camel_case
|
||||
ij_php_getters_setters_order_style = getters_first
|
||||
ij_php_global_weight = 28
|
||||
ij_php_group_use_wrap = on_every_item
|
||||
ij_php_if_brace_force = always
|
||||
@@ -362,7 +371,8 @@ ij_php_keep_control_statement_in_one_line = true
|
||||
ij_php_keep_first_column_comment = true
|
||||
ij_php_keep_indents_on_empty_lines = false
|
||||
ij_php_keep_line_breaks = true
|
||||
ij_php_keep_rparen_and_lbrace_on_one_line = true
|
||||
ij_php_keep_rparen_and_lbrace_on_one_line = false
|
||||
ij_php_keep_simple_classes_in_one_line = false
|
||||
ij_php_keep_simple_methods_in_one_line = false
|
||||
ij_php_lambda_brace_style = end_of_line
|
||||
ij_php_license_weight = 28
|
||||
@@ -370,6 +380,7 @@ ij_php_line_comment_add_space = false
|
||||
ij_php_line_comment_at_first_column = true
|
||||
ij_php_link_weight = 28
|
||||
ij_php_lower_case_boolean_const = true
|
||||
ij_php_lower_case_keywords = true
|
||||
ij_php_lower_case_null_const = true
|
||||
ij_php_method_brace_style = next_line
|
||||
ij_php_method_call_chain_wrap = off
|
||||
@@ -380,9 +391,11 @@ ij_php_method_weight = 28
|
||||
ij_php_modifier_list_wrap = false
|
||||
ij_php_multiline_chained_calls_semicolon_on_new_line = false
|
||||
ij_php_namespace_brace_style = 1
|
||||
ij_php_new_line_after_php_opening_tag = false
|
||||
ij_php_null_type_position = in_the_end
|
||||
ij_php_package_weight = 28
|
||||
ij_php_param_weight = 4
|
||||
ij_php_param_weight = 5
|
||||
ij_php_parameters_attributes_wrap = off
|
||||
ij_php_parentheses_expression_new_line_after_left_paren = false
|
||||
ij_php_parentheses_expression_right_paren_on_new_line = false
|
||||
ij_php_phpdoc_blank_line_before_tags = true
|
||||
@@ -399,11 +412,12 @@ ij_php_property_read_weight = 28
|
||||
ij_php_property_weight = 28
|
||||
ij_php_property_write_weight = 28
|
||||
ij_php_return_type_on_new_line = false
|
||||
ij_php_return_weight = 5
|
||||
ij_php_see_weight = 2
|
||||
ij_php_return_weight = 6
|
||||
ij_php_see_weight = 3
|
||||
ij_php_since_weight = 28
|
||||
ij_php_sort_phpdoc_elements = true
|
||||
ij_php_space_after_colon = true
|
||||
ij_php_space_after_colon_in_named_argument = true
|
||||
ij_php_space_after_colon_in_return_type = true
|
||||
ij_php_space_after_comma = true
|
||||
ij_php_space_after_for_semicolon = true
|
||||
@@ -417,6 +431,7 @@ ij_php_space_before_catch_parentheses = true
|
||||
ij_php_space_before_class_left_brace = true
|
||||
ij_php_space_before_closure_left_parenthesis = true
|
||||
ij_php_space_before_colon = true
|
||||
ij_php_space_before_colon_in_named_argument = false
|
||||
ij_php_space_before_colon_in_return_type = false
|
||||
ij_php_space_before_comma = false
|
||||
ij_php_space_before_do_left_brace = true
|
||||
@@ -433,6 +448,7 @@ ij_php_space_before_method_call_parentheses = false
|
||||
ij_php_space_before_method_left_brace = true
|
||||
ij_php_space_before_method_parentheses = false
|
||||
ij_php_space_before_quest = true
|
||||
ij_php_space_before_short_closure_left_parenthesis = false
|
||||
ij_php_space_before_switch_left_brace = true
|
||||
ij_php_space_before_switch_parentheses = true
|
||||
ij_php_space_before_try_left_brace = true
|
||||
@@ -465,11 +481,11 @@ ij_php_spaces_within_parentheses = false
|
||||
ij_php_spaces_within_short_echo_tags = true
|
||||
ij_php_spaces_within_switch_parentheses = false
|
||||
ij_php_spaces_within_while_parentheses = false
|
||||
ij_php_special_else_if_treatment = false
|
||||
ij_php_special_else_if_treatment = true
|
||||
ij_php_subpackage_weight = 28
|
||||
ij_php_ternary_operation_signs_on_next_line = false
|
||||
ij_php_ternary_operation_wrap = off
|
||||
ij_php_throws_weight = 6
|
||||
ij_php_throws_weight = 7
|
||||
ij_php_todo_weight = 28
|
||||
ij_php_unknown_tag_weight = 28
|
||||
ij_php_upper_case_boolean_const = false
|
||||
@@ -481,9 +497,24 @@ ij_php_version_weight = 28
|
||||
ij_php_while_brace_force = always
|
||||
ij_php_while_on_new_line = false
|
||||
|
||||
[{*.sht,*.htm,*.html,*.shtm,*.shtml}]
|
||||
[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,composer.lock,jest.config}]
|
||||
indent_size = 2
|
||||
ij_visual_guides = none
|
||||
ij_json_keep_blank_lines_in_code = 0
|
||||
ij_json_keep_indents_on_empty_lines = false
|
||||
ij_json_keep_line_breaks = true
|
||||
ij_json_space_after_colon = true
|
||||
ij_json_space_after_comma = true
|
||||
ij_json_space_before_colon = true
|
||||
ij_json_space_before_comma = false
|
||||
ij_json_spaces_within_braces = false
|
||||
ij_json_spaces_within_brackets = false
|
||||
ij_json_wrap_long_lines = false
|
||||
|
||||
[{*.htm,*.html,*.sht,*.shtm,*.shtml}]
|
||||
indent_style = tab
|
||||
ij_smart_tabs = true
|
||||
ij_visual_guides = none
|
||||
ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
|
||||
ij_html_align_attributes = true
|
||||
ij_html_align_text = false
|
||||
@@ -503,209 +534,38 @@ ij_html_keep_whitespaces_inside = span,pre,textarea
|
||||
ij_html_line_comment_at_first_column = true
|
||||
ij_html_new_line_after_last_attribute = never
|
||||
ij_html_new_line_before_first_attribute = never
|
||||
ij_html_quote_style = double
|
||||
ij_html_quote_style = none
|
||||
ij_html_remove_new_line_before_tags = br
|
||||
ij_html_space_after_tag_name = false
|
||||
ij_html_space_around_equality_in_attribute = false
|
||||
ij_html_space_inside_empty_tag = false
|
||||
ij_html_text_wrap = normal
|
||||
ij_html_uniform_ident = false
|
||||
|
||||
[{*.ts,*.ats}]
|
||||
ij_continuation_indent_size = 4
|
||||
ij_typescript_align_imports = false
|
||||
ij_typescript_align_multiline_array_initializer_expression = false
|
||||
ij_typescript_align_multiline_binary_operation = false
|
||||
ij_typescript_align_multiline_chained_methods = false
|
||||
ij_typescript_align_multiline_extends_list = false
|
||||
ij_typescript_align_multiline_for = true
|
||||
ij_typescript_align_multiline_parameters = true
|
||||
ij_typescript_align_multiline_parameters_in_calls = false
|
||||
ij_typescript_align_multiline_ternary_operation = false
|
||||
ij_typescript_align_object_properties = 0
|
||||
ij_typescript_align_union_types = false
|
||||
ij_typescript_align_var_statements = 0
|
||||
ij_typescript_array_initializer_new_line_after_left_brace = false
|
||||
ij_typescript_array_initializer_right_brace_on_new_line = false
|
||||
ij_typescript_array_initializer_wrap = off
|
||||
ij_typescript_assignment_wrap = off
|
||||
ij_typescript_binary_operation_sign_on_next_line = false
|
||||
ij_typescript_binary_operation_wrap = off
|
||||
ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**/*
|
||||
ij_typescript_blank_lines_after_imports = 1
|
||||
ij_typescript_blank_lines_around_class = 1
|
||||
ij_typescript_blank_lines_around_field = 0
|
||||
ij_typescript_blank_lines_around_field_in_interface = 0
|
||||
ij_typescript_blank_lines_around_function = 1
|
||||
ij_typescript_blank_lines_around_method = 1
|
||||
ij_typescript_blank_lines_around_method_in_interface = 1
|
||||
ij_typescript_block_brace_style = end_of_line
|
||||
ij_typescript_call_parameters_new_line_after_left_paren = false
|
||||
ij_typescript_call_parameters_right_paren_on_new_line = false
|
||||
ij_typescript_call_parameters_wrap = off
|
||||
ij_typescript_catch_on_new_line = false
|
||||
ij_typescript_chained_call_dot_on_new_line = true
|
||||
ij_typescript_class_brace_style = end_of_line
|
||||
ij_typescript_comma_on_new_line = false
|
||||
ij_typescript_do_while_brace_force = never
|
||||
ij_typescript_else_on_new_line = false
|
||||
ij_typescript_enforce_trailing_comma = keep
|
||||
ij_typescript_extends_keyword_wrap = off
|
||||
ij_typescript_extends_list_wrap = off
|
||||
ij_typescript_field_prefix = _
|
||||
ij_typescript_file_name_style = relaxed
|
||||
ij_typescript_finally_on_new_line = false
|
||||
ij_typescript_for_brace_force = never
|
||||
ij_typescript_for_statement_new_line_after_left_paren = false
|
||||
ij_typescript_for_statement_right_paren_on_new_line = false
|
||||
ij_typescript_for_statement_wrap = off
|
||||
ij_typescript_force_quote_style = false
|
||||
ij_typescript_force_semicolon_style = false
|
||||
ij_typescript_function_expression_brace_style = end_of_line
|
||||
ij_typescript_if_brace_force = never
|
||||
ij_typescript_import_merge_members = global
|
||||
ij_typescript_import_prefer_absolute_path = global
|
||||
ij_typescript_import_sort_members = true
|
||||
ij_typescript_import_sort_module_name = false
|
||||
ij_typescript_import_use_node_resolution = true
|
||||
ij_typescript_imports_wrap = on_every_item
|
||||
ij_typescript_indent_case_from_switch = true
|
||||
ij_typescript_indent_chained_calls = true
|
||||
ij_typescript_indent_package_children = 0
|
||||
ij_typescript_jsdoc_include_types = false
|
||||
ij_typescript_jsx_attribute_value = braces
|
||||
ij_typescript_keep_blank_lines_in_code = 2
|
||||
ij_typescript_keep_first_column_comment = true
|
||||
ij_typescript_keep_indents_on_empty_lines = false
|
||||
ij_typescript_keep_line_breaks = true
|
||||
ij_typescript_keep_simple_blocks_in_one_line = false
|
||||
ij_typescript_keep_simple_methods_in_one_line = false
|
||||
ij_typescript_line_comment_add_space = true
|
||||
ij_typescript_line_comment_at_first_column = false
|
||||
ij_typescript_method_brace_style = end_of_line
|
||||
ij_typescript_method_call_chain_wrap = off
|
||||
ij_typescript_method_parameters_new_line_after_left_paren = false
|
||||
ij_typescript_method_parameters_right_paren_on_new_line = false
|
||||
ij_typescript_method_parameters_wrap = off
|
||||
ij_typescript_object_literal_wrap = on_every_item
|
||||
ij_typescript_parentheses_expression_new_line_after_left_paren = false
|
||||
ij_typescript_parentheses_expression_right_paren_on_new_line = false
|
||||
ij_typescript_place_assignment_sign_on_next_line = false
|
||||
ij_typescript_prefer_as_type_cast = false
|
||||
ij_typescript_prefer_parameters_wrap = false
|
||||
ij_typescript_reformat_c_style_comments = false
|
||||
ij_typescript_space_after_colon = true
|
||||
ij_typescript_space_after_comma = true
|
||||
ij_typescript_space_after_dots_in_rest_parameter = false
|
||||
ij_typescript_space_after_generator_mult = true
|
||||
ij_typescript_space_after_property_colon = true
|
||||
ij_typescript_space_after_quest = true
|
||||
ij_typescript_space_after_type_colon = true
|
||||
ij_typescript_space_after_unary_not = false
|
||||
ij_typescript_space_before_async_arrow_lparen = true
|
||||
ij_typescript_space_before_catch_keyword = true
|
||||
ij_typescript_space_before_catch_left_brace = true
|
||||
ij_typescript_space_before_catch_parentheses = true
|
||||
ij_typescript_space_before_class_lbrace = true
|
||||
ij_typescript_space_before_class_left_brace = true
|
||||
ij_typescript_space_before_colon = true
|
||||
ij_typescript_space_before_comma = false
|
||||
ij_typescript_space_before_do_left_brace = true
|
||||
ij_typescript_space_before_else_keyword = true
|
||||
ij_typescript_space_before_else_left_brace = true
|
||||
ij_typescript_space_before_finally_keyword = true
|
||||
ij_typescript_space_before_finally_left_brace = true
|
||||
ij_typescript_space_before_for_left_brace = true
|
||||
ij_typescript_space_before_for_parentheses = true
|
||||
ij_typescript_space_before_for_semicolon = false
|
||||
ij_typescript_space_before_function_left_parenth = true
|
||||
ij_typescript_space_before_generator_mult = false
|
||||
ij_typescript_space_before_if_left_brace = true
|
||||
ij_typescript_space_before_if_parentheses = true
|
||||
ij_typescript_space_before_method_call_parentheses = false
|
||||
ij_typescript_space_before_method_left_brace = true
|
||||
ij_typescript_space_before_method_parentheses = false
|
||||
ij_typescript_space_before_property_colon = false
|
||||
ij_typescript_space_before_quest = true
|
||||
ij_typescript_space_before_switch_left_brace = true
|
||||
ij_typescript_space_before_switch_parentheses = true
|
||||
ij_typescript_space_before_try_left_brace = true
|
||||
ij_typescript_space_before_type_colon = false
|
||||
ij_typescript_space_before_unary_not = false
|
||||
ij_typescript_space_before_while_keyword = true
|
||||
ij_typescript_space_before_while_left_brace = true
|
||||
ij_typescript_space_before_while_parentheses = true
|
||||
ij_typescript_spaces_around_additive_operators = true
|
||||
ij_typescript_spaces_around_arrow_function_operator = true
|
||||
ij_typescript_spaces_around_assignment_operators = true
|
||||
ij_typescript_spaces_around_bitwise_operators = true
|
||||
ij_typescript_spaces_around_equality_operators = true
|
||||
ij_typescript_spaces_around_logical_operators = true
|
||||
ij_typescript_spaces_around_multiplicative_operators = true
|
||||
ij_typescript_spaces_around_relational_operators = true
|
||||
ij_typescript_spaces_around_shift_operators = true
|
||||
ij_typescript_spaces_around_unary_operator = false
|
||||
ij_typescript_spaces_within_array_initializer_brackets = false
|
||||
ij_typescript_spaces_within_brackets = false
|
||||
ij_typescript_spaces_within_catch_parentheses = false
|
||||
ij_typescript_spaces_within_for_parentheses = false
|
||||
ij_typescript_spaces_within_if_parentheses = false
|
||||
ij_typescript_spaces_within_imports = false
|
||||
ij_typescript_spaces_within_interpolation_expressions = false
|
||||
ij_typescript_spaces_within_method_call_parentheses = false
|
||||
ij_typescript_spaces_within_method_parentheses = false
|
||||
ij_typescript_spaces_within_object_literal_braces = false
|
||||
ij_typescript_spaces_within_object_type_braces = true
|
||||
ij_typescript_spaces_within_parentheses = false
|
||||
ij_typescript_spaces_within_switch_parentheses = false
|
||||
ij_typescript_spaces_within_type_assertion = false
|
||||
ij_typescript_spaces_within_union_types = true
|
||||
ij_typescript_spaces_within_while_parentheses = false
|
||||
ij_typescript_special_else_if_treatment = true
|
||||
ij_typescript_ternary_operation_signs_on_next_line = false
|
||||
ij_typescript_ternary_operation_wrap = off
|
||||
ij_typescript_union_types_wrap = on_every_item
|
||||
ij_typescript_use_chained_calls_group_indents = false
|
||||
ij_typescript_use_double_quotes = true
|
||||
ij_typescript_use_explicit_js_extension = global
|
||||
ij_typescript_use_path_mapping = always
|
||||
ij_typescript_use_public_modifier = false
|
||||
ij_typescript_use_semicolon_after_statement = true
|
||||
ij_typescript_var_declaration_wrap = normal
|
||||
ij_typescript_while_brace_force = never
|
||||
ij_typescript_while_on_new_line = false
|
||||
ij_typescript_wrap_comments = false
|
||||
[{*.markdown,*.md}]
|
||||
ij_visual_guides = none
|
||||
ij_markdown_force_one_space_after_blockquote_symbol = true
|
||||
ij_markdown_force_one_space_after_header_symbol = true
|
||||
ij_markdown_force_one_space_after_list_bullet = true
|
||||
ij_markdown_force_one_space_between_words = true
|
||||
ij_markdown_keep_indents_on_empty_lines = false
|
||||
ij_markdown_max_lines_around_block_elements = 1
|
||||
ij_markdown_max_lines_around_header = 1
|
||||
ij_markdown_max_lines_between_paragraphs = 1
|
||||
ij_markdown_min_lines_around_block_elements = 1
|
||||
ij_markdown_min_lines_around_header = 1
|
||||
ij_markdown_min_lines_between_paragraphs = 1
|
||||
|
||||
[{*.yml,*.yaml}]
|
||||
[{*.yaml,*.yml}]
|
||||
indent_size = 2
|
||||
ij_continuation_indent_size = 2
|
||||
ij_visual_guides = none
|
||||
ij_yaml_align_values_properties = do_not_align
|
||||
ij_yaml_autoinsert_sequence_marker = true
|
||||
ij_yaml_block_mapping_on_new_line = false
|
||||
ij_yaml_indent_sequence_value = true
|
||||
ij_yaml_keep_indents_on_empty_lines = false
|
||||
ij_yaml_keep_line_breaks = true
|
||||
|
||||
[{*.zsh,*.bash,*.sh}]
|
||||
ij_shell_binary_ops_start_line = false
|
||||
ij_shell_keep_column_alignment_padding = false
|
||||
ij_shell_minify_program = false
|
||||
ij_shell_redirect_followed_by_space = false
|
||||
ij_shell_switch_cases_indented = false
|
||||
|
||||
[{.stylelintrc,.eslintrc,.babelrc,jest.config,*.bowerrc,*.jsb3,*.jsb2,*.json}]
|
||||
indent_size = 2
|
||||
ij_json_keep_blank_lines_in_code = 0
|
||||
ij_json_keep_indents_on_empty_lines = false
|
||||
ij_json_keep_line_breaks = true
|
||||
ij_json_space_after_colon = true
|
||||
ij_json_space_after_comma = true
|
||||
ij_json_space_before_colon = true
|
||||
ij_json_space_before_comma = false
|
||||
ij_json_spaces_within_braces = false
|
||||
ij_json_spaces_within_brackets = false
|
||||
ij_json_wrap_long_lines = false
|
||||
|
||||
[{phpunit.xml.dist,*.jhm,*.rng,*.wsdl,*.fxml,*.xslt,*.jrxml,*.ant,*.xul,*.xsl,*.xsd,*.tld,*.jnlp,*.xml}]
|
||||
indent_size = 2
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
ij_smart_tabs = true
|
||||
ij_xml_block_comment_at_first_column = true
|
||||
ij_xml_keep_indents_on_empty_lines = false
|
||||
ij_xml_line_comment_at_first_column = true
|
||||
ij_yaml_sequence_on_new_line = false
|
||||
ij_yaml_space_before_colon = false
|
||||
ij_yaml_spaces_within_braces = true
|
||||
ij_yaml_spaces_within_brackets = true
|
||||
|
||||
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
|
||||
9
.gitflow
9
.gitflow
@@ -1,9 +0,0 @@
|
||||
[gitflow "branch"]
|
||||
master = master
|
||||
develop = develop
|
||||
[gitflow "prefix"]
|
||||
feature = feature/
|
||||
release = release/
|
||||
hotfix = hotfix/
|
||||
versiontag =
|
||||
support = support/
|
||||
83
.github/pull_request_template.md
vendored
Normal file
83
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
<!--
|
||||
|
||||
IMPORTANT: Please follow the guidelines within this PR template before submitting it, it will greatly help us process your PR. 🙏
|
||||
|
||||
Any PRs not following the guidelines or with missing information will not be considered.
|
||||
|
||||
-->
|
||||
|
||||
## Base information
|
||||
| Question | Answer
|
||||
|---------------------------------------------------------------|--------
|
||||
| Related to a SourceForge thead / Another PR / Combodo ticket? | <!-- Put the URL -->
|
||||
| Type of change? | Bug fix / Enhancement / Translations
|
||||
|
||||
|
||||
## Symptom (bug) / Objective (enhancement)
|
||||
<!--
|
||||
If it's a bug
|
||||
- Explain the symptom in details
|
||||
- If possible put error messages, logs or screenshots (you can paste image directly in this editor).
|
||||
|
||||
If it's an enhancement
|
||||
- Describe what is blocking you, what is the objective with as much details as possible.
|
||||
- Add screenshots if it's related to UI.
|
||||
-->
|
||||
|
||||
|
||||
## Reproduction procedure (bug)
|
||||
<!--
|
||||
Remove this section only if it's NOT a bug.
|
||||
|
||||
Otherwise, explain step by step how to reproduce the issue on a standard iTop Community.
|
||||
|
||||
If it requires a custom datamodel, provide the minimal XML delta to reproduce it on a standard iTop Community.
|
||||
-->
|
||||
|
||||
1. On iTop x.y.z <!-- Put complete iTop version (eg. 3.1.0-2) -->
|
||||
2. With PHP x.y.z <!-- Put complete PHP version (eg. 8.1.24) -->
|
||||
2. First go there
|
||||
2. Then do that
|
||||
3. ...
|
||||
4. Finally, see that...
|
||||
|
||||
|
||||
## Cause (bug)
|
||||
<!--
|
||||
Remove this section only if it's NOT a bug.
|
||||
|
||||
Otherwise, explain what is the cause of the issue (where in the code and why)
|
||||
-->
|
||||
|
||||
|
||||
## Proposed solution (bug and enhancement)
|
||||
<!--
|
||||
Explain in details how you are proposing to solve this:
|
||||
- What did you do in the code and why
|
||||
- If you changed something in the UI, put before / after screenshots (you can paste image directly in this editor)
|
||||
-->
|
||||
|
||||
|
||||
## Checklist before requesting a review
|
||||
<!--
|
||||
Don't remove these lines, check them once done.
|
||||
-->
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have tested all changes I made on an iTop instance
|
||||
- [ ] I have added a unit test, otherwise I have explained why I couldn't
|
||||
- [ ] Is the PR clear and detailed enough so anyone can understand digging in the code?
|
||||
|
||||
## Checklist of things to do before PR is ready to merge
|
||||
<!--
|
||||
Things that needs to be done in the PR before it can be considered as ready to be merged
|
||||
|
||||
Examples:
|
||||
- Changes requested in the review
|
||||
- Unit test to add
|
||||
- Dictionary entries to translate
|
||||
- ...
|
||||
-->
|
||||
|
||||
- [ ] ...
|
||||
- [ ] ...
|
||||
- [ ] ...
|
||||
43
.github/workflows/action.yml
vendored
Normal file
43
.github/workflows/action.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Add PRs to Combodo PRs Dashboard
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add PR to Combodo Project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if author is a member of the organization
|
||||
id: check-membership
|
||||
run: |
|
||||
ORG="Combodo"
|
||||
AUTHOR=$(jq -r .pull_request.user.login "$GITHUB_EVENT_PATH")
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
|
||||
"https://api.github.com/orgs/$ORG/members/$AUTHOR")
|
||||
if [ "$RESPONSE" == "404" ]; then
|
||||
echo "project_url=https://github.com/orgs/Combodo/projects/5" >> $GITHUB_ENV
|
||||
echo "is_member=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "project_url=https://github.com/orgs/Combodo/projects/4" >> $GITHUB_ENV
|
||||
echo "is_member=true" >> $GITHUB_ENV
|
||||
|
||||
fi
|
||||
|
||||
- name: Add internal tag if member
|
||||
if: env.is_member == 'true'
|
||||
run: |
|
||||
curl -X POST -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/Combodo/iTop/issues/${{ github.event.pull_request.number }}/labels \
|
||||
-d '{"labels":["internal"]}'
|
||||
env:
|
||||
is_member: ${{ env.is_member }}
|
||||
|
||||
- name: Add PR to the appropriate project
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: ${{ env.project_url }}
|
||||
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}
|
||||
24
.gitignore
vendored
24
.gitignore
vendored
@@ -6,16 +6,16 @@
|
||||
# maintenance mode (N°2240)
|
||||
/.maintenance
|
||||
|
||||
# listing prevention in conf directory
|
||||
# composer reserver directory, from sources, populate/update using "composer install"
|
||||
vendor/*
|
||||
tests/*/vendor/*
|
||||
|
||||
# all conf but listing prevention
|
||||
/conf/**
|
||||
!/conf/.htaccess
|
||||
!/conf/index.php
|
||||
!/conf/web.config
|
||||
|
||||
# composer reserver directory, from sources, populate/update using "composer install"
|
||||
vendor/*
|
||||
test/vendor/*
|
||||
|
||||
# all datas but listing prevention
|
||||
/data/**
|
||||
!/data/.htaccess
|
||||
@@ -24,7 +24,9 @@ test/vendor/*
|
||||
|
||||
# iTop extensions
|
||||
/extensions/**
|
||||
!/extensions/.htaccess
|
||||
!/extensions/readme.txt
|
||||
!/extensions/web.config
|
||||
|
||||
# all logs but listing prevention
|
||||
/log/**
|
||||
@@ -32,14 +34,15 @@ test/vendor/*
|
||||
!/log/index.php
|
||||
!/log/web.config
|
||||
|
||||
# PHPUnit: Cache file, local XML working copies
|
||||
/tests/php-unit-tests/.phpunit.result.cache
|
||||
/tests/php-unit-tests/phpunit.xml
|
||||
/tests/php-unit-tests/postbuild_integration.xml
|
||||
|
||||
|
||||
# Jetbrains
|
||||
/.idea/**
|
||||
!/.idea/encodings.xml
|
||||
!/.idea/codeStyles
|
||||
!/.idea/codeStyles/*
|
||||
!/.idea/inspectionProfiles
|
||||
!/.idea/inspectionProfiles/*
|
||||
!/.idea/IntelliLang.xml
|
||||
|
||||
# doc. generation
|
||||
/.doc/vendor
|
||||
@@ -134,4 +137,3 @@ local.properties
|
||||
.cache-main
|
||||
.scala_dependencies
|
||||
.worksheet
|
||||
|
||||
|
||||
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>
|
||||
74
.idea/codeStyles/Project.xml
generated
74
.idea/codeStyles/Project.xml
generated
@@ -1,74 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="LINE_SEPARATOR" value=" " />
|
||||
<option name="RIGHT_MARGIN" value="140" />
|
||||
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
|
||||
<option name="SOFT_MARGINS" value="140" />
|
||||
<HTMLCodeStyleSettings>
|
||||
<option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="html,body,thead,tbody,tfoot,style,script,head" />
|
||||
</HTMLCodeStyleSettings>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
|
||||
</JSCodeStyleSettings>
|
||||
<PHPCodeStyleSettings>
|
||||
<option name="CONCAT_SPACES" value="false" />
|
||||
<option name="COMMA_AFTER_LAST_ARRAY_ELEMENT" value="true" />
|
||||
<option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
|
||||
<option name="PHPDOC_BLANK_LINES_AROUND_PARAMETERS" value="true" />
|
||||
<option name="PHPDOC_WRAP_LONG_LINES" value="true" />
|
||||
<option name="THROWS_WEIGHT" value="6" />
|
||||
<option name="PARAM_WEIGHT" value="4" />
|
||||
<option name="RETURN_WEIGHT" value="5" />
|
||||
<option name="AUTHOR_WEIGHT" value="7" />
|
||||
<option name="INTERNAL_WEIGHT" value="0" />
|
||||
<option name="API_WEIGHT" value="1" />
|
||||
<option name="EXAMPLE_WEIGHT" value="3" />
|
||||
<option name="SEE_WEIGHT" value="2" />
|
||||
<option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
|
||||
<option name="LOWER_CASE_NULL_CONST" value="true" />
|
||||
<option name="BLANK_LINES_BEFORE_RETURN_STATEMENT" value="1" />
|
||||
<option name="KEEP_RPAREN_AND_LBRACE_ON_ONE_LINE" value="true" />
|
||||
<option name="PHPDOC_USE_FQCN" value="true" />
|
||||
</PHPCodeStyleSettings>
|
||||
<XML>
|
||||
<option name="XML_TEXT_WRAP" value="0" />
|
||||
<option name="XML_KEEP_LINE_BREAKS" value="false" />
|
||||
<option name="XML_KEEP_WHITE_SPACES_INSIDE_CDATA" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="BRACE_STYLE" value="2" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="SPACE_AROUND_ADDITIVE_OPERATORS" value="false" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="PHP">
|
||||
<option name="BLANK_LINES_AFTER_PACKAGE" value="1" />
|
||||
<option name="BRACE_STYLE" value="2" />
|
||||
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||
<option name="CATCH_ON_NEW_LINE" value="true" />
|
||||
<option name="FINALLY_ON_NEW_LINE" value="true" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="ARRAY_INITIALIZER_WRAP" value="5" />
|
||||
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
|
||||
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
<option name="SMART_TABS" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="WRAP_ON_TYPING" value="1" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Combodo" />
|
||||
</state>
|
||||
</component>
|
||||
6
.idea/encodings.xml
generated
6
.idea/encodings.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
171
.idea/inspectionProfiles/Combodo.xml
generated
171
.idea/inspectionProfiles/Combodo.xml
generated
@@ -1,171 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Combodo" />
|
||||
<inspection_tool class="CascadeStringReplacementInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="configuration">
|
||||
<list>
|
||||
<option value="\Codeception\Util\Debug::debug" />
|
||||
<option value="\Codeception\Util\Debug::pause" />
|
||||
<option value="\Doctrine\Common\Util\Debug::dump" />
|
||||
<option value="\Doctrine\Common\Util\Debug::export" />
|
||||
<option value="\Illuminate\Support\Debug\Dumper::dump" />
|
||||
<option value="\Symfony\Component\Debug\Debug::enable" />
|
||||
<option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
|
||||
<option value="\Symfony\Component\Debug\ErrorHandler::register" />
|
||||
<option value="\Symfony\Component\Debug\ExceptionHandler::register" />
|
||||
<option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
|
||||
<option value="\Zend\Debug\Debug::dump" />
|
||||
<option value="\Zend\Di\Display\Console::export" />
|
||||
<option value="dd" />
|
||||
<option value="debug_print_backtrace" />
|
||||
<option value="debug_zval_dump" />
|
||||
<option value="dpm" />
|
||||
<option value="dpq" />
|
||||
<option value="dsm" />
|
||||
<option value="dump" />
|
||||
<option value="dvm" />
|
||||
<option value="error_log" />
|
||||
<option value="kpr" />
|
||||
<option value="phpinfo" />
|
||||
<option value="print_r" />
|
||||
<option value="var_dump" />
|
||||
<option value="var_export" />
|
||||
<option value="xdebug_break" />
|
||||
<option value="xdebug_call_class" />
|
||||
<option value="xdebug_call_file" />
|
||||
<option value="xdebug_call_function" />
|
||||
<option value="xdebug_call_line" />
|
||||
<option value="xdebug_code_coverage_started" />
|
||||
<option value="xdebug_debug_zval" />
|
||||
<option value="xdebug_debug_zval_stdout" />
|
||||
<option value="xdebug_dump_superglobals" />
|
||||
<option value="xdebug_enable" />
|
||||
<option value="xdebug_get_code_coverage" />
|
||||
<option value="xdebug_get_collected_errors" />
|
||||
<option value="xdebug_get_declared_vars" />
|
||||
<option value="xdebug_get_function_stack" />
|
||||
<option value="xdebug_get_headers" />
|
||||
<option value="xdebug_get_monitored_functions" />
|
||||
<option value="xdebug_get_profiler_filename" />
|
||||
<option value="xdebug_get_stack_depth" />
|
||||
<option value="xdebug_get_tracefile_name" />
|
||||
<option value="xdebug_is_enabled" />
|
||||
<option value="xdebug_memory_usage" />
|
||||
<option value="xdebug_peak_memory_usage" />
|
||||
<option value="xdebug_print_function_stack" />
|
||||
<option value="xdebug_start_code_coverage" />
|
||||
<option value="xdebug_start_error_collection" />
|
||||
<option value="xdebug_start_function_monitor" />
|
||||
<option value="xdebug_start_trace" />
|
||||
<option value="xdebug_stop_code_coverage" />
|
||||
<option value="xdebug_stop_error_collection" />
|
||||
<option value="xdebug_stop_function_monitor" />
|
||||
<option value="xdebug_stop_trace" />
|
||||
<option value="xdebug_time_index" />
|
||||
<option value="xdebug_var_dump" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="migratedIntoUserSpace" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HtmlRequiredAltAttribute" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HtmlRequiredLangAttribute" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="IsNullFunctionUsageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="MysqlParsingInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpComposerExtensionStubsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PhpIncludeInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMethodParametersCountMismatchInspection" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpShortOpenTagInspection" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpTooManyParametersInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="limit" value="7" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUndefinedClassInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="DONT_REPORT_MULTI_RESOLVE" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUndefinedMethodInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpUnhandledExceptionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpUnusedLocalVariableInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="DONT_REPORT_INSIDE_LIST" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="DONT_REPORT_ABSTRACT_CLASS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="optionConfiguration">
|
||||
<list>
|
||||
<option value="barryvdh/laravel-debugbar" />
|
||||
<option value="behat/behat" />
|
||||
<option value="brianium/paratest" />
|
||||
<option value="codeception/codeception" />
|
||||
<option value="codedungeon/phpunit-result-printer" />
|
||||
<option value="composer/composer" />
|
||||
<option value="doctrine/coding-standard" />
|
||||
<option value="filp/whoops" />
|
||||
<option value="friendsofphp/php-cs-fixer" />
|
||||
<option value="humbug/humbug" />
|
||||
<option value="infection/infection" />
|
||||
<option value="jakub-onderka/php-parallel-lint" />
|
||||
<option value="johnkary/phpunit-speedtrap" />
|
||||
<option value="kalessil/production-dependencies-guard" />
|
||||
<option value="mikey179/vfsStream" />
|
||||
<option value="mockery/mockery" />
|
||||
<option value="mybuilder/phpunit-accelerator" />
|
||||
<option value="orchestra/testbench" />
|
||||
<option value="pdepend/pdepend" />
|
||||
<option value="phan/phan" />
|
||||
<option value="phing/phing" />
|
||||
<option value="phpcompatibility/php-compatibility" />
|
||||
<option value="phpmd/phpmd" />
|
||||
<option value="phpro/grumphp" />
|
||||
<option value="phpspec/phpspec" />
|
||||
<option value="phpspec/prophecy" />
|
||||
<option value="phpstan/phpstan" />
|
||||
<option value="phpunit/phpunit" />
|
||||
<option value="povils/phpmnd" />
|
||||
<option value="roave/security-advisories" />
|
||||
<option value="satooshi/php-coveralls" />
|
||||
<option value="sebastian/phpcpd" />
|
||||
<option value="slevomat/coding-standard" />
|
||||
<option value="spatie/phpunit-watcher" />
|
||||
<option value="squizlabs/php_codesniffer" />
|
||||
<option value="sstalle/php7cc" />
|
||||
<option value="symfony/debug" />
|
||||
<option value="symfony/maker-bundle" />
|
||||
<option value="symfony/phpunit-bridge" />
|
||||
<option value="symfony/var-dumper" />
|
||||
<option value="vimeo/psalm" />
|
||||
<option value="wimg/php-compatibility" />
|
||||
<option value="wp-coding-standards/wpcs" />
|
||||
<option value="yiisoft/yii2-coding-standards" />
|
||||
<option value="yiisoft/yii2-debug" />
|
||||
<option value="yiisoft/yii2-gii" />
|
||||
<option value="zendframework/zend-coding-standard" />
|
||||
<option value="zendframework/zend-debug" />
|
||||
<option value="zendframework/zend-test" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SqlAddNotNullColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlAmbiguousColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlAutoIncrementDuplicateInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlCheckUsingColumnsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlConstantConditionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlDeprecateTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlDerivedTableAliasInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlDialectInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlDropIndexedColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlIdentifierInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlInsertValuesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlNullComparisonInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlPostgresqlSelectFromProcedureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlResolveInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlShouldBeInGroupByInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlSideEffectsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlSignatureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlStorageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlUnusedVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
19
.idea/inspectionProfiles/Project_Default.xml
generated
19
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,19 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpIncludeInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpMethodParametersCountMismatchInspection" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpTooManyParametersInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="limit" value="7" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUndefinedClassInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="DONT_REPORT_MULTI_RESOLVE" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PhpUnhandledExceptionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="DONT_REPORT_ABSTRACT_CLASS" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
6
.idea/inspectionProfiles/profiles_settings.xml
generated
@@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Combodo" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
|
||||
# create target dirs
|
||||
mkdir -p var
|
||||
mkdir -p toolkit
|
||||
|
||||
# cleanup target dirs
|
||||
rm -rf toolkit/*
|
||||
|
||||
# fill target dirs
|
||||
curl https://www.combodo.com/documentation/iTopDataModelToolkit-2.3.zip > toolkit.zip
|
||||
unzip toolkit.zip
|
||||
rm toolkit.zip
|
||||
cp -r .jenkins/configuration/default-environment/unattended_install/* toolkit
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
|
||||
# on the root dir
|
||||
# composer install -a # => Not needed anymore (libs were added to git with N°2435)
|
||||
|
||||
|
||||
# under the test dir
|
||||
cd test
|
||||
composer install
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
|
||||
|
||||
|
||||
whoami
|
||||
pwd
|
||||
ls
|
||||
|
||||
echo "$BRANCH_NAME:${BRANCH_NAME}"
|
||||
|
||||
echo "printenv :"
|
||||
printenv
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
|
||||
cd test
|
||||
|
||||
export DEBUG_UNIT_TEST=0
|
||||
RUN_NONREG_TESTS=0
|
||||
|
||||
if [ $# -ge 1 -a "x$1" == "xtrue" ]
|
||||
then
|
||||
export DEBUG_UNIT_TEST=1
|
||||
else
|
||||
export DEBUG_UNIT_TEST=0
|
||||
fi
|
||||
|
||||
if [ $# -ge 2 -a "x$2" == "xtrue" ]
|
||||
then
|
||||
php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --teamcity
|
||||
else
|
||||
#echo php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --teamcity
|
||||
php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --exclude-group OQL --teamcity
|
||||
fi
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -x
|
||||
|
||||
chmod 666 conf/production/config-itop.php
|
||||
|
||||
cd toolkit
|
||||
php unattended_install.php --response_file=default-params.xml --clean=true
|
||||
@@ -1,284 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* Configuration file, generated by the iTop configuration wizard
|
||||
*
|
||||
* The file is used in MetaModel::LoadConfig() which does all the necessary initialization job
|
||||
*
|
||||
*/
|
||||
$MySettings = array(
|
||||
|
||||
// access_message: Message displayed to the users when there is any access restriction
|
||||
// default: 'iTop is temporarily frozen, please wait... (the admin team)'
|
||||
'access_message' => 'iTop is temporarily frozen, please wait... (the admin team)',
|
||||
|
||||
// access_mode: Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3
|
||||
// default: 3
|
||||
'access_mode' => 3,
|
||||
|
||||
'allowed_login_types' => 'form|basic|external',
|
||||
|
||||
// apc_cache.enabled: If set, the APC cache is allowed (the PHP extension must also be active)
|
||||
// default: true
|
||||
'apc_cache.enabled' => true,
|
||||
|
||||
// apc_cache.query_ttl: Time to live set in APC for the prepared queries (seconds - 0 means no timeout)
|
||||
// default: 3600
|
||||
'apc_cache.query_ttl' => 3600,
|
||||
|
||||
// app_root_url: Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server's name)
|
||||
// default: ''
|
||||
'app_root_url' => 'http://127.0.0.1/itop/svn/trunk/',
|
||||
|
||||
// buttons_position: Position of the forms buttons: bottom | top | both
|
||||
// default: 'both'
|
||||
'buttons_position' => 'both',
|
||||
|
||||
// cas_include_path: The path where to find the phpCAS library
|
||||
// default: '/usr/share/php'
|
||||
'cas_include_path' => '/usr/share/php',
|
||||
|
||||
// cron_max_execution_time: Duration (seconds) of the page cron.php, must be shorter than php setting max_execution_time and shorter than the web server response timeout
|
||||
// default: 600
|
||||
'cron_max_execution_time' => 600,
|
||||
|
||||
// csv_file_default_charset: Character set used by default for downloading and uploading data as a CSV file. Warning: it is case sensitive (uppercase is preferable).
|
||||
// default: 'ISO-8859-1'
|
||||
'csv_file_default_charset' => 'ISO-8859-1',
|
||||
|
||||
'csv_import_charsets' => array (
|
||||
),
|
||||
|
||||
// csv_import_history_display: Display the history tab in the import wizard
|
||||
// default: false
|
||||
'csv_import_history_display' => false,
|
||||
|
||||
// date_and_time_format: Format for date and time display (per language)
|
||||
// default: array (
|
||||
// 'default' =>
|
||||
// array (
|
||||
// 'date' => 'Y-m-d',
|
||||
// 'time' => 'H:i:s',
|
||||
// 'date_time' => '$date $time',
|
||||
// ),
|
||||
// )
|
||||
'date_and_time_format' => array (
|
||||
'default' =>
|
||||
array (
|
||||
'date' => 'Y-m-d',
|
||||
'time' => 'H:i:s',
|
||||
'date_time' => '$date $time',
|
||||
),
|
||||
'FR FR' =>
|
||||
array (
|
||||
'date' => 'd/m/Y',
|
||||
'time' => 'H:i:s',
|
||||
'date_time' => '$date $time',
|
||||
),
|
||||
),
|
||||
|
||||
'db_host' => '',
|
||||
|
||||
'db_name' => 'itop_ci',
|
||||
|
||||
'db_pwd' => 'IKnowYouSeeMeInJenkinsConf',
|
||||
|
||||
'db_subname' => '',
|
||||
|
||||
'db_user' => 'jenkins_itop',
|
||||
|
||||
// deadline_format: The format used for displaying "deadline" attributes: any string with the following placeholders: $date$, $difference$
|
||||
// default: '$difference$'
|
||||
'deadline_format' => '$difference$',
|
||||
|
||||
'default_language' => 'EN US',
|
||||
|
||||
// draft_attachments_lifetime: Lifetime (in seconds) of drafts' attachments and inline images: after this duration, the garbage collector will delete them.
|
||||
// default: 3600
|
||||
'draft_attachments_lifetime' => 3600,
|
||||
|
||||
// email_asynchronous: If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode
|
||||
// default: false
|
||||
'email_asynchronous' => false,
|
||||
|
||||
// email_default_sender_address: Default address provided in the email from header field.
|
||||
// default: ''
|
||||
'email_default_sender_address' => '',
|
||||
|
||||
// email_default_sender_label: Default label provided in the email from header field.
|
||||
// default: ''
|
||||
'email_default_sender_label' => '',
|
||||
|
||||
// email_transport: Mean to send emails: PHPMail (uses the function mail()) or SMTP (implements the client protocole)
|
||||
// default: 'PHPMail'
|
||||
'email_transport' => 'SMTP',
|
||||
|
||||
// email_transport_smtp.host: host name or IP address (optional)
|
||||
// default: 'localhost'
|
||||
'email_transport_smtp.host' => 'smtp.combodo.com',
|
||||
|
||||
// email_transport_smtp.password: Authentication password (optional)
|
||||
// default: ''
|
||||
'email_transport_smtp.password' => 'IDoNotWork',
|
||||
|
||||
// email_transport_smtp.port: port number (optional)
|
||||
// default: 25
|
||||
'email_transport_smtp.port' => 25,
|
||||
|
||||
// email_transport_smtp.username: Authentication user (optional)
|
||||
// default: ''
|
||||
'email_transport_smtp.username' => 'test2@combodo.com',
|
||||
|
||||
// email_validation_pattern: Regular expression to validate/detect the format of an eMail address
|
||||
// default: '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}'
|
||||
'email_validation_pattern' => '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}',
|
||||
|
||||
'encryption_key' => '@iT0pEncr1pti0n!',
|
||||
|
||||
'ext_auth_variable' => '$_SERVER[\'REMOTE_USER\']',
|
||||
|
||||
'fast_reload_interval' => '60',
|
||||
|
||||
// graphviz_path: Path to the Graphviz "dot" executable for graphing objects lifecycle
|
||||
// default: '/usr/bin/dot'
|
||||
'graphviz_path' => '/usr/bin/dot',
|
||||
|
||||
// inline_image_max_display_width: The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.
|
||||
// default: '250'
|
||||
'inline_image_max_display_width' => 250,
|
||||
|
||||
// inline_image_max_storage_width: The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.
|
||||
// default: '1600'
|
||||
'inline_image_max_storage_width' => 1600,
|
||||
|
||||
// link_set_attribute_qualifier: Link set from string: attribute qualifier (encloses both the attcode and the value)
|
||||
// default: '\''
|
||||
'link_set_attribute_qualifier' => '\'',
|
||||
|
||||
// link_set_attribute_separator: Link set from string: attribute separator
|
||||
// default: ';'
|
||||
'link_set_attribute_separator' => ';',
|
||||
|
||||
// link_set_item_separator: Link set from string: line separator
|
||||
// default: '|'
|
||||
'link_set_item_separator' => '|',
|
||||
|
||||
// link_set_value_separator: Link set from string: value separator (between the attcode and the value itself
|
||||
// default: ':'
|
||||
'link_set_value_separator' => ':',
|
||||
|
||||
'log_global' => true,
|
||||
|
||||
'log_issue' => true,
|
||||
|
||||
'log_notification' => true,
|
||||
|
||||
'log_web_service' => true,
|
||||
|
||||
// max_combo_length: The maximum number of elements in a drop-down list. If more then an autocomplete will be used
|
||||
// default: 50
|
||||
'max_combo_length' => 50,
|
||||
|
||||
'max_display_limit' => '15',
|
||||
|
||||
// max_linkset_output: Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.
|
||||
// default: 100
|
||||
'max_linkset_output' => 100,
|
||||
|
||||
'min_display_limit' => '10',
|
||||
|
||||
// online_help: Hyperlink to the online-help web page
|
||||
// default: 'http://www.combodo.com/itop-help'
|
||||
'online_help' => 'http://www.combodo.com/itop-help',
|
||||
|
||||
// php_path: Path to the php executable in CLI mode
|
||||
// default: 'php'
|
||||
'php_path' => 'php',
|
||||
|
||||
// portal_tickets: CSV list of classes supported in the portal
|
||||
// default: 'UserRequest'
|
||||
'portal_tickets' => 'UserRequest',
|
||||
|
||||
'query_cache_enabled' => true,
|
||||
|
||||
// search_manual_submit: Force manual submit of search requests (class => true)
|
||||
// default: false
|
||||
'search_manual_submit' => array (
|
||||
'Person' => true,
|
||||
),
|
||||
|
||||
'secure_connection_required' => false,
|
||||
|
||||
// session_name: The name of the cookie used to store the PHP session id
|
||||
// default: 'iTop'
|
||||
'session_name' => 'iTop',
|
||||
|
||||
// shortcut_actions: Actions that are available as direct buttons next to the "Actions" menu
|
||||
// default: 'UI:Menu:Modify,UI:Menu:New'
|
||||
'shortcut_actions' => 'UI:Menu:Modify,UI:Menu:New',
|
||||
|
||||
// source_dir: Source directory for the datamodel files. (which gets compiled to env-production).
|
||||
// default: ''
|
||||
'source_dir' => 'datamodels/2.x/',
|
||||
|
||||
'standard_reload_interval' => '300',
|
||||
|
||||
// synchro_trace: Synchronization details: none, display, save (includes 'display')
|
||||
// default: 'none'
|
||||
'synchro_trace' => 'none',
|
||||
|
||||
// timezone: Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitely configured in PHP
|
||||
// default: 'Europe/Paris'
|
||||
'timezone' => 'Europe/Paris',
|
||||
|
||||
// tracking_level_linked_set_default: Default tracking level if not explicitely set at the attribute level, for AttributeLinkedSet (defaults to NONE in case of a fresh install, LIST otherwise - this to preserve backward compatibility while upgrading from a version older than 2.0.3 - see TRAC #936)
|
||||
// default: 1
|
||||
'tracking_level_linked_set_default' => 0,
|
||||
|
||||
// url_validation_pattern: Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)
|
||||
// default: '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9%+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\\$_.-]*)?'
|
||||
'url_validation_pattern' => '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9%+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\\$_.-]*)?',
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Modules specific settings
|
||||
*
|
||||
*/
|
||||
$MyModuleSettings = array(
|
||||
'authent-local' => array (
|
||||
'password_validation.pattern' => '',
|
||||
),
|
||||
'itop-attachments' => array (
|
||||
'allowed_classes' => array (
|
||||
0 => 'Ticket',
|
||||
),
|
||||
'position' => 'relations',
|
||||
'preview_max_width' => 290,
|
||||
),
|
||||
'itop-backup' => array (
|
||||
'mysql_bindir' => '',
|
||||
'week_days' => 'monday, tuesday, wednesday, thursday, friday',
|
||||
'time' => '23:30',
|
||||
'retention_count' => 5,
|
||||
'enabled' => true,
|
||||
'debug' => false,
|
||||
),
|
||||
'molkobain-console-tooltips' => array (
|
||||
'decoration_class' => 'fas fa-question',
|
||||
'enabled' => true,
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* Data model modules to be loaded. Names are specified as relative paths
|
||||
*
|
||||
*/
|
||||
$MyModules = array(
|
||||
'addons' => array (
|
||||
'user rights' => 'addons/userrights/userrightsprofile.class.inc.php',
|
||||
),
|
||||
);
|
||||
?>
|
||||
@@ -1,72 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<installation>
|
||||
<!-- On manual installs, this file is generated in setup/install-*.xml -->
|
||||
<mode>upgrade</mode>
|
||||
<preinstall>
|
||||
<copies type="array"/>
|
||||
</preinstall>
|
||||
<source_dir>datamodels/2.x/</source_dir>
|
||||
<datamodel_version>2.5.0</datamodel_version>
|
||||
<previous_configuration_file>default-config-itop.php</previous_configuration_file>
|
||||
<extensions_dir>extensions</extensions_dir>
|
||||
<target_env>production</target_env>
|
||||
<workspace_dir></workspace_dir>
|
||||
<database>
|
||||
<server></server>
|
||||
<user>jenkins_itop</user>
|
||||
<pwd>IKnowYouSeeMeInJenkinsConf</pwd>
|
||||
<name>itop_ci</name>
|
||||
<db_tls_enabled></db_tls_enabled>
|
||||
<db_tls_ca></db_tls_ca>
|
||||
<prefix></prefix>
|
||||
</database>
|
||||
<url>http://127.0.0.1/itop/svn/trunk/</url>
|
||||
<graphviz_path>/usr/bin/dot</graphviz_path>
|
||||
<admin_account>
|
||||
<user>admin</user>
|
||||
<pwd>admin</pwd>
|
||||
<language>EN US</language>
|
||||
</admin_account>
|
||||
<language>EN US</language>
|
||||
<selected_modules type="array">
|
||||
<item>authent-external</item>
|
||||
<item>authent-local</item>
|
||||
<item>itop-backup</item>
|
||||
<item>itop-config</item>
|
||||
<item>itop-profiles-itil</item>
|
||||
<item>itop-sla-computation</item>
|
||||
<item>itop-tickets</item>
|
||||
<item>itop-welcome-itil</item>
|
||||
<item>itop-config-mgmt</item>
|
||||
<item>itop-attachments</item>
|
||||
<item>itop-datacenter-mgmt</item>
|
||||
<item>itop-endusers-devices</item>
|
||||
<item>itop-storage-mgmt</item>
|
||||
<item>itop-virtualization-mgmt</item>
|
||||
<item>itop-bridge-virtualization-storage</item>
|
||||
<item>itop-service-mgmt</item>
|
||||
<item>itop-request-mgmt</item>
|
||||
<item>itop-portal</item>
|
||||
<item>itop-portal-base</item>
|
||||
<item>itop-change-mgmt</item>
|
||||
<item>itop-knownerror-mgmt</item>
|
||||
</selected_modules>
|
||||
<selected_extensions type="array">
|
||||
<item>itop-config-mgmt-core</item>
|
||||
<item>itop-config-mgmt-datacenter</item>
|
||||
<item>itop-config-mgmt-end-user</item>
|
||||
<item>itop-config-mgmt-storage</item>
|
||||
<item>itop-config-mgmt-virtualization</item>
|
||||
<item>itop-service-mgmt-enterprise</item>
|
||||
<item>itop-ticket-mgmt-simple-ticket</item>
|
||||
<item>itop-ticket-mgmt-simple-ticket-enhanced-portal</item>
|
||||
<item>itop-change-mgmt-simple</item>
|
||||
<item>itop-kown-error-mgmt</item>
|
||||
</selected_extensions>
|
||||
<sample_data>1</sample_data>
|
||||
<old_addon></old_addon>
|
||||
<options>
|
||||
<generate_config>1</generate_config>
|
||||
</options>
|
||||
<mysql_bindir></mysql_bindir>
|
||||
</installation>
|
||||
@@ -1,208 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2019 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
|
||||
*/
|
||||
|
||||
//this scrit will be run under the ./toolkit directory, relatively to the document root
|
||||
|
||||
require_once('../approot.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
require_once(APPROOT.'/application/clipage.class.inc.php');
|
||||
require_once(APPROOT.'/core/config.class.inc.php');
|
||||
require_once(APPROOT.'/core/log.class.inc.php');
|
||||
require_once(APPROOT.'/core/kpi.class.inc.php');
|
||||
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
|
||||
require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
|
||||
require_once(APPROOT.'/setup/applicationinstaller.class.inc.php');
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
$sParamFile = utils::ReadParam('response_file', 'default-params.xml', true /* CLI allowed */, 'raw_data');
|
||||
$bCheckConsistency = (utils::ReadParam('check_consistency', '0', true /* CLI allowed */) == '1');
|
||||
|
||||
$oParams = new XMLParameters($sParamFile);
|
||||
$sMode = $oParams->Get('mode');
|
||||
|
||||
if ($sMode == 'install')
|
||||
{
|
||||
echo "Installation mode detected.\n";
|
||||
$bClean = utils::ReadParam('clean', false, true /* CLI allowed */);
|
||||
if ($bClean)
|
||||
{
|
||||
echo "Cleanup mode detected.\n";
|
||||
$sTargetEnvironment = $oParams->Get('target_env', '');
|
||||
if ($sTargetEnvironment == '')
|
||||
{
|
||||
$sTargetEnvironment = 'production';
|
||||
}
|
||||
$sTargetDir = APPROOT.'env-'.$sTargetEnvironment;
|
||||
|
||||
// Configuration file
|
||||
$sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE;
|
||||
if (file_exists($sConfigFile))
|
||||
{
|
||||
echo "Trying to delete the configuration file: '$sConfigFile'.\n";
|
||||
@chmod($sConfigFile, 0770); // RWX for owner and group, nothing for others
|
||||
unlink($sConfigFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "No config file to delete ($sConfigFile does not exist).\n";
|
||||
}
|
||||
|
||||
// env-xxx directory
|
||||
if (file_exists($sTargetDir))
|
||||
{
|
||||
if (is_dir($sTargetDir))
|
||||
{
|
||||
echo "Emptying the target directory '$sTargetDir'.\n";
|
||||
SetupUtils::tidydir($sTargetDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
die("ERROR the target dir '$sTargetDir' exists, but is NOT a directory !!!\nExiting.\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "No target directory to delete ($sTargetDir does not exist).\n";
|
||||
}
|
||||
|
||||
// Database
|
||||
$aDBSettings = $oParams->Get('database', array());
|
||||
$sDBServer = $aDBSettings['server'];
|
||||
$sDBUser = $aDBSettings['user'];
|
||||
$sDBPwd = $aDBSettings['pwd'];
|
||||
$sDBName = $aDBSettings['name'];
|
||||
$sDBPrefix = $aDBSettings['prefix'];
|
||||
|
||||
if ($sDBPrefix != '')
|
||||
{
|
||||
die("Cleanup not implemented for a partial database (prefix= '$sDBPrefix')\nExiting.");
|
||||
}
|
||||
|
||||
$oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd);
|
||||
if ($oMysqli->connect_errno)
|
||||
{
|
||||
die("Cannot connect to the MySQL server (".$mysqli->connect_errno . ") ".$mysqli->connect_error."\nExiting");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($oMysqli->select_db($sDBName))
|
||||
{
|
||||
echo "Deleting database '$sDBName'\n";
|
||||
$oMysqli->query("DROP DATABASE `$sDBName`");
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "The database '$sDBName' does not seem to exist. Nothing to cleanup.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$bHasErrors = false;
|
||||
$aChecks = SetupUtils::CheckBackupPrerequisites(APPROOT.'data'); // mmm should be the backup destination dir
|
||||
|
||||
$aSelectedModules = $oParams->Get('selected_modules');
|
||||
$sSourceDir = $oParams->Get('source_dir', 'datamodels/latest');
|
||||
$sExtensionDir = $oParams->Get('extensions_dir', 'extensions');
|
||||
$aChecks = array_merge($aChecks, SetupUtils::CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules));
|
||||
|
||||
|
||||
foreach($aChecks as $oCheckResult)
|
||||
{
|
||||
switch($oCheckResult->iSeverity)
|
||||
{
|
||||
case CheckResult::ERROR:
|
||||
$bHasErrors = true;
|
||||
$sHeader = "Error";
|
||||
break;
|
||||
|
||||
case CheckResult::WARNING:
|
||||
$sHeader = "Warning";
|
||||
break;
|
||||
|
||||
case CheckResult::INFO:
|
||||
default:
|
||||
$sHeader = "Info";
|
||||
break;
|
||||
}
|
||||
echo $sHeader.": ".$oCheckResult->sLabel;
|
||||
if (strlen($oCheckResult->sDescription))
|
||||
{
|
||||
echo ' - '.$oCheckResult->sDescription;
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
if ($bHasErrors)
|
||||
{
|
||||
echo "Encountered stopper issues. Aborting...\n";
|
||||
die;
|
||||
}
|
||||
|
||||
$bFoundIssues = false;
|
||||
|
||||
$bInstall = utils::ReadParam('install', true, true /* CLI allowed */);
|
||||
if ($bInstall)
|
||||
{
|
||||
echo "Starting the unattended installation...\n";
|
||||
$oWizard = new ApplicationInstaller($oParams);
|
||||
$bRes = $oWizard->ExecuteAllSteps();
|
||||
if (!$bRes)
|
||||
{
|
||||
echo "\nencountered installation issues!";
|
||||
$bFoundIssues = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "No installation requested.\n";
|
||||
}
|
||||
if (!$bFoundIssues && $bCheckConsistency)
|
||||
{
|
||||
echo "Checking data model consistency.\n";
|
||||
ob_start();
|
||||
$sCheckRes = '';
|
||||
try
|
||||
{
|
||||
MetaModel::CheckDefinitions(false);
|
||||
$sCheckRes = ob_get_clean();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$sCheckRes = ob_get_clean()."\nException: ".$e->getMessage();
|
||||
}
|
||||
if (strlen($sCheckRes) > 0)
|
||||
{
|
||||
echo $sCheckRes;
|
||||
echo "\nfound consistency issues!";
|
||||
$bFoundIssues = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bFoundIssues)
|
||||
{
|
||||
// last line: used to check the install
|
||||
// the only way to track issues in case of Fatal error or even parsing error!
|
||||
echo "\ninstalled!";
|
||||
exit;
|
||||
}
|
||||
90
.make/build/afterBuild.php
Normal file
90
.make/build/afterBuild.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
$iBeginTime = time();
|
||||
|
||||
chdir(__DIR__);
|
||||
|
||||
$aCommands = [
|
||||
'php composer/rmDeniedTestDir.php',
|
||||
'php build/commands/setupCssCompiler.php',
|
||||
// 'bash /tmp/gabuzomeu.sh',
|
||||
];
|
||||
|
||||
$aFailedCommands=[];
|
||||
foreach ($aCommands as $sCommand)
|
||||
{
|
||||
if (!ExecCommand($sCommand))
|
||||
{
|
||||
$aFailedCommands[] = $sCommand;
|
||||
}
|
||||
}
|
||||
|
||||
$iElapsed = time() - $iBeginTime;
|
||||
|
||||
if (count($aFailedCommands))
|
||||
{
|
||||
fwrite(STDERR, "\nafterBuild execution failed! (in ${iElapsed}s)\n");
|
||||
fwrite(STDERR, "List of failling commands:\n - " . implode("\n - ", $aFailedCommands) . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
echo "\nDone (${iElapsed}s)\n";
|
||||
exit(0);
|
||||
|
||||
/**
|
||||
* Executes a command and returns an array with exit code, stdout and stderr content
|
||||
*
|
||||
* @param string $cmd - Command to execute
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
function ExecCommand($cmd) {
|
||||
$iBeginTime = time();
|
||||
|
||||
|
||||
echo sprintf("command: %s", str_pad("$cmd ", 50));
|
||||
|
||||
$descriptorspec = array(
|
||||
0 => array("pipe", "r"), // stdin
|
||||
1 => array("pipe", "w"), // stdout
|
||||
2 => array("pipe", "w"), // stderr
|
||||
);
|
||||
$process = proc_open($cmd, $descriptorspec, $pipes, __DIR__ . '/..', null);
|
||||
|
||||
$stdout = stream_get_contents($pipes[1]);
|
||||
fclose($pipes[1]);
|
||||
|
||||
$stderr = stream_get_contents($pipes[2]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
$iCode = proc_close($process);
|
||||
$bSuccess = (0 === $iCode);
|
||||
|
||||
$iElapsed = time() - $iBeginTime;
|
||||
if (!$bSuccess) {
|
||||
fwrite(STDERR, sprintf(
|
||||
"\nCOMMAND FAILED! (%s) \n - status:%s \n - stderr:%s \n - stdout: %s\n - elapsed:%ss\n\n",
|
||||
$cmd,
|
||||
$iCode,
|
||||
rtrim($stderr),
|
||||
rtrim($stdout),
|
||||
$iElapsed
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "| elapsed:${iElapsed}s \n";
|
||||
}
|
||||
|
||||
if (!empty($stderr))
|
||||
{
|
||||
fwrite(STDERR, "$stderr\n");
|
||||
}
|
||||
if (!empty($stdout))
|
||||
{
|
||||
echo "stdout :$stdout\n\n";
|
||||
}
|
||||
|
||||
return $bSuccess;
|
||||
}
|
||||
51
.make/build/commands/setupCssCompiler.php
Normal file
51
.make/build/commands/setupCssCompiler.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2010-2020 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http: *www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Composer\iTopComposer;
|
||||
|
||||
$iTopFolder = __DIR__."/../../../";
|
||||
|
||||
require_once("$iTopFolder/approot.inc.php");
|
||||
require_once(APPROOT."/application/utils.inc.php");
|
||||
|
||||
if (PHP_SAPI !== 'cli')
|
||||
{
|
||||
throw new \Exception('This script can only run from CLI');
|
||||
}
|
||||
|
||||
$sCssFile = APPROOT.'/css/setup.css';
|
||||
if (file_exists($sCssFile))
|
||||
{
|
||||
fwrite(STDERR, "$sCssFile already exists (it should not), removing it.");
|
||||
if (!unlink($sCssFile))
|
||||
{
|
||||
fwrite(STDERR, "Failed to remove $sCssFile, exiting.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
$sCssRelPath = utils::GetCSSFromSASS('css/setup.scss');
|
||||
|
||||
if (!file_exists($sCssFile))
|
||||
{
|
||||
fwrite(STDERR, "Failed to compile $sCssFile, exiting.");
|
||||
exit(1);
|
||||
}
|
||||
@@ -26,7 +26,7 @@ $iTopFolder = __DIR__ . "/../../" ;
|
||||
require_once ("$iTopFolder/approot.inc.php");
|
||||
require_once (APPROOT."/setup/setuputils.class.inc.php");
|
||||
|
||||
if (php_sapi_name() !== 'cli')
|
||||
if (PHP_SAPI !== 'cli')
|
||||
{
|
||||
throw new \Exception('This script can only run from CLI');
|
||||
}
|
||||
@@ -36,22 +36,38 @@ clearstatcache();
|
||||
$oiTopComposer = new iTopComposer();
|
||||
$aDeniedButStillPresent = $oiTopComposer->ListDeniedButStillPresent();
|
||||
|
||||
echo "\n";
|
||||
foreach ($aDeniedButStillPresent as $sDir)
|
||||
{
|
||||
if (! preg_match('#[tT]ests?/?$#', $sDir))
|
||||
if (false === iTopComposer::IsTestDir($sDir))
|
||||
{
|
||||
echo "\nfound INVALID denied test dir: '$sDir'\n";
|
||||
echo "ERROR found INVALID denied test dir: '$sDir'\n";
|
||||
throw new \Exception("$sDir must end with /Test/ or /test/");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SetupUtils::rrmdir($sDir);
|
||||
echo "Remove denied test dir: '$sDir'\n";
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
echo "\nFAILED to remove denied test dir: '$sDir'\n";
|
||||
if (false === file_exists($sDir)) {
|
||||
echo "INFO $sDir is in denied list, but not existing on disk => skipping !\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
try {
|
||||
SetupUtils::rrmdir($sDir);
|
||||
echo "OK Remove denied test dir: '$sDir'\n";
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* script used to sort license file (usefull for autogeneration)
|
||||
* Example: php
|
||||
* script used to sort license file (useful for autogeneration)
|
||||
*
|
||||
* Requirements :
|
||||
* * bash (on Windows, use Git Bash)
|
||||
* * composer (if you use the phar version, mind to create a `Composer` alias !)
|
||||
* * JQ command
|
||||
* to install on Windows :
|
||||
* `curl -L -o /usr/bin/jq.exe https://github.com/stedolan/jq/releases/latest/download/jq-win64.exe`
|
||||
* this is a Windows port : https://stedolan.github.io/jq/
|
||||
*
|
||||
* Licenses sources :
|
||||
* * `composer licenses --format json` (see https://getcomposer.org/doc/03-cli.md#licenses)
|
||||
* * keep every existing nodes with `/licenses/license[11]/product/@scope` not in ['lib', 'datamodels']
|
||||
* ⚠ If licenses were added manually, they might be removed by this tool ! Be very careful to check for the result before pushing !
|
||||
*
|
||||
* To launch, check requirements and run `php updateLicenses.php`
|
||||
* The target license file path is in `$xmlFilePath`
|
||||
*/
|
||||
|
||||
$iTopFolder = __DIR__ . "/../../" ;
|
||||
$xmlFilePath = $iTopFolder . "setup/licenses/community-licenses.xml";
|
||||
$iTopFolder = __DIR__."/../../";
|
||||
$xmlFilePath = $iTopFolder."setup/licenses/community-licenses.xml";
|
||||
|
||||
function get_scope($product_node)
|
||||
{
|
||||
$jqExec = shell_exec("jq -V"); // a param is mandatory otherwise the script will freeze
|
||||
if ((null === $jqExec) || (false === $jqExec)) {
|
||||
echo "/!\ JQ is required but cannot be launched :( \n";
|
||||
echo "Check this script PHPDoc block for instructions\n";
|
||||
die(-1);
|
||||
}
|
||||
|
||||
|
||||
function get_scope($product_node) {
|
||||
$scope = $product_node->getAttribute("scope");
|
||||
|
||||
if ($scope === "")
|
||||
{ //put iTop first
|
||||
if ($scope === "") { //put iTop first
|
||||
return "aaaaaaaaa";
|
||||
}
|
||||
|
||||
return $scope;
|
||||
}
|
||||
|
||||
@@ -51,39 +73,83 @@ function get_license_nodes($file_path)
|
||||
$xp = new DOMXPath($dom);
|
||||
|
||||
$licenseList = $xp->query('/licenses/license');
|
||||
$licenses = iterator_to_array($licenseList);
|
||||
$licenses = iterator_to_array($licenseList);
|
||||
|
||||
usort($licenses, 'sort_by_product');
|
||||
|
||||
return $licenses;
|
||||
}
|
||||
|
||||
/** @noinspection SuspiciousAssignmentsInspection */
|
||||
function fix_product_name(DOMNode &$oProductNode)
|
||||
{
|
||||
$sProductNameOrig = $oProductNode->nodeValue;
|
||||
|
||||
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//lib/symfony/polyfill-ctype`
|
||||
$sProductNameFixed = remove_dir_from_string($sProductNameOrig, 'lib/');
|
||||
|
||||
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//datamodels/2.x/authent-cas/vendor/apereo/phpcas`
|
||||
$sProductNameFixed = remove_dir_from_string($sProductNameFixed, 'vendor/');
|
||||
|
||||
$oProductNode->nodeValue = $sProductNameFixed;
|
||||
}
|
||||
|
||||
function remove_dir_from_string($sString, $sNeedle)
|
||||
{
|
||||
if (strpos($sString, $sNeedle) === false) {
|
||||
return $sString;
|
||||
}
|
||||
|
||||
$sStringTmp = strstr($sString, $sNeedle);
|
||||
$sStringFixed = str_replace($sNeedle, '', $sStringTmp);
|
||||
|
||||
// DEBUG trace O:)
|
||||
// echo "$sNeedle = $sString => $sStringFixed\n";
|
||||
|
||||
return $sStringFixed;
|
||||
}
|
||||
|
||||
$old_licenses = get_license_nodes($xmlFilePath);
|
||||
|
||||
//generate file with updated licenses
|
||||
$generated_license_file_path = __DIR__."/provfile.xml";
|
||||
exec("bash " . __DIR__ . "/gen-community-license.sh $iTopFolder > ". $generated_license_file_path);
|
||||
echo "- Generating licences...";
|
||||
exec("bash ".__DIR__."/gen-community-license.sh $iTopFolder > ".$generated_license_file_path);
|
||||
echo "OK!\n";
|
||||
|
||||
echo "- Get licenses nodes...";
|
||||
$new_licenses = get_license_nodes($generated_license_file_path);
|
||||
exec("rm -f ". $generated_license_file_path);
|
||||
unlink($generated_license_file_path);
|
||||
|
||||
foreach ($old_licenses as $b) {
|
||||
$aProductNode = get_product_node($b);
|
||||
|
||||
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels" )
|
||||
{
|
||||
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels") {
|
||||
$new_licenses[] = $b;
|
||||
}
|
||||
}
|
||||
|
||||
usort($new_licenses, 'sort_by_product');
|
||||
echo "OK!\n";
|
||||
|
||||
echo "- Overwritting Combodo license file...";
|
||||
$new_dom = new DOMDocument("1.0");
|
||||
$new_dom->formatOutput = true;
|
||||
$root = $new_dom->createElement("licenses");
|
||||
$new_dom->appendChild($root);
|
||||
|
||||
foreach ($new_licenses as $b) {
|
||||
$node = $new_dom->importNode($b,true);
|
||||
$root->appendChild($new_dom->importNode($b,true));
|
||||
$node = $new_dom->importNode($b, true);
|
||||
|
||||
// N°3870 fix when running script in Windows
|
||||
// fix should be in gen-community-license.sh but it is easier to do it here !
|
||||
if (strncasecmp(PHP_OS, 'WIN', 3) === 0) {
|
||||
$oProductNodeOrig = get_product_node($node);
|
||||
fix_product_name($oProductNodeOrig);
|
||||
}
|
||||
|
||||
$root->appendChild($node);
|
||||
}
|
||||
|
||||
$new_dom->save($xmlFilePath);
|
||||
$new_dom->save($xmlFilePath);
|
||||
echo "OK!\n";
|
||||
77
.make/release/changelog.php
Normal file
77
.make/release/changelog.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* Usage :
|
||||
* `php changelog.php 2.7.4`
|
||||
*
|
||||
* As argument is passed the git ref (tag name or sha1) we want to use as reference
|
||||
*
|
||||
* Outputs :
|
||||
*
|
||||
* 1. List of bugs as CSV :
|
||||
* bug ref;link
|
||||
* Example :
|
||||
* <code>
|
||||
* Bug_ref;Bug_URL;sha1
|
||||
* 1234;https://support.combodo.com/pages/UI.php?operation=details&class=Bug&id=1234;949b213f9|b1ca1f263|a1271da74
|
||||
* </code>
|
||||
*
|
||||
* 2. List of commits sha1/message without bug ref
|
||||
* Example :
|
||||
* <code>
|
||||
* sha1;subject
|
||||
* a6aa183e2;:bookmark: Prepare 2.7.5
|
||||
* </code>
|
||||
*/
|
||||
|
||||
|
||||
if (count($argv) === 1) {
|
||||
echo '⚠ You must pass the base tag/sha1 as parameter';
|
||||
exit(1);
|
||||
}
|
||||
$sBaseReference = $argv[1];
|
||||
|
||||
|
||||
//--- Get log
|
||||
$sGitLogCommand = 'git log --decorate --pretty="%h;%s" --date-order --no-merges '.$sBaseReference.'..HEAD';
|
||||
$sGitLogRaw = shell_exec($sGitLogCommand);
|
||||
|
||||
|
||||
//--- Analyze log
|
||||
$aGitLogLines = preg_split('/\n/', trim($sGitLogRaw));;
|
||||
$aLogLinesWithBugRef = [];
|
||||
$aLogLineNoBug = [];
|
||||
foreach ($aGitLogLines as $sLogLine) {
|
||||
$sBugRef = preg_match('/[nN]°(\d{3,4})/', $sLogLine, $aLineBugRef);
|
||||
if (($sBugRef === false) || empty($aLineBugRef)) {
|
||||
$aLogLineNoBug[] = $sLogLine;
|
||||
continue;
|
||||
}
|
||||
|
||||
$iBugId = $aLineBugRef[1];
|
||||
$sSha = substr($sLogLine, 0, 9);
|
||||
|
||||
if (array_key_exists($iBugId, $aLogLinesWithBugRef)) {
|
||||
$aBugShaRefs = $aLogLinesWithBugRef[$iBugId];
|
||||
$aBugShaRefs[] = $sSha;
|
||||
$aLogLinesWithBugRef[$iBugId] = $aBugShaRefs;
|
||||
} else {
|
||||
$aLogLinesWithBugRef[$iBugId] = [$sSha];
|
||||
}
|
||||
}
|
||||
$aBugsList = array_keys($aLogLinesWithBugRef);
|
||||
sort($aBugsList, SORT_NUMERIC);
|
||||
|
||||
|
||||
//-- Output results
|
||||
echo "# Bugs included\n";
|
||||
echo "Bug_ref;Bug_URL;sha1\n";
|
||||
foreach ($aBugsList as $sBugRef) {
|
||||
$sShaRefs = implode('|', $aLogLinesWithBugRef[$sBugRef]);
|
||||
echo "{$sBugRef};https://support.combodo.com/pages/UI.php?operation=details&class=Bug&id={$sBugRef};$sShaRefs\n";
|
||||
}
|
||||
echo "\n";
|
||||
echo "# Logs line without bug referenced\n";
|
||||
echo "sha1;subject\n";
|
||||
foreach ($aLogLineNoBug as $sLogLine) {
|
||||
echo "$sLogLine\n";
|
||||
}
|
||||
@@ -27,6 +27,7 @@ $aFilesUpdaters = array(
|
||||
new iTopVersionFileUpdater(),
|
||||
new CssVariablesFileUpdater(),
|
||||
new DatamodelsModulesFiles(),
|
||||
new ConstantFileUpdater('ITOP_CORE_VERSION', 'approot.inc.php'),
|
||||
);
|
||||
|
||||
if (count($argv) === 1)
|
||||
|
||||
@@ -69,6 +69,40 @@ abstract class AbstractSingleFileVersionUpdater extends FileVersionUpdater
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°4714
|
||||
*/
|
||||
class ConstantFileUpdater extends AbstractSingleFileVersionUpdater {
|
||||
/** @var string */
|
||||
private $sConstantName;
|
||||
|
||||
/**
|
||||
* @param $sConstantName constant to search, for example `ITOP_CORE_VERSION`
|
||||
* @param $sFileToUpdate file containing constant definition
|
||||
*/
|
||||
public function __construct($sConstantName, $sFileToUpdate)
|
||||
{
|
||||
$this->sConstantName = $sConstantName;
|
||||
parent::__construct($sFileToUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath)
|
||||
{
|
||||
$sConstantSearchPattern = <<<REGEXP
|
||||
/define\('{$this->sConstantName}', ?'[^']+'\);/
|
||||
REGEXP;
|
||||
|
||||
return preg_replace(
|
||||
$sConstantSearchPattern,
|
||||
"define('{$this->sConstantName}', '{$sVersionLabel}');",
|
||||
$sFileContent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class iTopVersionFileUpdater extends AbstractSingleFileVersionUpdater
|
||||
{
|
||||
public function __construct()
|
||||
|
||||
@@ -37,57 +37,68 @@ If you want to use another license, you may [create an extension][wiki new ext].
|
||||
[wiki new ext]: https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Astart#by_writing_your_own_extension
|
||||
|
||||
|
||||
## 🔀 Branch model
|
||||
## 🔀 iTop branch model
|
||||
|
||||
TL;DR:
|
||||
> **create a fork from iTop main repository,
|
||||
> create a branch based on the develop branch**
|
||||
When we first start with Git, we were using the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branch model. As
|
||||
there was some confusions about branches to use for current developed release and previous maintained release, and also because we were
|
||||
using just a very few of the GitFlow commands, we decided to add just a little modification to this branch model : since april 2020
|
||||
we don't have anymore a `master` branch.
|
||||
|
||||
We are using the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branch model. That means we have in our repo those
|
||||
main branches:
|
||||
Here are the branches we use and their meaning :
|
||||
|
||||
- develop: ongoing development version
|
||||
- release/\*: if present, that means we are working on a beta version
|
||||
- master: previous stable version
|
||||
- support/\*: maintenance branches for older versions
|
||||
- `develop`: ongoing development version
|
||||
- `release/*`: if present, that means we are working on a alpha/beta/rc version for shipping
|
||||
- `support/*`: maintenance branches for older versions
|
||||
|
||||
For example, if no beta version is currently ongoing we could have:
|
||||
For example, if no version is currently prepared for shipping we could have:
|
||||
|
||||
- develop containing future 2.8.0 version
|
||||
- master containing 2.7.x maintenance version
|
||||
- support/2.6 containing 2.6.x maintenance version
|
||||
- support/2.5 containing 2.5.x maintenance version
|
||||
- `develop` containing future 2.8.0 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 2.8.0-beta is shipped that will become:
|
||||
|
||||
- develop: future 2.9.0 version
|
||||
- release/2.8: 2.8.0-beta
|
||||
- master: 2.7.x maintenance version
|
||||
- support/2.6 containing 2.6.x maintenance version
|
||||
- support/2.5 containing 2.5.x maintenance version
|
||||
- `develop`: future 2.9.0 version
|
||||
- `release/2.8`: 2.8.0-beta
|
||||
- `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 2.8.0 final will be out:
|
||||
|
||||
- develop: future 2.9.0 version
|
||||
- master: 2.8.x maintenance version
|
||||
- support/2.7 : 2.7.x maintenance version
|
||||
- support/2.6 containing 2.6.x maintenance version
|
||||
- support/2.5 containing 2.5.x maintenance version
|
||||
- `develop`: future 2.9.0 version
|
||||
- `support/2.8`: 2.8.x maintenance version (will host developments for 2.8.1)
|
||||
- `support/2.7`: 2.7.x maintenance version
|
||||
- `support/2.6`: 2.6.x maintenance version
|
||||
- `support/2.5`: 2.5.x maintenance version
|
||||
|
||||
Most of the time you should based your developments on the develop branch.
|
||||
That may be different if you want to fix a bug, please use develop anyway and ask in your PR if rebase is possible.
|
||||
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.
|
||||
|
||||
|
||||
## Coding
|
||||
|
||||
### 🎨 PHP styleguide
|
||||
|
||||
Please follow [our guidelines](https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Acoding_standards).
|
||||
|
||||
### 🌐 Translations
|
||||
|
||||
A [dedicated page](https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Atranslation) is available in the official wiki.
|
||||
|
||||
### Where to start ?
|
||||
|
||||
1. Create a fork from our repository (see [Working with forks - GitHub Help](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/working-with-forks))
|
||||
2. Create a branch in this fork, based on the develop branch
|
||||
3. Code !
|
||||
|
||||
Do create a dedicated branch for each modification you want to propose : if you don't it will be very hard to merge back your work !
|
||||
|
||||
Most of the time you should based your developments on the develop branch.
|
||||
That may be different if you want to fix a bug, please use develop anyway and ask in your PR if rebase is possible.
|
||||
|
||||
|
||||
### 🎨 PHP styleguide
|
||||
|
||||
Please follow [our guidelines](https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Acoding_standards).
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
Please create tests that covers as much as possible the code you're submitting.
|
||||
@@ -100,9 +111,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
|
||||
@@ -121,7 +132,7 @@ 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,
|
||||
* squash as much as possible your commits,
|
||||
* rebase your branch on our repo last commit,
|
||||
* create a pull request.
|
||||
|
||||
|
||||
80
Jenkinsfile
vendored
80
Jenkinsfile
vendored
@@ -1,69 +1,19 @@
|
||||
pipeline {
|
||||
agent any
|
||||
parameters {
|
||||
booleanParam(name: 'debugMode', defaultValue: 'false', description: 'Debug mode?')
|
||||
booleanParam(name: 'runNonRegOQLTests', defaultValue: 'false', description: 'Do You want to run legacy OQL regression tests?')
|
||||
}
|
||||
stages {
|
||||
def infra
|
||||
|
||||
stage('init') {
|
||||
parallel {
|
||||
stage('debug') {
|
||||
steps {
|
||||
sh './.jenkins/bin/init/debug.sh'
|
||||
}
|
||||
}
|
||||
stage('append files to project') {
|
||||
steps {
|
||||
sh './.jenkins/bin/init/append_files.sh'
|
||||
}
|
||||
}
|
||||
stage('composer install') {
|
||||
steps {
|
||||
sh './.jenkins/bin/init/composer_install.sh'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node(){
|
||||
properties([
|
||||
buildDiscarder(
|
||||
logRotator(
|
||||
daysToKeepStr: "28",
|
||||
numToKeepStr: "500")
|
||||
)
|
||||
])
|
||||
|
||||
stage('unattended_install') {
|
||||
parallel {
|
||||
stage('unattended_install default env') {
|
||||
steps {
|
||||
sh './.jenkins/bin/unattended_install/default_env.sh'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkout scm
|
||||
|
||||
stage('test') {
|
||||
parallel {
|
||||
stage('phpunit') {
|
||||
steps {
|
||||
sh './.jenkins/bin/tests/phpunit.sh ${debugMode} ${runNonRegOQLTests}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
junit 'var/test/phpunit-log.junit.xml'
|
||||
}
|
||||
failure {
|
||||
slackSend(channel: "#jenkins-itop", color: '#FF0000', message: "Ho no! Build failed! (${currentBuild.result}), Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
|
||||
}
|
||||
fixed {
|
||||
slackSend(channel: "#jenkins-itop", color: '#FFa500', message: "Yes! Build repaired! (${currentBuild.result}), Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
|
||||
}
|
||||
}
|
||||
|
||||
environment {
|
||||
DEBUG_UNIT_TEST = '0'
|
||||
}
|
||||
options {
|
||||
timeout(time: 20, unit: 'MINUTES')
|
||||
}
|
||||
infra = load '/var/lib/jenkins/workspace/itop-test-infra_master/src/Infra.groovy'
|
||||
}
|
||||
|
||||
|
||||
infra.call()
|
||||
|
||||
|
||||
59
README.md
59
README.md
@@ -21,6 +21,19 @@ iTop also offers mass import tools and web services to integrate with your IT
|
||||
- [Data synchronization][18] (for data federation)
|
||||
|
||||
|
||||
## Latest release
|
||||
|
||||
- [Changes since the previous version][62]
|
||||
- [New features][63]
|
||||
- [Installation notes][64]
|
||||
- [Download][65]
|
||||
|
||||
[62]: https://www.itophub.io/wiki/page?id=latest:release:change_log
|
||||
[63]: https://www.itophub.io/wiki/page?id=latest:release:start
|
||||
[64]: https://www.itophub.io/wiki/page?id=latest:install:start
|
||||
[65]: https://sourceforge.net/projects/itop/files/latest/download
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
- [iTop Forums][1]: community support
|
||||
@@ -49,47 +62,6 @@ iTop also offers mass import tools and web services to integrate with your IT
|
||||
|
||||
|
||||
|
||||
## Last releases
|
||||
|
||||
### Versions 2.7.*
|
||||
- 2.7.0-beta2 published on January 29, 2020
|
||||
- [Changes since the previous version][62]
|
||||
- [New features][63]
|
||||
- [Migration notes][64]
|
||||
- [Download iTop 2.7.0-beta2][65]
|
||||
|
||||
[62]: https://www.itophub.io/wiki/page?id=2_7_0:release:change_log
|
||||
[63]: https://www.itophub.io/wiki/page?id=2_7_0:release:2_7_whats_new
|
||||
[64]: https://www.itophub.io/wiki/page?id=2_7_0:install:260_to_270_migration_notes
|
||||
[65]: https://sourceforge.net/projects/itop/files/itop/2.7.0-beta2
|
||||
|
||||
|
||||
### Versions 2.6.*
|
||||
- 2.6.0 published on January 9, 2019
|
||||
- [Changes since the previous version][58]
|
||||
- [New features][59]
|
||||
- [Migration notes][60]
|
||||
- [Download iTop 2.6.3][61]
|
||||
|
||||
[58]: https://www.itophub.io/wiki/page?id=2_6_0:release:change_log
|
||||
[59]: https://www.itophub.io/wiki/page?id=2_6_0:release:2_6_whats_new
|
||||
[60]: https://www.itophub.io/wiki/page?id=2_6_0:install:250_to_260_migration_notes
|
||||
[61]: https://sourceforge.net/projects/itop/files/itop/2.6.3
|
||||
|
||||
|
||||
### Versions 2.5.*
|
||||
- 2.5.0 published on July 11, 2018
|
||||
- [Changes since the previous version][54]
|
||||
- [New features][55]
|
||||
- [Migration notes][56]
|
||||
- [Download iTop 2.5.1][57]
|
||||
|
||||
[54]: https://www.itophub.io/wiki/page?id=2_5_0:release:change_log
|
||||
[55]: https://www.itophub.io/wiki/page?id=2_5_0:release:2_5_whats_new
|
||||
[56]: https://www.itophub.io/wiki/page?id=2_5_0:install:240_to_250_migration_notes
|
||||
[57]: https://sourceforge.net/projects/itop/files/itop/2.5.1
|
||||
|
||||
|
||||
## About Us
|
||||
|
||||
iTop development is sponsored, led and supported by [Combodo][0].
|
||||
@@ -123,8 +95,9 @@ We would like to give a special thank you to the people from the community who c
|
||||
- Lassiter, Dennis
|
||||
- Lazcano, Federico
|
||||
- Lucas, Jonathan
|
||||
- Malik, Remie
|
||||
- Rosenke, Stephan
|
||||
- Malik, Remie
|
||||
- Mindêllo de Andrade, Lucas (a.k.a @rokam)
|
||||
- Rosenke, Stephan
|
||||
- Seki, Shoji
|
||||
- Shilov, Vladimir
|
||||
- Tulio, Marco
|
||||
|
||||
@@ -23,7 +23,7 @@ define('PORTAL_PROFILE_NAME', 'Portal user');
|
||||
class UserRightsBaseClassGUI extends cmdbAbstractObject
|
||||
{
|
||||
// Whenever something changes, reload the privileges
|
||||
|
||||
|
||||
protected function AfterInsert()
|
||||
{
|
||||
UserRights::FlushPrivileges();
|
||||
@@ -73,7 +73,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
}
|
||||
|
||||
protected static $m_aCacheProfiles = null;
|
||||
|
||||
|
||||
public static function DoCreateProfile($sName, $sDescription)
|
||||
{
|
||||
if (is_null(self::$m_aCacheProfiles))
|
||||
@@ -85,7 +85,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
{
|
||||
self::$m_aCacheProfiles[$oProfile->Get('name')] = $oProfile->GetKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sCacheKey = $sName;
|
||||
if (isset(self::$m_aCacheProfiles[$sCacheKey]))
|
||||
@@ -96,10 +96,10 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
$oNewObj->Set('name', $sName);
|
||||
$oNewObj->Set('description', $sDescription);
|
||||
$iId = $oNewObj->DBInsertNoReload();
|
||||
self::$m_aCacheProfiles[$sCacheKey] = $iId;
|
||||
self::$m_aCacheProfiles[$sCacheKey] = $iId;
|
||||
return $iId;
|
||||
}
|
||||
|
||||
|
||||
function GetGrantAsHtml($oUserRights, $sClass, $sAction)
|
||||
{
|
||||
$bGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction);
|
||||
@@ -116,7 +116,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function DoShowGrantSumary($oPage)
|
||||
{
|
||||
if ($this->GetRawName() == "Administrator")
|
||||
@@ -128,7 +128,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
|
||||
// Note: for sure, we assume that the instance is derived from UserRightsProfile
|
||||
$oUserRights = UserRights::GetModuleInstance();
|
||||
|
||||
|
||||
$aDisplayData = array();
|
||||
foreach (MetaModel::GetClasses('bizmodel,grant_by_profile') as $sClass)
|
||||
{
|
||||
@@ -137,12 +137,12 @@ 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>';
|
||||
}
|
||||
}
|
||||
$sStimuli = implode(', ', $aStimuli);
|
||||
|
||||
|
||||
$aDisplayData[] = array(
|
||||
'class' => MetaModel::GetName($sClass),
|
||||
'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'r'),
|
||||
@@ -154,7 +154,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
'stimuli' => $sStimuli,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$aDisplayConfig = array();
|
||||
$aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+'));
|
||||
$aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+'));
|
||||
@@ -214,7 +214,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
|
||||
* @param $aReasons array To store the reasons why the attribute is read-only (info about the synchro replicas)
|
||||
* @param $sTargetState string The target state in which to evalutate the flags, if empty the current state will be used
|
||||
* @return integer Flags: the binary combination of the flags applicable to this attribute
|
||||
*/
|
||||
*/
|
||||
public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '')
|
||||
{
|
||||
$iFlags = parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState);
|
||||
@@ -397,7 +397,7 @@ class URP_UserOrg extends UserRightsBaseClassGUI
|
||||
{
|
||||
if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { return; }
|
||||
|
||||
$oUser = UserRights::GetUserObject();
|
||||
$oUser = UserRights::GetUserObject();
|
||||
$oAddon = UserRights::GetModuleInstance();
|
||||
$aOrgs = $oAddon->GetUserOrgs($oUser, '');
|
||||
if (count($aOrgs) > 0)
|
||||
@@ -425,6 +425,12 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
UR_ACTION_BULK_DELETE => 'bd',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array $aUsersProfilesList Cache of users' profiles. Hash array of user ID => [profile ID => profile friendlyname, profile ID => profile friendlyname, ...]
|
||||
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6887
|
||||
*/
|
||||
private $aUsersProfilesList = [];
|
||||
|
||||
// Installation: create the very first user
|
||||
public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
|
||||
{
|
||||
@@ -434,7 +440,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
// Support drastic data model changes: no organization class (or not writable)!
|
||||
if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization'))
|
||||
{
|
||||
$oOrg = new Organization();
|
||||
$oOrg = MetaModel::NewObject('Organization');
|
||||
$oOrg->Set('name', 'My Company/Department');
|
||||
$oOrg->Set('code', 'SOMECODE');
|
||||
$iOrgId = $oOrg->DBInsertNoReload();
|
||||
@@ -442,17 +448,13 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
// Support drastic data model changes: no Person class (or not writable)!
|
||||
if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person'))
|
||||
{
|
||||
$oContact = new Person();
|
||||
$oContact = MetaModel::NewObject('Person');
|
||||
$oContact->Set('name', 'My last name');
|
||||
$oContact->Set('first_name', 'My first name');
|
||||
if (MetaModel::IsValidAttCode('Person', 'org_id'))
|
||||
{
|
||||
$oContact->Set('org_id', $iOrgId);
|
||||
}
|
||||
if (MetaModel::IsValidAttCode('Person', 'phone'))
|
||||
{
|
||||
$oContact->Set('phone', '+00 000 000 000');
|
||||
}
|
||||
$oContact->Set('email', 'my.email@foo.org');
|
||||
$iContactId = $oContact->DBInsertNoReload();
|
||||
}
|
||||
@@ -525,7 +527,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
$oSearch->AllowAllData();
|
||||
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
|
||||
$oSearch->AddConditionExpression($oCondition);
|
||||
|
||||
|
||||
$oUserOrgSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
|
||||
while ($oUserOrg = $oUserOrgSet->Fetch())
|
||||
{
|
||||
@@ -561,7 +563,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
|
||||
/**
|
||||
* @param $oUser User
|
||||
* @return array
|
||||
* @return bool
|
||||
*/
|
||||
public function IsAdministrator($oUser)
|
||||
{
|
||||
@@ -571,16 +573,22 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
|
||||
/**
|
||||
* @param $oUser User
|
||||
* @return array
|
||||
* @return bool
|
||||
*/
|
||||
public function IsPortalUser($oUser)
|
||||
{
|
||||
// UserRights caches the list for us
|
||||
return UserRights::HasProfile(PORTAL_PROFILE_NAME, $oUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oUser User
|
||||
* @return bool
|
||||
*
|
||||
* @return array
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function ListProfiles($oUser)
|
||||
{
|
||||
@@ -644,14 +652,20 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
// load and cache permissions for the current user on the given class
|
||||
//
|
||||
$iUser = $oUser->GetKey();
|
||||
$aTest = @$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode];
|
||||
if (is_array($aTest)) return $aTest;
|
||||
if (isset($this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode])){
|
||||
$aTest = $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode];
|
||||
if (is_array($aTest)) return $aTest;
|
||||
}
|
||||
|
||||
$sAction = self::$m_aActionCodes[$iActionCode];
|
||||
|
||||
$bStatus = null;
|
||||
// Cache user's profiles
|
||||
if(false === array_key_exists($iUser, $this->aUsersProfilesList)){
|
||||
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
|
||||
}
|
||||
// Call the API of UserRights because it caches the list for us
|
||||
foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
|
||||
foreach($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile)
|
||||
{
|
||||
$bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
|
||||
if (!is_null($bGrant))
|
||||
@@ -777,11 +791,16 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
// Note: this code is VERY close to the code of IsActionAllowed()
|
||||
$iUser = $oUser->GetKey();
|
||||
|
||||
// Cache user's profiles
|
||||
if(false === array_key_exists($iUser, $this->aUsersProfilesList)){
|
||||
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
|
||||
}
|
||||
|
||||
// Note: The object set is ignored because it was interesting to optimize for huge data sets
|
||||
// and acceptable to consider only the root class of the object set
|
||||
$bStatus = null;
|
||||
// Call the API of UserRights because it caches the list for us
|
||||
foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
|
||||
foreach($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile)
|
||||
{
|
||||
$bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
|
||||
if (!is_null($bGrant))
|
||||
@@ -810,9 +829,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
}
|
||||
|
||||
/**
|
||||
* Find out which attribute is corresponding the the dimension 'owner org'
|
||||
* returns null if no such attribute has been found (no filtering should occur)
|
||||
*/
|
||||
* @param string $sClass
|
||||
* @return string|null Find out which attribute is corresponding the dimension 'owner org'
|
||||
* returns null if no such attribute has been found (no filtering should occur)
|
||||
*/
|
||||
public static function GetOwnerOrganizationAttCode($sClass)
|
||||
{
|
||||
$sAttCode = null;
|
||||
|
||||
@@ -535,7 +535,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
// Support drastic data model changes: no organization class (or not writable)!
|
||||
if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization'))
|
||||
{
|
||||
$oOrg = new Organization();
|
||||
$oOrg = MetaModel::NewObject('Organization');
|
||||
$oOrg->Set('name', 'My Company/Department');
|
||||
$oOrg->Set('code', 'SOMECODE');
|
||||
$oOrg::SetCurrentChange($oChange);
|
||||
@@ -544,17 +544,13 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
// Support drastic data model changes: no Person class (or not writable)!
|
||||
if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person'))
|
||||
{
|
||||
$oContact = new Person();
|
||||
$oContact = MetaModel::NewObject('Person');
|
||||
$oContact->Set('name', 'My last name');
|
||||
$oContact->Set('first_name', 'My first name');
|
||||
if (MetaModel::IsValidAttCode('Person', 'org_id'))
|
||||
{
|
||||
$oContact->Set('org_id', $iOrgId);
|
||||
}
|
||||
if (MetaModel::IsValidAttCode('Person', 'phone'))
|
||||
{
|
||||
$oContact->Set('phone', '+00 000 000 000');
|
||||
}
|
||||
$oContact->Set('email', 'my.email@foo.org');
|
||||
$oContact::SetCurrentChange($oChange);
|
||||
$iContactId = $oContact->DBInsertNoReload();
|
||||
@@ -608,10 +604,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
/**
|
||||
* Read and cache organizations allowed to the given user
|
||||
*
|
||||
* @param $oUser
|
||||
* @param $sClass (not used here but can be used in overloads)
|
||||
* @param User $oUser
|
||||
* @param string $sClass (not used here but can be used in overloads)
|
||||
*
|
||||
* @return array
|
||||
* @return array keys of the User allowed org
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -711,7 +707,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
|
||||
public function LoadCache()
|
||||
{
|
||||
if (!is_null($this->m_aProfiles)) return;
|
||||
if (!is_null($this->m_aProfiles)) return false;
|
||||
// Could be loaded in a shared memory (?)
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
|
||||
@@ -24,31 +24,42 @@ class ajax_page extends WebPage implements iTabbedPage
|
||||
/**
|
||||
* Jquery style ready script
|
||||
* @var array
|
||||
*/
|
||||
*/
|
||||
protected $m_sReadyScript;
|
||||
protected $m_oTabs;
|
||||
private $m_sMenu; // If set, then the menu will be updated
|
||||
|
||||
/**
|
||||
* constructor for the web page
|
||||
* @param string $s_title Not used
|
||||
*/
|
||||
function __construct($s_title)
|
||||
{
|
||||
|
||||
/**
|
||||
* constructor for the web page
|
||||
*
|
||||
* @param string $s_title Not used
|
||||
*/
|
||||
function __construct($s_title) {
|
||||
$sPrintable = utils::ReadParam('printable', '0');
|
||||
$bPrintable = ($sPrintable == '1');
|
||||
|
||||
parent::__construct($s_title, $bPrintable);
|
||||
$this->m_sReadyScript = "";
|
||||
parent::__construct($s_title, $bPrintable);
|
||||
$this->m_sReadyScript = "";
|
||||
//$this->add_header("Content-type: text/html; charset=utf-8");
|
||||
$this->add_header("Cache-control: no-cache");
|
||||
$this->no_cache();
|
||||
$this->add_http_headers();
|
||||
$this->m_oTabs = new TabManager();
|
||||
$this->sContentType = 'text/html';
|
||||
$this->sContentDisposition = 'inline';
|
||||
$this->m_sMenu = "";
|
||||
|
||||
utils::InitArchiveMode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabling sending the header so that resource won't be blocked by CORB. See parent method documentation.
|
||||
* @return void
|
||||
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°4368 method creation
|
||||
*/
|
||||
public function add_xcontent_type_options()
|
||||
{
|
||||
// Nothing to do !
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
|
||||
@@ -29,9 +29,9 @@ require_once(APPROOT.'application/newsroomprovider.class.inc.php');
|
||||
* You may implement such interfaces in a module file (e.g. main.mymodule.php)
|
||||
*
|
||||
* @api
|
||||
* @package LoginExtensibilityAPI
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @package Extensibility
|
||||
* @since 2.7.0
|
||||
*/
|
||||
interface iLoginExtension
|
||||
@@ -39,12 +39,16 @@ interface iLoginExtension
|
||||
/**
|
||||
* Return the list of supported login modes for this plugin
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @return array of supported login modes
|
||||
*/
|
||||
public function ListSupportedLoginModes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @package LoginExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
interface iLoginFSMExtension extends iLoginExtension
|
||||
@@ -56,6 +60,7 @@ interface iLoginFSMExtension extends iLoginExtension
|
||||
* if LoginWebPage::LOGIN_FSM_RETURN_OK is returned then the login is OK and terminated
|
||||
* if LoginWebPage::LOGIN_FSM_RETURN_IGNORE is returned then the FSM will proceed to next plugin or state
|
||||
*
|
||||
* @api
|
||||
* @param string $sLoginState (see LoginWebPage::LOGIN_STATE_...)
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
@@ -65,6 +70,8 @@ interface iLoginFSMExtension extends iLoginExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @package LoginExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
@@ -72,15 +79,14 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public abstract function ListSupportedLoginModes();
|
||||
abstract public function ListSupportedLoginModes();
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function LoginAction($sLoginState, &$iErrorCode)
|
||||
{
|
||||
switch ($sLoginState)
|
||||
{
|
||||
switch ($sLoginState) {
|
||||
case LoginWebPage::LOGIN_STATE_START:
|
||||
return $this->OnStart($iErrorCode);
|
||||
|
||||
@@ -112,6 +118,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
/**
|
||||
* Initialization
|
||||
*
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -125,6 +132,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
* Detect login mode explicitly without respecting configured order (legacy mode)
|
||||
* In most case do nothing here
|
||||
*
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -141,6 +149,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
* 1 - display login form
|
||||
* 2 - read the values posted by the user
|
||||
*
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -154,6 +163,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
* Control the validity of the data provided by the user
|
||||
* Automatic user provisioning can be done here
|
||||
*
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -164,6 +174,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -174,6 +185,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -184,6 +196,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -194,6 +207,7 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...)
|
||||
*
|
||||
* @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE
|
||||
@@ -205,22 +219,28 @@ abstract class AbstractLoginFSMExtension implements iLoginFSMExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @package LoginExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
interface iLogoutExtension extends iLoginExtension
|
||||
{
|
||||
/**
|
||||
* Execute all actions to log out properly
|
||||
* @api
|
||||
*/
|
||||
public function LogoutAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
interface iLoginUIExtension extends iLoginExtension
|
||||
{
|
||||
/**
|
||||
* @api
|
||||
* @return LoginTwigContext
|
||||
*/
|
||||
public function GetTwigContext();
|
||||
@@ -228,18 +248,20 @@ interface iLoginUIExtension extends iLoginExtension
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package PreferencesExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
interface iPreferencesExtension
|
||||
{
|
||||
/**
|
||||
* @api
|
||||
* @param \WebPage $oPage
|
||||
*
|
||||
*/
|
||||
public function DisplayPreferences(WebPage $oPage);
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param \WebPage $oPage
|
||||
* @param string $sOperation
|
||||
*
|
||||
@@ -252,7 +274,7 @@ interface iPreferencesExtension
|
||||
* Extend this class instead of implementing iPreferencesExtension if you don't need to overload all methods
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package PreferencesExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
abstract class AbstractPreferencesExtension implements iPreferencesExtension
|
||||
@@ -298,7 +320,7 @@ abstract class AbstractPreferencesExtension implements iPreferencesExtension
|
||||
* A recommended pattern is to cache data by the mean of static members.
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
*/
|
||||
interface iApplicationUIExtension
|
||||
{
|
||||
@@ -320,6 +342,7 @@ interface iApplicationUIExtension
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being displayed
|
||||
* @param WebPage $oPage The output context
|
||||
* @param boolean $bEditMode True if the edition form is being displayed
|
||||
@@ -333,6 +356,7 @@ interface iApplicationUIExtension
|
||||
*
|
||||
* The method is called rigth after all the tabs have been displayed
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being displayed
|
||||
* @param WebPage $oPage The output context
|
||||
* @param boolean $bEditMode True if the edition form is being displayed
|
||||
@@ -347,6 +371,7 @@ interface iApplicationUIExtension
|
||||
* The method is called after the changes from the standard form have been
|
||||
* taken into account, and before saving the changes into the database.
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being edited
|
||||
* @param string $sFormPrefix Prefix given to the HTML form inputs
|
||||
*
|
||||
@@ -361,6 +386,7 @@ interface iApplicationUIExtension
|
||||
* javascript into the edition form, and if that code requires to store temporary data
|
||||
* (this is the case when a file must be uploaded).
|
||||
*
|
||||
* @api
|
||||
* @param string $sTempId Unique temporary identifier made of session_id and transaction_id. It identifies the object in a unique way.
|
||||
*
|
||||
* @return void
|
||||
@@ -372,6 +398,7 @@ interface iApplicationUIExtension
|
||||
*
|
||||
* Sorry, the verb has been reserved. You must implement it, but it is not called as of now.
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being displayed
|
||||
*
|
||||
* @return string[] desc
|
||||
@@ -383,6 +410,7 @@ interface iApplicationUIExtension
|
||||
*
|
||||
* Sorry, the verb has been reserved. You must implement it, but it is not called as of now.
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being displayed
|
||||
*
|
||||
* @return string Path of the icon, relative to the modules directory.
|
||||
@@ -402,6 +430,7 @@ interface iApplicationUIExtension
|
||||
* * HILIGHT_CLASS_OK
|
||||
* * HILIGHT_CLASS_NONE
|
||||
*
|
||||
* @api
|
||||
* @param DBObject $oObject The object being displayed
|
||||
*
|
||||
* @return integer The value representing the mood of the object
|
||||
@@ -428,6 +457,7 @@ interface iApplicationUIExtension
|
||||
*
|
||||
* See also iPopupMenuExtension for greater flexibility
|
||||
*
|
||||
* @api
|
||||
* @param DBObjectSet $oSet A set of persistent objects (DBObject)
|
||||
*
|
||||
* @return string[string]
|
||||
@@ -439,7 +469,7 @@ interface iApplicationUIExtension
|
||||
* Extend this class instead of implementing iApplicationUIExtension if you don't need to overload
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
|
||||
@@ -513,7 +543,7 @@ abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
|
||||
* or through the GUI.
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package ORMExtensibilityAPI
|
||||
*/
|
||||
interface iApplicationObjectExtension
|
||||
{
|
||||
@@ -526,6 +556,7 @@ interface iApplicationObjectExtension
|
||||
* If the extension returns false, then the framework will perform the usual evaluation.
|
||||
* Otherwise, the answer is definitively "yes, the object has changed".
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
*
|
||||
* @return boolean True if something has changed for the target object
|
||||
@@ -538,6 +569,7 @@ interface iApplicationObjectExtension
|
||||
* The GUI calls this verb and reports any issue.
|
||||
* Anyhow, this API can be called in other contexts such as the CSV import tool.
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
*
|
||||
* @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user.
|
||||
@@ -551,6 +583,7 @@ interface iApplicationObjectExtension
|
||||
*
|
||||
* Please not that it is not possible to cascade deletion by this mean: only stopper issues can be handled.
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
*
|
||||
* @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user.
|
||||
@@ -566,6 +599,7 @@ interface iApplicationObjectExtension
|
||||
* * {@see DBObject::ListPreviousValuesForUpdatedAttributes()} : list of changed attributes and their values before the change
|
||||
* * {@see DBObject::Get()} : for a given attribute the new value that was persisted
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
|
||||
* once for all the changes made within the current page
|
||||
@@ -581,6 +615,7 @@ interface iApplicationObjectExtension
|
||||
*
|
||||
* The method is called right <b>after</b> the object has been written to the database.
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
|
||||
* once for all the changes made within the current page
|
||||
@@ -594,6 +629,7 @@ interface iApplicationObjectExtension
|
||||
*
|
||||
* The method is called right <b>before</b> the object will be deleted from the database.
|
||||
*
|
||||
* @api
|
||||
* @param \cmdbAbstractObject $oObject The target object
|
||||
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
|
||||
* once for all the changes made within the current page
|
||||
@@ -607,7 +643,7 @@ interface iApplicationObjectExtension
|
||||
* Extend this class instead of iApplicationObjectExtension if you don't need to overload all methods
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package ORMExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
abstract class AbstractApplicationObjectExtension implements iApplicationObjectExtension
|
||||
@@ -667,7 +703,7 @@ abstract class AbstractApplicationObjectExtension implements iApplicationObjectE
|
||||
* by the application, as long as the class definition is included somewhere in the code
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
interface iPopupMenuExtension
|
||||
@@ -676,18 +712,21 @@ interface iPopupMenuExtension
|
||||
* Insert an item into the Actions menu of a list
|
||||
*
|
||||
* $param is a DBObjectSet containing the list of objects
|
||||
* @api
|
||||
*/
|
||||
const MENU_OBJLIST_ACTIONS = 1;
|
||||
/**
|
||||
* Insert an item into the Toolkit menu of a list
|
||||
*
|
||||
* $param is a DBObjectSet containing the list of objects
|
||||
* @api
|
||||
*/
|
||||
const MENU_OBJLIST_TOOLKIT = 2;
|
||||
/**
|
||||
* Insert an item into the Actions menu on an object details page
|
||||
*
|
||||
* $param is a DBObject instance: the object currently displayed
|
||||
* @api
|
||||
*/
|
||||
const MENU_OBJDETAILS_ACTIONS = 3;
|
||||
/**
|
||||
@@ -697,12 +736,14 @@ interface iPopupMenuExtension
|
||||
* is being displayed.
|
||||
*
|
||||
* $param is a Dashboard instance: the dashboard currently displayed
|
||||
* @api
|
||||
*/
|
||||
const MENU_DASHBOARD_ACTIONS = 4;
|
||||
/**
|
||||
* Insert an item into the User menu (upper right corner)
|
||||
*
|
||||
* $param is null
|
||||
* @api
|
||||
*/
|
||||
const MENU_USER_ACTIONS = 5;
|
||||
/**
|
||||
@@ -710,6 +751,7 @@ interface iPopupMenuExtension
|
||||
*
|
||||
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object on
|
||||
* the current line)
|
||||
* @api
|
||||
*/
|
||||
const PORTAL_OBJLISTITEM_ACTIONS = 7;
|
||||
/**
|
||||
@@ -717,6 +759,7 @@ interface iPopupMenuExtension
|
||||
*
|
||||
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object
|
||||
* currently displayed)
|
||||
* @api
|
||||
*/
|
||||
const PORTAL_OBJDETAILS_ACTIONS = 8;
|
||||
|
||||
@@ -754,6 +797,7 @@ interface iPopupMenuExtension
|
||||
* This method is called by the framework for each menu.
|
||||
* The items will be inserted in the menu in the order of the returned array.
|
||||
*
|
||||
* @api
|
||||
* @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx
|
||||
* @param mixed $param Depends on $iMenuId, see the constants defined above
|
||||
*
|
||||
@@ -766,7 +810,7 @@ interface iPopupMenuExtension
|
||||
* Base class for the various types of custom menus
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
abstract class ApplicationPopupMenuItem
|
||||
@@ -827,6 +871,7 @@ abstract class ApplicationPopupMenuItem
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @param $aCssClasses
|
||||
*/
|
||||
public function SetCssClasses($aCssClasses)
|
||||
@@ -837,6 +882,7 @@ abstract class ApplicationPopupMenuItem
|
||||
/**
|
||||
* Adds a CSS class to the CSS classes that will be put on the menu item
|
||||
*
|
||||
* @api
|
||||
* @param $sCssClass
|
||||
*/
|
||||
public function AddCssClass($sCssClass)
|
||||
@@ -863,7 +909,7 @@ abstract class ApplicationPopupMenuItem
|
||||
* Class for adding an item into a popup menu that browses to the given URL
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
@@ -876,6 +922,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @api
|
||||
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
|
||||
* @param string $sLabel The display label of the menu (must be localized)
|
||||
* @param string $sURL If the menu is an hyperlink, provide the absolute hyperlink here
|
||||
@@ -899,7 +946,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
* Class for adding an item into a popup menu that triggers some Javascript code
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
class JSPopupMenuItem extends ApplicationPopupMenuItem
|
||||
@@ -951,7 +998,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
|
||||
* will automatically reduce several consecutive separators to just one
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
|
||||
@@ -960,6 +1007,7 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @api
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -977,7 +1025,7 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
|
||||
* Class for adding an item as a button that browses to the given URL
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
class URLButtonItem extends URLPopupMenuItem
|
||||
@@ -989,7 +1037,7 @@ class URLButtonItem extends URLPopupMenuItem
|
||||
* Class for adding an item as a button that runs some JS code
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
class JSButtonItem extends JSPopupMenuItem
|
||||
@@ -1013,7 +1061,7 @@ class JSButtonItem extends JSPopupMenuItem
|
||||
* the specified place and can use the passed iTopWebPage object to add javascript or CSS definitions
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.0
|
||||
*/
|
||||
interface iPageUIExtension
|
||||
@@ -1021,6 +1069,7 @@ interface iPageUIExtension
|
||||
/**
|
||||
* Add content to the North pane
|
||||
*
|
||||
* @api
|
||||
* @param iTopWebPage $oPage The page to insert stuff into.
|
||||
*
|
||||
* @return string The HTML content to add into the page
|
||||
@@ -1030,6 +1079,7 @@ interface iPageUIExtension
|
||||
/**
|
||||
* Add content to the South pane
|
||||
*
|
||||
* @api
|
||||
* @param iTopWebPage $oPage The page to insert stuff into.
|
||||
*
|
||||
* @return string The HTML content to add into the page
|
||||
@@ -1039,6 +1089,7 @@ interface iPageUIExtension
|
||||
/**
|
||||
* Add content to the "admin banner"
|
||||
*
|
||||
* @api
|
||||
* @param iTopWebPage $oPage The page to insert stuff into.
|
||||
*
|
||||
* @return string The HTML content to add into the page
|
||||
@@ -1050,7 +1101,7 @@ interface iPageUIExtension
|
||||
* Extend this class instead of iPageUIExtension if you don't need to overload all methods
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package UIExtensibilityAPI
|
||||
* @since 2.7.0
|
||||
*/
|
||||
abstract class AbstractPageUIExtension implements iPageUIExtension
|
||||
@@ -1084,11 +1135,11 @@ abstract class AbstractPageUIExtension implements iPageUIExtension
|
||||
/**
|
||||
* Implement this interface to add content to any enhanced portal page
|
||||
*
|
||||
* IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now!
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @since 2.4.0
|
||||
* @package PortalExtensibilityAPI
|
||||
*
|
||||
* @since 2.4.0 interface creation
|
||||
* @since 2.7.0 change method signatures due to Silex to Symfony migration
|
||||
*/
|
||||
interface iPortalUIExtension
|
||||
{
|
||||
@@ -1099,6 +1150,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns an array of CSS file urls
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return array
|
||||
@@ -1108,6 +1160,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns inline (raw) CSS
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return string
|
||||
@@ -1117,6 +1170,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns an array of JS file urls
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return array
|
||||
@@ -1126,6 +1180,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns raw JS code
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return string
|
||||
@@ -1135,6 +1190,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the <body> tag
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return string
|
||||
@@ -1144,6 +1200,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the #main-wrapper element
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return string
|
||||
@@ -1153,6 +1210,7 @@ interface iPortalUIExtension
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the #topbar and #sidebar elements
|
||||
*
|
||||
* @api
|
||||
* @param \Symfony\Component\DependencyInjection\Container $oContainer
|
||||
*
|
||||
* @return string
|
||||
@@ -1161,7 +1219,11 @@ interface iPortalUIExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now!
|
||||
* Extend this class instead of iPortalUIExtension if you don't need to overload all methods
|
||||
*
|
||||
* @api
|
||||
* @package PortalExtensibilityAPI
|
||||
* @since 2.4.0
|
||||
*/
|
||||
abstract class AbstractPortalUIExtension implements iPortalUIExtension
|
||||
{
|
||||
@@ -1226,7 +1288,7 @@ abstract class AbstractPortalUIExtension implements iPortalUIExtension
|
||||
* Implement this interface to add new operations to the REST/JSON web service
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package RESTExtensibilityAPI
|
||||
* @since 2.0.1
|
||||
*/
|
||||
interface iRestServiceProvider
|
||||
@@ -1234,6 +1296,7 @@ interface iRestServiceProvider
|
||||
/**
|
||||
* Enumerate services delivered by this class
|
||||
*
|
||||
* @api
|
||||
* @param string $sVersion The version (e.g. 1.0) supported by the services
|
||||
*
|
||||
* @return array An array of hash 'verb' => verb, 'description' => description
|
||||
@@ -1243,6 +1306,7 @@ interface iRestServiceProvider
|
||||
/**
|
||||
* Enumerate services delivered by this class
|
||||
*
|
||||
* @api
|
||||
* @param string $sVersion The version (e.g. 1.0) supported by the services
|
||||
* @param string $sVerb
|
||||
* @param array $aParams
|
||||
@@ -1252,81 +1316,115 @@ interface iRestServiceProvider
|
||||
public function ExecOperation($sVersion, $sVerb, $aParams);
|
||||
}
|
||||
|
||||
interface iRestInputSanitizer
|
||||
{
|
||||
public function SanitizeJsonInput(string $sJsonInput): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal REST response structure. Derive this structure to add response data and error codes.
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package RESTExtensibilityAPI
|
||||
* @since 2.0.1
|
||||
*/
|
||||
class RestResult
|
||||
{
|
||||
/**
|
||||
* Result: no issue has been encountered
|
||||
* @api
|
||||
*/
|
||||
const OK = 0;
|
||||
/**
|
||||
* Result: missing/wrong credentials or the user does not have enough rights to perform the requested operation
|
||||
* @api
|
||||
*/
|
||||
const UNAUTHORIZED = 1;
|
||||
/**
|
||||
* Result: the parameter 'version' is missing
|
||||
* @api
|
||||
*/
|
||||
const MISSING_VERSION = 2;
|
||||
/**
|
||||
* Result: the parameter 'json_data' is missing
|
||||
* @api
|
||||
*/
|
||||
const MISSING_JSON = 3;
|
||||
/**
|
||||
* Result: the input structure is not a valid JSON string
|
||||
* @api
|
||||
*/
|
||||
const INVALID_JSON = 4;
|
||||
/**
|
||||
* Result: the parameter 'auth_user' is missing, authentication aborted
|
||||
* @api
|
||||
*/
|
||||
const MISSING_AUTH_USER = 5;
|
||||
/**
|
||||
* Result: the parameter 'auth_pwd' is missing, authentication aborted
|
||||
* @api
|
||||
*/
|
||||
const MISSING_AUTH_PWD = 6;
|
||||
/**
|
||||
* Result: no operation is available for the specified version
|
||||
* @api
|
||||
*/
|
||||
const UNSUPPORTED_VERSION = 10;
|
||||
/**
|
||||
* Result: the requested operation is not valid for the specified version
|
||||
* @api
|
||||
*/
|
||||
const UNKNOWN_OPERATION = 11;
|
||||
/**
|
||||
* Result: the requested operation cannot be performed because it can cause data (integrity) loss
|
||||
* @api
|
||||
*/
|
||||
const UNSAFE = 12;
|
||||
/**
|
||||
* Result: the request page number is not valid. It must be an integer greater than 0
|
||||
* @api
|
||||
*/
|
||||
const INVALID_PAGE = 13;
|
||||
/**
|
||||
* Result: the operation could not be performed, see the message for troubleshooting
|
||||
* @api
|
||||
*/
|
||||
const INTERNAL_ERROR = 100;
|
||||
|
||||
/**
|
||||
* Default constructor - ok!
|
||||
* @api
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->code = RestResult::OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @api
|
||||
*/
|
||||
public $code;
|
||||
/**
|
||||
* @var string
|
||||
* @api
|
||||
*/
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* Sanitize the content of this result to hide sensitive information
|
||||
*/
|
||||
public function SanitizeContent()
|
||||
{
|
||||
// The default implementation does nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helpers for implementing REST services
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @package RESTExtensibilityAPI
|
||||
*/
|
||||
class RestUtils
|
||||
{
|
||||
@@ -1475,6 +1573,7 @@ class RestUtils
|
||||
/**
|
||||
* Read and interpret object search criteria from a Rest/Json structure
|
||||
*
|
||||
* @api
|
||||
* @param string $sClass Name of the class
|
||||
* @param StdClass $oCriteria Hash of attribute code => value (can be a substructure or a scalar, depending on the nature of the
|
||||
* attriute)
|
||||
@@ -1535,6 +1634,8 @@ class RestUtils
|
||||
*
|
||||
* @return DBObject The object found
|
||||
* @throws Exception If the input structure is not valid or it could not find exactly one object
|
||||
*
|
||||
* @see DBObject::CheckChangedExtKeysValues() generic method to check that we can access the linked object isn't used in that use case because values can be literal, OQL, friendlyname
|
||||
*/
|
||||
public static function FindObjectFromKey($sClass, $key, $bAllowNullValue = false)
|
||||
{
|
||||
@@ -1584,6 +1685,7 @@ class RestUtils
|
||||
/**
|
||||
* Search objects from a polymorph search specification (Rest/Json)
|
||||
*
|
||||
* @api
|
||||
* @param string $sClass Name of the class
|
||||
* @param mixed $key Either search criteria (substructure), or an object or an OQL string.
|
||||
* @param int $iLimit The limit of results to return
|
||||
@@ -1620,8 +1722,16 @@ class RestUtils
|
||||
elseif (is_string($key))
|
||||
{
|
||||
// OQL
|
||||
$oSearch = DBObjectSearch::FromOQL($key);
|
||||
}
|
||||
try {
|
||||
$oSearch = DBObjectSearch::FromOQL($key);
|
||||
} catch (Exception $e) {
|
||||
throw new CoreOqlException('Query failed to execute', [
|
||||
'query' => $key,
|
||||
'exception_class' => get_class($e),
|
||||
'exception_message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Wrong format for key");
|
||||
@@ -1770,4 +1880,28 @@ class RestUtils
|
||||
interface iModuleExtension
|
||||
{
|
||||
public function __construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* KPI logging extensibility point
|
||||
*
|
||||
* KPI Logger extension
|
||||
*/
|
||||
interface iKPILoggerExtension
|
||||
{
|
||||
/**
|
||||
* Init the statistics collected
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function InitStats();
|
||||
|
||||
/**
|
||||
* Add a new KPI to the stats
|
||||
*
|
||||
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKPILogData
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function LogOperation($oKPILogData);
|
||||
}
|
||||
@@ -239,8 +239,11 @@ EOF
|
||||
foreach($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData)
|
||||
{
|
||||
$sMsgClass = 'message_'.$aMessageData['severity'];
|
||||
$aMessages[] = "<div class=\"header_message $sMsgClass\">".$aMessageData['message']."</div>";
|
||||
$aRanks[] = $aMessageData['rank'];
|
||||
if(!in_array("<div class=\"header_message $sMsgClass\">".$aMessageData['message']."</div>",$aMessages))
|
||||
{
|
||||
$aMessages[] = "<div class=\"header_message $sMsgClass\">".$aMessageData['message']."</div>";
|
||||
$aRanks[] = $aMessageData['rank'];
|
||||
}
|
||||
}
|
||||
unset($_SESSION['obj_messages'][$sMessageKey]);
|
||||
}
|
||||
@@ -538,7 +541,7 @@ EOF
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$oPage->AddAjaxTab($oAttDef->GetLabel(), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=dashboard&class='.get_class($this).'&id='.$this->GetKey().'&attcode='.$oAttDef->GetCode(), true, 'Class:'.$sClass.'/Attribute:'.$sAttCode);
|
||||
$oPage->AddAjaxTab( 'Class:'.$sClass.'/Attribute:'.$sAttCode, utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=dashboard&class='.get_class($this).'&id='.$this->GetKey().'&attcode='.$oAttDef->GetCode(), true, $oAttDef->GetLabel());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -555,12 +558,6 @@ EOF
|
||||
$oLinkSet = $oOrmLinkSet->ToDBObjectSet(utils::ShowObsoleteData());
|
||||
|
||||
$iCount = $oLinkSet->Count();
|
||||
$sCount = '';
|
||||
if ($iCount != 0)
|
||||
{
|
||||
$sCount = " ($iCount)";
|
||||
}
|
||||
$oPage->SetCurrentTab('Class:'.$sClass.'/Attribute:'.$sAttCode, $oAttDef->GetLabel().$sCount);
|
||||
if ($this->IsNew())
|
||||
{
|
||||
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
|
||||
@@ -606,6 +603,9 @@ EOF
|
||||
continue;
|
||||
}
|
||||
|
||||
$sCount = ($iCount != 0) ? " ($iCount)" : "";
|
||||
$oPage->SetCurrentTab('Class:'.$sClass.'/Attribute:'.$sAttCode, $oAttDef->GetLabel().$sCount);
|
||||
|
||||
$aArgs = array('this' => $this);
|
||||
$bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE));
|
||||
if ($bEditMode && (!$bReadOnly))
|
||||
@@ -1557,6 +1557,9 @@ HTML
|
||||
* @param array $aParams
|
||||
*
|
||||
* @throws \Exception
|
||||
* only used in old and deprecated export.php
|
||||
*
|
||||
* @internal Only to be used by `/webservices/export.php` : this is a legacy method that produces wrong HTML (no TR on table body rows)
|
||||
*/
|
||||
public static function DisplaySetAsHTMLSpreadsheet(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array())
|
||||
{
|
||||
@@ -1577,6 +1580,8 @@ HTML
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \Exception
|
||||
*
|
||||
* @internal Only to be used by `/webservices/export.php` : this is a legacy method that produces wrong HTML (no TR on table body rows)
|
||||
*/
|
||||
public static function GetSetAsHTMLSpreadsheet(DBObjectSet $oSet, $aParams = array())
|
||||
{
|
||||
@@ -2148,20 +2153,30 @@ EOF
|
||||
$iMaxFileSize = utils::ConvertToBytes(ini_get('upload_max_filesize'));
|
||||
$sHTMLValue = "<div class=\"field_input_zone field_input_document\">\n";
|
||||
$sHTMLValue .= "<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"$iMaxFileSize\" />\n";
|
||||
$sHTMLValue .= "<input type=\"hidden\" id=\"do_remove_{$iId}\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}[remove]\" value=\"0\"/>\n";
|
||||
|
||||
$sHTMLValue .= "<input name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}[filename]\" type=\"hidden\" id=\"$iId\" \" value=\"".htmlentities($sFileName,
|
||||
ENT_QUOTES, 'UTF-8')."\"/>\n";
|
||||
$sHTMLValue .= "<span id=\"name_$iInputId\"'>".htmlentities($sFileName, ENT_QUOTES,
|
||||
'UTF-8')."</span><br/>\n";
|
||||
$sHTMLValue .= "<span id=\"name_$iInputId\"' >".htmlentities($sFileName, ENT_QUOTES,
|
||||
'UTF-8')."</span>  ";
|
||||
$sHTMLValue .= "<div title=\"".htmlentities(Dict::S('UI:Button:RemoveDocument'), ENT_QUOTES, 'UTF-8'). "\" id=\"remove_attr_$iId\" class=\"button\" onClick=\"$('#file_$iId').val('');UpdateFileName('$iId', '');\" style=\"display: contents;\">";
|
||||
$sHTMLValue .= "<div class=\"ui-icon ui-icon-trash\"></div></div>";
|
||||
$sHTMLValue .= "</div>";
|
||||
$sHTMLValue .= "<br/>\n";
|
||||
$sHTMLValue .= "<input title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}[fcontents]\" type=\"file\" id=\"file_$iId\" onChange=\"UpdateFileName('$iId', this.value)\"/>\n";
|
||||
$sHTMLValue .= "</div>\n";
|
||||
$sHTMLValue .= "{$sValidationSpan}{$sReloadSpan}\n";
|
||||
if ($sFileName == '')
|
||||
{
|
||||
$oPage->add_ready_script("$('#remove_attr_{$iId}').hide();");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Image':
|
||||
$aEventsList[] = 'validate';
|
||||
$aEventsList[] = 'change';
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/edit_image.js');
|
||||
$oDocument = $value; // Value is an ormDocument object
|
||||
$oDocument = $value; // Value is an ormDocument objectm
|
||||
$sDefaultUrl = $oAttDef->Get('default_image');
|
||||
if (is_object($oDocument) && !$oDocument->IsEmpty())
|
||||
{
|
||||
@@ -3002,10 +3017,38 @@ HTML
|
||||
$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef,
|
||||
$this->Get($sAttCode), $this->GetEditValue($sAttCode), 'att_'.$iFieldIndex, '', $iExpectCode,
|
||||
$aArgs);
|
||||
$aDetails[] = array(
|
||||
$aAttrib = array(
|
||||
'label' => '<span>'.$oAttDef->GetLabel().'</span>',
|
||||
'value' => "<span id=\"field_att_$iFieldIndex\">$sHTMLValue</span>",
|
||||
);
|
||||
|
||||
//add attrib for data-attribute
|
||||
// Prepare metadata attributes
|
||||
$sAttCode = $oAttDef->GetCode();
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$sAttDefClass = get_class($oAttDef);
|
||||
$sAttLabel = MetaModel::GetLabel($sClass, $sAttCode);
|
||||
|
||||
$aAttrib['attcode'] = $sAttCode;
|
||||
$aAttrib['atttype'] = $sAttDefClass;
|
||||
$aAttrib['attlabel'] = $sAttLabel;
|
||||
// - Attribute flags
|
||||
$aAttrib['attflags'] = $this->GetFormAttributeFlags($sAttCode) ;
|
||||
// - How the field should be rendered
|
||||
$aAttrib['layout'] = (in_array($oAttDef->GetEditClass(), static::GetAttEditClassesToRenderAsLargeField())) ? 'large' : 'small';
|
||||
// - For simple fields, we get the raw (stored) value as well
|
||||
$bExcludeRawValue = false;
|
||||
foreach (static::GetAttDefClassesToExcludeFromMarkupMetadataRawValue() as $sAttDefClassToExclude)
|
||||
{
|
||||
if (is_a($sAttDefClass, $sAttDefClassToExclude, true))
|
||||
{
|
||||
$bExcludeRawValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$aAttrib['value_raw'] = ($bExcludeRawValue === false) ? $this->Get($sAttCode) : '';
|
||||
|
||||
$aDetails[] = $aAttrib;
|
||||
$aFieldsMap[$sAttCode] = 'att_'.$iFieldIndex;
|
||||
$iFieldIndex++;
|
||||
}
|
||||
@@ -3175,11 +3218,18 @@ EOF
|
||||
if ($oAttDef->GetEditClass() == 'Document')
|
||||
{
|
||||
$oDocument = $this->Get($sAttCode);
|
||||
$sDisplayValue = $this->GetAsHTML($sAttCode);
|
||||
$sDisplayValue .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_',
|
||||
$oDocument->GetDisplayLink(get_class($this), $this->GetKey(), $sAttCode)).", \n";
|
||||
$sDisplayValue .= "<br/>".Dict::Format('UI:DownloadDocument_',
|
||||
$oDocument->GetDownloadLink(get_class($this), $this->GetKey(), $sAttCode)).", \n";
|
||||
if (is_object($oDocument) && !$oDocument->IsEmpty())
|
||||
{
|
||||
$sDisplayValue = $this->GetAsHTML($sAttCode);
|
||||
$sDisplayValue .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_',
|
||||
$oDocument->GetDisplayLink(get_class($this), $this->GetKey(), $sAttCode)).", \n";
|
||||
$sDisplayValue .= "<br/>".Dict::Format('UI:DownloadDocument_',
|
||||
$oDocument->GetDownloadLink(get_class($this), $this->GetKey(), $sAttCode)).", \n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDisplayValue ='';
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeDashboard)
|
||||
{
|
||||
@@ -3211,16 +3261,15 @@ EOF
|
||||
*/
|
||||
public function DisplayDocumentInline(WebPage $oPage, $sAttCode)
|
||||
{
|
||||
/** @var \ormDocument $oDoc */
|
||||
$oDoc = $this->Get($sAttCode);
|
||||
$sClass = get_class($this);
|
||||
$Id = $this->GetKey();
|
||||
switch ($oDoc->GetMainMimeType())
|
||||
{
|
||||
switch ($oDoc->GetMainMimeType()) {
|
||||
case 'text':
|
||||
case 'html':
|
||||
$data = $oDoc->GetData();
|
||||
switch ($oDoc->GetMimeType())
|
||||
{
|
||||
switch ($oDoc->GetMimeType()) {
|
||||
case 'text/xml':
|
||||
$oPage->add("<iframe id='preview_$sAttCode' src=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" width=\"100%\" height=\"400\">Loading...</iframe>\n");
|
||||
break;
|
||||
@@ -3518,14 +3567,6 @@ EOF
|
||||
switch ($oAttDef->GetEditClass())
|
||||
{
|
||||
case 'Document':
|
||||
// There should be an uploaded file with the named attr_<attCode>
|
||||
$oDocument = $value['fcontents'];
|
||||
if (!$oDocument->IsEmpty())
|
||||
{
|
||||
// A new file has been uploaded
|
||||
$this->Set($sAttCode, $oDocument);
|
||||
}
|
||||
break;
|
||||
case 'Image':
|
||||
// There should be an uploaded file with the named attr_<attCode>
|
||||
if ($value['remove'])
|
||||
@@ -3751,23 +3792,29 @@ EOF
|
||||
switch ($oAttDef->GetEditClass())
|
||||
{
|
||||
case 'Document':
|
||||
$value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents'));
|
||||
$aOtherData = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
|
||||
$value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents'), 'remove' => $aOtherData['remove']);
|
||||
break;
|
||||
|
||||
case 'Image':
|
||||
$value = null;
|
||||
$oImage = utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents');
|
||||
$aSize = utils::GetImageSize($oImage->GetData());
|
||||
$oImage = utils::ResizeImageToFit($oImage, $aSize[0], $aSize[1], $oAttDef->Get('storage_max_width'),
|
||||
$oAttDef->Get('storage_max_height'));
|
||||
if (!is_null($oImage->GetData()))
|
||||
{
|
||||
$aSize = utils::GetImageSize($oImage->GetData());
|
||||
$oImage = utils::ResizeImageToFit(
|
||||
$oImage,
|
||||
$aSize[0],
|
||||
$aSize[1],
|
||||
$oAttDef->Get('storage_max_width'),
|
||||
$oAttDef->Get('storage_max_height')
|
||||
);
|
||||
}
|
||||
$aOtherData = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
|
||||
if (is_array($aOtherData))
|
||||
{
|
||||
$value = array('fcontents' => $oImage, 'remove' => $aOtherData['remove']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'RedundancySetting':
|
||||
@@ -3956,7 +4003,9 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
|
||||
}
|
||||
|
||||
return $res;
|
||||
@@ -3973,13 +4022,16 @@ EOF
|
||||
|
||||
protected function DBCloneTracked_Internal($newKey = null)
|
||||
{
|
||||
$oNewObj = parent::DBCloneTracked_Internal($newKey);
|
||||
/** @var cmdbAbstractObject $oNewObj */
|
||||
$oNewObj = MetaModel::GetObject(get_class($this), parent::DBCloneTracked_Internal($newKey));
|
||||
|
||||
// Invoke extensions after insertion (the object must exist, have an id, etc.)
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
|
||||
}
|
||||
|
||||
return $oNewObj;
|
||||
@@ -4007,7 +4059,9 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBUpdate');
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
@@ -4053,7 +4107,9 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
|
||||
}
|
||||
|
||||
return parent::DBDeleteTracked_Internal($oDeletionPlan);
|
||||
@@ -4071,7 +4127,10 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
if ($oExtensionInstance->OnIsModified($this))
|
||||
$oKPI = new ExecutionKPI();
|
||||
$bIsModified = $oExtensionInstance->OnIsModified($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
|
||||
if ($bIsModified)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -4115,7 +4174,9 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
|
||||
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
|
||||
{
|
||||
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
|
||||
@@ -4163,7 +4224,9 @@ EOF
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
|
||||
if (is_array($aNewIssues) && count($aNewIssues) > 0)
|
||||
{
|
||||
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
|
||||
@@ -4675,7 +4738,7 @@ EOF
|
||||
$bResult = (count($aErrors) == 0);
|
||||
if ($bResult)
|
||||
{
|
||||
list($bResult, $aErrors) = $oObj->CheckToWrite();
|
||||
[$bResult, $aErrors] = $oObj->CheckToWrite();
|
||||
}
|
||||
if ($bPreview)
|
||||
{
|
||||
@@ -4688,15 +4751,24 @@ EOF
|
||||
$sCSSClass = $bResult ? HILIGHT_CLASS_NONE : HILIGHT_CLASS_CRITICAL;
|
||||
$sChecked = $bResult ? 'checked' : '';
|
||||
$sDisabled = $bResult ? '' : 'disabled';
|
||||
|
||||
$aErrorsToDisplay = array_map(function($sError) {
|
||||
return utils::HtmlEntities($sError);
|
||||
}, $aErrors);
|
||||
$aRows[] = array(
|
||||
'form::select' => "<input type=\"checkbox\" class=\"selectList\" $sChecked $sDisabled\"></input>",
|
||||
'object' => $oObj->GetHyperlink(),
|
||||
'status' => $sStatus,
|
||||
'errors' => '<p>'.($bResult ? '' : implode('</p><p>', $aErrors)).'</p>',
|
||||
'errors' => '<p>'.($bResult ? '' : implode('</p><p>', $aErrorsToDisplay)).'</p>',
|
||||
'@class' => $sCSSClass,
|
||||
);
|
||||
if ($bResult && (!$bPreview))
|
||||
{
|
||||
// doing the check will load multiple times same objects :/
|
||||
// but it shouldn't cost too much on execution time
|
||||
// user can mitigate by selecting less extkeys/lnk to set and/or less objects to update 🤷♂️
|
||||
$oObj->CheckChangedExtKeysValues();
|
||||
|
||||
$oObj->DBUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,13 @@ require_once(APPROOT."/application/webpage.class.inc.php");
|
||||
|
||||
class CSVPage extends WebPage
|
||||
{
|
||||
function __construct($s_title)
|
||||
{
|
||||
parent::__construct($s_title);
|
||||
$this->add_header("Content-type: text/plain; charset=".self::PAGES_CHARSET);
|
||||
$this->add_header("Cache-control: no-cache");
|
||||
function __construct($s_title) {
|
||||
parent::__construct($s_title);
|
||||
$this->add_header("Content-type: text/plain; charset=".self::PAGES_CHARSET);
|
||||
$this->no_cache();
|
||||
$this->add_http_headers();
|
||||
//$this->add_header("Content-Transfer-Encoding: binary");
|
||||
}
|
||||
}
|
||||
|
||||
public function output()
|
||||
{
|
||||
|
||||
@@ -852,28 +852,29 @@ class RuntimeDashboard extends Dashboard
|
||||
{
|
||||
$bCustomized = false;
|
||||
|
||||
if (!appUserPreferences::GetPref('display_original_dashboard_'.$sDashBoardId, false))
|
||||
{
|
||||
$sDashboardFileSanitized = utils::RealPath($sDashboardFile, APPROOT);
|
||||
if (false === $sDashboardFileSanitized) {
|
||||
throw new SecurityException('Invalid dashboard file !');
|
||||
}
|
||||
|
||||
if (!appUserPreferences::GetPref('display_original_dashboard_'.$sDashBoardId, false)) {
|
||||
// Search for an eventual user defined dashboard
|
||||
$oUDSearch = new DBObjectSearch('UserDashboard');
|
||||
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
|
||||
$oUDSearch->AddCondition('menu_code', $sDashBoardId, '=');
|
||||
$oUDSet = new DBObjectSet($oUDSearch);
|
||||
if ($oUDSet->Count() > 0)
|
||||
{
|
||||
if ($oUDSet->Count() > 0) {
|
||||
// Assuming there is at most one couple {user, menu}!
|
||||
$oUserDashboard = $oUDSet->Fetch();
|
||||
$sDashboardDefinition = $oUserDashboard->Get('contents');
|
||||
$bCustomized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDashboardDefinition = @file_get_contents($sDashboardFile);
|
||||
} else {
|
||||
$sDashboardDefinition = @file_get_contents($sDashboardFileSanitized);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDashboardDefinition = @file_get_contents($sDashboardFile);
|
||||
$sDashboardDefinition = @file_get_contents($sDashboardFileSanitized);
|
||||
}
|
||||
|
||||
if ($sDashboardDefinition !== false)
|
||||
@@ -881,7 +882,7 @@ class RuntimeDashboard extends Dashboard
|
||||
$oDashboard = new RuntimeDashboard($sDashBoardId);
|
||||
$oDashboard->FromXml($sDashboardDefinition);
|
||||
$oDashboard->SetCustomFlag($bCustomized);
|
||||
$oDashboard->SetDefinitionFile($sDashboardFile);
|
||||
$oDashboard->SetDefinitionFile($sDashboardFileSanitized);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1192,12 +1193,12 @@ EOF
|
||||
$sOkButtonLabel = Dict::S('UI:Button:Save');
|
||||
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
|
||||
|
||||
$sId = addslashes($this->sId);
|
||||
$sLayoutClass = addslashes($this->sLayoutClass);
|
||||
$sId = json_encode($this->sId);
|
||||
$sLayoutClass = json_encode($this->sLayoutClass);
|
||||
$sAutoReload = $this->bAutoReload ? 'true' : 'false';
|
||||
$sAutoReloadSec = (string) $this->iAutoReloadSec;
|
||||
$sTitle = addslashes($this->sTitle);
|
||||
$sFile = addslashes($this->GetDefinitionFile());
|
||||
$sTitle = json_encode($this->sTitle);
|
||||
$sFile = json_encode($this->GetDefinitionFile());
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
|
||||
$sReloadURL = $this->GetReloadURL();
|
||||
|
||||
@@ -1249,15 +1250,15 @@ $('#dashboard_editor').dialog({
|
||||
});
|
||||
|
||||
$('#dashboard_editor .ui-layout-center').runtimedashboard({
|
||||
dashboard_id: '$sId',
|
||||
layout_class: '$sLayoutClass',
|
||||
title: '$sTitle',
|
||||
dashboard_id: $sId,
|
||||
layout_class: $sLayoutClass,
|
||||
title: $sTitle,
|
||||
auto_reload: $sAutoReload,
|
||||
auto_reload_sec: $sAutoReloadSec,
|
||||
submit_to: '$sUrl',
|
||||
submit_parameters: {operation: 'save_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
submit_parameters: {operation: 'save_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
render_to: '$sUrl',
|
||||
render_parameters: {operation: 'render_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
render_parameters: {operation: 'render_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
new_dashlet_parameters: {operation: 'new_dashlet'}
|
||||
});
|
||||
|
||||
@@ -1477,6 +1478,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
|
||||
*/
|
||||
@@ -1574,9 +1598,16 @@ JS
|
||||
private function GetDashletObjectListAppUserPreferencesPrefix(DashletObjectList $oDashlet, $aExtraParams, $sDashletId)
|
||||
{
|
||||
$sDataTableId = Dashlet::APPUSERPREFERENCES_PREFIX.$sDashletId;
|
||||
$oFilter = $oDashlet->GetDBSearch($aExtraParams);
|
||||
$aClassAliases = $oFilter->GetSelectedClasses();
|
||||
|
||||
$aClassAliases = array();
|
||||
try{
|
||||
$oFilter = $oDashlet->GetDBSearch($aExtraParams);
|
||||
$aClassAliases = $oFilter->GetSelectedClasses();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
//on error, return default value
|
||||
return null;
|
||||
}
|
||||
return DataTableSettings::GetAppUserPreferenceKey($aClassAliases, $sDataTableId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ abstract class Dashlet
|
||||
catch(OqlException $e)
|
||||
{
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
$oPage->p($e->GetUserFriendlyDescription());
|
||||
$oPage->p(utils::HtmlEntities($e->GetUserFriendlyDescription()));
|
||||
$oPage->add('</div>');
|
||||
}
|
||||
catch(Exception $e)
|
||||
@@ -459,17 +459,21 @@ EOF
|
||||
$sAttType = $aTargetAttCodes[$sTargetAttCode];
|
||||
$sExtFieldAttCode = $sTargetAttCode;
|
||||
}
|
||||
if (is_a($sAttType, 'AttributeLinkedSet', true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (is_a($sAttType, 'AttributeFriendlyName', true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (is_a($sAttType, 'AttributeOneWayPassword', true))
|
||||
{
|
||||
continue;
|
||||
|
||||
$aForbidenAttType = [
|
||||
'AttributeLinkedSet',
|
||||
'AttributeFriendlyName',
|
||||
|
||||
'iAttributeNoGroupBy', //we cannot only use iAttributeNoGroupBy since this method is also used by the designer who do not have access to the classes' PHP reflection API. So the known classes has to be listed altogether
|
||||
'AttributeOneWayPassword',
|
||||
'AttributeEncryptedString',
|
||||
'AttributePassword',
|
||||
];
|
||||
foreach ($aForbidenAttType as $sForbidenAttType) {
|
||||
if (is_a($sAttType, $sForbidenAttType, true))
|
||||
{
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
|
||||
@@ -922,7 +926,7 @@ class DashletObjectList extends Dashlet
|
||||
$sHtmlTitle = utils::HtmlEntities(Dict::S($sTitle)); // done in the itop block
|
||||
if ($sHtmlTitle != '')
|
||||
{
|
||||
$oPage->add('<h1>'.$sHtmlTitle.'</h1>');
|
||||
$oPage->add('<div class="main_header"><h1> '.$sHtmlTitle.'</h1></div>');
|
||||
}
|
||||
$oFilter = $this->GetDBSearch($aExtraParams);
|
||||
$oBlock = new DisplayBlock($oFilter, 'list');
|
||||
@@ -1121,7 +1125,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$this->sFunction = null;
|
||||
}
|
||||
|
||||
if (empty($this->aProperties['order_direction']))
|
||||
if ((!is_null($this->sClass)) && empty($this->aProperties['order_direction']))
|
||||
{
|
||||
$aAttributeTypes = $this->oModelReflection->ListAttributes($this->sClass);
|
||||
if (isset($aAttributeTypes[$this->sGroupByAttCode]))
|
||||
@@ -1272,10 +1276,10 @@ abstract class DashletGroupBy extends Dashlet
|
||||
break;
|
||||
}
|
||||
|
||||
$oPage->add('<div style="text-align:center" class="dashlet-content">');
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
if ($sHtmlTitle != '')
|
||||
{
|
||||
$oPage->add('<h1>'.$sHtmlTitle.'</h1>');
|
||||
$oPage->add('<div class="main_header"><h1> '.$sHtmlTitle.'</h1></div>');
|
||||
}
|
||||
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
|
||||
$oBlock = new DisplayBlock($oFilter, $sType);
|
||||
@@ -1868,11 +1872,11 @@ class DashletGroupByTable extends DashletGroupBy
|
||||
$iTotal += $aDisplayData['value'];
|
||||
}
|
||||
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
|
||||
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
|
||||
|
||||
$oPage->add('<div id="'.$sBlockId.'" class="display_block">');
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
$oPage->add('<p>'.Dict::Format('UI:Pagination:HeaderNoSelection', $iTotal).'</p>');
|
||||
$oPage->add('<table class="listResults">');
|
||||
$oPage->add('<thead>');
|
||||
@@ -1926,7 +1930,7 @@ class DashletHeaderStatic extends Dashlet
|
||||
$oPage->add('<div class="main_header">');
|
||||
|
||||
$oPage->add('<img src="'.$sIconPath.'">');
|
||||
$oPage->add('<h1>'.$this->oModelReflection->DictString($sTitle).'</h1>');
|
||||
$oPage->add('<div class="main_header"><h1> '.$this->oModelReflection->DictString($sTitle).'</h1></div>');
|
||||
|
||||
$oPage->add('</div>');
|
||||
$oPage->add('</div>');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.7">
|
||||
<portals>
|
||||
<portal id="backoffice" _delta="define">
|
||||
<url>pages/UI.php</url>
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
class DataTable
|
||||
{
|
||||
protected $iListId; // Unique ID inside the web page
|
||||
/** @var string */
|
||||
private $sDatatableContainerId;
|
||||
protected $sTableId; // identifier for saving the settings (combined with the class aliases)
|
||||
protected $oSet; // The set of objects to display
|
||||
protected $aClassAliases; // The aliases (alias => class) inside the set
|
||||
@@ -29,10 +31,10 @@ class DataTable
|
||||
protected $bShowObsoleteData;
|
||||
|
||||
/**
|
||||
* @param $iListId mixed Unique ID for this div/table in the page
|
||||
* @param $oSet DBObjectSet The set of data to display
|
||||
* @param $aClassAliases array The list of classes/aliases to be displayed in this set $sAlias => $sClassName
|
||||
* @param $sTableId mixed A string (or null) identifying this table in order to persist its settings
|
||||
* @param string $iListId Unique ID for this div/table in the page
|
||||
* @param DBObjectSet $oSet The set of data to display
|
||||
* @param array$aClassAliases The list of classes/aliases to be displayed in this set $sAlias => $sClassName
|
||||
* @param string $sTableId A string (or null) identifying this table in order to persist its settings
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
@@ -42,6 +44,7 @@ class DataTable
|
||||
public function __construct($iListId, $oSet, $aClassAliases, $sTableId = null)
|
||||
{
|
||||
$this->iListId = utils::GetSafeId($iListId); // Make a "safe" ID for jQuery
|
||||
$this->sDatatableContainerId = 'datatable_'.utils::GetSafeId($iListId);
|
||||
$this->oSet = $oSet;
|
||||
$this->aClassAliases = $aClassAliases;
|
||||
$this->sTableId = $sTableId;
|
||||
@@ -165,7 +168,7 @@ class DataTable
|
||||
$sDataTable = $this->GetHTMLTable($oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
|
||||
$sConfigDlg = $this->GetTableConfigDlg($oPage, $aColumns, $bViewLink, $iDefaultPageSize);
|
||||
|
||||
$sHtml = "<table id=\"datatable_{$this->iListId}\" class=\"datatable\">";
|
||||
$sHtml = "<table id=\"{$this->sDatatableContainerId}\" class=\"datatable\">";
|
||||
$sHtml .= "<tr><td>";
|
||||
$sHtml .= "<table style=\"width:100%;\">";
|
||||
$sHtml .= "<tr><td class=\"pagination_container\">$sObjectsCount</td><td class=\"menucontainer\">$sToolkitMenu $sActionsMenu</td></tr>";
|
||||
@@ -201,7 +204,7 @@ class DataTable
|
||||
$aOptions['oDefaultSettings'] = $this->GetAsHash($this->oDefaultSettings);
|
||||
}
|
||||
$sJSOptions = json_encode($aOptions);
|
||||
$oPage->add_ready_script("$('#datatable_{$this->iListId}').datatable($sJSOptions);");
|
||||
$oPage->add_ready_script("$('#{$this->sDatatableContainerId}').datatable($sJSOptions);");
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
@@ -369,7 +372,7 @@ EOF;
|
||||
if (!$oPage->IsPrintableVersion())
|
||||
{
|
||||
$sMenuTitle = Dict::S('UI:ConfigureThisList');
|
||||
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><i class="fas fa-tools"></i><i class="fas fa-caret-down"></i><ul>';
|
||||
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li aria-label="'.Dict::S('UI:Menu:Toolkit').'"><i class="fas fa-tools"></i><i class="fas fa-caret-down"></i><ul>';
|
||||
|
||||
$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
|
||||
$aActions = array(
|
||||
@@ -418,15 +421,15 @@ EOF;
|
||||
$sHtml .= "<input id=\"dtbl_dlg_all_{$this->iListId}\" type=\"radio\" name=\"scope\" $sGenericChecked value=\"defaults\"><label for=\"dtbl_dlg_all_{$this->iListId}\"> ".Dict::S('UI:ForAllLists').'</label></p>';
|
||||
$sHtml .= "</fieldset>";
|
||||
$sHtml .= '<table style="width:100%"><tr><td style="text-align:center;">';
|
||||
$sHtml .= '<button type="button" onclick="$(\'#datatable_'.$this->iListId.'\').datatable(\'onDlgCancel\'); $(\'#datatable_dlg_'.$this->iListId.'\').dialog(\'close\')">'.Dict::S('UI:Button:Cancel').'</button>';
|
||||
$sHtml .= '<button type="button" onclick="$(\'#'.$this->sDatatableContainerId.'\').datatable(\'onDlgCancel\'); $(\'#datatable_dlg_'.$this->iListId.'\').dialog(\'close\')">'.Dict::S('UI:Button:Cancel').'</button>';
|
||||
$sHtml .= '</td><td style="text-align:center;">';
|
||||
$sHtml .= '<button type="submit" onclick="$(\'#datatable_'.$this->iListId.'\').datatable(\'onDlgOk\');$(\'#datatable_dlg_'.$this->iListId.'\').dialog(\'close\');">'.Dict::S('UI:Button:Ok').'</button>';
|
||||
$sHtml .= '<button type="submit" onclick="$(\'#'.$this->sDatatableContainerId.'\').datatable(\'onDlgOk\');$(\'#datatable_dlg_'.$this->iListId.'\').dialog(\'close\');">'.Dict::S('UI:Button:Ok').'</button>';
|
||||
$sHtml .= '</td></tr></table>';
|
||||
$sHtml .= "</form>";
|
||||
$sHtml .= "</div>";
|
||||
|
||||
$sDlgTitle = addslashes(Dict::S('UI:ListConfigurationTitle'));
|
||||
$oPage->add_ready_script("$('#datatable_dlg_{$this->iListId}').dialog({autoOpen: false, title: '$sDlgTitle', width: 500, close: function() { $('#datatable_{$this->iListId}').datatable('onDlgCancel'); } });");
|
||||
$oPage->add_ready_script("$('#datatable_dlg_{$this->iListId}').dialog({autoOpen: false, title: '$sDlgTitle', width: 500, close: function() { $('#{$this->sDatatableContainerId}').datatable('onDlgCancel'); } });");
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
@@ -745,12 +748,25 @@ EOF;
|
||||
}
|
||||
$sOQL = addslashes($this->oSet->GetFilter()->serialize());
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
var oTable = $('#{$this->iListId} table.listResults');
|
||||
<<<JS
|
||||
var oTable = $('#{$this->sDatatableContainerId} table.listResults');
|
||||
oTable.tableHover();
|
||||
oTable.tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $('#pager{$this->iListId}'), totalRows:$iCount, size: $iPageSize, filter: '$sOQL', extra_params: '$sExtraParams', select_mode: '$sSelectModeJS', displayKey: $sDisplayKey, table_id: '{$this->iListId}', columns: $sJSColumns, class_aliases: $sJSClassAliases $sCssCount});
|
||||
EOF
|
||||
);
|
||||
oTable
|
||||
.tablesorter({ $sHeaders widgets: ['myZebra', 'truncatedList']})
|
||||
.tablesorterPager({
|
||||
container: $('#pager{$this->iListId}'),
|
||||
totalRows:$iCount,
|
||||
size: $iPageSize,
|
||||
filter: '$sOQL',
|
||||
extra_params: '$sExtraParams',
|
||||
select_mode: '$sSelectModeJS',
|
||||
displayKey: $sDisplayKey,
|
||||
table_id: '{$this->sDatatableContainerId}',
|
||||
columns: $sJSColumns,
|
||||
class_aliases: $sJSClassAliases $sCssCount
|
||||
});
|
||||
JS
|
||||
);
|
||||
if ($sFakeSortList != '')
|
||||
{
|
||||
$oPage->add_ready_script("oTable.trigger(\"fakesorton\", [$sFakeSortList]);");
|
||||
|
||||
@@ -324,8 +324,10 @@ class DisplayBlock
|
||||
* @throws DictExceptionMissingString
|
||||
* @throws MySQLException
|
||||
* @throws Exception
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 add type hinting to $aExtraParams
|
||||
*/
|
||||
public function GetRenderContent(WebPage $oPage, $aExtraParams, $sId)
|
||||
public function GetRenderContent(WebPage $oPage, array $aExtraParams, $sId)
|
||||
{
|
||||
$sHtml = '';
|
||||
// Add the extra params into the filter if they make sense for such a filter
|
||||
@@ -446,8 +448,21 @@ class DisplayBlock
|
||||
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
|
||||
}
|
||||
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
|
||||
switch($this->m_sStyle)
|
||||
{
|
||||
|
||||
switch($this->m_sStyle) {
|
||||
case 'list_search':
|
||||
case 'list':
|
||||
break;
|
||||
default:
|
||||
// N°3473: except for 'list_search' and 'list' (which have more granularity, see the other switch below),
|
||||
// refuse to render if the user is not allowed to see the class.
|
||||
if (! UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) {
|
||||
$sHtml .= $oPage->GetP(Dict::Format('UI:Error:ReadNotAllowedOn_Class', $this->m_oSet->GetClass()));
|
||||
return $sHtml;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($this->m_sStyle) {
|
||||
case 'count':
|
||||
if (isset($aExtraParams['group_by']))
|
||||
{
|
||||
@@ -837,7 +852,8 @@ class DisplayBlock
|
||||
|
||||
foreach($aStates as $sStateValue)
|
||||
{
|
||||
$aStateLabels[$sStateValue] = htmlentities($oAttDef->GetValueLabel($sStateValue), ENT_QUOTES, 'UTF-8');
|
||||
$sHtmlValue=$aGroupBy['group1']->MakeValueLabel($this->m_oFilter, $sStateValue, $sStateValue);
|
||||
$aStateLabels[$sStateValue] = strip_tags($sHtmlValue);
|
||||
|
||||
$aCounts[$sStateValue] = (array_key_exists($sStateValue, $aCountsQueryResults))
|
||||
? $aCountsQueryResults[$sStateValue]
|
||||
@@ -946,7 +962,7 @@ class DisplayBlock
|
||||
$iChartCounter++;
|
||||
|
||||
$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
|
||||
$sTitle = isset($aExtraParams['chart_title']) ? '<h1 style="text-align:center">'.htmlentities(Dict::S($aExtraParams['chart_title']), ENT_QUOTES, 'UTF-8').'</h1>' : '';
|
||||
$sTitle = isset($aExtraParams['chart_title']) ? '<div class="main_header"><h1> '.htmlentities(Dict::S($aExtraParams['chart_title']), ENT_QUOTES, 'UTF-8').'</h1></div>' : '';
|
||||
$sHtml = "$sTitle<div style=\"height:200px;width:100%\" class=\"dashboard_chart\" id=\"my_chart_$sId{$iChartCounter}\"><div style=\"height:200px;line-height:200px;vertical-align:center;text-align:center;width:100%\"><img src=\"../images/indicator.gif\"></div></div>\n";
|
||||
$sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : '';
|
||||
$sGroupByExpr = isset($aExtraParams['group_by_expr']) ? '¶ms[group_by_expr]='.$aExtraParams['group_by_expr'] : '';
|
||||
@@ -993,6 +1009,7 @@ EOF
|
||||
$iTotalCount = 0;
|
||||
$aValues = array();
|
||||
$aURLs = array();
|
||||
|
||||
foreach ($aRes as $iRow => $aRow)
|
||||
{
|
||||
$sValue = $aRow['grouped_by_1'];
|
||||
@@ -1000,7 +1017,8 @@ EOF
|
||||
$aGroupBy[(int)$iRow] = (int) $aRow[$sFctVar];
|
||||
$iTotalCount += $aRow['_itop_count_'];
|
||||
$aValues[] = array('label' => html_entity_decode(strip_tags($sHtmlValue), ENT_QUOTES, 'UTF-8'), 'label_html' => $sHtmlValue, 'value' => (int) $aRow[$sFctVar]);
|
||||
|
||||
|
||||
|
||||
// Build the search for this subset
|
||||
$oSubsetSearch = $this->m_oFilter->DeepClone();
|
||||
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue));
|
||||
@@ -1014,16 +1032,23 @@ EOF
|
||||
{
|
||||
case 'bars':
|
||||
$aNames = array();
|
||||
$iMaxNbCharsInLabel = 0;
|
||||
foreach($aValues as $idx => $aValue)
|
||||
{
|
||||
$aNames[$idx] = $aValue['label'];
|
||||
if ($iMaxNbCharsInLabel < mb_strlen($aValue['label'])) {
|
||||
$iMaxNbCharsInLabel = mb_strlen($aValue['label']);
|
||||
}
|
||||
}
|
||||
$sJSNames = json_encode($aNames);
|
||||
|
||||
$sJson = json_encode($aValues);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
|
||||
var iChartDefaultHeight = 200,
|
||||
iChartLegendHeight = 6 * $iMaxNbCharsInLabel,
|
||||
iChartTotalHeight = iChartDefaultHeight + iChartLegendHeight;
|
||||
$('#my_chart_$sId').height(iChartTotalHeight+ 'px');
|
||||
var chart = c3.generate({
|
||||
bindto: d3.select('#my_chart_$sId'),
|
||||
data: {
|
||||
@@ -1091,8 +1116,19 @@ EOF
|
||||
}
|
||||
$sJSColumns = json_encode($aColumns);
|
||||
$sJSNames = json_encode($aNames);
|
||||
$iNbLinesToAddForName = 0;
|
||||
if (count($aNames) > 50) {
|
||||
// Calculation of the number of legends line add to the height of the graph to have a maximum of 5 legend columns
|
||||
$iNbLinesIncludedInChartHeight = 10;
|
||||
$iNbLinesToAddForName = ceil(count($aNames) / 5) - $iNbLinesIncludedInChartHeight;
|
||||
}
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
// Calculate height of graph : 200px (minimum height for the chart) + 20*iNbLinesToAddForName for the legend
|
||||
var iChartDefaultHeight = 200,
|
||||
iChartLegendHeight = 20 * $iNbLinesToAddForName,
|
||||
iChartTotalHeight = (iChartDefaultHeight + iChartLegendHeight);
|
||||
$('#my_chart_$sId').height(iChartTotalHeight + 'px');
|
||||
var chart = c3.generate({
|
||||
bindto: d3.select('#my_chart_$sId'),
|
||||
data: {
|
||||
@@ -1404,8 +1440,25 @@ class HistoryBlock extends DisplayBlock
|
||||
$this->iLimitStart = $iStart;
|
||||
$this->iLimitCount = $iCount;
|
||||
}
|
||||
|
||||
public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
|
||||
|
||||
/**
|
||||
* @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, $sId)
|
||||
{
|
||||
$sHtml = '';
|
||||
$bTruncated = false;
|
||||
@@ -1544,8 +1597,10 @@ class MenuBlock extends DisplayBlock
|
||||
* @throws \Exception
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value and add type hinting on $aExtraParams for PHP 8.0 compatibility
|
||||
*/
|
||||
public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
|
||||
public function GetRenderContent(WebPage $oPage, array $aExtraParams, $sId)
|
||||
{
|
||||
if ($this->m_sStyle == 'popup') // popup is a synonym of 'list' for backward compatibility
|
||||
{
|
||||
@@ -1880,11 +1935,13 @@ class MenuBlock extends DisplayBlock
|
||||
{
|
||||
if (count($aFavoriteActions) > 0)
|
||||
{
|
||||
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:OtherActions')."<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
|
||||
$sActionsMenuLabel = Dict::S('UI:Menu:OtherActions');
|
||||
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li aria-label=\"{$sActionsMenuLabel}\">{$sActionsMenuLabel}<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
|
||||
$sActionsMenuLabel = Dict::S('UI:Menu:Actions');
|
||||
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li aria-label=\"{$sActionsMenuLabel}\">{$sActionsMenuLabel}<i class=\"fas fa-caret-down\"></i>"."\n<ul>\n";
|
||||
}
|
||||
|
||||
$sHtml .= $oPage->RenderPopupMenuItems($aActions, $aFavoriteActions);
|
||||
|
||||
82
application/errorpage.class.inc.php
Normal file
82
application/errorpage.class.inc.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2020 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
class ErrorPage extends NiceWebPage
|
||||
{
|
||||
public function __construct($sTitle)
|
||||
{
|
||||
parent::__construct($sTitle);
|
||||
$this->add_linked_script("../js/jquery.blockUI.js");
|
||||
$this->add_linked_script("../setup/setup.js");
|
||||
$this->add_saas("css/setup.scss");
|
||||
}
|
||||
|
||||
public function info($sText)
|
||||
{
|
||||
$this->add("<p class=\"info\">$sText</p>\n");
|
||||
$this->log_info($sText);
|
||||
}
|
||||
|
||||
public function ok($sText)
|
||||
{
|
||||
$this->add("<div class=\"message message-valid\"><span class=\"message-title\">Success:</span>$sText</div>");
|
||||
$this->log_ok($sText);
|
||||
}
|
||||
|
||||
public function warning($sText)
|
||||
{
|
||||
$this->add("<div class=\"message message-warning\"><span class=\"message-title\">Warning:</span>$sText</div>");
|
||||
$this->log_warning($sText);
|
||||
}
|
||||
|
||||
public function error($sText)
|
||||
{
|
||||
$this->add("<div class=\"message message-error\">$sText</div>");
|
||||
$this->log_error($sText);
|
||||
}
|
||||
|
||||
public function output()
|
||||
{
|
||||
$sLogo = utils::GetAbsoluteUrlAppRoot().'/images/itop-logo.png';
|
||||
$sTimeStamp = utils::GetCacheBusterTimestamp();
|
||||
$sTitle = utils::HtmlEntities($this->s_title);
|
||||
$this->s_content = <<<HTML
|
||||
<div id="header" class="error_page">
|
||||
<h1><a href="http://www.combodo.com/itop" target="_blank"><img title="iTop by Combodo" alt=" " src="{$sLogo}?t={$sTimeStamp}"></a> {$sTitle}</h1>
|
||||
</div>
|
||||
<div id="setup" class="error_page">
|
||||
{$this->s_content}
|
||||
</div>
|
||||
HTML;
|
||||
return parent::output();
|
||||
}
|
||||
|
||||
public static function log_error($sText)
|
||||
{
|
||||
IssueLog::Error($sText);
|
||||
}
|
||||
|
||||
public static function log_warning($sText)
|
||||
{
|
||||
IssueLog::Warning($sText);
|
||||
}
|
||||
|
||||
public static function log_info($sText)
|
||||
{
|
||||
IssueLog::Info($sText);
|
||||
}
|
||||
|
||||
public static function log_ok($sText)
|
||||
{
|
||||
IssueLog::Ok($sText);
|
||||
}
|
||||
|
||||
public static function log($sText)
|
||||
{
|
||||
IssueLog::Ok($sText);
|
||||
}
|
||||
}
|
||||
@@ -1223,7 +1223,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);
|
||||
}
|
||||
@@ -1271,18 +1271,14 @@ 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'));
|
||||
$sHtmlValue = str_replace(' ', ' ', $sDisplayValue);
|
||||
$sHtml .= "<option value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\" $sSelected>$sHtmlValue</option>";
|
||||
}
|
||||
$sHtml .= "</select>";
|
||||
|
||||
@@ -60,8 +60,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
// Create a breadcrumb entry for the current page, but get its title as late as possible (page title could be changed later)
|
||||
$this->bBreadCrumbEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
$this->bBreadCrumbEnabled = false;
|
||||
}
|
||||
|
||||
@@ -71,7 +70,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$this->m_aMessages = array();
|
||||
$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
|
||||
$this->add_header("Content-type: text/html; charset=".self::PAGES_CHARSET);
|
||||
$this->add_header("Cache-control: no-cache");
|
||||
$this->no_cache();
|
||||
$this->add_http_headers();
|
||||
$this->add_linked_stylesheet("../css/jquery.treeview.css");
|
||||
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
|
||||
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
|
||||
@@ -354,14 +354,15 @@ JS
|
||||
);
|
||||
|
||||
// Highlight code content created with CKEditor
|
||||
// Note: We check for the <code> tag inside the <pre> tag to only target code from CKEditor, otherwise we might highlight some others things. See N°3810
|
||||
$this->add_ready_script(
|
||||
<<<JS
|
||||
// Highlight code content for HTML AttributeText
|
||||
$("[data-attribute-type='AttributeText'] .HTML pre").each(function(i, block) {
|
||||
$("[data-attribute-type='AttributeText'] .HTML pre > code").parent().each(function(i, block) {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
// Highlight code content for CaseLogs
|
||||
$("[data-attribute-type='AttributeCaseLog'] .caselog_entry_html pre").each(function(i, block) {
|
||||
$("[data-attribute-type='AttributeCaseLog'] .caselog_entry_html pre > code").parent().each(function(i, block) {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
JS
|
||||
@@ -623,7 +624,7 @@ JS
|
||||
ShowDebug();
|
||||
$('#logOffBtn>ul').popupmenu();
|
||||
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry,.caselog_entry_html').toggle(); });
|
||||
$('body').on('click', '.caselog_header', function () { $(this).toggleClass('open').next('.caselog_entry,.caselog_entry_html').toggle(); });
|
||||
|
||||
$(document).ajaxSend(function(event, jqxhr, options) {
|
||||
jqxhr.setRequestHeader('X-Combodo-Ajax', 'true');
|
||||
@@ -1218,7 +1219,7 @@ EOF;
|
||||
{
|
||||
$sLogonMessage = Dict::Format('UI:LoggedAsMessage', $sUserName);
|
||||
}
|
||||
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li><i class=\"top-right-icon icon-additional-arrow fas fa-power-off\"></i><ul>";
|
||||
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li aria-label=\"" . Dict::S("UI:PowerMenu") . "\"><i class=\"top-right-icon icon-additional-arrow fas fa-power-off\"></i><ul>";
|
||||
$sLogOffMenu .= "<li><span>$sLogonMessage</span></li>\n";
|
||||
$aActions = array();
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
{
|
||||
$_SESSION['login_mode'] = 'basic';
|
||||
}
|
||||
elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
|
||||
{
|
||||
$_SESSION['login_mode'] = 'basic';
|
||||
}
|
||||
elseif (isset($_SERVER['PHP_AUTH_USER']))
|
||||
{
|
||||
$_SESSION['login_mode'] = 'basic';
|
||||
@@ -36,7 +40,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnReadCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (!isset($_SESSION['login_mode']) || $_SESSION['login_mode'] == 'basic')
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
|
||||
$_SESSION['login_temp_auth_user'] = $sAuthUser;
|
||||
@@ -47,7 +51,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
|
||||
@@ -55,15 +59,17 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
// Save the checked user
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
|
||||
{
|
||||
list($sAuthUser) = $this->GetAuthUserAndPassword();
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
@@ -71,8 +77,13 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
|
||||
{
|
||||
$iOnExit = LoginWebPage::getIOnExit();
|
||||
if ($iOnExit === LoginWebPage::EXIT_RETURN)
|
||||
{
|
||||
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
|
||||
}
|
||||
LoginWebPage::HTTP401Error();
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
@@ -80,7 +91,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'basic')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'basic')
|
||||
{
|
||||
$_SESSION['can_logoff'] = true;
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
@@ -92,9 +103,19 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
{
|
||||
$sAuthUser = '';
|
||||
$sAuthPwd = null;
|
||||
$sAuthorization = '';
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
|
||||
$sAuthorization = $_SERVER['HTTP_AUTHORIZATION'];
|
||||
}
|
||||
elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
|
||||
{
|
||||
$sAuthorization = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
|
||||
}
|
||||
|
||||
if (!empty($sAuthorization))
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = explode(':', base64_decode(substr($sAuthorization, 6)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -121,4 +142,4 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
}
|
||||
return array($sAuthUser, $sAuthPwd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
{
|
||||
self::ResetLoginSession();
|
||||
$iOnExit = LoginWebPage::getIOnExit();
|
||||
if ($iOnExit == LoginWebPage::EXIT_RETURN)
|
||||
if ($iOnExit === LoginWebPage::EXIT_RETURN)
|
||||
{
|
||||
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
|
||||
}
|
||||
@@ -93,6 +93,12 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
{
|
||||
if (!isset($_SESSION['login_mode']))
|
||||
{
|
||||
// N°6358 - if EXIT_RETURN was asked, send an error
|
||||
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
|
||||
// If no plugin validated the user, exit
|
||||
self::ResetLoginSession();
|
||||
exit();
|
||||
@@ -111,6 +117,11 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
unset($_SESSION['login_temp_auth_user']);
|
||||
if (is_null(UserRights::GetUserObject())){
|
||||
//N°7085 avoid infinite loop
|
||||
IssueLog::Error("No user logged in. exit");
|
||||
exit(-1);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
@@ -126,4 +137,4 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
|
||||
{
|
||||
$sAuthUser = $this->GetAuthUser();
|
||||
if (!UserRights::CheckCredentials($sAuthUser, '', $_SESSION['login_mode'], 'external'))
|
||||
@@ -43,15 +43,17 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
// Save the checked user
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
|
||||
{
|
||||
$sAuthUser = $this->GetAuthUser();
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', $_SESSION['login_mode']);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
@@ -59,7 +61,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'external')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
|
||||
{
|
||||
$_SESSION['can_logoff'] = false;
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
@@ -67,6 +69,20 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'external')
|
||||
{
|
||||
$iOnExit = LoginWebPage::getIOnExit();
|
||||
if ($iOnExit === LoginWebPage::EXIT_RETURN)
|
||||
{
|
||||
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
|
||||
}
|
||||
LoginWebPage::HTTP401Error();
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,10 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
exit;
|
||||
}
|
||||
|
||||
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
// No credentials yet, display the form
|
||||
$oPage = LoginWebPage::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm($this->bForceFormOnError);
|
||||
@@ -62,7 +66,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
|
||||
{
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
|
||||
@@ -71,6 +75,8 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
// Save the checked user
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -80,17 +86,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
|
||||
{
|
||||
if (isset($_SESSION['auth_user']))
|
||||
{
|
||||
// If FSM reenter this state (example 2FA) then the auth_user is not resubmitted
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
|
||||
}
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
// Store 'auth_user' in session for further use
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
|
||||
}
|
||||
@@ -102,7 +100,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
|
||||
{
|
||||
$this->bForceFormOnError = true;
|
||||
}
|
||||
@@ -114,7 +112,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
*/
|
||||
protected function OnConnected(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'form')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form')
|
||||
{
|
||||
$_SESSION['can_logoff'] = true;
|
||||
return LoginWebPage::CheckLoggedUser($iErrorCode);
|
||||
|
||||
@@ -40,7 +40,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnReadCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
|
||||
{
|
||||
$_SESSION['login_temp_auth_user'] = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
|
||||
{
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
@@ -58,15 +58,17 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
// Save the checked user
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
protected function OnCredentialsOK(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
|
||||
{
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
@@ -74,7 +76,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
|
||||
protected function OnError(&$iErrorCode)
|
||||
{
|
||||
if ($_SESSION['login_mode'] == 'url')
|
||||
if (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'url')
|
||||
{
|
||||
$this->bErrorOccurred = true;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class LoginWebPage extends NiceWebPage
|
||||
{
|
||||
const EXIT_PROMPT = 0;
|
||||
const EXIT_HTTP_401 = 1;
|
||||
const EXIT_RETURN = 2;
|
||||
const EXIT_RETURN = 2; // Non interactive mode (ajax, rest, ...)
|
||||
|
||||
const EXIT_CODE_OK = 0;
|
||||
const EXIT_CODE_MISSINGLOGIN = 1;
|
||||
@@ -78,14 +78,14 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
public function __construct($sTitle = null)
|
||||
{
|
||||
if($sTitle === null)
|
||||
{
|
||||
$sTitle = Dict::S('UI:Login:Title');
|
||||
}
|
||||
if ($sTitle === null) {
|
||||
$sTitle = Dict::S('UI:Login:Title');
|
||||
}
|
||||
|
||||
parent::__construct($sTitle);
|
||||
$this->SetStyleSheet();
|
||||
$this->add_header("Cache-control: no-cache");
|
||||
$this->no_cache();
|
||||
$this->add_http_headers();
|
||||
}
|
||||
|
||||
public function SetStyleSheet()
|
||||
@@ -208,7 +208,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);
|
||||
@@ -352,14 +352,20 @@ class LoginWebPage extends NiceWebPage
|
||||
$this->output();
|
||||
}
|
||||
|
||||
public static function ResetSession()
|
||||
public static function ResetSession($bFullCleanup = false)
|
||||
{
|
||||
// Unset all of the session variables.
|
||||
unset($_SESSION['auth_user']);
|
||||
unset($_SESSION['login_state']);
|
||||
unset($_SESSION['can_logoff']);
|
||||
unset($_SESSION['archive_mode']);
|
||||
unset($_SESSION['impersonate_user']);
|
||||
if ($bFullCleanup) {
|
||||
// Unset all of the session variables.
|
||||
foreach (array_keys($_SESSION) as $sKey) {
|
||||
unset($_SESSION[$sKey]);
|
||||
}
|
||||
} else {
|
||||
unset($_SESSION['auth_user']);
|
||||
unset($_SESSION['login_state']);
|
||||
unset($_SESSION['can_logoff']);
|
||||
unset($_SESSION['archive_mode']);
|
||||
unset($_SESSION['impersonate_user']);
|
||||
}
|
||||
UserRights::_ResetSessionCache();
|
||||
// If it's desired to kill the session, also delete the session cookie.
|
||||
// Note: This will destroy the session, and not just the session data!
|
||||
@@ -685,7 +691,7 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
public static function HTTPReload()
|
||||
{
|
||||
$sOriginURL = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
|
||||
$sOriginURL = utils::GetCurrentAbsoluteUrl();
|
||||
if (!utils::StartsWith($sOriginURL, utils::GetAbsoluteUrlAppRoot()))
|
||||
{
|
||||
// If the found URL does not start with the configured AppRoot URL
|
||||
@@ -931,7 +937,7 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iOnExit == self::EXIT_RETURN)
|
||||
if ($iOnExit === self::EXIT_RETURN)
|
||||
{
|
||||
return self::EXIT_CODE_PORTALUSERNOTAUTHORIZED;
|
||||
}
|
||||
@@ -987,14 +993,14 @@ class LoginWebPage extends NiceWebPage
|
||||
{
|
||||
if ($bMustBeAdmin && !UserRights::IsAdministrator())
|
||||
{
|
||||
if ($iOnExit == self::EXIT_RETURN)
|
||||
if ($iOnExit === self::EXIT_RETURN)
|
||||
{
|
||||
return self::EXIT_CODE_MUSTBEADMIN;
|
||||
}
|
||||
else
|
||||
{
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
$oP = new SetupPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessAdmin')."</h1>\n");
|
||||
$oP->p("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/logoff.php\">".Dict::S('UI:LogOffMenu')."</a>");
|
||||
$oP->output();
|
||||
@@ -1003,7 +1009,7 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
$iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $sRequestedPortalId, $iOnExit);
|
||||
}
|
||||
if ($iOnExit == self::EXIT_RETURN)
|
||||
if ($iOnExit === self::EXIT_RETURN)
|
||||
{
|
||||
return $iRet;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ function _MaintenanceSetupPageMessage($sTitle, $sMessage)
|
||||
@include_once(APPROOT.'setup/setuppage.class.inc.php');
|
||||
if (class_exists('SetupPage'))
|
||||
{
|
||||
$oP = new SetupPage($sTitle);
|
||||
$oP = new ErrorPage($sTitle);
|
||||
$oP->p("<h2 class=\"center\">$sMessage</h2>");
|
||||
$oP->add_ready_script(
|
||||
<<<JS
|
||||
|
||||
@@ -128,7 +128,7 @@ class ApplicationMenu
|
||||
if (is_null($oMenuNode) || !$oMenuNode->IsEnabled())
|
||||
{
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
$oP = new SetupPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessRestricted')."</h1>\n");
|
||||
$oP->p("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/logoff.php\">".Dict::S('UI:LogOffMenu')."</a>");
|
||||
$oP->output();
|
||||
|
||||
@@ -35,19 +35,25 @@ register_shutdown_function(function()
|
||||
$sReservedMemory = null;
|
||||
if (!is_null($err = error_get_last()) && ($err['type'] == E_ERROR))
|
||||
{
|
||||
IssueLog::error($err['message']);
|
||||
if (strpos($err['message'], 'Allowed memory size of') !== false)
|
||||
{
|
||||
// Remove stack trace from MySQLException (since 2.7.2 see N°3174)
|
||||
$sMessage = $err['message'];
|
||||
if (strpos($sMessage, 'MySQLException') !== false) {
|
||||
$iStackTracePos = strpos($sMessage, 'Stack trace:');
|
||||
if ($iStackTracePos !== false) {
|
||||
$sMessage = substr($sMessage, 0, $iStackTracePos);
|
||||
}
|
||||
}
|
||||
// Log additional info but message from $err (since 2.7.6 N°4174)
|
||||
$aErrToLog = $err;
|
||||
unset($aErrToLog['message']);
|
||||
IssueLog::error($sMessage, null, $aErrToLog);
|
||||
if (strpos($err['message'], 'Allowed memory size of') !== false) {
|
||||
$sLimit = ini_get('memory_limit');
|
||||
echo "<p>iTop: Allowed memory size of $sLimit exhausted, contact your administrator to increase 'memory_limit' in php.ini</p>\n";
|
||||
}
|
||||
elseif (strpos($err['message'], 'Maximum execution time') !== false)
|
||||
{
|
||||
} elseif (strpos($err['message'], 'Maximum execution time') !== false) {
|
||||
$sLimit = ini_get('max_execution_time');
|
||||
echo "<p>iTop: Maximum execution time of $sLimit exceeded, contact your administrator to increase 'max_execution_time' in php.ini</p>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
echo "<p>iTop: An error occurred, check server error log for more information.</p>\n";
|
||||
}
|
||||
}
|
||||
@@ -85,4 +91,10 @@ else
|
||||
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
|
||||
}
|
||||
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
|
||||
try {
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
|
||||
}
|
||||
catch (MySQLException $e) {
|
||||
IssueLog::Debug($e->getMessage());
|
||||
throw new MySQLException('Could not connect to the DB server', []);
|
||||
}
|
||||
@@ -57,6 +57,10 @@ class ThemeHandler
|
||||
{
|
||||
try
|
||||
{
|
||||
if (is_null(MetaModel::GetConfig()))
|
||||
{
|
||||
throw new CoreException('no config');
|
||||
}
|
||||
$sThemeId = MetaModel::GetConfig()->Get('backoffice_default_theme');
|
||||
}
|
||||
catch(CoreException $oCompileException)
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
class privUITransaction
|
||||
{
|
||||
/**
|
||||
@@ -99,9 +97,10 @@ class privUITransaction
|
||||
}
|
||||
|
||||
/**
|
||||
* The original (and by default) mechanism for storing transaction information
|
||||
* as an array in the $_SESSION variable
|
||||
* The original mechanism for storing transaction information as an array in the $_SESSION variable
|
||||
*
|
||||
* Warning, since 2.6.0 the session is regenerated on each login (see PR #20) !
|
||||
* Also, we saw some problems when using memcached as the PHP session implementation (see N°1835)
|
||||
*/
|
||||
class privUITransactionSession
|
||||
{
|
||||
@@ -194,9 +193,35 @@ class privUITransactionSession
|
||||
*/
|
||||
class privUITransactionFile
|
||||
{
|
||||
/** @var int Value to use when no user logged */
|
||||
const UNAUTHENTICATED_USER_ID = -666;
|
||||
|
||||
/**
|
||||
* @return int current user id, or {@see self::UNAUTHENTICATED_USER_ID} if no user logged
|
||||
*
|
||||
* @since 2.6.5 2.7.6 3.0.0 N°4289 method creation
|
||||
*/
|
||||
private static function GetCurrentUserId()
|
||||
{
|
||||
$iCurrentUserId = UserRights::GetConnectedUserId();
|
||||
if ('' === $iCurrentUserId) {
|
||||
$iCurrentUserId = static::UNAUTHENTICATED_USER_ID;
|
||||
}
|
||||
|
||||
return $iCurrentUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new transaction id, store it in the session and return its id
|
||||
*
|
||||
* @param void
|
||||
*
|
||||
* @return int The new transaction identifier
|
||||
*
|
||||
* @throws \SecurityException
|
||||
* @throws \Exception
|
||||
*
|
||||
* @since 2.6.5 2.7.6 3.0.0 security hardening + throws SecurityException if no user logged
|
||||
*/
|
||||
public static function GetNewTransactionId()
|
||||
{
|
||||
@@ -213,88 +238,115 @@ class privUITransactionFile
|
||||
throw new Exception('Failed to create the directory "'.APPROOT.'data/transactions". Ajust the rights on the parent directory or let an administrator create the transactions directory and give the web sever enough rights to write into it.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_writable(APPROOT.'data/transactions'))
|
||||
{
|
||||
throw new Exception('The directory "'.APPROOT.'data/transactions" must be writable to the application.');
|
||||
}
|
||||
self::CleanupOldTransactions();
|
||||
$id = basename(tempnam(APPROOT.'data/transactions', static::GetUserPrefix()));
|
||||
self::Info('GetNewTransactionId: Created transaction: '.$id);
|
||||
|
||||
return (string)$id;
|
||||
$iCurrentUserId = static::GetCurrentUserId();
|
||||
|
||||
self::CleanupOldTransactions();
|
||||
|
||||
$sTransactionIdFullPath = tempnam(APPROOT.'data/transactions', static::GetUserPrefix());
|
||||
file_put_contents($sTransactionIdFullPath, $iCurrentUserId, LOCK_EX);
|
||||
|
||||
$sTransactionIdFileName = basename($sTransactionIdFullPath);
|
||||
self::Info('GetNewTransactionId: Created transaction: '.$sTransactionIdFileName);
|
||||
|
||||
return $sTransactionIdFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a transaction is valid or not and (optionally) remove the valid transaction from
|
||||
* the session so that another call to IsTransactionValid for the same transaction id
|
||||
* will return false
|
||||
*
|
||||
* @param int $id Identifier of the transaction, as returned by GetNewTransactionId
|
||||
* @param bool $bRemoveTransaction True if the transaction must be removed
|
||||
*
|
||||
* @return bool True if the transaction is valid, false otherwise
|
||||
*
|
||||
* @since 2.6.5 2.7.6 3.0.0 N°4289 security hardening
|
||||
*/
|
||||
public static function IsTransactionValid($id, $bRemoveTransaction = true)
|
||||
{
|
||||
$sFilepath = APPROOT.'data/transactions/'.$id;
|
||||
// Constraint the transaction file within APPROOT.'data/transactions'
|
||||
$sTransactionDir = realpath(APPROOT.'data/transactions');
|
||||
$sFilepath = utils::RealPath($sTransactionDir.'/'.$id, $sTransactionDir);
|
||||
if (($sFilepath === false) || (strlen($sTransactionDir) == strlen($sFilepath)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
clearstatcache(true, $sFilepath);
|
||||
$bResult = file_exists($sFilepath);
|
||||
if ($bResult)
|
||||
|
||||
if (false === $bResult) {
|
||||
self::Info("IsTransactionValid: Transaction '$id' not found. Pending transactions:\n".implode("\n", self::GetPendingTransactions()));
|
||||
return false;
|
||||
}
|
||||
|
||||
$iCurrentUserId = static::GetCurrentUserId();
|
||||
$sTransactionIdUserId = file_get_contents($sFilepath);
|
||||
if ($iCurrentUserId != $sTransactionIdUserId) {
|
||||
self::Info("IsTransactionValid: Transaction '$id' not existing for current user. Pending transactions:\n".implode("\n", self::GetPendingTransactions()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($bRemoveTransaction)
|
||||
{
|
||||
if ($bRemoveTransaction)
|
||||
$bResult = @unlink($sFilepath);
|
||||
if (!$bResult)
|
||||
{
|
||||
$bResult = @unlink($sFilepath);
|
||||
if (!$bResult)
|
||||
{
|
||||
self::Error('IsTransactionValid: FAILED to remove transaction '.$id);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::Info('IsTransactionValid: OK. Removed transaction: '.$id);
|
||||
}
|
||||
self::Error('IsTransactionValid: FAILED to remove transaction '.$id);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::Info('IsTransactionValid: OK. Removed transaction: '.$id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self::Info("IsTransactionValid: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions()));
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the transaction specified by its id
|
||||
* @param int $id The Identifier (as returned by GetNewTransactionId) of the transaction to be removed.
|
||||
* @return void
|
||||
* @return bool true if the token can be removed
|
||||
*
|
||||
* @since 2.6.5 2.7.6 3.0.0 N°4289 security hardening
|
||||
*/
|
||||
public static function RemoveTransaction($id)
|
||||
{
|
||||
$bSuccess = true;
|
||||
$sFilepath = APPROOT.'data/transactions/'.$id;
|
||||
clearstatcache(true, $sFilepath);
|
||||
if(!file_exists($sFilepath))
|
||||
{
|
||||
$bSuccess = false;
|
||||
self::Error("RemoveTransaction: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions()));
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
$bResult = static::IsTransactionValid($id, true);
|
||||
if (false === $bResult) {
|
||||
self::Error("RemoveTransaction: Transaction '$id' is invalid. Pending transactions:\n"
|
||||
.implode("\n", self::GetPendingTransactions()));
|
||||
return false;
|
||||
}
|
||||
$bSuccess = @unlink($sFilepath);
|
||||
if (!$bSuccess)
|
||||
{
|
||||
self::Error('RemoveTransaction: FAILED to remove transaction '.$id);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::Info('RemoveTransaction: OK '.$id);
|
||||
}
|
||||
return $bSuccess;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup old transactions which have been pending since more than 24 hours
|
||||
* Use filemtime instead of filectime since filectime may be affected by operations on the directory (like changing the access rights)
|
||||
*/
|
||||
protected static function CleanupOldTransactions()
|
||||
protected static function CleanupOldTransactions($sTransactionDir = null)
|
||||
{
|
||||
$iLimit = time() - 24*3600;
|
||||
$iThreshold = (int) MetaModel::GetConfig()->Get('transactions_gc_threshold');
|
||||
$iThreshold = min(100, $iThreshold);
|
||||
$iThreshold = max(1, $iThreshold);
|
||||
if ((100 != $iThreshold) && (rand(1, 100) > $iThreshold)) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearstatcache();
|
||||
$aTransactions = glob(APPROOT.'data/transactions/*-*');
|
||||
$iLimit = time() - 24*3600;
|
||||
$sPattern = $sTransactionDir ? "$sTransactionDir/*" : APPROOT.'data/transactions/*';
|
||||
$aTransactions = glob($sPattern);
|
||||
foreach($aTransactions as $sFileName)
|
||||
{
|
||||
if (filemtime($sFileName) < $iLimit)
|
||||
@@ -348,22 +400,35 @@ class privUITransactionFile
|
||||
{
|
||||
self::Write('Error | '.$sText);
|
||||
}
|
||||
|
||||
|
||||
protected static function IsLogEnabled() {
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
if (is_null($oConfig)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$bLogTransactions = $oConfig->Get('log_transactions');
|
||||
if (true === $bLogTransactions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static function Write($sText)
|
||||
{
|
||||
$bLogEnabled = MetaModel::GetConfig()->Get('log_transactions');
|
||||
if ($bLogEnabled)
|
||||
{
|
||||
if (false === static::IsLogEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hLogFile = @fopen(APPROOT.'log/transactions.log', 'a');
|
||||
if ($hLogFile !== false)
|
||||
{
|
||||
if ($hLogFile !== false) {
|
||||
flock($hLogFile, LOCK_EX);
|
||||
$sDate = date('Y-m-d H:i:s');
|
||||
fwrite($hLogFile, "$sDate | $sText\n");
|
||||
fflush($hLogFile);
|
||||
flock($hLogFile, LOCK_UN);
|
||||
fclose($hLogFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace Combodo\iTop;
|
||||
use AttributeDateTime;
|
||||
use Dict;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
use Twig_Environment;
|
||||
use Twig_SimpleFilter;
|
||||
use Twig_SimpleFunction;
|
||||
@@ -115,14 +114,6 @@ class TwigExtension
|
||||
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 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)
|
||||
|
||||
@@ -71,7 +71,11 @@ class UIExtKeyWidget
|
||||
protected $bSearchMode;
|
||||
|
||||
//public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
|
||||
static public function DisplayFromAttCode($oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false)
|
||||
|
||||
/**
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Add default value for $aArgs for PHP 8.0 compat
|
||||
*/
|
||||
public static function DisplayFromAttCode($oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs = [], $bSearchMode = false)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
@@ -372,10 +376,10 @@ EOF
|
||||
$sHTML .= "</form>\n";
|
||||
$sHTML .= '</div></div>';
|
||||
|
||||
$sDialogTitle = addslashes($sTitle);
|
||||
$sDialogTitleSanitized = addslashes(utils::HtmlToText($sTitle));
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#ac_dlg_{$this->iId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });
|
||||
$('#ac_dlg_{$this->iId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$sDialogTitleSanitized', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });
|
||||
$('#fs_{$this->iId}').bind('submit.uiAutocomplete', oACWidget_{$this->iId}.DoSearchObjects);
|
||||
$('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes);
|
||||
EOF
|
||||
@@ -426,8 +430,10 @@ EOF
|
||||
*
|
||||
* @throws CoreException
|
||||
* @throws OQLException
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $oObj for PHP 8.0 compatibility
|
||||
*/
|
||||
public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null)
|
||||
public function AutoComplete(WebPage $oP, $sFilter, $oObj, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null)
|
||||
{
|
||||
if (is_null($sFilter))
|
||||
{
|
||||
@@ -636,15 +642,22 @@ HTML
|
||||
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
|
||||
$sHKAttCode = MetaModel::IsHierarchicalClass($this->sTargetClass);
|
||||
$this->DumpTree($oPage, $oSet, $sHKAttCode, $currValue);
|
||||
$bHasChildLeafs = $this->DumpTree($oPage, $oSet, $sHKAttCode, $currValue);
|
||||
|
||||
$oPage->add('</td></tr></table>');
|
||||
$oPage->add('</div>');
|
||||
|
||||
if ($bHasChildLeafs)
|
||||
{
|
||||
$oPage->add('<div class="treecontrol" id="treecontrolid"><a href="?#">'.Dict::S("UI:Treeview:CollapseAll").'</a> | <a href="?#">'.Dict::S("UI:Treeview:ExpandAll").'</a></div>');
|
||||
}
|
||||
|
||||
$oPage->add("<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_tree_{$this->iId}').dialog('close');\"> ");
|
||||
$oPage->add("<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoHKOk();\">");
|
||||
|
||||
$oPage->add('</div></div>');
|
||||
$oPage->add_ready_script("\$('#tree_$this->iId ul').treeview();\n");
|
||||
|
||||
$oPage->add_ready_script("\$('#tree_$this->iId ul').treeview({ control: '#treecontrolid', persist: 'false'});\n");
|
||||
$oPage->add_ready_script("\$('#dlg_tree_$this->iId').dialog({ width: 'auto', height: 'auto', autoOpen: true, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.OnHKResize, close: oACWidget_{$this->iId}.OnHKClose });\n");
|
||||
}
|
||||
|
||||
@@ -673,6 +686,18 @@ HTML
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oP
|
||||
* @param \DBObjectSet $oSet
|
||||
* @param string $sParentAttCode
|
||||
* @param string $currValue
|
||||
*
|
||||
* @return bool true if there are at least one child leaf, false if only roots nodes are present
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
function DumpTree($oP, $oSet, $sParentAttCode, $currValue)
|
||||
{
|
||||
$aTree = array();
|
||||
@@ -701,6 +726,9 @@ HTML
|
||||
{
|
||||
$this->DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue);
|
||||
}
|
||||
|
||||
$bHasOnlyRootNodes = (count($aTree) === 1);
|
||||
return !$bHasOnlyRootNodes;
|
||||
}
|
||||
|
||||
function DumpNodes($oP, $iRootId, $aTree, $aNodes, $currValue)
|
||||
@@ -728,7 +756,7 @@ HTML
|
||||
$sSelect = '<input id="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'" type="radio" value="'.$aNodes[$id]->GetKey().'" name="selectObject" '.$sChecked.'> ';
|
||||
}
|
||||
}
|
||||
$oP->add('<li>'.$sSelect.'<label for="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'">'.$aNodes[$id]->GetName().'</label>');
|
||||
$oP->add('<li class="closed">'.$sSelect.'<label for="input_'.$fUniqueId.'_'.$aNodes[$id]->GetKey().'">'.$aNodes[$id]->GetName().'</label>');
|
||||
$this->DumpNodes($oP, $id, $aTree, $aNodes, $currValue);
|
||||
$oP->add("</li>\n");
|
||||
}
|
||||
|
||||
@@ -85,9 +85,15 @@ class UILinksWidgetDirect
|
||||
* @param array $aArgs
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (handling wrong values at method start)
|
||||
*/
|
||||
public function Display(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
|
||||
public function Display(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj)
|
||||
{
|
||||
if (empty($aArgs)) {
|
||||
$aArgs = [];
|
||||
}
|
||||
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
switch($oLinksetDef->GetEditMode())
|
||||
{
|
||||
@@ -137,8 +143,10 @@ class UILinksWidgetDirect
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param bool $bDisplayMenu
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (protected method, always called with default value)
|
||||
*/
|
||||
protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
{
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$sTargetClass = $oLinksetDef->GetLinkedClass();
|
||||
@@ -239,8 +247,10 @@ class UILinksWidgetDirect
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param array $aButtons
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (protected method, caller already handles it)
|
||||
*/
|
||||
protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
{
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -482,13 +482,75 @@ class WebPage implements Page
|
||||
$this->a_headers[] = $s_header;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $sXFrameOptionsHeaderValue passed to {@see add_xframe_options}
|
||||
*
|
||||
* @return void
|
||||
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°4368 method creation, replace {@see add_xframe_options} consumers call
|
||||
*/
|
||||
public function add_http_headers($sXFrameOptionsHeaderValue = null)
|
||||
{
|
||||
$this->add_xframe_options($sXFrameOptionsHeaderValue);
|
||||
$this->add_xcontent_type_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $sHeaderValue for example `SAMESITE`. If null will set the header using the `security_header_xframe` config parameter value.
|
||||
*
|
||||
* @since 2.7.3 3.0.0 N°3416
|
||||
* @uses \utils::GetConfig()
|
||||
*
|
||||
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options HTTP header MDN documentation
|
||||
*/
|
||||
public function add_xframe_options($sHeaderValue = null)
|
||||
{
|
||||
if (is_null($sHeaderValue)) {
|
||||
$sHeaderValue = utils::GetConfig()->Get('security_header_xframe');
|
||||
}
|
||||
|
||||
$this->add_header('X-Frame-Options: '.$sHeaderValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning : this header will trigger the Cross-Origin Read Blocking (CORB) protection for some mime types (HTML, XML except SVG, JSON, text/plain)
|
||||
* In consequence some children pages will override this method.
|
||||
*
|
||||
* Sending header can be disabled globally using the `security.enable_header_xcontent_type_options` optional config parameter.
|
||||
*
|
||||
* @return void
|
||||
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°4368 method creation
|
||||
*
|
||||
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options HTTP header MDN documentation
|
||||
* @link https://chromium.googlesource.com/chromium/src/+/master/services/network/cross_origin_read_blocking_explainer.md#determining-whether-a-response-is-corb_protected "Determining whether a response is CORB-protected"
|
||||
*/
|
||||
public function add_xcontent_type_options()
|
||||
{
|
||||
try {
|
||||
$oConfig = utils::GetConfig();
|
||||
} catch (ConfigException|CoreException $e) {
|
||||
$oConfig = null;
|
||||
}
|
||||
if (is_null($oConfig)) {
|
||||
$bSendXContentTypeOptionsHttpHeader = true;
|
||||
} else {
|
||||
$bSendXContentTypeOptionsHttpHeader = $oConfig->Get('security.enable_header_xcontent_type_options');
|
||||
}
|
||||
|
||||
if ($bSendXContentTypeOptionsHttpHeader === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->add_header('X-Content-Type-Options: nosniff');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add needed headers to the page so that it will no be cached
|
||||
*/
|
||||
public function no_cache()
|
||||
{
|
||||
$this->add_header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
|
||||
$this->add_header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past
|
||||
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
|
||||
$this->add_header('Pragma: no-cache');
|
||||
$this->add_header('Expires: 0');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1546,6 +1608,12 @@ class TabManager
|
||||
{
|
||||
unset($aTabs['tabs'][$sTabCode]);
|
||||
}
|
||||
|
||||
// N°3320: Do not display empty tabs
|
||||
if (empty($aTabData['html']) && empty($aTabData['url']))
|
||||
{
|
||||
unset($aTabs['tabs'][$sTabCode]);
|
||||
}
|
||||
}
|
||||
|
||||
// Render tabs
|
||||
@@ -1584,7 +1652,7 @@ EOF
|
||||
$sTabs .= "<div class=\"printable-tab\" id=\"$sTabId\"><h2 class=\"printable-tab-title\">$sTabTitleForHtml</h2><div class=\"printable-tab-content\">".$sTabHtml."</div></div>\n";
|
||||
$oPage->add_ready_script(
|
||||
<<< EOF
|
||||
oHiddeableChapters['$sTabId'] = '$sTabCodeForJs';
|
||||
oHiddeableChapters['$sTabId'] = '$sTabTitleForHtml';
|
||||
EOF
|
||||
);
|
||||
$i++;
|
||||
|
||||
@@ -43,9 +43,20 @@ class XMLPage extends WebPage
|
||||
$this->m_bPassThrough = $bPassThrough;
|
||||
$this->m_bHeaderSent = false;
|
||||
$this->add_header("Content-type: text/xml; charset=".self::PAGES_CHARSET);
|
||||
$this->add_header("Cache-control: no-cache");
|
||||
$this->no_cache();
|
||||
$this->add_http_headers();
|
||||
$this->add_header("Content-location: export.xml");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabling sending the header so that resource won't be blocked by CORB. See parent method documentation.
|
||||
* @return void
|
||||
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°4368 method creation
|
||||
*/
|
||||
public function add_xcontent_type_options()
|
||||
{
|
||||
// Nothing to do !
|
||||
}
|
||||
|
||||
public function output()
|
||||
{
|
||||
|
||||
@@ -3,4 +3,18 @@
|
||||
define('APPROOT', dirname(__FILE__).'/');
|
||||
define('APPCONF', APPROOT.'conf/');
|
||||
|
||||
|
||||
/**
|
||||
* Constant containing the iTop core version, whatever application was built
|
||||
*
|
||||
* Note that in iTop 3.0.0 we used {@see ITOP_DESIGN_LATEST_VERSION} to get core version.
|
||||
* When releasing, both constants should be updated : see `.make/release/update-versions.php` for that !
|
||||
*
|
||||
* @since 2.7.7 3.0.1 3.1.0 N°4714 constant creation
|
||||
* @used-by utils::GetItopVersionWikiSyntax()
|
||||
* @used-by iTopModulesPhpVersionIntegrationTest
|
||||
*/
|
||||
define('ITOP_CORE_VERSION', '2.7.12');
|
||||
|
||||
|
||||
require_once APPROOT.'bootstrap.inc.php';
|
||||
|
||||
@@ -22,14 +22,8 @@ define('ITOP_DEFAULT_ENV', 'production');
|
||||
define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
|
||||
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
|
||||
|
||||
if (function_exists('microtime'))
|
||||
{
|
||||
$fItopStarted = microtime(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$fItopStarted = 1000 * time();
|
||||
}
|
||||
$fItopStarted = microtime(true);
|
||||
$iItopInitialMemory = memory_get_usage(true);
|
||||
|
||||
if (! isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false)
|
||||
{
|
||||
@@ -54,7 +48,7 @@ if (file_exists(MAINTENANCE_MODE_FILE) && !$bBypassMaintenance)
|
||||
http_response_code(503);
|
||||
// Display message depending on the request
|
||||
include(APPROOT.'application/maintenancemsg.php');
|
||||
$sSAPIName = strtoupper(trim(php_sapi_name()));
|
||||
$sSAPIName = strtoupper(trim(PHP_SAPI));
|
||||
|
||||
switch (true)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"name": "combodo/itop",
|
||||
"description": "IT Operations Portal",
|
||||
"type": "project",
|
||||
"license": "AGPLv3",
|
||||
"license": "AGPL-3.0-only",
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"php": ">=7.1.3",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
@@ -10,22 +12,29 @@
|
||||
"ext-json": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-soap": "*",
|
||||
"combodo/tcpdf": "6.3.4",
|
||||
"nikic/php-parser": "^3.1",
|
||||
"pear/archive_tar": "1.4.9",
|
||||
"pelago/emogrifier": "2.1.0",
|
||||
"combodo/tcpdf": "~6.4.4",
|
||||
"firebase/php-jwt": "~6.4.0",
|
||||
"guzzlehttp/guzzle": "^6.5.8",
|
||||
"guzzlehttp/psr7": "~1.9.1",
|
||||
"laminas/laminas-mail": "^2.11",
|
||||
"laminas/laminas-servicemanager": "^3.5",
|
||||
"league/oauth2-google": "^3.0",
|
||||
"nikic/php-parser": "~4.13.2",
|
||||
"pear/archive_tar": "~1.4.14",
|
||||
"pelago/emogrifier": "~3.1.0",
|
||||
"scssphp/scssphp": "1.0.6",
|
||||
"swiftmailer/swiftmailer": "5.4.12",
|
||||
"symfony/console": "3.4.*",
|
||||
"symfony/dotenv": "3.4.*",
|
||||
"symfony/framework-bundle": "3.4.*",
|
||||
"symfony/polyfill-php70": "1.*",
|
||||
"symfony/twig-bundle": "3.4.*",
|
||||
"symfony/yaml": "3.4.*"
|
||||
"swiftmailer/swiftmailer": "~6.3.0",
|
||||
"symfony/console": "~3.4.47",
|
||||
"symfony/dotenv": "~3.4.47",
|
||||
"symfony/framework-bundle": "~3.4.47",
|
||||
"symfony/twig-bundle": "~3.4.47",
|
||||
"symfony/yaml": "~3.4.47",
|
||||
"thenetworg/oauth2-azure": "^2.0",
|
||||
"twig/twig": "~1.42.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/stopwatch": "3.4.*",
|
||||
"symfony/web-profiler-bundle": "3.4.*"
|
||||
"symfony/stopwatch": "~3.4.47",
|
||||
"symfony/web-profiler-bundle": "~3.4.47"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Required to use the AttributeEncryptedString.",
|
||||
@@ -37,7 +46,7 @@
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "5.6.0"
|
||||
"php": "7.1.3"
|
||||
},
|
||||
"vendor-dir": "lib",
|
||||
"preferred-install": {
|
||||
@@ -51,7 +60,10 @@
|
||||
"core",
|
||||
"application",
|
||||
"sources/application",
|
||||
"sources/Composer"
|
||||
"sources/Composer",
|
||||
"sources/Controller",
|
||||
"sources/Service",
|
||||
"sources/Core"
|
||||
],
|
||||
"exclude-from-classmap": [
|
||||
"core/dbobjectsearch.class.php",
|
||||
|
||||
2856
composer.lock
generated
2856
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.web>
|
||||
<authorization>
|
||||
<deny users="*" /> <!-- Denies all users -->
|
||||
</authorization>
|
||||
</system.web>
|
||||
<system.webServer>
|
||||
<security>
|
||||
<requestFiltering>
|
||||
<fileExtensions applyToWebDAV="false" allowUnlisted="false"></fileExtensions>
|
||||
</requestFiltering>
|
||||
<authorization>
|
||||
<deny users="*" /> <!-- Denies all users -->
|
||||
</authorization>
|
||||
</security>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
@@ -67,8 +67,7 @@ class MyHelpers
|
||||
// format sss.mmmuuupppnnn
|
||||
public static function getmicrotime()
|
||||
{
|
||||
list($usec, $sec) = explode(" ",microtime());
|
||||
return ((float)$usec + (float)$sec);
|
||||
return microtime(true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -420,6 +419,7 @@ class MyHelpers
|
||||
//}
|
||||
return $sOutput;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -524,5 +524,3 @@ class Str
|
||||
return (strtolower($sString) == $sString);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -179,7 +179,7 @@ class ActionEmail extends ActionNotification
|
||||
protected function FindRecipients($sRecipAttCode, $aArgs)
|
||||
{
|
||||
$sOQL = $this->Get($sRecipAttCode);
|
||||
if (strlen($sOQL) == '') return '';
|
||||
if (strlen($sOQL) === 0) return '';
|
||||
|
||||
try
|
||||
{
|
||||
@@ -314,42 +314,58 @@ class ActionEmail extends ActionNotification
|
||||
{
|
||||
$this->m_iRecipients = 0;
|
||||
$this->m_aMailErrors = array();
|
||||
$bRes = false; // until we do succeed in sending the email
|
||||
|
||||
|
||||
// Determine recicipients
|
||||
//
|
||||
$sTo = $this->FindRecipients('to', $aContextArgs);
|
||||
$sCC = $this->FindRecipients('cc', $aContextArgs);
|
||||
$sBCC = $this->FindRecipients('bcc', $aContextArgs);
|
||||
|
||||
|
||||
$sFrom = MetaModel::ApplyParams($this->Get('from'), $aContextArgs);
|
||||
$sReplyTo = MetaModel::ApplyParams($this->Get('reply_to'), $aContextArgs);
|
||||
|
||||
|
||||
$sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
|
||||
$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
|
||||
|
||||
|
||||
$oObj = $aContextArgs['this->object()'];
|
||||
$sMessageId = sprintf('iTop_%s_%d_%f@%s.openitop.org', get_class($oObj), $oObj->GetKey(), microtime(true /* get as float*/), MetaModel::GetEnvironmentId());
|
||||
$sMessageId = sprintf('iTop_%s_%d_%F@%s.openitop.org',
|
||||
get_class($oObj),
|
||||
$oObj->GetKey(),
|
||||
microtime(true /* get as float*/),
|
||||
MetaModel::GetEnvironmentId()
|
||||
);
|
||||
$sReference = '<'.$sMessageId.'>';
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
|
||||
throw $e;
|
||||
}
|
||||
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
|
||||
|
||||
if (!is_null($oLog))
|
||||
{
|
||||
catch (Exception $e) {
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
throw $e;
|
||||
}
|
||||
finally {
|
||||
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
|
||||
}
|
||||
|
||||
if (!is_null($oLog)) {
|
||||
// Note: we have to secure this because those values are calculated
|
||||
// inside the try statement, and we would like to keep track of as
|
||||
// many data as we could while some variables may still be undefined
|
||||
if (isset($sTo)) $oLog->Set('to', $sTo);
|
||||
if (isset($sCC)) $oLog->Set('cc', $sCC);
|
||||
if (isset($sBCC)) $oLog->Set('bcc', $sBCC);
|
||||
if (isset($sFrom)) $oLog->Set('from', $sFrom);
|
||||
if (isset($sSubject)) $oLog->Set('subject', $sSubject);
|
||||
if (isset($sBody)) $oLog->Set('body', $sBody);
|
||||
if (isset($sTo)) {
|
||||
$oLog->Set('to', $sTo);
|
||||
}
|
||||
if (isset($sCC)) {
|
||||
$oLog->Set('cc', $sCC);
|
||||
}
|
||||
if (isset($sBCC)) {
|
||||
$oLog->Set('bcc', $sBCC);
|
||||
}
|
||||
if (isset($sFrom)) {
|
||||
$oLog->Set('from', $sFrom);
|
||||
}
|
||||
if (isset($sSubject)) {
|
||||
$oLog->Set('subject', $sSubject);
|
||||
}
|
||||
if (isset($sBody)) {
|
||||
$oLog->Set('body', $sBody);
|
||||
}
|
||||
}
|
||||
$sStyles = file_get_contents(APPROOT.'css/email.css');
|
||||
$sStyles .= MetaModel::GetConfig()->Get('email_css');
|
||||
@@ -439,4 +455,3 @@ class ActionEmail extends ActionNotification
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
42
core/apc-service.class.inc.php
Normal file
42
core/apc-service.class.inc.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ApcService
|
||||
* @since 2.7.6 N°4125
|
||||
*/
|
||||
class ApcService {
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function_name
|
||||
* @return bool
|
||||
* @see function_exists()
|
||||
*/
|
||||
public function function_exists($function_name) {
|
||||
return function_exists($function_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $key
|
||||
* @return mixed
|
||||
* @see apc_fetch()
|
||||
*/
|
||||
function apc_fetch($key)
|
||||
{
|
||||
return apc_fetch($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $key
|
||||
* @param $var
|
||||
* @param int $ttl
|
||||
* @return array|bool
|
||||
* @see apc_store()
|
||||
*/
|
||||
function apc_store($key, $var = NULL, $ttl = 0)
|
||||
{
|
||||
return apc_store($key, $var, $ttl);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -230,7 +230,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());
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Form\Field\LabelField;
|
||||
use Combodo\iTop\Form\Field\TextAreaField;
|
||||
use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator;
|
||||
use Combodo\iTop\Form\Validator\Validator;
|
||||
|
||||
@@ -102,6 +103,14 @@ define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu
|
||||
define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place
|
||||
define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place
|
||||
|
||||
/**
|
||||
* Attributes implementing this interface won't be accepted as `group by` field
|
||||
* @since 2.7.4 N°3473
|
||||
*/
|
||||
interface iAttributeNoGroupBy
|
||||
{
|
||||
//no method, just a contract on implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.)
|
||||
@@ -129,7 +138,7 @@ abstract class AttributeDefinition
|
||||
|
||||
protected $aCSSClasses;
|
||||
|
||||
public function GetType()
|
||||
public function GetType()
|
||||
{
|
||||
return Dict::S('Core:'.get_class($this));
|
||||
}
|
||||
@@ -2686,6 +2695,11 @@ class AttributeObjectKey extends AttributeDBFieldVoid
|
||||
return ($proposedValue == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param int|DBObject $proposedValue Object key or valid ({@see MetaModel::IsValidObject()}) datamodel object
|
||||
*/
|
||||
public function MakeRealValue($proposedValue, $oHostObj)
|
||||
{
|
||||
if (is_null($proposedValue))
|
||||
@@ -2698,7 +2712,6 @@ class AttributeObjectKey extends AttributeDBFieldVoid
|
||||
}
|
||||
if (MetaModel::IsValidObject($proposedValue))
|
||||
{
|
||||
/** @var \DBObject $proposedValue */
|
||||
return $proposedValue->GetKey();
|
||||
}
|
||||
|
||||
@@ -3760,9 +3773,9 @@ class AttributeFinalClass extends AttributeString
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class AttributePassword extends AttributeString
|
||||
class AttributePassword extends AttributeString implements iAttributeNoGroupBy
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
/**
|
||||
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
|
||||
@@ -3836,9 +3849,9 @@ class AttributePassword extends AttributeString
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class AttributeEncryptedString extends AttributeString
|
||||
class AttributeEncryptedString extends AttributeString implements iAttributeNoGroupBy
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
static $sKey = null; // Encryption key used for all encrypted fields
|
||||
static $sLibrary = null; // Encryption library used for all encrypted fields
|
||||
@@ -5931,6 +5944,11 @@ class AttributeDateTime extends AttributeDBField
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param int|string $proposedValue timestamp ({@see DateTime::getTimestamp()) or date as string, following the {@see GetInternalFormat} format.
|
||||
*/
|
||||
public function MakeRealValue($proposedValue, $oHostObj)
|
||||
{
|
||||
if (is_null($proposedValue))
|
||||
@@ -7449,6 +7467,12 @@ class AttributeExternalField extends AttributeDefinition
|
||||
}
|
||||
}
|
||||
parent::MakeFormField($oObject, $oFormField);
|
||||
if ($oFormField instanceof TextAreaField) {
|
||||
if (method_exists($oRemoteAttDef, 'GetFormat')) {
|
||||
/** @var \Combodo\iTop\Form\Field\TextAreaField $oFormField */
|
||||
$oFormField->SetFormat($oRemoteAttDef->GetFormat());
|
||||
}
|
||||
}
|
||||
|
||||
// Manually setting for remote ExternalKey, otherwise, the id would be displayed.
|
||||
if ($oRemoteAttDef instanceof AttributeExternalKey)
|
||||
@@ -7466,6 +7490,16 @@ class AttributeExternalField extends AttributeDefinition
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function GetFormat()
|
||||
{
|
||||
$oRemoteAttDef = $this->GetExtAttDef();
|
||||
if (method_exists($oRemoteAttDef, 'GetFormat')) {
|
||||
/** @var \Combodo\iTop\Form\Field\TextAreaField $oFormField */
|
||||
return $oRemoteAttDef->GetFormat();
|
||||
}
|
||||
return 'text';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7630,9 +7664,9 @@ class AttributeBlob extends AttributeDefinition
|
||||
}
|
||||
|
||||
/**
|
||||
* Users can provide the document from an URL (including an URL on iTop itself)
|
||||
* for CSV import. Administrators can even provide the path to a local file
|
||||
* {@inheritDoc}
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param string $proposedValue Can be an URL (including an URL to iTop itself), or a local path (CSV import)
|
||||
*
|
||||
* @see AttributeDefinition::MakeRealValue()
|
||||
*/
|
||||
@@ -7715,10 +7749,17 @@ class AttributeBlob extends AttributeDefinition
|
||||
// (temporary tables created on disk)
|
||||
// We will have to remove the blobs from the list of attributes when doing the select
|
||||
// then the use of Get() should finalize the load
|
||||
if ($value instanceOf ormDocument && !$value->IsEmpty())
|
||||
if ($value instanceOf ormDocument)
|
||||
{
|
||||
$aValues = array();
|
||||
$aValues[$this->GetCode().'_data'] = $value->GetData();
|
||||
if (!$value->IsEmpty())
|
||||
{
|
||||
$aValues[$this->GetCode().'_data'] = $value->GetData();
|
||||
}
|
||||
else
|
||||
{
|
||||
$aValues[$this->GetCode().'_data'] = '';
|
||||
}
|
||||
$aValues[$this->GetCode().'_mimetype'] = $value->GetMimeType();
|
||||
$aValues[$this->GetCode().'_filename'] = $value->GetFileName();
|
||||
}
|
||||
@@ -7946,6 +7987,13 @@ class AttributeImage extends AttributeBlob
|
||||
{
|
||||
$oDoc = parent::MakeRealValue($proposedValue, $oHostObj);
|
||||
|
||||
if (($oDoc instanceof ormDocument)
|
||||
&& (false === $oDoc->IsEmpty())
|
||||
&& ($oDoc->GetMimeType() === 'image/svg+xml')) {
|
||||
$sCleanSvg = HTMLSanitizer::Sanitize($oDoc->GetData(), 'svg_sanitizer');
|
||||
$oDoc = new ormDocument($sCleanSvg, $oDoc->GetMimeType(), $oDoc->GetFileName());
|
||||
}
|
||||
|
||||
// The validation of the MIME Type is done by CheckFormat below
|
||||
return $oDoc;
|
||||
}
|
||||
@@ -9193,9 +9241,9 @@ class AttributeSubItem extends AttributeDefinition
|
||||
/**
|
||||
* One way encrypted (hashed) password
|
||||
*/
|
||||
class AttributeOneWayPassword extends AttributeDefinition
|
||||
class AttributeOneWayPassword extends AttributeDefinition implements iAttributeNoGroupBy
|
||||
{
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
|
||||
|
||||
/**
|
||||
* Useless constructor, but if not present PHP 7.4.0/7.4.1 is crashing :( (N°2329)
|
||||
@@ -9634,7 +9682,7 @@ class AttributePropertySet extends AttributeTable
|
||||
$sValue = '*****';
|
||||
}
|
||||
$sRes .= "<TR>";
|
||||
$sCell = str_replace("\n", "<br>\n", Str::pure2html((string)$sValue));
|
||||
$sCell = str_replace("\n", "<br>\n", Str::pure2html(@(string)$sValue));
|
||||
$sRes .= "<TD class=\"label\">$sProperty</TD><TD>$sCell</TD>";
|
||||
$sRes .= "</TR>";
|
||||
}
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 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/>
|
||||
|
||||
|
||||
/**
|
||||
* interface iProcess
|
||||
* Something that can be executed
|
||||
* Copyright (C) 2010-2020 Combodo SARL
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
||||
interface iProcess
|
||||
{
|
||||
/**
|
||||
@@ -78,7 +72,7 @@ interface iScheduledProcess extends iProcess
|
||||
* * week_days
|
||||
* * time
|
||||
*
|
||||
* Param names and some of their default values are in constant that can be overriden.
|
||||
* Param names and some of their default values are in constant that can be overridden.
|
||||
*
|
||||
* Other info (module name and time default value) should be provided using a method that needs to be implemented.
|
||||
*
|
||||
@@ -87,13 +81,18 @@ interface iScheduledProcess extends iProcess
|
||||
*/
|
||||
abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
|
||||
{
|
||||
// param have default names/values but can be overriden
|
||||
// param have default names/values but can be overridden
|
||||
const MODULE_SETTING_ENABLED = 'enabled';
|
||||
const DEFAULT_MODULE_SETTING_ENABLED = true;
|
||||
const MODULE_SETTING_WEEKDAYS = 'week_days';
|
||||
const DEFAULT_MODULE_SETTING_WEEKDAYS = 'monday, tuesday, wednesday, thursday, friday, saturday, sunday';
|
||||
const MODULE_SETTING_TIME = 'time';
|
||||
|
||||
/**
|
||||
* @var Config can be used to mock config for tests
|
||||
*/
|
||||
protected $oConfig;
|
||||
|
||||
/**
|
||||
* Module must be declared in each implementation
|
||||
*
|
||||
@@ -107,6 +106,20 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
|
||||
*/
|
||||
abstract protected function GetDefaultModuleSettingTime();
|
||||
|
||||
/**
|
||||
* @return \Config
|
||||
*/
|
||||
public function getOConfig()
|
||||
{
|
||||
if (!isset($this->oConfig))
|
||||
{
|
||||
$this->oConfig = MetaModel::GetConfig();
|
||||
}
|
||||
|
||||
return $this->oConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interpret current setting for the week days
|
||||
*
|
||||
@@ -125,7 +138,7 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
|
||||
'sunday' => 7,
|
||||
);
|
||||
$aDays = array();
|
||||
$sWeekDays = MetaModel::GetConfig()->GetModuleSetting(
|
||||
$sWeekDays = $this->getOConfig()->GetModuleSetting(
|
||||
$this->GetModuleName(),
|
||||
static::MODULE_SETTING_WEEKDAYS,
|
||||
static::DEFAULT_MODULE_SETTING_WEEKDAYS
|
||||
@@ -158,21 +171,26 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the exact time at which the process must be run next time
|
||||
* @param string $sCurrentTime Date string to extract time dependency
|
||||
* this parameter is not present in the interface but as it is optional it's ok
|
||||
*
|
||||
* @return DateTime
|
||||
* @throws Exception
|
||||
* @return DateTime the exact time at which the process must be run next time
|
||||
* @throws \ProcessInvalidConfigException
|
||||
*/
|
||||
public function GetNextOccurrence()
|
||||
public function GetNextOccurrence($sCurrentTime = 'now')
|
||||
{
|
||||
$bEnabled = MetaModel::GetConfig()->GetModuleSetting(
|
||||
$bEnabled = $this->getOConfig()->GetModuleSetting(
|
||||
$this->GetModuleName(),
|
||||
static::MODULE_SETTING_ENABLED,
|
||||
static::DEFAULT_MODULE_SETTING_ENABLED
|
||||
);
|
||||
|
||||
$sItopTimeZone = $this->getOConfig()->Get('timezone');
|
||||
$timezone = new DateTimeZone($sItopTimeZone);
|
||||
|
||||
if (!$bEnabled)
|
||||
{
|
||||
return new DateTime('3000-01-01');
|
||||
return new DateTime('3000-01-01', $timezone);
|
||||
}
|
||||
|
||||
// 1st - Interpret the list of days as ordered numbers (monday = 1)
|
||||
@@ -181,7 +199,7 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
|
||||
|
||||
// 2nd - Find the next active week day
|
||||
//
|
||||
$sProcessTime = MetaModel::GetConfig()->GetModuleSetting(
|
||||
$sProcessTime = $this->getOConfig()->GetModuleSetting(
|
||||
$this->GetModuleName(),
|
||||
static::MODULE_SETTING_TIME,
|
||||
static::GetDefaultModuleSettingTime()
|
||||
@@ -190,7 +208,8 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
|
||||
{
|
||||
throw new ProcessInvalidConfigException($this->GetModuleName().": wrong format for setting '".static::MODULE_SETTING_TIME."' (found '$sProcessTime')");
|
||||
}
|
||||
$oNow = new DateTime();
|
||||
|
||||
$oNow = new DateTime($sCurrentTime, $timezone);
|
||||
$iNextPos = false;
|
||||
$sDay = $oNow->format('N');
|
||||
for ($iDay = (int) $sDay; $iDay <= 7; $iDay++)
|
||||
@@ -225,6 +244,7 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
|
||||
$oRet->modify('+'.$iMove.' days');
|
||||
}
|
||||
list($sHours, $sMinutes) = explode(':', $sProcessTime);
|
||||
/** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection non used new parameter in PHP 7.1 */
|
||||
$oRet->setTime((int)$sHours, (int)$sMinutes);
|
||||
|
||||
return $oRet;
|
||||
@@ -243,13 +263,14 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
|
||||
/**
|
||||
* Exception for {@link iProcess} implementations.<br>
|
||||
* An error happened during the processing but we can go on with the next implementations.
|
||||
* @since 2.5.0 N°1195
|
||||
*/
|
||||
class ProcessException extends CoreException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7.0
|
||||
* @since 2.7.0 PR #89
|
||||
*/
|
||||
class ProcessInvalidConfigException extends ProcessException
|
||||
{
|
||||
@@ -259,6 +280,7 @@ class ProcessInvalidConfigException extends ProcessException
|
||||
* Class ProcessFatalException
|
||||
* Exception for iProcess implementations.<br>
|
||||
* A big error occurred, we have to stop the iProcess processing.
|
||||
* @since 2.5.0 N°1195
|
||||
*/
|
||||
class ProcessFatalException extends CoreException
|
||||
{
|
||||
|
||||
@@ -26,6 +26,24 @@
|
||||
*/
|
||||
class BackgroundTask extends DBObject
|
||||
{
|
||||
protected $bDebug = false;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function IsDebug()
|
||||
{
|
||||
return $this->bDebug;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $bDebug
|
||||
*/
|
||||
public function SetDebug($bDebug)
|
||||
{
|
||||
$this->bDebug = $bDebug;
|
||||
}
|
||||
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
|
||||
@@ -309,7 +309,7 @@ class BulkChange
|
||||
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
}
|
||||
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
|
||||
$aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]);
|
||||
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
|
||||
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
||||
@@ -363,6 +363,7 @@ class BulkChange
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
{
|
||||
// Default reporting
|
||||
// $aRowData[$iCol] is always null
|
||||
$aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]);
|
||||
}
|
||||
if ($oExtKey->IsNullAllowed())
|
||||
@@ -395,7 +396,7 @@ class BulkChange
|
||||
}
|
||||
$aCacheKeys[] = $value;
|
||||
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
|
||||
$aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]);
|
||||
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
|
||||
$iForeignKey = null;
|
||||
@@ -465,7 +466,7 @@ class BulkChange
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
{
|
||||
// Report the change on reconciliation values as well
|
||||
$aResults[$iCol] = new CellStatus_Modify($aRowData[$iCol]);
|
||||
$aResults[$iCol] = new CellStatus_Modify(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,7 +539,7 @@ class BulkChange
|
||||
{
|
||||
if ($sAttCode == 'id')
|
||||
{
|
||||
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
|
||||
$aResults[$iCol]= new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -554,7 +555,7 @@ class BulkChange
|
||||
}
|
||||
if (isset($aErrors[$sAttCode]))
|
||||
{
|
||||
$aResults[$iCol]= new CellStatus_Issue($aRowData[$iCol], $sOrigValue, $aErrors[$sAttCode]);
|
||||
$aResults[$iCol]= new CellStatus_Issue(utils::HtmlEntities($aRowData[$iCol]), $sOrigValue, $aErrors[$sAttCode]);
|
||||
}
|
||||
elseif (array_key_exists($sAttCode, $aChangedFields))
|
||||
{
|
||||
@@ -577,7 +578,7 @@ class BulkChange
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
|
||||
$aResults[$iCol]= new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -924,7 +925,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, $this->m_aData[$iRow][$iCol], 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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1082,7 +1083,7 @@ class BulkChange
|
||||
{
|
||||
if (!array_key_exists($iCol, $aResult[$iRow]))
|
||||
{
|
||||
$aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]);
|
||||
$aResult[$iRow][$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
}
|
||||
foreach($this->m_aExtKeys as $sAttCode => $aForeignAtts)
|
||||
@@ -1096,7 +1097,7 @@ class BulkChange
|
||||
if (!array_key_exists($iCol, $aResult[$iRow]))
|
||||
{
|
||||
// The foreign attribute is one of our reconciliation key
|
||||
$aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]);
|
||||
$aResult[$iRow][$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,9 @@ abstract class BulkExport
|
||||
$this->oSearch = null;
|
||||
$this->iChunkSize = 0;
|
||||
$this->sFormatCode = null;
|
||||
$this->aStatusInfo = array();
|
||||
$this->aStatusInfo = [
|
||||
'show_obsolete_data' => utils::ShowObsoleteData(),
|
||||
];
|
||||
$this->oBulkExportResult = null;
|
||||
$this->sTmpFile = '';
|
||||
$this->bLocalizeOutput = false;
|
||||
@@ -203,15 +205,17 @@ abstract class BulkExport
|
||||
if ($oInfo && ($oInfo->Get('user_id') == UserRights::GetUserId()))
|
||||
{
|
||||
$sFormatCode = $oInfo->Get('format');
|
||||
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
|
||||
$aStatusInfo = json_decode($oInfo->Get('status_info'),true);
|
||||
|
||||
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
|
||||
$oSearch->SetShowObsoleteData($aStatusInfo['show_obsolete_data']);
|
||||
$oBulkExporter = self::FindExporter($sFormatCode, $oSearch);
|
||||
if ($oBulkExporter)
|
||||
{
|
||||
$oBulkExporter->SetFormat($sFormatCode);
|
||||
$oBulkExporter->SetObjectList($oSearch);
|
||||
$oBulkExporter->SetChunkSize($oInfo->Get('chunk_size'));
|
||||
$oBulkExporter->SetStatusInfo(json_decode($oInfo->Get('status_info'), true));
|
||||
$oBulkExporter->SetStatusInfo($aStatusInfo);
|
||||
|
||||
$oBulkExporter->SetLocalizeOutput($oInfo->Get('localize_output'));
|
||||
|
||||
@@ -289,6 +293,7 @@ abstract class BulkExport
|
||||
*/
|
||||
public function SetObjectList(DBSearch $oSearch)
|
||||
{
|
||||
$oSearch->SetShowObsoleteData($this->aStatusInfo['show_obsolete_data']);
|
||||
$this->oSearch = $oSearch;
|
||||
}
|
||||
|
||||
@@ -345,10 +350,10 @@ abstract class BulkExport
|
||||
$this->oBulkExportResult->Set('format', $this->sFormatCode);
|
||||
$this->oBulkExportResult->Set('search', $this->oSearch->serialize());
|
||||
$this->oBulkExportResult->Set('chunk_size', $this->iChunkSize);
|
||||
$this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile);
|
||||
$this->oBulkExportResult->Set('localize_output', $this->bLocalizeOutput);
|
||||
}
|
||||
$this->oBulkExportResult->Set('status_info', json_encode($this->GetStatusInfo()));
|
||||
$this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile);
|
||||
utils::PushArchiveMode(false);
|
||||
$ret = $this->oBulkExportResult->DBWrite();
|
||||
utils::PopArchiveMode();
|
||||
@@ -420,6 +425,11 @@ abstract class BulkExport
|
||||
public function GetStatistics()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function SetFields($sFields)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function GetDownloadFileName()
|
||||
|
||||
@@ -63,22 +63,30 @@ class CMDBChangeOp extends DBObject
|
||||
|
||||
/**
|
||||
* Describe (as a text string) the modifications corresponding to this change
|
||||
*/
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -544,7 +552,7 @@ class CMDBChangeOpSetAttributeEncrypted extends CMDBChangeOpSetAttribute
|
||||
// The attribute was renamed or removed from the object ?
|
||||
$sAttName = $this->Get('attcode');
|
||||
}
|
||||
$sPrevString = $this->Get('prevstring');
|
||||
$sPrevString = $this->GetAsHTML('prevstring');
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sPrevString);
|
||||
}
|
||||
return $sResult;
|
||||
|
||||
@@ -95,15 +95,45 @@ abstract class CMDBObject extends DBObject
|
||||
protected static $m_oCurrChange = null;
|
||||
protected static $m_sInfo = null; // null => the information is built in a standard way
|
||||
protected static $m_sOrigin = null; // null => the origin is 'interactive'
|
||||
|
||||
|
||||
/**
|
||||
* Specify another change (this is mainly for backward compatibility)
|
||||
* Specify the change to be used by the API to attach any CMDBChangeOp* object created
|
||||
*
|
||||
* @see SetTrackInfo if CurrentChange is null, then a new one will be create using trackinfo
|
||||
*
|
||||
* @param CMDBChange|null $oChange use null so that the API will recreate a new CMDBChange using TrackInfo & TrackOrigin
|
||||
* If providing a CMDBChange, you should persist it first ! Indeed the API will automatically create CMDBChangeOp (see
|
||||
* \CMDBObject::RecordObjCreation / RecordAttChange / RecordObjDeletion for example) and link them to the current change : in
|
||||
* consequence this CMDBChange must have a key set !
|
||||
*
|
||||
* @since 2.7.2 N°3219 can now reset CMDBChange by passing null
|
||||
* @since 2.7.2 N°3218 PHPDoc about persisting the $oChange parameter first
|
||||
*/
|
||||
public static function SetCurrentChange(CMDBChange $oChange)
|
||||
public static function SetCurrentChange($oChange)
|
||||
{
|
||||
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)
|
||||
@@ -112,7 +142,11 @@ abstract class CMDBObject extends DBObject
|
||||
// GetCurrentChange to create a default change if not already done in the current context
|
||||
//
|
||||
/**
|
||||
* Get a change record (create it if not existing)
|
||||
* @param bool $bAutoCreate if true calls {@link CreateChange} to get a new persisted object
|
||||
*
|
||||
* @return \CMDBChange
|
||||
*
|
||||
* @uses CreateChange
|
||||
*/
|
||||
public static function GetCurrentChange($bAutoCreate = true)
|
||||
{
|
||||
@@ -126,11 +160,17 @@ abstract class CMDBObject extends DBObject
|
||||
/**
|
||||
* Override the additional information (defaulting to user name)
|
||||
* A call to this verb should replace every occurence of
|
||||
* $oMyChange = MetaModel::NewObject("CMDBChange");
|
||||
* $oMyChange = MetaModel::NewObject("CMDBChange");
|
||||
* $oMyChange->Set("date", time());
|
||||
* $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
|
||||
*/
|
||||
public static function SetTrackInfo($sInfo)
|
||||
{
|
||||
self::$m_sInfo = $sInfo;
|
||||
@@ -138,24 +178,28 @@ abstract class CMDBObject extends DBObject
|
||||
|
||||
/**
|
||||
* Provides information about the origin of the change
|
||||
* @param $sOrigin String: one of: interactive, csv-interactive, csv-import.php, webservice-soap, webservice-rest, syncho-data-source, email-processing, custom-extension
|
||||
*/
|
||||
*
|
||||
* **warning** : this will do nothing if current change already exists !
|
||||
*
|
||||
* @see SetTrackInfo
|
||||
* @see SetCurrentChange to specify a CMDBObject instance instead
|
||||
*
|
||||
* @param $sOrigin String: one of: interactive, csv-interactive, csv-import.php, webservice-soap, webservice-rest, syncho-data-source,
|
||||
* email-processing, custom-extension
|
||||
*/
|
||||
public static function SetTrackOrigin($sOrigin)
|
||||
{
|
||||
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
|
||||
{
|
||||
} else {
|
||||
return self::$m_sInfo;
|
||||
}
|
||||
}
|
||||
@@ -174,17 +218,19 @@ abstract class CMDBObject extends DBObject
|
||||
return self::$m_sOrigin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a standard change record (done here 99% of the time, and nearly once per page)
|
||||
*/
|
||||
* Set to {@link $m_oCurrChange} a standard change record (done here 99% of the time, and nearly once per page)
|
||||
*
|
||||
* @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()
|
||||
{
|
||||
self::$m_oCurrChange = MetaModel::NewObject("CMDBChange");
|
||||
self::$m_oCurrChange->Set("date", time());
|
||||
self::$m_oCurrChange->Set("userinfo", self::GetTrackInfo());
|
||||
self::$m_oCurrChange->Set("origin", self::GetTrackOrigin());
|
||||
self::$m_oCurrChange->DBInsert();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,6 +39,7 @@ class MySQLException extends CoreException
|
||||
*/
|
||||
public function __construct($sIssue, $aContext, $oException = null, $oMysqli = null)
|
||||
{
|
||||
|
||||
if ($oException != null)
|
||||
{
|
||||
$aContext['mysql_errno'] = $oException->getCode();
|
||||
@@ -58,6 +59,11 @@ class MySQLException extends CoreException
|
||||
$aContext['mysql_error'] = CMDBSource::GetError();
|
||||
}
|
||||
parent::__construct($sIssue, $aContext);
|
||||
//if is connection error, don't log the default message with password in
|
||||
if (mysqli_connect_errno()) {
|
||||
error_log($this->message);
|
||||
error_reporting(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +95,7 @@ class MySQLHasGoneAwayException extends MySQLException
|
||||
{
|
||||
return array(
|
||||
2006,
|
||||
2013
|
||||
2013,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,10 +113,18 @@ class MySQLNoTransactionException extends MySQLException
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°5538
|
||||
*/
|
||||
class MySQLTransactionNotClosedException extends MySQLException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CMDBSource
|
||||
* database access wrapper
|
||||
* database access wrapper
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
@@ -119,7 +133,24 @@ class CMDBSource
|
||||
const ENUM_DB_VENDOR_MYSQL = 'MySQL';
|
||||
const ENUM_DB_VENDOR_MARIADB = 'MariaDB';
|
||||
const ENUM_DB_VENDOR_PERCONA = 'Percona';
|
||||
|
||||
|
||||
/**
|
||||
* @since 2.7.10 3.0.4 3.1.2 3.0.2 N°6889 constant creation
|
||||
* @internal will be removed in a future version
|
||||
*/
|
||||
const MYSQL_DEFAULT_PORT = 3306;
|
||||
|
||||
/**
|
||||
* Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT)
|
||||
* Message: Lock wait timeout exceeded; try restarting transaction
|
||||
*/
|
||||
const MYSQL_ERRNO_WAIT_TIMEOUT = 1205;
|
||||
/**
|
||||
* Error: 1213 SQLSTATE: 40001 (ER_LOCK_DEADLOCK)
|
||||
* Message: Deadlock found when trying to get lock; try restarting transaction
|
||||
*/
|
||||
const MYSQL_ERRNO_DEADLOCK = 1213;
|
||||
|
||||
protected static $m_sDBHost;
|
||||
protected static $m_sDBUser;
|
||||
protected static $m_sDBPwd;
|
||||
@@ -137,6 +168,17 @@ class CMDBSource
|
||||
|
||||
/** @var mysqli $m_oMysqli */
|
||||
protected static $m_oMysqli;
|
||||
/**
|
||||
* The mysqli object is really hard to mock ! This attribute is used only in certain methods, so that we can mock only a very little subset of the mysqli object.
|
||||
* We are setting it in {@see Init}, by default it is a copy of {@see $m_oMysqli}
|
||||
* The mock can be injected using the setter {@see SetMySQLiForQuery}
|
||||
*
|
||||
* @var mysqli $oMySQLiForQuery
|
||||
* @see GetMySQLiForQuery
|
||||
* @see SetMySQLiForQuery
|
||||
* @since 2.7.5 N°3513 new var to allow mock in tests ({@see \Combodo\iTop\Test\UnitTest\Core\TransactionsTest})
|
||||
*/
|
||||
protected static $oMySQLiForQuery;
|
||||
|
||||
/**
|
||||
* @var int number of level for nested transactions : 0 if no transaction was ever opened, +1 for each 'START TRANSACTION' sent
|
||||
@@ -203,6 +245,7 @@ class CMDBSource
|
||||
self::$m_sDBTlsCA = empty($sTlsCA) ? null : $sTlsCA;
|
||||
|
||||
self::$m_oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
|
||||
self::SetMySQLiForQuery(self::$m_oMysqli);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,12 +259,12 @@ class CMDBSource
|
||||
*
|
||||
* @return \mysqli
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @uses IsOpenedDbConnectionUsingTls when asking for a TLS connection, to check if it was really opened using TLS
|
||||
*/
|
||||
public static function GetMysqliInstance(
|
||||
$sDbHost, $sUser, $sPwd, $sSource = '', $bTlsEnabled = false, $sTlsCa = null, $bCheckTlsAfterConnection = false
|
||||
) {
|
||||
$oMysqli = null;
|
||||
|
||||
$sServer = null;
|
||||
$iPort = null;
|
||||
self::InitServerAndPort($sDbHost, $sServer, $iPort);
|
||||
@@ -251,7 +294,7 @@ class CMDBSource
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser), $e);
|
||||
throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser),$e);
|
||||
}
|
||||
|
||||
if ($bTlsEnabled
|
||||
@@ -282,16 +325,19 @@ class CMDBSource
|
||||
/**
|
||||
* @param string $sDbHost initial value ("p:domain:port" syntax)
|
||||
* @param string $sServer server variable to update
|
||||
* @param int $iPort port variable to update
|
||||
* @param int|null $iPort port variable to update, will return null if nothing is specified in $sDbHost
|
||||
*
|
||||
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°6889 will return null in $iPort if port isn't present in $sDbHost. Use {@see MYSQL_DEFAULT_PORT} if needed
|
||||
*
|
||||
* @link http://php.net/manual/en/mysqli.persistconns.php documentation for the "p:" prefix (persistent connexion)
|
||||
*/
|
||||
public static function InitServerAndPort($sDbHost, &$sServer, &$iPort)
|
||||
{
|
||||
$aConnectInfo = explode(':', $sDbHost);
|
||||
|
||||
$bUsePersistentConnection = false;
|
||||
if (strcasecmp($aConnectInfo[0], 'p') == 0)
|
||||
if (strcasecmp($aConnectInfo[0], 'p') === 0)
|
||||
{
|
||||
// we might have "p:" prefix to use persistent connections (see http://php.net/manual/en/mysqli.persistconns.php)
|
||||
$bUsePersistentConnection = true;
|
||||
$sServer = $aConnectInfo[0].':'.$aConnectInfo[1];
|
||||
}
|
||||
@@ -309,10 +355,6 @@ class CMDBSource
|
||||
{
|
||||
$iPort = (int)($aConnectInfo[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$iPort = 3306;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,41 +362,41 @@ class CMDBSource
|
||||
* parameters were used.<br>
|
||||
* This method can be called to ensure that the DB connection really uses TLS.
|
||||
*
|
||||
* <p>We're using this object connection : {@link self::$m_oMysqli}
|
||||
* <p>We're using our own mysqli instance to do the check as this check is done when creating the mysqli instance : the consumer
|
||||
* might want a dedicated object, and if so we don't want to overwrite the one saved in CMDBSource !<br>
|
||||
* This is the case for example with {@see \iTopMutex} !
|
||||
*
|
||||
* @param \mysqli $oMysqli
|
||||
*
|
||||
* @return boolean true if the connection was really established using TLS
|
||||
* @return boolean true if the connection was really established using TLS, false otherwise
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @used-by GetMysqliInstance
|
||||
* @uses IsMySqlVarNonEmpty
|
||||
* @uses 'ssl_version' MySQL var
|
||||
* @uses 'ssl_cipher' MySQL var
|
||||
*/
|
||||
private static function IsOpenedDbConnectionUsingTls($oMysqli)
|
||||
{
|
||||
if (self::$m_oMysqli == null)
|
||||
{
|
||||
self::$m_oMysqli = $oMysqli;
|
||||
}
|
||||
|
||||
$bNonEmptySslVersionVar = self::IsMySqlVarNonEmpty('ssl_version');
|
||||
$bNonEmptySslCipherVar = self::IsMySqlVarNonEmpty('ssl_cipher');
|
||||
$bNonEmptySslVersionVar = self::IsMySqlVarNonEmpty('ssl_version', $oMysqli);
|
||||
$bNonEmptySslCipherVar = self::IsMySqlVarNonEmpty('ssl_cipher', $oMysqli);
|
||||
|
||||
return ($bNonEmptySslVersionVar && $bNonEmptySslCipherVar);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sVarName
|
||||
* @param mysqli $oMysqli connection to use for the query
|
||||
*
|
||||
* @return bool
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @uses SHOW STATUS queries
|
||||
* @uses 'SHOW SESSION STATUS' queries
|
||||
*/
|
||||
private static function IsMySqlVarNonEmpty($sVarName)
|
||||
private static function IsMySqlVarNonEmpty($sVarName, $oMysqli)
|
||||
{
|
||||
try
|
||||
{
|
||||
$sResult = self::QueryToScalar("SHOW SESSION STATUS LIKE '$sVarName'", 1);
|
||||
$sResult = self::QueryToScalar("SHOW SESSION STATUS LIKE '$sVarName'", 1, $oMysqli);
|
||||
}
|
||||
catch (MySQLQueryHasNoResultException $e)
|
||||
{
|
||||
@@ -418,6 +460,12 @@ class CMDBSource
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @uses \CMDBSource::QueryToCol() so needs a connection opened !
|
||||
*/
|
||||
public static function GetDBVersion()
|
||||
{
|
||||
$aVersions = self::QueryToCol('SELECT Version() as version', 'version');
|
||||
@@ -435,8 +483,10 @@ class CMDBSource
|
||||
/**
|
||||
* Get the DB vendor between MySQL and its main forks
|
||||
* @return string
|
||||
*
|
||||
* @uses \CMDBSource::GetServerVariable() so needs a connection opened !
|
||||
*/
|
||||
static public function GetDBVendor()
|
||||
public static function GetDBVendor()
|
||||
{
|
||||
$sDBVendor = static::ENUM_DB_VENDOR_MYSQL;
|
||||
|
||||
@@ -490,6 +540,7 @@ class CMDBSource
|
||||
{
|
||||
self::$m_sDBName = '';
|
||||
}
|
||||
self::_TablesInfoCacheReset(); // reset the table info cache!
|
||||
}
|
||||
|
||||
public static function CreateTable($sQuery)
|
||||
@@ -519,6 +570,24 @@ class CMDBSource
|
||||
return self::$m_oMysqli;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
private static function GetMySQLiForQuery()
|
||||
{
|
||||
return self::$oMySQLiForQuery;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used for test purpose (mysqli mock)
|
||||
* @param $oMySQLi
|
||||
*/
|
||||
private static function SetMySQLiForQuery($oMySQLi)
|
||||
{
|
||||
self::$oMySQLiForQuery = $oMySQLi;
|
||||
}
|
||||
|
||||
public static function GetErrNo()
|
||||
{
|
||||
if (self::$m_oMysqli->errno != 0)
|
||||
@@ -605,10 +674,9 @@ class CMDBSource
|
||||
/**
|
||||
* @param string $sSQLQuery
|
||||
*
|
||||
* @return \mysqli_result|null
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \CoreException
|
||||
* @return mysqli_result|null
|
||||
* @throws MySQLException
|
||||
* @throws MySQLHasGoneAwayException
|
||||
*
|
||||
* @since 2.7.0 N°679 handles nested transactions
|
||||
*/
|
||||
@@ -654,33 +722,82 @@ class CMDBSource
|
||||
*/
|
||||
private static function DBQuery($sSql)
|
||||
{
|
||||
$sShortSQL = substr(preg_replace("/\s+/", " ", substr($sSql, 0, 180)), 0, 150);
|
||||
if (substr_compare($sShortSQL, "SELECT", 0, strlen("SELECT")) !== 0) {
|
||||
IssueLog::Trace("$sShortSQL", 'cmdbsource');
|
||||
}
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
}
|
||||
catch (mysqli_sql_exception $e)
|
||||
{
|
||||
self::LogDeadLock($e);
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
}
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
} finally {
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
}
|
||||
if ($oResult === false)
|
||||
{
|
||||
$aContext = array('query' => $sSql);
|
||||
|
||||
$iMySqlErrorNo = self::$m_oMysqli->errno;
|
||||
$iMySqlErrorNo = self::GetMySQLiForQuery()->errno;
|
||||
$aMySqlHasGoneAwayErrorCodes = MySQLHasGoneAwayException::getErrorCodes();
|
||||
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes))
|
||||
{
|
||||
throw new MySQLHasGoneAwayException(self::GetError(), $aContext);
|
||||
}
|
||||
|
||||
throw new MySQLException('Failed to issue SQL query', $aContext);
|
||||
$e = new MySQLException('Failed to issue SQL query', $aContext);
|
||||
self::LogDeadLock($e);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $oResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Exception $e
|
||||
*
|
||||
* @since 2.7.1
|
||||
*/
|
||||
private static function LogDeadLock(Exception $e)
|
||||
{
|
||||
// checks MySQL error code
|
||||
$iMySqlErrorNo = self::GetMySQLiForQuery()->errno;
|
||||
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get error info
|
||||
$sUser = UserRights::GetUser();
|
||||
$oError = self::GetMySQLiForQuery()->query('SHOW ENGINE INNODB STATUS');
|
||||
if ($oError !== false)
|
||||
{
|
||||
$aData = $oError->fetch_all(MYSQLI_ASSOC);
|
||||
$sInnodbStatus = $aData[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sInnodbStatus = 'Get status query cannot execute';
|
||||
}
|
||||
|
||||
// log !
|
||||
$sMessage = "deadlock detected: user= $sUser; errno=$iMySqlErrorNo";
|
||||
$aLogContext = array(
|
||||
'userinfo' => $sUser,
|
||||
'errno' => $iMySqlErrorNo,
|
||||
'ex_msg' => $e->getMessage(),
|
||||
'callstack' => $e->getTraceAsString(),
|
||||
'data' => $sInnodbStatus,
|
||||
);
|
||||
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
|
||||
|
||||
IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* If nested transaction, we are not starting a new one : only one global transaction will exist.
|
||||
*
|
||||
@@ -694,10 +811,15 @@ class CMDBSource
|
||||
*/
|
||||
private static function StartTransaction()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
|
||||
$bHasExistingTransactions = self::IsInsideTransaction();
|
||||
if (!$bHasExistingTransactions)
|
||||
{
|
||||
IssueLog::Trace("START TRANSACTION was sent to the DB", LogChannels::CMDB_SOURCE, ['stacktrace' => $aStackTrace]);
|
||||
self::DBQuery('START TRANSACTION');
|
||||
} else {
|
||||
IssueLog::Trace("START TRANSACTION ignored as a transaction is already opened", LogChannels::CMDB_SOURCE, ['stacktrace' => $aStackTrace]);
|
||||
}
|
||||
|
||||
self::AddTransactionLevel();
|
||||
@@ -715,9 +837,12 @@ class CMDBSource
|
||||
*/
|
||||
private static function Commit()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if (!self::IsInsideTransaction())
|
||||
{
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction COMMIT $sCaller", 'cmdbsource');
|
||||
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
|
||||
}
|
||||
|
||||
@@ -725,8 +850,10 @@ class CMDBSource
|
||||
|
||||
if (self::IsInsideTransaction())
|
||||
{
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") COMMIT $sCaller", 'cmdbsource');
|
||||
return;
|
||||
}
|
||||
IssueLog::Trace("COMMIT $sCaller", 'cmdbsource');
|
||||
self::DBQuery('COMMIT');
|
||||
}
|
||||
|
||||
@@ -745,17 +872,22 @@ class CMDBSource
|
||||
*/
|
||||
private static function Rollback()
|
||||
{
|
||||
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
|
||||
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
|
||||
if (!self::IsInsideTransaction())
|
||||
{
|
||||
// should not happen !
|
||||
IssueLog::Error("No Transaction ROLLBACK $sCaller", 'cmdbsource');
|
||||
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
|
||||
}
|
||||
self::RemoveLastTransactionLevel();
|
||||
if (self::IsInsideTransaction())
|
||||
{
|
||||
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") ROLLBACK $sCaller", 'cmdbsource');
|
||||
return;
|
||||
}
|
||||
|
||||
IssueLog::Trace("ROLLBACK $sCaller", 'cmdbsource');
|
||||
self::DBQuery('ROLLBACK');
|
||||
}
|
||||
|
||||
@@ -805,6 +937,17 @@ class CMDBSource
|
||||
self::$m_iTransactionLevel = 0;
|
||||
}
|
||||
|
||||
public static function IsDeadlockException(Exception $e)
|
||||
{
|
||||
while ($e instanceof Exception) {
|
||||
if (($e instanceof MySQLException) && ($e->getCode() == 1213)) {
|
||||
return true;
|
||||
}
|
||||
$e = $e->getPrevious();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated 2.7.0 N°1627 use ItopCounter instead
|
||||
@@ -858,17 +1001,21 @@ class CMDBSource
|
||||
/**
|
||||
* @param string $sSql
|
||||
* @param int $iCol beginning at 0
|
||||
* @param mysqli $oMysqli if not null will query using this connection, otherwise will use {@see GetMySQLiForQuery}
|
||||
*
|
||||
* @return string corresponding cell content on the first line
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
* @since 2.7.5-2 2.7.6 3.0.0 N°4215 new optional mysqli param
|
||||
*/
|
||||
public static function QueryToScalar($sSql, $iCol = 0)
|
||||
public static function QueryToScalar($sSql, $iCol = 0, $oMysqli = null)
|
||||
{
|
||||
$oMysqliToQuery = (is_null($oMysqli)) ? self::GetMySQLiForQuery() : $oMysqli;
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
$oResult = $oMysqliToQuery->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -908,7 +1055,7 @@ class CMDBSource
|
||||
$oKPI = new ExecutionKPI();
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -990,7 +1137,7 @@ class CMDBSource
|
||||
{
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -1127,23 +1274,30 @@ class CMDBSource
|
||||
}
|
||||
|
||||
/**
|
||||
* There may have some differences between DB : for example in MySQL 5.7 we have "INT", while in MariaDB >= 10.2 you get "int DEFAULT 'NULL'"
|
||||
* There may have some differences between DB ! For example in :
|
||||
* * MySQL 5.7 we have `INT`
|
||||
* * MariaDB >= 10.2 you get `int DEFAULT 'NULL'`
|
||||
*
|
||||
* We still do a case sensitive comparison for enum values !
|
||||
* We still need to do a case sensitive comparison for enum values !
|
||||
*
|
||||
* A better solution would be to generate SQL field definitions ({@link GetFieldSpec} method) based on the DB used... But for
|
||||
* now (N°2490 / SF #1756 / PR #91) we did implement this simpler solution
|
||||
*
|
||||
* @param string $sItopGeneratedFieldType
|
||||
* @see GetFieldDataTypeAndOptions extracts all info from the SQL field definition
|
||||
*
|
||||
* @param string $sDbFieldType
|
||||
*
|
||||
* @param string $sItopGeneratedFieldType
|
||||
*
|
||||
* @return bool true if same type and options (case sensitive comparison only for type options), false otherwise
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @since 2.7.0 N°2490
|
||||
*/
|
||||
public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType)
|
||||
{
|
||||
list($sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
|
||||
list($sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sDbFieldType);
|
||||
[$sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
|
||||
[$sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sDbFieldType);
|
||||
|
||||
if (strcasecmp($sItopFieldDataType, $sDbFieldDataType) !== 0)
|
||||
{
|
||||
@@ -1185,24 +1339,68 @@ class CMDBSource
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sCompleteFieldType sql field type, for example 'VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
|
||||
* @see \self::GetEnumOptions() specific processing for ENUM fields
|
||||
*
|
||||
* @param string $sCompleteFieldType sql field type, for example `VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0`
|
||||
*
|
||||
* @return string[] consisting of 3 items :
|
||||
* 1. data type : for example 'VARCHAR'
|
||||
* 2. type value : for example '255'
|
||||
* 3. other options : for example ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
|
||||
* 1. data type : for example `VARCHAR`
|
||||
* 2. type value : for example `255`
|
||||
* 3. other options : for example `CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0`
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
private static function GetFieldDataTypeAndOptions($sCompleteFieldType)
|
||||
{
|
||||
preg_match('/^([a-zA-Z]+)(\(([^\)]+)\))?( .+)?$/', $sCompleteFieldType, $aMatches);
|
||||
preg_match('/^([a-zA-Z]+)(\(([^\)]+)\))?( .+)?/', $sCompleteFieldType, $aMatches);
|
||||
|
||||
$sDataType = isset($aMatches[1]) ? $aMatches[1] : '';
|
||||
|
||||
if (strcasecmp($sDataType, 'ENUM') === 0){
|
||||
try{
|
||||
return self::GetEnumOptions($sDataType, $sCompleteFieldType);
|
||||
}catch(CoreException $e){
|
||||
//do nothing ; especially do not block setup.
|
||||
IssueLog::Warning("enum was not parsed properly: $sCompleteFieldType. it should not happen during setup.");
|
||||
}
|
||||
}
|
||||
|
||||
$sDataType = $aMatches[1];
|
||||
$sTypeOptions = isset($aMatches[2]) ? $aMatches[3] : '';
|
||||
$sOtherOptions = isset($aMatches[4]) ? $aMatches[4] : '';
|
||||
|
||||
return array($sDataType, $sTypeOptions, $sOtherOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sDataType for example `ENUM`
|
||||
* @param string $sCompleteFieldType Example:
|
||||
* `ENUM('CSP A','CSP (aaaa) M','NA','OEM(ROC)','OPEN(VL)','RETAIL (Boite)') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`
|
||||
*
|
||||
* @return string[] consisting of 3 items :
|
||||
* 1. data type : ENUM or enum here
|
||||
* 2. type value : in-between EUM parenthesis
|
||||
* 3. other options : for example ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
|
||||
* @throws \CoreException
|
||||
* @since 2.7.4 N°3065 specific processing for enum fields : fix no alter table when enum values containing parenthesis
|
||||
* Handle ENUM options
|
||||
*/
|
||||
private static function GetEnumOptions($sDataType, $sCompleteFieldType)
|
||||
{
|
||||
$iFirstOpeningParenthesis = strpos($sCompleteFieldType, '(');
|
||||
$iLastEndingParenthesis = strrpos($sCompleteFieldType, ')');
|
||||
|
||||
if ($iFirstOpeningParenthesis === false || $iLastEndingParenthesis === false ){
|
||||
//should never happen as GetFieldDataTypeAndOptions regexp matched.
|
||||
//except if regexp is modiied/broken somehow one day...
|
||||
throw new CoreException("GetEnumOptions issue with $sDataType parsing : " . $sCompleteFieldType);
|
||||
}
|
||||
|
||||
$sTypeOptions = substr($sCompleteFieldType, $iFirstOpeningParenthesis + 1, $iLastEndingParenthesis - 1);
|
||||
$sOtherOptions = substr($sCompleteFieldType, $iLastEndingParenthesis + 1);
|
||||
|
||||
return array($sDataType, $sTypeOptions, $sOtherOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sTable
|
||||
* @param string $sField
|
||||
@@ -1426,7 +1624,7 @@ class CMDBSource
|
||||
$sSql = "SELECT * FROM `$sTable`";
|
||||
try
|
||||
{
|
||||
$oResult = self::$m_oMysqli->query($sSql);
|
||||
$oResult = self::GetMySQLiForQuery()->query($sSql);
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
@@ -1536,8 +1734,20 @@ class CMDBSource
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string query to upgrade database charset and collation if needed, null if not
|
||||
public static function GetClusterNb()
|
||||
{
|
||||
$result = 0;
|
||||
$sSql = "SHOW STATUS LIKE 'wsrep_cluster_size';";
|
||||
$aRows = self::QueryToArray($sSql);
|
||||
if (count($aRows) > 0)
|
||||
{
|
||||
$result = $aRows[0]['Value'];
|
||||
}
|
||||
return intval($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string query to upgrade database charset and collation if needed, null if not
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @since 2.5.0 N°1001 switch to utf8mb4
|
||||
|
||||
@@ -22,7 +22,15 @@
|
||||
|
||||
define('ITOP_APPLICATION', 'iTop');
|
||||
define('ITOP_APPLICATION_SHORT', 'iTop');
|
||||
|
||||
/**
|
||||
* Constant containing the application version
|
||||
* Warning: this might be different from iTop core version!
|
||||
*
|
||||
* @see ITOP_CORE_VERSION to get iTop core version
|
||||
*/
|
||||
define('ITOP_VERSION', '2.7.0-dev');
|
||||
|
||||
define('ITOP_REVISION', 'svn');
|
||||
define('ITOP_BUILD_DATE', '$WCNOW$');
|
||||
define('ITOP_VERSION_FULL', ITOP_VERSION.'-'.ITOP_REVISION);
|
||||
@@ -468,11 +476,11 @@ class Config
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'cron_max_execution_time' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Duration (seconds) of the page cron.php, must be shorter than php setting max_execution_time and shorter than the web server response timeout',
|
||||
'default' => 600,
|
||||
'value' => 600,
|
||||
'source_of_value' => '',
|
||||
'type' => 'integer',
|
||||
'description' => 'Duration (seconds) of the cron.php script : if exceeded the script will exit even if there are remaining tasks to process. Must be shorter than php max_execution_time setting (note than when using CLI, this is set to 0 by default which means unlimited). If cron.php is ran via web, it must be shorter than the web server response timeout.',
|
||||
'default' => 600,
|
||||
'value' => 600,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'cron_sleep' => array(
|
||||
@@ -501,7 +509,7 @@ class Config
|
||||
),
|
||||
'email_transport' => array(
|
||||
'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' => '',
|
||||
@@ -547,6 +555,22 @@ class Config
|
||||
'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' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'CSS that will override the standard stylesheet used for the notifications',
|
||||
@@ -595,6 +619,13 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
/**
|
||||
* The timezone is automatically set using this parameter in \utils::InitTimeZone
|
||||
* This method is called almost everywhere, cause it's called in \MetaModel::LoadConfig and exec.php... but you might
|
||||
* need to get it yourself !
|
||||
*
|
||||
* @used-by utils::InitTimeZone()
|
||||
*/
|
||||
'timezone' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitly configured in PHP',
|
||||
@@ -838,6 +869,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'impact_analysis_lazy_loading' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'In the impact analysis view: display the analysis or filter before display',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'url_validation_pattern' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)',
|
||||
@@ -924,6 +963,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'log_kpi_generate_legacy_report' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Generate the legacy KPI report (kpi.html)',
|
||||
'default' => true,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'max_linkset_output' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.',
|
||||
@@ -1065,6 +1112,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'transactions_gc_threshold' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'probability in percent for the garbage collector to be triggered (100 mean always)',
|
||||
'default' => 10, // added in itop 2.7.4, before the GC was always called
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'log_transactions' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to enable the debug log for the transactions.',
|
||||
@@ -1105,6 +1160,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'svg_sanitizer' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'The class to use for SVG sanitization : allow to provide a custom made sanitizer',
|
||||
'default' => 'SVGDOMSanitizer',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'inline_image_max_display_width' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.',
|
||||
@@ -1249,6 +1312,38 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'security_header_xframe' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Value of the X-Frame-Options HTTP header sent by iTop. Possible values : DENY, SAMEORIGIN, or empty string.',
|
||||
'default' => 'SAMEORIGIN',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'security.enable_header_xcontent_type_options' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If set to false, iTop will stop sending the X-Content-Type-Options HTTP header. This header could trigger CORB protection on certain resources (JSON, XML, HTML, text) therefore blocking them.',
|
||||
'default' => true,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'behind_reverse_proxy' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
],
|
||||
'security.disable_inline_documents_sandbox' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'If true then the sandbox for documents displayed in a browser tab will be disabled; enabling scripts and other interactive content. Note that setting this to true will open the application to potential XSS attacks!',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -1539,7 +1634,7 @@ 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>'));
|
||||
}
|
||||
@@ -1814,6 +1909,24 @@ class Config
|
||||
$this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7.11 N°7085
|
||||
* Add login mode if not configured already
|
||||
* @param string $sLoginMode
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function AddAllowedLoginTypes($sLoginMode)
|
||||
{
|
||||
$aAllowedLoginTypes = $this->GetAllowedLoginTypes();
|
||||
if (in_array($sLoginMode, $aAllowedLoginTypes)){
|
||||
return;
|
||||
}
|
||||
|
||||
$aAllowedLoginTypes[] = $sLoginMode;
|
||||
$this->SetAllowedLoginTypes($aAllowedLoginTypes);
|
||||
}
|
||||
|
||||
public function SetExternalAuthenticationVariable($sExtAuthVariable)
|
||||
{
|
||||
$this->m_sExtAuthVariable = $sExtAuthVariable;
|
||||
@@ -2333,7 +2446,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;
|
||||
@@ -2386,4 +2499,4 @@ class ConfigPlaceholdersResolver
|
||||
IssueLog::Error($sErrorMessage, self::class, array($sSourceName, $sKey, $sDefault, $sWholeMask));
|
||||
throw new ConfigException($sErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,31 +28,39 @@
|
||||
|
||||
class CoreException extends Exception
|
||||
{
|
||||
public function __construct($sIssue, $aContextData = null, $sImpact = '')
|
||||
/**
|
||||
* CoreException constructor.
|
||||
*
|
||||
* @param string $sIssue error message
|
||||
* @param array|null $aContextData key/value array, value MUST implements _toString
|
||||
* @param string $sImpact
|
||||
* @param Exception|null $oPrevious
|
||||
*/
|
||||
public function __construct($sIssue, $aContextData = null, $sImpact = '', $oPrevious = null)
|
||||
{
|
||||
$this->m_sIssue = $sIssue;
|
||||
$this->m_sImpact = $sImpact;
|
||||
$this->m_aContextData = $aContextData ? $aContextData : array();
|
||||
|
||||
|
||||
if (is_array($aContextData)) {
|
||||
$this->m_aContextData = $aContextData;
|
||||
} else {
|
||||
$this->m_aContextData = [];
|
||||
}
|
||||
|
||||
$sMessage = $sIssue;
|
||||
if (!empty($sImpact)) $sMessage .= "($sImpact)";
|
||||
if (count($this->m_aContextData) > 0)
|
||||
{
|
||||
if (!empty($sImpact)) {
|
||||
$sMessage .= "($sImpact)";
|
||||
}
|
||||
if (count($this->m_aContextData) > 0) {
|
||||
$sMessage .= ": ";
|
||||
$aContextItems = array();
|
||||
foreach($this->m_aContextData as $sKey => $value)
|
||||
{
|
||||
if (is_array($value))
|
||||
{
|
||||
foreach ($this->m_aContextData as $sKey => $value) {
|
||||
if (is_array($value)) {
|
||||
$aPairs = array();
|
||||
foreach($value as $key => $val)
|
||||
{
|
||||
if (is_array($val))
|
||||
{
|
||||
foreach ($value as $key => $val) {
|
||||
if (is_array($val)) {
|
||||
$aPairs[] = $key.'=>('.implode(', ', $val).')';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aPairs[] = $key.'=>'.$val;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +74,7 @@ class CoreException extends Exception
|
||||
}
|
||||
$sMessage .= implode(', ', $aContextItems);
|
||||
}
|
||||
parent::__construct($sMessage, 0);
|
||||
parent::__construct($sMessage, 0, $oPrevious);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,6 +89,16 @@ class CoreException extends Exception
|
||||
return $this->getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* getTraceAsString() cannot be overrided and it is limited as only current exception stack is returned.
|
||||
* we need stack of all previous exceptions
|
||||
* @uses __tostring() already does the work.
|
||||
* @since 2.7.2/ 2.8.0
|
||||
*/
|
||||
public function getFullStackTraceAsString(){
|
||||
return "" . $this;
|
||||
}
|
||||
|
||||
public function getTraceAsHtml()
|
||||
{
|
||||
$aBackTrace = $this->getTrace();
|
||||
@@ -131,14 +149,14 @@ class CoreCannotSaveObjectException extends CoreException
|
||||
*
|
||||
* @param array $aContextData containing at least those keys : issues, id, class
|
||||
*/
|
||||
public function __construct($aContextData)
|
||||
public function __construct($aContextData, $oPrevious = null)
|
||||
{
|
||||
$this->aIssues = $aContextData['issues'];
|
||||
$this->iObjectId = $aContextData['id'];
|
||||
$this->sObjectClass = $aContextData['class'];
|
||||
|
||||
$sIssues = implode(', ', $this->aIssues);
|
||||
parent::__construct($sIssues, $aContextData);
|
||||
parent::__construct($sIssues, $aContextData, '', $oPrevious);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,19 +165,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)
|
||||
{
|
||||
if (count($this->aIssues) == 1) {
|
||||
$sIssue = reset($this->aIssues);
|
||||
$sContent .= " <span>{$sIssue}</span>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sContent .= " <span>".utils::HtmlEntities($sIssue)."</span>";
|
||||
} else {
|
||||
$sContent .= '<ul>';
|
||||
foreach ($this->aIssues as $sError)
|
||||
{
|
||||
$sContent .= "<li>$sError</li>";
|
||||
foreach ($this->aIssues as $sError) {
|
||||
$sContent .= "<li>".utils::HtmlEntities($sError)."</li>";
|
||||
}
|
||||
$sContent .= '</ul>';
|
||||
}
|
||||
@@ -215,12 +229,47 @@ class CoreUnexpectedValue extends CoreException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6458 object creation
|
||||
*/
|
||||
class InvalidExternalKeyValueException extends CoreUnexpectedValue
|
||||
{
|
||||
private const ENUM_PARAMS_OBJECT = 'current_object';
|
||||
private const ENUM_PARAMS_ATTCODE = 'attcode';
|
||||
private const ENUM_PARAMS_ATTVALUE = 'attvalue';
|
||||
private const ENUM_PARAMS_USER = 'current_user';
|
||||
|
||||
public function __construct($oObject, $sAttCode, $aContextData = null, $oPrevious = null)
|
||||
{
|
||||
$aContextData[self::ENUM_PARAMS_OBJECT] = get_class($oObject) . '::' . $oObject->GetKey();
|
||||
$aContextData[self::ENUM_PARAMS_ATTCODE] = $sAttCode;
|
||||
$aContextData[self::ENUM_PARAMS_ATTVALUE] = $oObject->Get($sAttCode);
|
||||
|
||||
$oCurrentUser = UserRights::GetUserObject();
|
||||
if (false === is_null($oCurrentUser)) {
|
||||
$aContextData[self::ENUM_PARAMS_USER] = get_class($oCurrentUser) . '::' . $oCurrentUser->GetKey();
|
||||
}
|
||||
|
||||
parent::__construct('Attribute pointing to an object that is either non existing or not readable by the current user', $aContextData, '', $oPrevious);
|
||||
}
|
||||
|
||||
public function GetAttCode(): string
|
||||
{
|
||||
return $this->getContextData()[self::ENUM_PARAMS_ATTCODE];
|
||||
}
|
||||
|
||||
public function GetAttValue(): string
|
||||
{
|
||||
return $this->getContextData()[self::ENUM_PARAMS_ATTVALUE];
|
||||
}
|
||||
}
|
||||
|
||||
class SecurityException extends CoreException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Throwned when querying on an object that exists in the database but is archived
|
||||
* Thrown when querying on an object that exists in the database but is archived
|
||||
*
|
||||
* @see N.1108
|
||||
* @since 2.5.1
|
||||
|
||||
@@ -188,8 +188,8 @@ final class ItopCounter
|
||||
|
||||
if (!$hDBLink)
|
||||
{
|
||||
throw new Exception("Could not connect to the DB server (host=$sDBHost, user=$sDBUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
|
||||
}
|
||||
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser));
|
||||
}
|
||||
|
||||
return $hDBLink;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
use Combodo\iTop\Application\Helper\ExportHelper;
|
||||
|
||||
/**
|
||||
* Bulk export: CSV export
|
||||
@@ -113,6 +114,7 @@ class CSVBulkExport extends TabularBulkExport
|
||||
|
||||
case 'csv_options':
|
||||
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:CSVOptions').'</legend>');
|
||||
$oP->add(ExportHelper::GetAlertForExcelMaliciousInjection());
|
||||
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
|
||||
$oP->add('<h3>'.Dict::S('UI:CSVImport:SeparatorCharacter').'</h3>');
|
||||
$sRawSeparator = utils::ReadParam('separator', ',', true, 'raw_data');
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.7">
|
||||
<user_rights>
|
||||
<profiles>
|
||||
<profile id="1024" _delta="define">
|
||||
<name>REST Services User</name>
|
||||
<description>Only users having this profile are allowed to use the REST Web Services (unless 'secure_rest_services' is set to false in the configuration file).</description>
|
||||
<groups />
|
||||
<description>Only users having this profile are allowed to use the REST Web Services (unless 'secure_rest_services' is set to false
|
||||
in the configuration file).
|
||||
</description>
|
||||
<groups/>
|
||||
</profile>
|
||||
</profiles>
|
||||
</user_rights>
|
||||
|
||||
@@ -532,11 +532,10 @@ abstract class DBObject implements iDisplay
|
||||
* Attributes setter
|
||||
*
|
||||
* Set $sAttCode to $value.
|
||||
* The value must be valid according to the type of attribute.
|
||||
* The value must be valid according to the type of attribute : see the different {@see AttributeDefinition::MakeRealValue()} implementations
|
||||
* The value will not be recorded into the DB until DBObject::DBWrite() is called.
|
||||
*
|
||||
* @api
|
||||
* @see DBWrite()
|
||||
*
|
||||
* @param string $sAttCode
|
||||
* @param mixed $value
|
||||
@@ -544,6 +543,8 @@ abstract class DBObject implements iDisplay
|
||||
* @return bool
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
*
|
||||
* @see DBWrite()
|
||||
*/
|
||||
public function Set($sAttCode, $value)
|
||||
{
|
||||
@@ -856,13 +857,17 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \DBObject::ListPreviousValuesForUpdatedAttributes() to get previous values anywhere in the CRUD stack
|
||||
* @see https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Asequence_crud iTop CRUD stack documentation
|
||||
*
|
||||
* @param string $sAttCode
|
||||
*
|
||||
* @return mixed|null the value as it was before changed with {@see DBObject::Set()}.
|
||||
* Returns null if the attribute wasn't changed.
|
||||
* Values are reset during {@see DBObject::DBUpdate()}
|
||||
*
|
||||
* @see DBObject::$m_aOrigValues
|
||||
* @throws CoreException if the attribute is unknown for the current object
|
||||
* @uses DBObject::$m_aOrigValues
|
||||
*/
|
||||
public function GetOriginal($sAttCode)
|
||||
{
|
||||
@@ -1973,9 +1978,9 @@ abstract class DBObject implements iDisplay
|
||||
/**
|
||||
* check attributes together
|
||||
*
|
||||
* @overwritable-hook You can extend this method in order to provide your own logic.
|
||||
*
|
||||
* @return bool
|
||||
* @overwritable-hook You can extend this method in order to provide your own logic.
|
||||
*
|
||||
* @return true|string true if successful, the error description otherwise
|
||||
*/
|
||||
public function CheckConsistency()
|
||||
{
|
||||
@@ -2080,16 +2085,17 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aUniquenessRuleProperties uniqueness rule properties
|
||||
*
|
||||
* @param string $sUniquenessRuleId uniqueness rule ID
|
||||
* @return \DBSearch
|
||||
* @throws \OQLException
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string $sUniquenessRuleId uniqueness rule ID
|
||||
* @param array $aUniquenessRuleProperties uniqueness rule properties
|
||||
*
|
||||
* @return \DBSearch
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
* @since 2.6.0 N°659 uniqueness constraint
|
||||
* @api
|
||||
* @since 2.6.0 N°659 uniqueness constraint
|
||||
* @since 2.7.11 3.1.2 3.2.0 N°4314 Fix Uniqueness rules not working with Silo
|
||||
*/
|
||||
protected function GetSearchForUniquenessRule($sUniquenessRuleId, $aUniquenessRuleProperties)
|
||||
{
|
||||
@@ -2119,8 +2125,10 @@ abstract class DBObject implements iDisplay
|
||||
$oUniquenessQuery->AddConditionForInOperatorUsingParam('finalclass', $aChildClassesWithRuleDisabled, false);
|
||||
}
|
||||
|
||||
return $oUniquenessQuery;
|
||||
}
|
||||
$oUniquenessQuery->AllowAllData();
|
||||
|
||||
return $oUniquenessQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check integrity rules (before inserting or updating the object)
|
||||
@@ -2136,7 +2144,6 @@ abstract class DBObject implements iDisplay
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*
|
||||
*/
|
||||
public function DoCheckToWrite()
|
||||
{
|
||||
@@ -2191,7 +2198,6 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @api
|
||||
* @api-advanced
|
||||
*
|
||||
@@ -2207,7 +2213,6 @@ abstract class DBObject implements iDisplay
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*
|
||||
*/
|
||||
final public function CheckToWrite()
|
||||
{
|
||||
@@ -2221,7 +2226,7 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DoCheckToWrite();
|
||||
$oKPI->ComputeStats('CheckToWrite', get_class($this));
|
||||
$oKPI->ComputeStatsForExtension($this, 'DoCheckToWrite');
|
||||
if (count($this->m_aCheckIssues) == 0)
|
||||
{
|
||||
$this->m_bCheckStatus = true;
|
||||
@@ -2234,6 +2239,87 @@ abstract class DBObject implements iDisplay
|
||||
return array($this->m_bCheckStatus, $this->m_aCheckIssues, $this->m_bSecurityIssue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for extkey attributes values. This will throw exception on non-existing as well as non-accessible objects (silo, scopes).
|
||||
* That's why the test is done for all users including Administrators
|
||||
*
|
||||
* Note that due to perf issues, this isn't called directly by the ORM, but has to be called by consumers when possible.
|
||||
*
|
||||
* @param callable(string, string):bool|null $oIsObjectLoadableCallback Override to check if object is accessible.
|
||||
* Parameters are object class and key
|
||||
* Return value should be false if cannot access object, true otherwise
|
||||
* @return void
|
||||
*
|
||||
* @throws ArchivedObjectException
|
||||
* @throws CoreException if cannot get object attdef list
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws InvalidExternalKeyValueException
|
||||
* @throws MySQLException
|
||||
* @throws SecurityException if one extkey is pointing to an invalid value
|
||||
*
|
||||
* @link https://github.com/Combodo/iTop/security/advisories/GHSA-245j-66p9-pwmh
|
||||
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6458
|
||||
*
|
||||
* @see \RestUtils::FindObjectFromKey for the same check in the REST endpoint
|
||||
*/
|
||||
final public function CheckChangedExtKeysValues(callable $oIsObjectLoadableCallback = null)
|
||||
{
|
||||
if (is_null($oIsObjectLoadableCallback)) {
|
||||
$oIsObjectLoadableCallback = function ($sClass, $sId) {
|
||||
$oRemoteObject = MetaModel::GetObject($sClass, $sId, false);
|
||||
if (is_null($oRemoteObject)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
$aChanges = $this->ListChanges();
|
||||
$aAttCodesChanged = array_keys($aChanges);
|
||||
foreach ($aAttCodesChanged as $sAttDefCode) {
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttDefCode);
|
||||
|
||||
if ($oAttDef instanceof AttributeLinkedSetIndirect) {
|
||||
/** @var ormLinkSet $oOrmSet */
|
||||
$oOrmSet = $this->Get($sAttDefCode);
|
||||
while ($oLnk = $oOrmSet->Fetch()) {
|
||||
$oLnk->CheckChangedExtKeysValues($oIsObjectLoadableCallback);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @noinspection PhpConditionCheckedByNextConditionInspection */
|
||||
/** @noinspection NotOptimalIfConditionsInspection */
|
||||
if (($oAttDef instanceof AttributeHierarchicalKey) || ($oAttDef instanceof AttributeExternalKey)) {
|
||||
$sRemoteObjectClass = $oAttDef->GetTargetClass();
|
||||
$sRemoteObjectKey = $this->Get($sAttDefCode);
|
||||
} else if ($oAttDef instanceof AttributeObjectKey) {
|
||||
$sRemoteObjectClassAttCode = $oAttDef->Get('class_attcode');
|
||||
$sRemoteObjectClass = $this->Get($sRemoteObjectClassAttCode);
|
||||
$sRemoteObjectKey = $this->Get($sAttDefCode);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (utils::IsNullOrEmptyString($sRemoteObjectClass)
|
||||
|| utils::IsNullOrEmptyString($sRemoteObjectKey)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 0 : Undefined ext. key (EG. non-mandatory and no value provided)
|
||||
// < 0 : Non yet persisted object
|
||||
/** @noinspection TypeUnsafeComparisonInspection Non-strict comparison as object ID can be string */
|
||||
if ($sRemoteObjectKey <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (false === $oIsObjectLoadableCallback($sRemoteObjectClass, $sRemoteObjectKey)) {
|
||||
throw new InvalidExternalKeyValueException($this, $sAttDefCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it is allowed to delete the existing object from the database
|
||||
*
|
||||
@@ -2375,15 +2461,17 @@ abstract class DBObject implements iDisplay
|
||||
return $aDelta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @api-advanced
|
||||
*
|
||||
* @return array attname => currentvalue List the attributes that have been changed using {@see DBObject::Set()}. Reset during {@see DBObject::DBUpdate()}
|
||||
* @uses m_aCurrValues
|
||||
* @see \DBObject::ListPreviousValuesForUpdatedAttributes()
|
||||
* @throws Exception
|
||||
*/
|
||||
/**
|
||||
* @api
|
||||
* @api-advanced
|
||||
*
|
||||
* @return array attcode => currentvalue List the attributes that have been changed using {@see DBObject::Set()}.
|
||||
* Reset during {@see DBObject::DBUpdate()}
|
||||
* @throws Exception
|
||||
* @see \DBObject::ListPreviousValuesForUpdatedAttributes() to get previous values anywhere in the CRUD stack
|
||||
* @see https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Asequence_crud iTop CRUD stack documentation
|
||||
* @uses m_aCurrValues
|
||||
*/
|
||||
public function ListChanges()
|
||||
{
|
||||
if ($this->m_bIsInDB)
|
||||
@@ -2402,10 +2490,10 @@ abstract class DBObject implements iDisplay
|
||||
*
|
||||
* To get values that were set to the changed fields, simply use {@link \DBObject::Get()}
|
||||
*
|
||||
* @see \DBObject::ListChanges() old method, but using data that are reset during DBObject::DBUpdate
|
||||
* @return array attname => value : value that was present before the last {@see DBObject::Set()} call.
|
||||
* This array is set at the beginning of {@see DBObject::DBpdate()} using {@see DBObject::InitPreviousValuesForUpdatedAttributes()}.
|
||||
* @uses m_aPreviousValuesForUpdatedAttributes
|
||||
* @see \DBObject::ListChanges()
|
||||
* @since 2.7.0 N°2293
|
||||
*/
|
||||
public function ListPreviousValuesForUpdatedAttributes()
|
||||
@@ -2674,7 +2762,6 @@ abstract class DBObject implements iDisplay
|
||||
* @throws \Exception
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public function DBInsertNoReload()
|
||||
{
|
||||
@@ -2687,8 +2774,12 @@ abstract class DBObject implements iDisplay
|
||||
$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
|
||||
// Ensure the update of the values (we are accessing the data directly)
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DoComputeValues();
|
||||
$oKPI->ComputeStatsForExtension($this, 'DoComputeValues');
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->OnInsert();
|
||||
$oKPI->ComputeStatsForExtension($this, 'OnInsert');
|
||||
|
||||
if ($this->m_iKey < 0)
|
||||
{
|
||||
@@ -2706,7 +2797,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
// Ultimate check - ensure DB integrity
|
||||
list($bRes, $aIssues) = $this->CheckToWrite();
|
||||
[$bRes, $aIssues] = $this->CheckToWrite();
|
||||
if (!$bRes)
|
||||
{
|
||||
throw new CoreCannotSaveObjectException(array('issues' => $aIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
|
||||
@@ -2733,50 +2824,72 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
$iTransactionRetry = 1;
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
try
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
}
|
||||
|
||||
// First query built upon on the root class, because the ID must be created first
|
||||
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
|
||||
|
||||
// Then do the leaf class, if different from the root class
|
||||
if ($sClass != $sRootClass)
|
||||
{
|
||||
$this->DBInsertSingleTable($sClass);
|
||||
}
|
||||
|
||||
// Then do the other classes
|
||||
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
|
||||
{
|
||||
if ($sParentClass == $sRootClass)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$this->DBInsertSingleTable($sParentClass);
|
||||
}
|
||||
|
||||
$this->OnObjectKeyReady();
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
while ($iTransactionRetry > 0) {
|
||||
try {
|
||||
$iTransactionRetry--;
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
}
|
||||
|
||||
// First query built upon on the root class, because the ID must be created first
|
||||
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
|
||||
|
||||
// Then do the leaf class, if different from the root class
|
||||
if ($sClass != $sRootClass) {
|
||||
$this->DBInsertSingleTable($sClass);
|
||||
}
|
||||
|
||||
// Then do the other classes
|
||||
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) {
|
||||
if ($sParentClass == $sRootClass) {
|
||||
continue;
|
||||
}
|
||||
$this->DBInsertSingleTable($sParentClass);
|
||||
}
|
||||
|
||||
$this->OnObjectKeyReady();
|
||||
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Insert TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Error("Insert Deadlock TRANSACTION prevention failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->m_bIsInDB = true;
|
||||
@@ -2790,7 +2903,9 @@ abstract class DBObject implements iDisplay
|
||||
$this->m_aOrigValues[$sAttCode] = $value;
|
||||
}
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->AfterInsert();
|
||||
$oKPI->ComputeStatsForExtension($this, 'AfterInsert');
|
||||
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
@@ -2798,8 +2913,16 @@ abstract class DBObject implements iDisplay
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectCreate AS t WHERE t.target_class IN (:class_list)"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
/** @var \TriggerOnObjectCreate $oTrigger */
|
||||
try
|
||||
{
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$this->RecordObjCreation();
|
||||
@@ -2914,12 +3037,10 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Persist an object to the DB, for the first time
|
||||
*
|
||||
/**
|
||||
* Persist an object to the DB, for the first time
|
||||
*
|
||||
* @api
|
||||
* @see DBWrite
|
||||
*
|
||||
* @return int|null inserted object key
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
@@ -2929,10 +3050,12 @@ abstract class DBObject implements iDisplay
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*
|
||||
* @see DBWrite
|
||||
*/
|
||||
public function DBInsert()
|
||||
{
|
||||
$this->DBInsertNoReload();
|
||||
$this->DBInsertNoReload();
|
||||
|
||||
if (MetaModel::DBIsReadOnly())
|
||||
{
|
||||
@@ -3031,13 +3154,13 @@ abstract class DBObject implements iDisplay
|
||||
* Update an object in DB
|
||||
*
|
||||
* @api
|
||||
* @see DBObject::DBWrite()
|
||||
*
|
||||
* @return int object key
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \CoreCannotSaveObjectException if CheckToWrite() returns issues
|
||||
* @throws \Exception
|
||||
*
|
||||
* @see DBObject::DBWrite()
|
||||
*/
|
||||
public function DBUpdate()
|
||||
{
|
||||
@@ -3045,6 +3168,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
throw new CoreException("DBUpdate: could not update a newly created object, please call DBInsert instead");
|
||||
}
|
||||
|
||||
// Protect against reentrance (e.g. cascading the update of ticket logs)
|
||||
static $aUpdateReentrance = array();
|
||||
$sKey = get_class($this).'::'.$this->GetKey();
|
||||
@@ -3058,8 +3182,11 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
try
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DoComputeValues();
|
||||
// Stop watches
|
||||
$oKPI->ComputeStatsForExtension($this, 'DoComputeValues');
|
||||
|
||||
// Stop watches
|
||||
$sState = $this->GetState();
|
||||
if ($sState != '')
|
||||
{
|
||||
@@ -3078,7 +3205,9 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->OnUpdate();
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->OnUpdate();
|
||||
$oKPI->ComputeStatsForExtension($this, 'OnUpdate');
|
||||
|
||||
$aChanges = $this->ListChanges();
|
||||
if (count($aChanges) == 0)
|
||||
@@ -3090,7 +3219,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
// Ultimate check - ensure DB integrity
|
||||
list($bRes, $aIssues) = $this->CheckToWrite();
|
||||
[$bRes, $aIssues] = $this->CheckToWrite();
|
||||
if (!$bRes)
|
||||
{
|
||||
throw new CoreCannotSaveObjectException(array(
|
||||
@@ -3110,8 +3239,14 @@ abstract class DBObject implements iDisplay
|
||||
array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
/** @var \TriggerOnObjectUpdate $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$bHasANewExternalKeyValue = false;
|
||||
@@ -3138,9 +3273,11 @@ abstract class DBObject implements iDisplay
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
$iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iIsTransactionRetryCount;
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
while ($iTransactionRetry > 0)
|
||||
{
|
||||
@@ -3208,13 +3345,6 @@ abstract class DBObject implements iDisplay
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
|
||||
// new values are already in the object (call {@see DBObject::Get()} to get them)
|
||||
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
|
||||
$this->m_bDirty = false;
|
||||
$this->m_aTouchedAtt = array();
|
||||
$this->m_aModifiedAtt = array();
|
||||
|
||||
if (count($aChanges) != 0)
|
||||
{
|
||||
$this->RecordAttChanges($aChanges, $aOriginalValues);
|
||||
@@ -3228,18 +3358,18 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if ($e->getCode() == 1213)
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction
|
||||
IssueLog::Error($e->getMessage());
|
||||
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Update TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry));
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -3253,10 +3383,11 @@ abstract class DBObject implements iDisplay
|
||||
'id' => $this->GetKey(),
|
||||
'class' => get_class($this),
|
||||
'issues' => $aErrors
|
||||
));
|
||||
), $e);
|
||||
}
|
||||
catch (CoreCannotSaveObjectException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
@@ -3265,6 +3396,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
@@ -3278,9 +3410,18 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
|
||||
// new values are already in the object (call {@see DBObject::Get()} to get them)
|
||||
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
|
||||
$this->m_bDirty = false;
|
||||
$this->m_aTouchedAtt = array();
|
||||
$this->m_aModifiedAtt = array();
|
||||
|
||||
try
|
||||
{
|
||||
$this->AfterUpdate();
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->AfterUpdate();
|
||||
$oKPI->ComputeStatsForExtension($this, 'AfterUpdate');
|
||||
|
||||
// Reload to get the external attributes
|
||||
if ($bHasANewExternalKeyValue)
|
||||
@@ -3359,13 +3500,18 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
/**
|
||||
* Make the current changes persistent - clever wrapper for Insert or Update
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @throws ArchivedObjectException
|
||||
* @throws CoreCannotSaveObjectException
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws CoreWarning
|
||||
* @throws MySQLException
|
||||
* @throws OQLException
|
||||
*/
|
||||
public function DBWrite()
|
||||
{
|
||||
@@ -3426,8 +3572,15 @@ abstract class DBObject implements iDisplay
|
||||
$aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
/** @var \TriggerOnObjectDelete $oTrigger */
|
||||
try
|
||||
{
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$this->RecordObjDeletion($this->m_iKey); // May cause a reload for storing history information
|
||||
@@ -3469,9 +3622,11 @@ abstract class DBObject implements iDisplay
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
$iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iIsTransactionRetryCount;
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
while ($iTransactionRetry > 0)
|
||||
{
|
||||
@@ -3494,18 +3649,18 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if ($e->getCode() == 1213)
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Delete TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry));
|
||||
usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -3637,7 +3792,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @overwritable-hook You can extend this method in order to provide your own logic.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
@@ -3682,34 +3837,55 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
/**
|
||||
* Apply a stimulus (workflow)
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $sStimulusCode
|
||||
* @param bool $bDoNotWrite
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $sStimulusCode
|
||||
* @param bool $bDoNotWrite if true we won't save the object !
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
*
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
*
|
||||
* @uses \AttributeStopWatch::Start
|
||||
* @uses \AttributeStopWatch::Stop
|
||||
* @uses \DBObject::DBWrite
|
||||
* @uses \TriggerOnStateLeave::DoActivate
|
||||
* @uses \TriggerOnStateEnter::DoActivate
|
||||
*/
|
||||
public function ApplyStimulus($sStimulusCode, $bDoNotWrite = false)
|
||||
{
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
|
||||
$sClass = get_class($this);
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
|
||||
if (empty($sStateAttCode))
|
||||
{
|
||||
throw new CoreException('No lifecycle for the class '.get_class($this));
|
||||
throw new CoreException('No lifecycle for the class '.$sClass);
|
||||
}
|
||||
|
||||
MyHelpers::CheckKeyInArray('object lifecycle stimulus', $sStimulusCode, MetaModel::EnumStimuli(get_class($this)));
|
||||
MyHelpers::CheckKeyInArray('object lifecycle stimulus', $sStimulusCode, MetaModel::EnumStimuli($sClass));
|
||||
|
||||
$aStateTransitions = $this->EnumTransitions();
|
||||
if (!array_key_exists($sStimulusCode, $aStateTransitions))
|
||||
{
|
||||
// This simulus has no effect in the current state... do nothing
|
||||
IssueLog::Error(get_class($this).": Transition $sStimulusCode is not allowed in ".$this->Get($sStateAttCode));
|
||||
// This stimulus has no effect in the current state... do nothing
|
||||
IssueLog::Error("$sClass: Transition $sStimulusCode is not allowed in ".$this->Get($sStateAttCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
// save current object values in case of an action failure (in memory rollback)
|
||||
$aBackupValues = array();
|
||||
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) {
|
||||
if (isset($this->m_aCurrValues[$sAttCode])) {
|
||||
$value = $this->m_aCurrValues[$sAttCode];
|
||||
if (is_object($value)) {
|
||||
$aBackupValues[$sAttCode] = clone $value;
|
||||
} else {
|
||||
$aBackupValues[$sAttCode] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aTransitionDef = $aStateTransitions[$sStimulusCode];
|
||||
|
||||
// Change the state before proceeding to the actions, this is necessary because an action might
|
||||
@@ -3728,11 +3904,11 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
// Old (pre-2.1.0 modules) action definition without any parameter
|
||||
$aActionCallSpec = array($this, $actionHandler);
|
||||
$sActionDesc = get_class($this).'::'.$actionHandler;
|
||||
$sActionDesc = $sClass.'::'.$actionHandler;
|
||||
|
||||
if (!is_callable($aActionCallSpec))
|
||||
{
|
||||
throw new CoreException("Unable to call action: ".get_class($this)."::$actionHandler");
|
||||
throw new CoreException("Unable to call action: $sClass::$actionHandler");
|
||||
}
|
||||
$bRet = call_user_func($aActionCallSpec, $sStimulusCode);
|
||||
}
|
||||
@@ -3740,7 +3916,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
// New syntax: 'verb' and typed parameters
|
||||
$sAction = $actionHandler['verb'];
|
||||
$sActionDesc = get_class($this).'::'.$sAction;
|
||||
$sActionDesc = "$sClass::$sAction";
|
||||
$aParams = array();
|
||||
foreach($actionHandler['params'] as $aDefinition)
|
||||
{
|
||||
@@ -3776,14 +3952,12 @@ abstract class DBObject implements iDisplay
|
||||
// (in case there is no returned value, null is obtained and means "ok")
|
||||
if ($bRet === false)
|
||||
{
|
||||
IssueLog::Info("Lifecycle action $sActionDesc returned false on object #".$this->GetKey());
|
||||
IssueLog::Info("Lifecycle action $sActionDesc returned false on object #$sClass:".$this->GetKey());
|
||||
$bSuccess = false;
|
||||
}
|
||||
}
|
||||
if ($bSuccess)
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
|
||||
// Stop watches
|
||||
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
@@ -3802,8 +3976,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bDoNotWrite)
|
||||
{
|
||||
if (!$bDoNotWrite) {
|
||||
$this->DBWrite();
|
||||
}
|
||||
|
||||
@@ -3811,19 +3984,38 @@ abstract class DBObject implements iDisplay
|
||||
$aParams = array(
|
||||
'class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL),
|
||||
'previous_state' => $sPreviousState,
|
||||
'new_state' => $sNewState);
|
||||
'new_state' => $sNewState,
|
||||
);
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class IN (:class_list) AND t.state=:previous_state"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnStateLeave $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class IN (:class_list) AND t.state=:new_state"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnStateEnter $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// At least one action failed, rollback the object value to its previous value
|
||||
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
/** @var \Trigger $oTrigger */
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
$this->m_aCurrValues[$sAttCode] = $aBackupValues[$sAttCode];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3851,7 +4043,32 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function Copy($sDestAttCode, $sSourceAttCode)
|
||||
{
|
||||
$this->Set($sDestAttCode, $this->Get($sSourceAttCode));
|
||||
$oTypeValueToCopy = MetaModel::GetAttributeDef(get_class($this), $sSourceAttCode);
|
||||
$oTypeValueDest = MetaModel::GetAttributeDef(get_class($this), $sDestAttCode);
|
||||
if ($oTypeValueToCopy instanceof AttributeText && $oTypeValueDest instanceof AttributeText)
|
||||
{
|
||||
if ($oTypeValueToCopy->GetFormat() == $oTypeValueDest->GetFormat())
|
||||
{
|
||||
$sValueToCopy = $this->Get($sSourceAttCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($oTypeValueToCopy->GetFormat() == 'text')// and $oTypeValueDest->GetFormat()=='HTML'
|
||||
{
|
||||
$sValueToCopy = $this->GetAsHTML($sSourceAttCode);
|
||||
}
|
||||
else
|
||||
{// $oTypeValueToCopy->GetFormat() == 'HTML' and $oTypeValueDest->GetFormat()=='Text'
|
||||
$sValueToCopy = utils::HtmlToText($this->Get($sSourceAttCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValueToCopy = $this->Get($sSourceAttCode);
|
||||
}
|
||||
$this->Set($sDestAttCode, $sValueToCopy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5209,10 +5426,6 @@ abstract class DBObject implements iDisplay
|
||||
throw new Exception('Missing argument #1: stimulus');
|
||||
}
|
||||
$sStimulus = $aParams[0];
|
||||
if (!in_array($sStimulus, MetaModel::EnumStimuli(get_class($this))))
|
||||
{
|
||||
throw new Exception("Unknown stimulus ".get_class($this)."::".$sStimulus);
|
||||
}
|
||||
$this->ApplyStimulus($sStimulus);
|
||||
break;
|
||||
|
||||
|
||||
@@ -63,9 +63,15 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if (is_null($sClassAlias)) $sClassAlias = $sClass;
|
||||
if(!is_string($sClass)) throw new Exception('DBObjectSearch::__construct called with a non-string parameter: $sClass = '.print_r($sClass, true));
|
||||
if(!MetaModel::IsValidClass($sClass)) throw new Exception('DBObjectSearch::__construct called for an invalid class: "'.$sClass.'"');
|
||||
if (is_null($sClassAlias)) {
|
||||
$sClassAlias = $sClass;
|
||||
}
|
||||
if (!is_string($sClass)) {
|
||||
throw new Exception('DBObjectSearch::__construct called with a non-string parameter: $sClass = '.print_r($sClass, true));
|
||||
}
|
||||
if (!MetaModel::IsValidClass($sClass)) {
|
||||
throw new Exception('DBObjectSearch::__construct called for an invalid class: "'.$sClass.'"');
|
||||
}
|
||||
|
||||
$this->m_aSelectedClasses = array($sClassAlias => $sClass);
|
||||
$this->m_aClasses = array($sClassAlias => $sClass);
|
||||
@@ -75,30 +81,43 @@ class DBObjectSearch extends DBSearch
|
||||
$this->m_aReferencedBy = array();
|
||||
}
|
||||
|
||||
public function AllowAllData($bAllowAllData = true) {$this->m_bAllowAllData = $bAllowAllData;}
|
||||
public function IsAllDataAllowed() {return $this->m_bAllowAllData;}
|
||||
protected function IsDataFiltered() {return $this->m_bDataFiltered; }
|
||||
protected function SetDataFiltered() {$this->m_bDataFiltered = true;}
|
||||
public function AllowAllData($bAllowAllData = true) {
|
||||
$this->m_bAllowAllData = $bAllowAllData;
|
||||
|
||||
$this->m_oSearchCondition->Browse(function ($oThisExpression) use ($bAllowAllData) {
|
||||
ExpressionHelper::ExpressionAllowAllDataCallback($oThisExpression, $bAllowAllData);
|
||||
});
|
||||
}
|
||||
|
||||
public function IsAllDataAllowed() {
|
||||
return $this->m_bAllowAllData;
|
||||
}
|
||||
|
||||
protected function IsDataFiltered() {
|
||||
return $this->m_bDataFiltered;
|
||||
}
|
||||
|
||||
protected function SetDataFiltered() {
|
||||
$this->m_bDataFiltered = true;
|
||||
}
|
||||
|
||||
// Create a search definition that leads to 0 result, still a valid search object
|
||||
static public function FromEmptySet($sClass)
|
||||
{
|
||||
public static function FromEmptySet($sClass) {
|
||||
$oResultFilter = new DBObjectSearch($sClass);
|
||||
$oResultFilter->m_oSearchCondition = new FalseExpression;
|
||||
|
||||
return $oResultFilter;
|
||||
}
|
||||
|
||||
|
||||
public function GetJoinedClasses() {return $this->m_aClasses;}
|
||||
public function GetJoinedClasses() {
|
||||
return $this->m_aClasses;
|
||||
}
|
||||
|
||||
public function GetClassName($sAlias)
|
||||
{
|
||||
if (array_key_exists($sAlias, $this->m_aSelectedClasses))
|
||||
{
|
||||
public function GetClassName($sAlias) {
|
||||
if (array_key_exists($sAlias, $this->m_aSelectedClasses)) {
|
||||
return $this->m_aSelectedClasses[$sAlias];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
throw new CoreException("Invalid class alias '$sAlias'");
|
||||
}
|
||||
}
|
||||
@@ -313,6 +332,11 @@ class DBObjectSearch extends DBSearch
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move conditions from $oFilter to $this
|
||||
* @param \DBSearch $oFilter
|
||||
* @param $aTranslation
|
||||
*/
|
||||
protected function TransferConditionExpression($oFilter, $aTranslation)
|
||||
{
|
||||
// Prevent collisions in the parameter names by renaming them if needed
|
||||
@@ -335,6 +359,7 @@ class DBObjectSearch extends DBSearch
|
||||
$oTranslated = $oFilter->GetCriteria()->Translate($aTranslation, false, false /* leave unresolved fields */);
|
||||
$this->AddConditionExpression($oTranslated);
|
||||
$this->m_aParams = array_merge($this->m_aParams, $oFilter->m_aParams);
|
||||
$oFilter->ResetCondition();
|
||||
}
|
||||
|
||||
public function RenameParam($sOldName, $sNewName)
|
||||
@@ -352,37 +377,35 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
foreach($this->m_aReferencedBy as $sForeignClass => $aReferences)
|
||||
{
|
||||
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator)
|
||||
{
|
||||
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters)
|
||||
{
|
||||
foreach ($aFilters as $oForeignFilter)
|
||||
{
|
||||
foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) {
|
||||
foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) {
|
||||
foreach ($aFilters as $oForeignFilter) {
|
||||
$oForeignFilter->RenameParam($sOldName, $sNewName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function ResetCondition()
|
||||
{
|
||||
|
||||
public function ResetCondition() {
|
||||
$this->m_oSearchCondition = new TrueExpression();
|
||||
// ? is that usefull/enough, do I need to rebuild the list after the subqueries ?
|
||||
}
|
||||
|
||||
public function MergeConditionExpression($oExpression)
|
||||
{
|
||||
$this->m_oSearchCondition = $this->m_oSearchCondition->LogOr($oExpression);
|
||||
public function MergeConditionExpression($oExpression) {
|
||||
$this->m_oSearchCondition = $this->m_oSearchCondition->LogOr($oExpression);
|
||||
}
|
||||
|
||||
public function AddConditionExpression($oExpression)
|
||||
{
|
||||
$this->m_oSearchCondition = $this->m_oSearchCondition->LogAnd($oExpression);
|
||||
public function AddConditionExpression($oExpression) {
|
||||
$this->m_oSearchCondition = $this->m_oSearchCondition->LogAnd($oExpression);
|
||||
|
||||
$bRootSearchAllowAllData = $this->IsAllDataAllowed();
|
||||
$oExpression->Browse(function ($oThisExpression) use ($bRootSearchAllowAllData) {
|
||||
ExpressionHelper::ExpressionAllowAllDataCallback($oThisExpression, $bRootSearchAllowAllData);
|
||||
});
|
||||
}
|
||||
|
||||
public function AddNameCondition($sName)
|
||||
{
|
||||
public function AddNameCondition($sName) {
|
||||
$oValueExpr = new ScalarExpression($sName);
|
||||
$oNameExpr = new FieldExpression('friendlyname', $this->GetClassAlias());
|
||||
$oNewCondition = new BinaryExpression($oNameExpr, '=', $oValueExpr);
|
||||
@@ -393,6 +416,10 @@ class DBObjectSearch extends DBSearch
|
||||
* @param string $sFilterCode
|
||||
* @param mixed $value
|
||||
* @param string $sOpCode operator to use : 'IN', 'NOT IN', 'Contains',' Begins with', 'Finishes with', ...
|
||||
* If no operator is specified then :
|
||||
* * for id field we will use "="
|
||||
* * for other fields we will call the corresponding {@link AttributeDefinition::GetSmartConditionExpression} method impl
|
||||
* to generate the expression
|
||||
* @param bool $bParseSearchString
|
||||
*
|
||||
* @throws \CoreException
|
||||
@@ -437,7 +464,6 @@ class DBObjectSearch extends DBSearch
|
||||
case '<|':
|
||||
case '=|':
|
||||
throw new CoreException('Deprecated operator, please consider using OQL (SQL) expressions like "(TO_DAYS(NOW()) - TO_DAYS(x)) AS AgeDays"', array('operator' => $sOpCode));
|
||||
break;
|
||||
|
||||
case 'IN':
|
||||
if (!is_array($value)) $value = array($value);
|
||||
@@ -522,6 +548,8 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for IN / NOT IN conditions : values won't be parsed in the expression tree, that will save some time !
|
||||
*
|
||||
* @param string $sFilterCode attribute code to use
|
||||
* @param array $aValues
|
||||
* @param bool $bPositiveMatch if true will add a IN filter, else a NOT IN
|
||||
@@ -617,22 +645,40 @@ class DBObjectSearch extends DBSearch
|
||||
public function AddCondition_FullText($sNeedle)
|
||||
{
|
||||
// Transform the full text condition into additional condition expression
|
||||
$aFullTextFields = array();
|
||||
foreach (MetaModel::ListAttributeDefs($this->GetClass()) as $sAttCode => $oAttDef)
|
||||
{
|
||||
$aAttCodes = [];
|
||||
foreach (MetaModel::ListAttributeDefs($this->GetClass()) as $sAttCode => $oAttDef) {
|
||||
if (!$oAttDef->IsScalar()) continue;
|
||||
if ($oAttDef->IsExternalKey()) continue;
|
||||
if (!$oAttDef->IsSearchable()) continue;
|
||||
$aAttCodes[] = $sAttCode;
|
||||
}
|
||||
$this->AddCondition_FullTextOnAttributes($aAttCodes, $sNeedle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aAttCodes array of attCodes to search into
|
||||
* @param string $sNeedle one word to be searched
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function AddCondition_FullTextOnAttributes(array $aAttCodes, $sNeedle)
|
||||
{
|
||||
$aFullTextFields = [];
|
||||
foreach ($aAttCodes as $sAttCode) {
|
||||
$aFullTextFields[] = new FieldExpression($sAttCode, $this->GetClassAlias());
|
||||
}
|
||||
|
||||
$oTextFields = new CharConcatWSExpression(' ', $aFullTextFields);
|
||||
|
||||
$sQueryParam = 'needle';
|
||||
$sQueryParam = str_replace('.', '', uniqid('needle_', true));
|
||||
$oFlexNeedle = new CharConcatExpression(array(new ScalarExpression('%'), new VariableExpression($sQueryParam), new ScalarExpression('%')));
|
||||
|
||||
$oNewCond = new BinaryExpression($oTextFields, 'LIKE', $oFlexNeedle);
|
||||
$this->AddConditionExpression($oNewCond);
|
||||
$this->m_aParams[$sQueryParam] = $sNeedle;
|
||||
//replace in order to search the character "_" ("_" in mysql is like "%" for only one character).
|
||||
$sFullText = str_replace('_', '\_', $sNeedle);
|
||||
|
||||
$this->m_aParams[$sQueryParam] = $sFullText;
|
||||
}
|
||||
|
||||
protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation, $bTranslateMainAlias = true)
|
||||
@@ -781,10 +827,11 @@ class DBObjectSearch extends DBSearch
|
||||
* Helper to
|
||||
* - convert a translation table (format optimized for the translation in an expression tree) into simple hash
|
||||
* - compile over an eventually existing map
|
||||
* - accept multiple translations for the same alias for unions
|
||||
*
|
||||
* @param array $aRealiasingMap Map to update
|
||||
* @param array $aAliasTranslation Translation table resulting from calls to MergeWith_InNamespace
|
||||
* @return void of <old-alias> => <new-alias>
|
||||
* @return void of [old-alias][] => new-alias (@since 2.7.2)
|
||||
*/
|
||||
protected function UpdateRealiasingMap(&$aRealiasingMap, $aAliasTranslation)
|
||||
{
|
||||
@@ -792,17 +839,33 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
foreach ($aAliasTranslation as $sPrevAlias => $aRules)
|
||||
{
|
||||
if (isset($aRules['*']))
|
||||
if (!isset($aRules['*']))
|
||||
{
|
||||
$sNewAlias = $aRules['*'];
|
||||
$sOriginalAlias = array_search($sPrevAlias, $aRealiasingMap);
|
||||
if ($sOriginalAlias !== false)
|
||||
continue;
|
||||
}
|
||||
|
||||
$sNewAlias = $aRules['*'];
|
||||
$bOriginalFound = false;
|
||||
$iIndex = 0;
|
||||
foreach ($aRealiasingMap as $sOriginalAlias => $aAliases)
|
||||
{
|
||||
$iIndex = array_search($sPrevAlias, $aAliases);
|
||||
if ($iIndex !== false)
|
||||
{
|
||||
$aRealiasingMap[$sOriginalAlias] = $sNewAlias;
|
||||
$bOriginalFound = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
|
||||
}
|
||||
if ($bOriginalFound)
|
||||
{
|
||||
$aRealiasingMap[$sOriginalAlias][$iIndex] = $sNewAlias;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isset($aRealiasingMap[$sPrevAlias]) || !in_array($sNewAlias, $aRealiasingMap[$sPrevAlias]))
|
||||
{
|
||||
$aRealiasingMap[$sPrevAlias] = $sNewAlias;
|
||||
$aRealiasingMap[$sPrevAlias][] = $sNewAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -848,7 +911,7 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param DBObjectSearch $oFilter (can be modified)
|
||||
* @param $sExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
@@ -940,7 +1003,7 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param DBObjectSearch $oFilter (can be modified)
|
||||
* @param $sForeignExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
@@ -1036,7 +1099,7 @@ class DBObjectSearch extends DBSearch
|
||||
public function Filter($sClassAlias, DBSearch $oFilter)
|
||||
{
|
||||
// If the conditions are the correct ones for Intersect
|
||||
if (($this->GetFirstJoinedClass() == $oFilter->GetFirstJoinedClass()))
|
||||
if (MetaModel::IsParentClass($oFilter->GetFirstJoinedClass(),$this->GetFirstJoinedClass()))
|
||||
{
|
||||
return $this->Intersect($oFilter);
|
||||
}
|
||||
@@ -1068,7 +1131,6 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
if (($oSearch->GetFirstJoinedClassAlias() == $sClassAlias))
|
||||
{
|
||||
$oSearch->ResetCondition();
|
||||
$oSearch = $oSearch->IntersectSubClass($oFilter, $aRootClasses);
|
||||
return $oSearch->GetCriteria();
|
||||
}
|
||||
@@ -1170,7 +1232,7 @@ class DBObjectSearch extends DBSearch
|
||||
elseif (MetaModel::IsParentClass($oRightFilter->GetFirstJoinedClass(), $oLeftFilter->GetClass()))
|
||||
{
|
||||
// Specialize $oRightFilter
|
||||
$oRightFilter->ChangeClass($oLeftFilter->GetClass());
|
||||
$oRightFilter->ChangeClass($oLeftFilter->GetFirstJoinedClass());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2087,4 +2149,9 @@ class DBObjectSearch extends DBSearch
|
||||
}
|
||||
return $oExpression;
|
||||
}
|
||||
|
||||
public function ListParameters()
|
||||
{
|
||||
return $this->GetCriteria()->ListParameters();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,7 +424,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param bool $bWithId
|
||||
* @param bool $bWithId if true array key will be set to object id
|
||||
*
|
||||
* @return DBObject[]
|
||||
*
|
||||
@@ -763,7 +763,10 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
|
||||
try
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->m_oSQLResult = CMDBSource::Query($sSQL);
|
||||
$sOQL = $this->GetPseudoOQL($this->m_oFilter, $this->GetRealSortOrder(), $this->m_iLimitCount, $this->m_iLimitStart, false);
|
||||
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||
} catch (MySQLException $e)
|
||||
{
|
||||
// 1116 = ER_TOO_MANY_TABLES
|
||||
@@ -843,8 +846,11 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), 0, 0, true);
|
||||
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||
if (!$resQuery) return 0;
|
||||
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
@@ -855,6 +861,42 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBSearch $oFilter
|
||||
* @param array $aOrder
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @param bool $bCount
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function GetPseudoOQL($oFilter, $aOrder, $iLimitCount, $iLimitStart, $bCount)
|
||||
{
|
||||
$sOQL = '';
|
||||
if ($bCount) {
|
||||
$sOQL .= 'COUNT ';
|
||||
}
|
||||
$sOQL .= $oFilter->ToOQL();
|
||||
|
||||
if ($iLimitCount > 0) {
|
||||
$sOQL .= ' LIMIT ';
|
||||
if ($iLimitStart > 0) {
|
||||
$sOQL .= "$iLimitStart, ";
|
||||
}
|
||||
$sOQL .= "$iLimitCount";
|
||||
}
|
||||
|
||||
if (count($aOrder) > 0) {
|
||||
$sOQL .= ' ORDER BY ';
|
||||
$aOrderBy = [];
|
||||
foreach ($aOrder as $sAttCode => $bAsc) {
|
||||
$aOrderBy[] = $sAttCode.' '.($bAsc ? 'ASC' : 'DESC');
|
||||
}
|
||||
$sOQL .= implode(', ', $aOrderBy);
|
||||
}
|
||||
return $sOQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the count exceeds a given limit
|
||||
*
|
||||
@@ -871,8 +913,11 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
|
||||
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||
if ($resQuery)
|
||||
{
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
@@ -883,7 +928,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
$iCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$iCount = $this->m_iNumTotalDBRows;
|
||||
@@ -908,8 +953,11 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
|
||||
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
|
||||
if ($resQuery)
|
||||
{
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
@@ -920,7 +968,7 @@ class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
$iCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$iCount = $this->m_iNumTotalDBRows;
|
||||
|
||||
@@ -97,42 +97,40 @@ abstract class DBSearch
|
||||
/**
|
||||
* Perform a deep clone (as opposed to "clone" which does copy a reference to the underlying objects)
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return \DBSearch
|
||||
**/
|
||||
**/
|
||||
public function DeepClone()
|
||||
{
|
||||
return unserialize(serialize($this)); // Beware this serializes/unserializes the search and its parameters as well
|
||||
}
|
||||
|
||||
/**
|
||||
* whether or not some information should be hidden to the current user.
|
||||
*
|
||||
* @api
|
||||
* @see IsAllDataAllowed()
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function AllowAllData();
|
||||
/**
|
||||
* @api
|
||||
* @see IsAllDataAllowed()
|
||||
*
|
||||
* @param bool $bAllowAllData whether or not some information should be hidden to the current user.
|
||||
*/
|
||||
abstract public function AllowAllData($bAllowAllData = true);
|
||||
|
||||
/**
|
||||
* Current state of AllowAllData
|
||||
*
|
||||
* @internal
|
||||
* @see AllowAllData()
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
/**
|
||||
* Current state of AllowAllData
|
||||
*
|
||||
* @internal
|
||||
* @see AllowAllData()
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function IsAllDataAllowed();
|
||||
|
||||
/**
|
||||
* Should the archives be fetched
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param $bEnable
|
||||
*/
|
||||
/**
|
||||
* Should the archives be fetched
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param $bEnable
|
||||
*/
|
||||
public function SetArchiveMode($bEnable)
|
||||
{
|
||||
$this->m_bArchiveMode = $bEnable;
|
||||
@@ -404,7 +402,9 @@ abstract class DBSearch
|
||||
*/
|
||||
abstract public function AddCondition_FullText($sFullText);
|
||||
|
||||
/**
|
||||
abstract public function AddCondition_FullTextOnAttributes(array $aAttCodes, $sNeedle);
|
||||
|
||||
/**
|
||||
* Perform a join, the remote class being matched by the mean of its primary key
|
||||
*
|
||||
* The join is performed
|
||||
@@ -507,6 +507,7 @@ abstract class DBSearch
|
||||
}
|
||||
else
|
||||
{
|
||||
/** @var \DBObjectSearch $oFilter */
|
||||
if ($iDirection === static::JOIN_POINTING_TO)
|
||||
{
|
||||
$oSourceFilter->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
@@ -630,7 +631,7 @@ abstract class DBSearch
|
||||
}
|
||||
|
||||
$sOql = $this->ToOql($bDevelopParams, $aContextParams);
|
||||
return json_encode(array($sOql, $aQueryParams, $this->m_aModifierProperties));
|
||||
return urlencode(json_encode(array($sOql, $aQueryParams, $this->m_aModifierProperties)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -647,7 +648,7 @@ abstract class DBSearch
|
||||
*/
|
||||
static public function unserialize($sValue)
|
||||
{
|
||||
$aData = json_decode($sValue, true);
|
||||
$aData = json_decode(urldecode($sValue), true);
|
||||
if (is_null($aData))
|
||||
{
|
||||
throw new CoreException("Invalid filter parameter");
|
||||
@@ -753,14 +754,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))
|
||||
{
|
||||
@@ -865,11 +866,11 @@ abstract class DBSearch
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($aColumns) == 0)
|
||||
{
|
||||
$aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass()));
|
||||
// Add the standard id (as first column)
|
||||
array_unshift($aColumns, 'id');
|
||||
if (count($aColumns) == 0)
|
||||
{
|
||||
$aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass()));
|
||||
// Add the standard id (as first column)
|
||||
array_unshift($aColumns, 'id');
|
||||
}
|
||||
|
||||
$aQueryCols = CMDBSource::GetColumns($resQuery, $sSQL);
|
||||
@@ -899,6 +900,55 @@ abstract class DBSearch
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a column ($sAttCode) from the specified class ($sClassAlias - default main class) of the DBsearch object and gives the result as an array
|
||||
* @param string $sAttCode
|
||||
* @param string|null $sClassAlias
|
||||
*
|
||||
* @return array
|
||||
* @throws ConfigException
|
||||
* @throws CoreException
|
||||
* @throws MissingQueryArgument
|
||||
* @throws MySQLException
|
||||
* @throws MySQLHasGoneAwayException
|
||||
*/
|
||||
public function SelectAttributeToArray(string $sAttCode, ?string $sClassAlias = null):array
|
||||
{
|
||||
if(is_null($sClassAlias)) {
|
||||
$sClassAlias = $this->GetClassAlias();
|
||||
}
|
||||
|
||||
$sClass = $this->GetClass();
|
||||
if($sAttCode === 'id'){
|
||||
$aAttToLoad[$sClassAlias]=[];
|
||||
} else {
|
||||
$aAttToLoad[$sClassAlias][$sAttCode] = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
}
|
||||
|
||||
$sSQL = $this->MakeSelectQuery([], [], $aAttToLoad);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
if (!$resQuery)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
$sColName = $sClassAlias.$sAttCode;
|
||||
|
||||
$aRes = [];
|
||||
while ($aRow = CMDBSource::FetchArray($resQuery))
|
||||
{
|
||||
$aMappedRow = array();
|
||||
if($sAttCode === 'id') {
|
||||
$aMappedRow[$sAttCode] = $aRow[$sColName];
|
||||
} else {
|
||||
$aMappedRow[$sAttCode] = $aAttToLoad[$sClassAlias][$sAttCode]->FromSQLToValue($aRow, $sColName);
|
||||
}
|
||||
$aRes[] = $aMappedRow;
|
||||
}
|
||||
CMDBSource::FreeResult($resQuery);
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Construction of the SQL queries
|
||||
@@ -979,7 +1029,7 @@ abstract class DBSearch
|
||||
$aAttToLoad = array();
|
||||
$oSQLQuery = $oQueryFilter->GetSQLQuery(array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr, $aSelectExpr);
|
||||
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams(), $this->GetExpectedArguments());
|
||||
try
|
||||
{
|
||||
$bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
|
||||
@@ -997,12 +1047,14 @@ abstract class DBSearch
|
||||
return $sRes;
|
||||
}
|
||||
|
||||
function GetExpectedArguments()
|
||||
{
|
||||
return $this->GetCriteria()->ListParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a SQL query from the current search
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* Generate a SQL query from the current search
|
||||
*
|
||||
* @param array $aOrderBy Array of '[<classalias>.]attcode' => bAscending
|
||||
* @param array $aArgs
|
||||
* @param null $aAttToLoad
|
||||
@@ -1010,12 +1062,16 @@ abstract class DBSearch
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @param bool $bGetCount
|
||||
* @param bool $bBeautifulSQL
|
||||
*
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
* @throws MissingQueryArgument
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false)
|
||||
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false, $bBeautifulSQL = true)
|
||||
{
|
||||
// Check the order by specification, and prefix with the class alias
|
||||
// and make sure that the ordering columns are going to be selected
|
||||
@@ -1076,12 +1132,11 @@ abstract class DBSearch
|
||||
else
|
||||
{
|
||||
// The complete list of arguments will include magic arguments (e.g. current_user->attcode)
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams(), $this->GetExpectedArguments());
|
||||
}
|
||||
try
|
||||
{
|
||||
// $bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
|
||||
$sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, true);
|
||||
$sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, $bBeautifulSQL);
|
||||
if ($sClassAlias == '_itop_')
|
||||
{
|
||||
IssueLog::Info('SQL Query (_itop_): '.$sRes);
|
||||
@@ -1107,6 +1162,8 @@ abstract class DBSearch
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
*
|
||||
* @since 2.7.0 N°2555
|
||||
*/
|
||||
public function GetFirstResult($bMustHaveOneResultMax = true, $aOrderBy = array(), $aSearchParams = array())
|
||||
{
|
||||
@@ -1174,12 +1231,35 @@ abstract class DBSearch
|
||||
if (is_object($oVisibleObjects))
|
||||
{
|
||||
$oVisibleObjects->AllowAllData();
|
||||
$oSearch = $this->Filter($sClassAlias, $oVisibleObjects);
|
||||
/** @var DBSearch $oSearch */
|
||||
$oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects);
|
||||
$oSearch->SetDataFiltered();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($aGroupByExpr))
|
||||
{
|
||||
foreach($aGroupByExpr as $sAlias => $oGroupByExp)
|
||||
{
|
||||
/** @var \Expression $oGroupByExp */
|
||||
|
||||
$aFields = $oGroupByExp->ListRequiredFields();
|
||||
foreach($aFields as $sFieldAlias)
|
||||
{
|
||||
$aMatches = array();
|
||||
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
|
||||
{
|
||||
$sFieldClass = $this->GetClassName($aMatches[1]);
|
||||
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
|
||||
if ( $oAttDef instanceof iAttributeNoGroupBy)
|
||||
{
|
||||
throw new Exception("Grouping on '$sFieldClass' fields is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$oSQLQuery = $oSearch->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, null, $aSelectExpr);
|
||||
$oSQLQuery->SetSourceOQL($oSearch->ToOQL());
|
||||
|
||||
@@ -1226,6 +1306,8 @@ abstract class DBSearch
|
||||
*/
|
||||
public abstract function GetCriteria();
|
||||
|
||||
public abstract function ListParameters();
|
||||
|
||||
/**
|
||||
* Shortcut to add efficient IN condition
|
||||
*
|
||||
@@ -1590,7 +1672,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
|
||||
|
||||
@@ -65,9 +65,7 @@ class DBUnionSearch extends DBSearch
|
||||
{
|
||||
$this->aSearches[] = $oSubSearch->DeepClone();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$this->aSearches[] = $oSearch->DeepClone();
|
||||
}
|
||||
}
|
||||
@@ -75,17 +73,16 @@ class DBUnionSearch extends DBSearch
|
||||
$this->ComputeSelectedClasses();
|
||||
}
|
||||
|
||||
public function AllowAllData()
|
||||
public function AllowAllData($bAllowAllData = true)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch) {
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
}
|
||||
|
||||
public function IsAllDataAllowed()
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch) {
|
||||
if ($oSearch->IsAllDataAllowed() === false) return false;
|
||||
}
|
||||
return true;
|
||||
@@ -376,19 +373,26 @@ class DBUnionSearch extends DBSearch
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
public function AddCondition_FullTextOnAttributes(array $aAttCodes, $sNeedle)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddCondition_FullTextOnAttributes($aAttCodes, $sNeedle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
* @throws CoreException
|
||||
* @throws CoreWarning
|
||||
* @param null $aRealiasingMap array of [old-alias][] => <new-alias>, for each alias that has changed (@since 2.7.2)
|
||||
*/
|
||||
public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
$oConditionFilter = $oFilter->DeepClone();
|
||||
$oSearch->AddCondition_PointingTo($oConditionFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,13 +400,14 @@ class DBUnionSearch extends DBSearch
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sForeignExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
* @param null $aRealiasingMap array of [old-alias][] => <new-alias>, for each alias that has changed (@since 2.7.2)
|
||||
*/
|
||||
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
$oConditionFilter = $oFilter->DeepClone();
|
||||
$oSearch->AddCondition_ReferencedBy($oConditionFilter, $sForeignExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +416,11 @@ class DBUnionSearch extends DBSearch
|
||||
$aSearches = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aSearches[] = $oSearch->Filter($sClassAlias, $oFilter);
|
||||
if (!$oSearch->IsAllDataAllowed()) {
|
||||
$aSearches[] = $oSearch->Filter($sClassAlias, $oFilter);
|
||||
} else {
|
||||
$aSearches[] = $oSearch;
|
||||
}
|
||||
}
|
||||
return new DBUnionSearch($aSearches);
|
||||
}
|
||||
@@ -667,6 +676,16 @@ class DBUnionSearch extends DBSearch
|
||||
return $oSQLQuery;
|
||||
}
|
||||
|
||||
function GetExpectedArguments()
|
||||
{
|
||||
$aVariableCriteria = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aVariableCriteria = array_merge($aVariableCriteria, $oSearch->GetExpectedArguments());
|
||||
}
|
||||
|
||||
return $aVariableCriteria;
|
||||
}
|
||||
/**
|
||||
* @return \Expression
|
||||
*/
|
||||
@@ -718,4 +737,14 @@ class DBUnionSearch extends DBSearch
|
||||
$oSearch->AddConditionExpression($oInCondition);
|
||||
}
|
||||
}
|
||||
|
||||
public function ListParameters()
|
||||
{
|
||||
$aParameters = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aParameters = array_merge($aParameters, $oSearch->ListParameters());
|
||||
}
|
||||
return $aParameters;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
/**
|
||||
* Class Dict
|
||||
* Management of localizable strings
|
||||
* Management of localizable strings
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2018 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
@@ -64,6 +64,8 @@ class Dict
|
||||
protected static $m_aLanguages = array(); // array( code => array( 'description' => '...', 'localized_description' => '...') ...)
|
||||
protected static $m_aData = array();
|
||||
protected static $m_sApplicationPrefix = null;
|
||||
/** @var \ApcService $m_oApcService */
|
||||
protected static $m_oApcService = null;
|
||||
|
||||
/**
|
||||
* @param $sLanguageCode
|
||||
@@ -142,50 +144,69 @@ class Dict
|
||||
* @return string
|
||||
*/
|
||||
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||
{
|
||||
$aInfo = self::GetLabelAndLangCode($sStringCode, $sDefault, $bUserLanguageOnly);
|
||||
return $aInfo['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a localised string from the dictonary with its associated lang code
|
||||
*
|
||||
* @param string $sStringCode The code identifying the dictionary entry
|
||||
* @param string $sDefault Default value if there is no match in the dictionary
|
||||
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
|
||||
*
|
||||
* @return array{
|
||||
* lang: string, label: string
|
||||
* } with localized label string and used lang code
|
||||
*/
|
||||
private static function GetLabelAndLangCode($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||
{
|
||||
// Attempt to find the string in the user language
|
||||
//
|
||||
self::InitLangIfNeeded(self::GetUserLanguage());
|
||||
$sLangCode = self::GetUserLanguage();
|
||||
self::InitLangIfNeeded($sLangCode);
|
||||
|
||||
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
|
||||
if (! array_key_exists($sLangCode, self::$m_aData))
|
||||
{
|
||||
IssueLog::Warning("Cannot find $sLangCode in all registered dictionaries.");
|
||||
// It may happen, when something happens before the dictionaries get loaded
|
||||
return $sStringCode;
|
||||
return [ 'label' => $sStringCode, 'lang' => $sLangCode ];
|
||||
}
|
||||
$aCurrentDictionary = self::$m_aData[self::GetUserLanguage()];
|
||||
if (array_key_exists($sStringCode, $aCurrentDictionary))
|
||||
$aCurrentDictionary = self::$m_aData[$sLangCode];
|
||||
if (is_array($aCurrentDictionary) && array_key_exists($sStringCode, $aCurrentDictionary))
|
||||
{
|
||||
return $aCurrentDictionary[$sStringCode];
|
||||
return [ 'label' => $aCurrentDictionary[$sStringCode], 'lang' => $sLangCode ];
|
||||
}
|
||||
if (!$bUserLanguageOnly)
|
||||
{
|
||||
// Attempt to find the string in the default language
|
||||
//
|
||||
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
||||
|
||||
|
||||
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
|
||||
if (array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
{
|
||||
return $aDefaultDictionary[$sStringCode];
|
||||
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => self::$m_sDefaultLanguage ];
|
||||
}
|
||||
// Attempt to find the string in english
|
||||
//
|
||||
self::InitLangIfNeeded('EN US');
|
||||
|
||||
$aDefaultDictionary = self::$m_aData['EN US'];
|
||||
if (array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
{
|
||||
return $aDefaultDictionary[$sStringCode];
|
||||
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => 'EN US' ];
|
||||
}
|
||||
}
|
||||
// Could not find the string...
|
||||
//
|
||||
if (is_null($sDefault))
|
||||
{
|
||||
return $sStringCode;
|
||||
return [ 'label' => $sStringCode, 'lang' => null ];
|
||||
}
|
||||
|
||||
return $sDefault;
|
||||
return [ 'label' => $sDefault, 'lang' => null ];
|
||||
}
|
||||
|
||||
|
||||
@@ -201,19 +222,25 @@ class Dict
|
||||
*/
|
||||
public static function Format($sFormatCode /*, ... arguments ....*/)
|
||||
{
|
||||
$sLocalizedFormat = self::S($sFormatCode);
|
||||
['label' => $sLocalizedFormat, 'lang' => $sLangCode] = self::GetLabelAndLangCode($sFormatCode);
|
||||
|
||||
$aArguments = func_get_args();
|
||||
array_shift($aArguments);
|
||||
|
||||
|
||||
if ($sLocalizedFormat == $sFormatCode)
|
||||
{
|
||||
// Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded)
|
||||
return $sFormatCode.' - '.implode(', ', $aArguments);
|
||||
}
|
||||
|
||||
return vsprintf($sLocalizedFormat, $aArguments);
|
||||
try{
|
||||
return vsprintf($sLocalizedFormat, $aArguments);
|
||||
} catch(\Throwable $e){
|
||||
\IssueLog::Error("Cannot format dict key", null, ["sFormatCode" => $sFormatCode, "sLangCode" => $sLangCode, 'exception_msg' => $e->getMessage() ]);
|
||||
return $sFormatCode.' - '.implode(', ', $aArguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize a the entries for a given language (replaces the former Add() method)
|
||||
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
|
||||
@@ -223,7 +250,7 @@ class Dict
|
||||
{
|
||||
self::$m_aData[$sLanguageCode] = $aEntries;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the list of available languages
|
||||
* @param hash $aLanguagesList
|
||||
@@ -232,7 +259,26 @@ class Dict
|
||||
{
|
||||
self::$m_aLanguages = $aLanguagesList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 2.7.6 N°4125
|
||||
* @return \ApcService
|
||||
*/
|
||||
public static function GetApcService() {
|
||||
if (self::$m_oApcService === null){
|
||||
self::$m_oApcService = new ApcService();
|
||||
}
|
||||
return self::$m_oApcService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7.6 N°4125
|
||||
* @param \ApcService $m_oApcService
|
||||
*/
|
||||
public static function SetApcService($oApcService) {
|
||||
self::$m_oApcService = $oApcService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a language from the language dictionary, if not already loaded
|
||||
* @param string $sLangCode Language code
|
||||
@@ -241,20 +287,23 @@ class Dict
|
||||
public static function InitLangIfNeeded($sLangCode)
|
||||
{
|
||||
if (array_key_exists($sLangCode, self::$m_aData)) return true;
|
||||
|
||||
|
||||
$bResult = false;
|
||||
|
||||
if (function_exists('apc_fetch') && (self::$m_sApplicationPrefix !== null))
|
||||
|
||||
if (self::GetApcService()->function_exists('apc_fetch')
|
||||
&& (self::$m_sApplicationPrefix !== null))
|
||||
{
|
||||
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
|
||||
//
|
||||
self::$m_aData[$sLangCode] = apc_fetch(self::$m_sApplicationPrefix.'-dict-'.$sLangCode);
|
||||
if (self::$m_aData[$sLangCode] === false)
|
||||
{
|
||||
self::$m_aData[$sLangCode] = self::GetApcService()->apc_fetch(self::$m_sApplicationPrefix.'-dict-'.$sLangCode);
|
||||
if (self::$m_aData[$sLangCode] === false) {
|
||||
unset(self::$m_aData[$sLangCode]);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else if (! is_array(self::$m_aData[$sLangCode])) {
|
||||
// N°4125: we dont fix dictionnary corrupted cache (on iTop side).
|
||||
// but we log an error in a dedicated channel to let itop administrator be aware of a potential APCu issue to fix.
|
||||
IssueLog::Error("APCu corrupted data (with $sLangCode dictionnary). APCu configuration and running version should be troubleshooted...", LogChannels::APC);
|
||||
$bResult = true;
|
||||
} else {
|
||||
$bResult = true;
|
||||
}
|
||||
}
|
||||
@@ -262,16 +311,17 @@ class Dict
|
||||
{
|
||||
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
|
||||
require_once($sDictFile);
|
||||
|
||||
if (function_exists('apc_store') && (self::$m_sApplicationPrefix !== null))
|
||||
|
||||
if (self::GetApcService()->function_exists('apc_store')
|
||||
&& (self::$m_sApplicationPrefix !== null))
|
||||
{
|
||||
apc_store(self::$m_sApplicationPrefix.'-dict-'.$sLangCode, self::$m_aData[$sLangCode]);
|
||||
self::GetApcService()->apc_store(self::$m_sApplicationPrefix.'-dict-'.$sLangCode, self::$m_aData[$sLangCode]);
|
||||
}
|
||||
$bResult = true;
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enable caching (cached using APC)
|
||||
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
|
||||
@@ -315,14 +365,14 @@ class Dict
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
|
||||
{
|
||||
$aMissing = array(); // Strings missing for the target language
|
||||
$aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary
|
||||
$aNotTranslated = array(); // Strings having the same value in both dictionaries
|
||||
$aOK = array(); // Strings having different values in both dictionaries
|
||||
|
||||
|
||||
foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue)
|
||||
{
|
||||
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode]))
|
||||
@@ -330,7 +380,7 @@ class Dict
|
||||
$aMissing[$sStringCode] = $sValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
|
||||
{
|
||||
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
|
||||
@@ -353,7 +403,7 @@ class Dict
|
||||
}
|
||||
return array($aMissing, $aUnexpected, $aNotTranslated, $aOK);
|
||||
}
|
||||
|
||||
|
||||
public static function Dump()
|
||||
{
|
||||
MyHelpers::var_dump_html(self::$m_aData);
|
||||
@@ -376,7 +426,7 @@ class Dict
|
||||
// No need to actually load the strings since it's only used to know the list of languages
|
||||
// at setup time !!
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export all the dictionary entries - of the given language - whose code matches the given prefix
|
||||
* missing entries in the current language will be replaced by entries in the default language
|
||||
@@ -389,7 +439,7 @@ class Dict
|
||||
self::InitLangIfNeeded(self::$m_sDefaultLanguage);
|
||||
$aEntries = array();
|
||||
$iLength = strlen($sStartingWith);
|
||||
|
||||
|
||||
// First prefill the array with entries from the default language
|
||||
foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
|
||||
{
|
||||
@@ -398,7 +448,7 @@ class Dict
|
||||
$aEntries[$sCode] = $sEntry;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Now put (overwrite) the entries for the user language
|
||||
foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
|
||||
{
|
||||
|
||||
@@ -1203,8 +1203,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();
|
||||
@@ -1431,83 +1433,25 @@ class DisplayableGraph extends SimpleGraph
|
||||
* @param int $iObjKey
|
||||
* @param string $sContextKey
|
||||
* @param array $aContextParams
|
||||
* @param bool $bLazyLoading since 2.7.7 3.0.1
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
*/
|
||||
function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams = array())
|
||||
{
|
||||
$aContextDefs = static::GetContextDefinitions($sContextKey, true, $aContextParams);
|
||||
$aExcludedByClass = array();
|
||||
foreach($aExcludedObjects as $oObj)
|
||||
{
|
||||
if (!array_key_exists(get_class($oObj), $aExcludedByClass))
|
||||
{
|
||||
$aExcludedByClass[get_class($oObj)] = array();
|
||||
}
|
||||
$aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
|
||||
}
|
||||
$sSftShort = Dict::S('UI:ElementsDisplayed');
|
||||
$sSearchToggle = Dict::S('UI:Search:Toggle');
|
||||
$oP->add("<div class=\"not-printable\">\n");
|
||||
$oP->add(
|
||||
<<<EOF
|
||||
<div id="ds_flash" class="search_box">
|
||||
<form id="dh_flash" class="search_form_handler closed">
|
||||
<h2 class="sf_title"><span class="sft_long">$sSftShort</span><span class="sft_short">$sSftShort</span><span class="sft_toggler fas fa-caret-down pull-right" title="$sSearchToggle"></span></h2>
|
||||
<div id="dh_flash_criterion_outer" class="sf_criterion_area"><div class="sf_criterion_row">
|
||||
EOF
|
||||
);
|
||||
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$("#dh_flash > .sf_title").click( function() {
|
||||
$("#dh_flash").toggleClass('closed');
|
||||
});
|
||||
$('#ReloadMovieBtn').button().button('disable');
|
||||
EOF
|
||||
);
|
||||
$aSortedElements = array();
|
||||
foreach($aResults as $sClassIdx => $aObjects)
|
||||
{
|
||||
foreach($aObjects as $oCurrObj)
|
||||
{
|
||||
$sSubClass = get_class($oCurrObj);
|
||||
$aSortedElements[$sSubClass] = MetaModel::GetName($sSubClass);
|
||||
}
|
||||
}
|
||||
|
||||
asort($aSortedElements);
|
||||
$idx = 0;
|
||||
foreach($aSortedElements as $sSubClass => $sClassName)
|
||||
{
|
||||
$oP->add("<span style=\"padding-right:2em; white-space:nowrap;\"><input type=\"checkbox\" id=\"exclude_$idx\" name=\"excluded[]\" value=\"$sSubClass\" checked onChange=\"$('#ReloadMovieBtn').button('enable')\"><label for=\"exclude_$idx\"> ".MetaModel::GetClassIcon($sSubClass)." $sClassName</label></span> ");
|
||||
$idx++;
|
||||
}
|
||||
$oP->add("<p style=\"text-align:right\"><button type=\"button\" id=\"ReloadMovieBtn\" onClick=\"DoReload()\">".Dict::S('UI:Button:Refresh')."</button></p>");
|
||||
$oP->add("</div></div></form>");
|
||||
$oP->add("</div>\n");
|
||||
$oP->add("</div>\n"); // class="not-printable"
|
||||
|
||||
$aAdditionalContexts = array();
|
||||
foreach($aContextDefs as $sKey => $aDefinition)
|
||||
{
|
||||
$aAdditionalContexts[] = array('key' => $sKey, 'label' => Dict::S($aDefinition['dict']), 'oql' => $aDefinition['oql'], 'default' => (array_key_exists('default', $aDefinition) && ($aDefinition['default'] == 'yes')));
|
||||
}
|
||||
|
||||
$sDirection = utils::ReadParam('d', 'horizontal');
|
||||
function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams = array(), $bLazyLoading = false)
|
||||
{
|
||||
list($aExcludedByClass, $aAdditionalContexts) = $this->DisplayFiltering($sContextKey, $aContextParams, $aExcludedObjects, $oP, $aResults, $bLazyLoading);
|
||||
$iGroupingThreshold = utils::ReadParam('g', 5);
|
||||
|
||||
|
||||
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js');
|
||||
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.contextMenu.css');
|
||||
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.contextMenu.js');
|
||||
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js');
|
||||
|
||||
try
|
||||
{
|
||||
$this->InitFromGraphviz();
|
||||
$sExportAsPdfURL = '';
|
||||
$sExportAsPdfURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_pdf&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up');
|
||||
$oAppcontext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
$sDrillDownURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class=%1$s&id=%2$s&'.$sContext;
|
||||
$sExportAsDocumentURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_attachment&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up');
|
||||
@@ -1586,7 +1530,14 @@ EOF
|
||||
// Export as Attachment requires GD (for building the PDF) AND a valid objclass/objkey couple
|
||||
unset($aParams['export_as_attachment']);
|
||||
}
|
||||
$oP->add_ready_script("$('#$sId').simple_graph(".json_encode($aParams).");");
|
||||
if ($oP->IsPrintableVersion() || !$bLazyLoading) {
|
||||
$oP->add_ready_script(" $('#$sId').simple_graph(".json_encode($aParams).");");
|
||||
} else {
|
||||
$oP->add_script("function Load(){var aExcluded = []; $('input[name^=excluded]').each( function() {if (!$(this).prop('checked')) { aExcluded.push($(this).val()); }} ); var params= $.extend(".json_encode($aParams).", {excluded_classes: aExcluded}); $('#$sId').simple_graph(params);}");
|
||||
$oP->add_ready_script("$('#impacted_objects_lists').html('".utils::TextToHtml(Dict::S('Relation:impacts/NoFilteredData'))."');$('#impacted_groups').html('".utils::TextToHtml(Dict::S('Relation:impacts/NoFilteredData'))."');");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
@@ -1618,5 +1569,86 @@ EOF
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sContextKey
|
||||
* @param array $aContextParams
|
||||
* @param array $aExcludedObjects
|
||||
* @param \WebPage $oP
|
||||
* @param array $aResults
|
||||
* @param bool $bLazyLoading
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
* @throws \DictExceptionMissingString
|
||||
* @since 2.7.7 & 3.0.1
|
||||
*/
|
||||
protected function DisplayFiltering($sContextKey, $aContextParams, $aExcludedObjects, $oP, $aResults, $bLazyLoading)
|
||||
{
|
||||
$aContextDefs = static::GetContextDefinitions($sContextKey, true, $aContextParams);
|
||||
$aExcludedByClass = array();
|
||||
foreach ($aExcludedObjects as $oObj) {
|
||||
if (!array_key_exists(get_class($oObj), $aExcludedByClass)) {
|
||||
$aExcludedByClass[get_class($oObj)] = array();
|
||||
}
|
||||
$aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
|
||||
}
|
||||
$sSftShort = Dict::S('UI:ElementsDisplayed');
|
||||
$sSearchToggle = Dict::S('UI:Search:Toggle');
|
||||
$oP->add("<div class=\"not-printable\">\n");
|
||||
$oP->add(
|
||||
<<<EOF
|
||||
<div id="ds_flash" class="search_box">
|
||||
<form id="dh_flash" class="search_form_handler">
|
||||
<h2 class="sf_title"><span class="sft_long">$sSftShort</span><span class="sft_short">$sSftShort</span><span class="sft_toggler fas fa-caret-down pull-right" title="$sSearchToggle"></span></h2>
|
||||
<div id="dh_flash_criterion_outer" class="sf_criterion_area"><div class="sf_criterion_row">
|
||||
EOF
|
||||
);
|
||||
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$("#dh_flash > .sf_title").click( function() {
|
||||
$("#dh_flash").toggleClass('closed');
|
||||
});
|
||||
$('#ReloadMovieBtn').button().button('disable');
|
||||
EOF
|
||||
);
|
||||
if ($bLazyLoading) {
|
||||
$oP->add_ready_script("$('#ReloadMovieBtn').button('enable');");
|
||||
} else {
|
||||
$oP->add_ready_script("$('#dh_flash').addClass('closed');");
|
||||
}
|
||||
$aSortedElements = array();
|
||||
foreach ($aResults as $sClassIdx => $aObjects) {
|
||||
foreach ($aObjects as $oCurrObj) {
|
||||
$sSubClass = get_class($oCurrObj);
|
||||
$aSortedElements[$sSubClass] = MetaModel::GetName($sSubClass);
|
||||
}
|
||||
}
|
||||
|
||||
asort($aSortedElements);
|
||||
$idx = 0;
|
||||
foreach ($aSortedElements as $sSubClass => $sClassName) {
|
||||
$oP->add("<span style=\"padding-right:2em; white-space:nowrap;\"><input type=\"checkbox\" id=\"exclude_$idx\" name=\"excluded[]\" value=\"$sSubClass\" checked onChange=\"$('#ReloadMovieBtn').button('enable')\"><label for=\"exclude_$idx\"> ".MetaModel::GetClassIcon($sSubClass)." $sClassName</label></span> ");
|
||||
$idx++;
|
||||
}
|
||||
if ($bLazyLoading) {
|
||||
$sOnCLick = "Load(); $('#ReloadMovieBtn').attr('onclick','DoReload()');$('#ReloadMovieBtn').html('".Dict::S('UI:Button:Refresh')."');";
|
||||
$oP->add("<p style=\"text-align:right\"><button type=\"button\" id=\"ReloadMovieBtn\" onClick=\"$sOnCLick\">".Dict::S('Relation:impacts/LoadData')."</button></p>");
|
||||
} else {
|
||||
$sOnCLick = "DoReload()";
|
||||
$oP->add("<p style=\"text-align:right\"><button type=\"button\" id=\"ReloadMovieBtn\" onClick=\"$sOnCLick\">".Dict::S('UI:Button:Refresh')."</button></p>");
|
||||
}
|
||||
$oP->add("</div></div></form>");
|
||||
$oP->add("</div>\n");
|
||||
$oP->add("</div>\n"); // class="not-printable"
|
||||
|
||||
$aAdditionalContexts = array();
|
||||
foreach ($aContextDefs as $sKey => $aDefinition) {
|
||||
$aAdditionalContexts[] = array('key' => $sKey, 'label' => Dict::S($aDefinition['dict']), 'oql' => $aDefinition['oql'], 'default' => (array_key_exists('default', $aDefinition) && ($aDefinition['default'] == 'yes')));
|
||||
}
|
||||
|
||||
return array($aExcludedByClass, $aAdditionalContexts);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,38 +24,69 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
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')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,481 +97,96 @@ 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)
|
||||
{
|
||||
$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']);
|
||||
}
|
||||
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))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null)
|
||||
{
|
||||
if (($sMimeType === 'text/html') && ($sCustomStyles !== null))
|
||||
{
|
||||
$emogrifier = new \Pelago\Emogrifier($sBody, $sCustomStyles);
|
||||
$sBody = $emogrifier->emogrify(); // Adds html/body tags if not already present
|
||||
}
|
||||
$this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType);
|
||||
$this->m_oMessage->setBody($sBody, $sMimeType);
|
||||
$this->oMailer->SetBody($sBody, $sMimeType, $sCustomStyles);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
$this->m_aData['reply_to'] = $sAddress;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -215,7 +215,7 @@ class EventIssue extends Event
|
||||
MetaModel::Init_AddAttribute(new AttributePropertySet("data", array("allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'issue', 'impact', 'page', 'arguments_post', 'arguments_get', 'callstack', 'data')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'message', 'userinfo', 'issue', 'impact', 'page', 'arguments_post', 'arguments_get', 'callstack', 'data')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'issue', 'impact')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Application\Helper\ExportHelper;
|
||||
|
||||
require_once(APPROOT.'application/xlsxwriter.class.php');
|
||||
|
||||
class ExcelBulkExport extends TabularBulkExport
|
||||
@@ -89,6 +91,7 @@ class ExcelBulkExport extends TabularBulkExport
|
||||
|
||||
case 'xlsx_options':
|
||||
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:XLSXOptions').'</legend>');
|
||||
$oP->add(ExportHelper::GetAlertForExcelMaliciousInjection());
|
||||
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
|
||||
|
||||
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';
|
||||
@@ -216,7 +219,14 @@ EOF
|
||||
}
|
||||
else if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
|
||||
{
|
||||
$sRet = $oAttDef->GetEditValue($value, $oObj);
|
||||
if ($oAttDef instanceof AttributeText && $oAttDef->GetFormat()=='html')
|
||||
{
|
||||
$sRet = str_replace(">", ">", $value);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = $oAttDef->GetEditValue($value, $oObj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -346,7 +356,8 @@ EOF
|
||||
|
||||
$fStartExcel = microtime(true);
|
||||
$writer = new XLSXWriter();
|
||||
$oDateTimeFormat = new DateTimeFormat($this->aStatusInfo['date_format']);
|
||||
$sDateFormat = isset($this->aStatusInfo['date_format']) ? $this->aStatusInfo['date_format'] : (string)AttributeDateTime::GetFormat();
|
||||
$oDateTimeFormat = new DateTimeFormat($sDateFormat);
|
||||
$writer->setDateTimeFormat($oDateTimeFormat->ToExcel());
|
||||
$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
|
||||
$writer->setDateFormat($oDateFormat->ToExcel());
|
||||
|
||||
@@ -34,43 +34,50 @@ abstract class HTMLSanitizer
|
||||
|
||||
/**
|
||||
* Sanitize an HTML string with the configured sanitizer, falling back to HTMLDOMSanitizer in case of Exception or invalid configuration
|
||||
*
|
||||
* @param string $sHTML
|
||||
* @param string $sConfigKey
|
||||
*
|
||||
* @return string
|
||||
* @noinspection SelfClassReferencingInspection
|
||||
*/
|
||||
public static function Sanitize($sHTML)
|
||||
public static function Sanitize($sHTML, $sConfigKey = 'html_sanitizer')
|
||||
{
|
||||
$sSanitizerClass = MetaModel::GetConfig()->Get('html_sanitizer');
|
||||
if(!class_exists($sSanitizerClass))
|
||||
{
|
||||
IssueLog::Warning('The configured "html_sanitizer" class "'.$sSanitizerClass.'" is not a valid class. Will use HTMLDOMSanitizer as the default sanitizer.');
|
||||
$sSanitizerClass = MetaModel::GetConfig()->Get($sConfigKey);
|
||||
if (!class_exists($sSanitizerClass)) {
|
||||
IssueLog::Warning('The configured "'.$sConfigKey.'" class "'.$sSanitizerClass.'" is not a valid class. Will use HTMLDOMSanitizer as the default sanitizer.');
|
||||
$sSanitizerClass = 'HTMLDOMSanitizer';
|
||||
} else if (false === is_subclass_of($sSanitizerClass, HTMLSanitizer::class)) {
|
||||
if ($sConfigKey === 'html_sanitizer') {
|
||||
IssueLog::Warning('The configured "'.$sConfigKey.'" class "'.$sSanitizerClass.'" is not a subclass of '.HTMLSanitizer::class.'. Will use HTMLDOMSanitizer as the default sanitizer.');
|
||||
$sSanitizerClass = 'HTMLDOMSanitizer';
|
||||
} else {
|
||||
IssueLog::Error('The configured "'.$sConfigKey.'" class "'.$sSanitizerClass.'" is not a subclass of '.HTMLSanitizer::class.' ! Won\'t sanitize string.');
|
||||
|
||||
return $sHTML;
|
||||
}
|
||||
}
|
||||
else if(!is_subclass_of($sSanitizerClass, 'HTMLSanitizer'))
|
||||
{
|
||||
IssueLog::Warning('The configured "html_sanitizer" class "'.$sSanitizerClass.'" is not a subclass of HTMLSanitizer. Will use HTMLDOMSanitizer as the default sanitizer.');
|
||||
$sSanitizerClass = 'HTMLDOMSanitizer';
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
try {
|
||||
$oSanitizer = new $sSanitizerClass();
|
||||
$sCleanHTML = $oSanitizer->DoSanitize($sHTML);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
if($sSanitizerClass != 'HTMLDOMSanitizer')
|
||||
{
|
||||
IssueLog::Warning('Failed to sanitize an HTML string with "'.$sSanitizerClass.'". The following exception occured: '.$e->getMessage());
|
||||
IssueLog::Warning('Will try to sanitize with HTMLDOMSanitizer.');
|
||||
// try again with the HTMLDOMSanitizer
|
||||
$oSanitizer = new HTMLDOMSanitizer();
|
||||
$sCleanHTML = $oSanitizer->DoSanitize($sHTML);
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Error('Failed to sanitize an HTML string with "HTMLDOMSanitizer". The following exception occured: '.$e->getMessage());
|
||||
IssueLog::Error('The HTML will NOT be sanitized.');
|
||||
$sCleanHTML = $sHTML;
|
||||
catch(Exception $e) {
|
||||
if ($sConfigKey === 'html_sanitizer') {
|
||||
if ($sSanitizerClass !== HTMLDOMSanitizer::class) {
|
||||
IssueLog::Warning('Failed to sanitize an HTML string with "'.$sSanitizerClass.'". The following exception occured: '.$e->getMessage());
|
||||
IssueLog::Warning('Will try to sanitize with HTMLDOMSanitizer.');
|
||||
// try again with the HTMLDOMSanitizer
|
||||
$oSanitizer = new HTMLDOMSanitizer();
|
||||
$sCleanHTML = $oSanitizer->DoSanitize($sHTML);
|
||||
} else {
|
||||
IssueLog::Error('Failed to sanitize an HTML string with "HTMLDOMSanitizer". The following exception occured: '.$e->getMessage());
|
||||
IssueLog::Error('The HTML will NOT be sanitized.');
|
||||
$sCleanHTML = $sHTML;
|
||||
}
|
||||
} else {
|
||||
IssueLog::Error('Failed to sanitize string with "'.$sSanitizerClass.'", will return original value ! Exception: '.$e->getMessage());
|
||||
$sCleanHTML = $sHTML;
|
||||
}
|
||||
}
|
||||
return $sCleanHTML;
|
||||
@@ -97,67 +104,179 @@ class HTMLNullSanitizer extends HTMLSanitizer
|
||||
{
|
||||
return $sHTML;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A standard-compliant HTMLSanitizer based on the HTMLPurifier library by Edward Z. Yang
|
||||
* Complete but quite slow
|
||||
* http://htmlpurifier.org
|
||||
* Common implementation for sanitizer using DOM parsing
|
||||
*/
|
||||
/*
|
||||
class HTMLPurifierSanitizer extends HTMLSanitizer
|
||||
abstract class DOMSanitizer extends HTMLSanitizer
|
||||
{
|
||||
protected static $oPurifier = null;
|
||||
|
||||
public function __construct()
|
||||
/** @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)
|
||||
{
|
||||
if (self::$oPurifier == null)
|
||||
{
|
||||
$sLibPath = APPROOT.'lib/htmlpurifier/HTMLPurifier.auto.php';
|
||||
if (!file_exists($sLibPath))
|
||||
{
|
||||
throw new Exception("Missing library '$sLibPath', cannot use HTMLPurifierSanitizer.");
|
||||
}
|
||||
require_once($sLibPath);
|
||||
|
||||
$oPurifierConfig = HTMLPurifier_Config::createDefault();
|
||||
$oPurifierConfig->set('Core.Encoding', 'UTF-8'); // defaults to 'UTF-8'
|
||||
$oPurifierConfig->set('HTML.Doctype', 'XHTML 1.0 Strict'); // defaults to 'XHTML 1.0 Transitional'
|
||||
$oPurifierConfig->set('URI.AllowedSchemes', array (
|
||||
'http' => true,
|
||||
'https' => true,
|
||||
'data' => true, // This one is not present by default
|
||||
));
|
||||
$sPurifierCache = APPROOT.'data/HTMLPurifier';
|
||||
if (!is_dir($sPurifierCache))
|
||||
{
|
||||
mkdir($sPurifierCache);
|
||||
}
|
||||
if (!is_dir($sPurifierCache))
|
||||
{
|
||||
throw new Exception("Could not create the cache directory '$sPurifierCache'");
|
||||
}
|
||||
$oPurifierConfig->set('Cache.SerializerPath', $sPurifierCache); // no trailing slash
|
||||
self::$oPurifier = new HTMLPurifier($oPurifierConfig);
|
||||
}
|
||||
parent::__construct();
|
||||
|
||||
$this->sInlineImageClassName = $sInlineImageClassName;
|
||||
}
|
||||
|
||||
|
||||
abstract public function GetTagsWhiteList();
|
||||
|
||||
abstract public function GetTagsBlackList();
|
||||
|
||||
abstract public function GetAttrsWhiteList();
|
||||
|
||||
abstract public function GetAttrsBlackList();
|
||||
|
||||
abstract public function GetStylesWhiteList();
|
||||
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
$sCleanHtml = self::$oPurifier->purify($sHTML);
|
||||
return $sCleanHtml;
|
||||
$this->oDoc = new DOMDocument();
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
|
||||
// MS outlook implements empty lines by the mean of <p><o:p></o:p></p>
|
||||
// We have to transform that into <p><br></p> (which is how Thunderbird implements empty lines)
|
||||
// Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace)
|
||||
// therefore we have to do the transformation upfront
|
||||
$sHTML = preg_replace('@<o:p>(\s| )*</o:p>@', '<br>', $sHTML);
|
||||
|
||||
$this->LoadDoc($sHTML);
|
||||
|
||||
$this->CleanNode($this->oDoc);
|
||||
|
||||
$sCleanHtml = $this->PrintDoc();
|
||||
|
||||
return $sCleanHtml;
|
||||
}
|
||||
|
||||
abstract public function LoadDoc($sHTML);
|
||||
|
||||
/**
|
||||
* @return string cleaned source
|
||||
* @uses \DOMSanitizer::oDoc
|
||||
*/
|
||||
abstract public function PrintDoc();
|
||||
|
||||
protected function CleanNode(DOMNode $oElement)
|
||||
{
|
||||
$aAttrToRemove = array();
|
||||
// Gather the attributes to remove
|
||||
if ($oElement->hasAttributes()) {
|
||||
foreach ($oElement->attributes as $oAttr) {
|
||||
$sAttr = strtolower($oAttr->name);
|
||||
if ((false === empty($this->GetAttrsBlackList()))
|
||||
&& (in_array($sAttr, $this->GetAttrsBlackList(), true))) {
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
} else if ((false === empty($this->GetTagsWhiteList()))
|
||||
&& (false === in_array($sAttr, $this->GetTagsWhiteList()[strtolower($oElement->tagName)]))) {
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
} else if (!$this->IsValidAttributeContent($sAttr, $oAttr->value)) {
|
||||
// Invalid content
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
} else if ($sAttr == 'style') {
|
||||
// Special processing for style tags
|
||||
$sCleanStyle = $this->CleanStyle($oAttr->value);
|
||||
if ($sCleanStyle == '') {
|
||||
// Invalid content
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
} else {
|
||||
$oElement->setAttribute($oAttr->name, $sCleanStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aAttrToRemove as $sName)
|
||||
{
|
||||
$oElement->removeAttribute($sName);
|
||||
}
|
||||
}
|
||||
|
||||
if ($oElement->hasChildNodes())
|
||||
{
|
||||
$aChildElementsToRemove = array();
|
||||
// Gather the child noes to remove
|
||||
foreach($oElement->childNodes as $oNode) {
|
||||
if ($oNode instanceof DOMElement) {
|
||||
$sNodeTagName = strtolower($oNode->tagName);
|
||||
}
|
||||
if (($oNode instanceof DOMElement)
|
||||
&& (false === empty($this->GetTagsBlackList()))
|
||||
&& (in_array($sNodeTagName, $this->GetTagsBlackList(), true))) {
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
} else if (($oNode instanceof DOMElement)
|
||||
&& (false === empty($this->GetTagsWhiteList()))
|
||||
&& (false === array_key_exists($sNodeTagName, $this->GetTagsWhiteList()))) {
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
} else if ($oNode instanceof DOMComment) {
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
} else {
|
||||
// Recurse
|
||||
$this->CleanNode($oNode);
|
||||
if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img')) {
|
||||
$this->sInlineImageClassName::ProcessImageTag($oNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aChildElementsToRemove as $oDomElement)
|
||||
{
|
||||
$oElement->removeChild($oDomElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function IsValidAttributeContent($sAttributeName, $sValue)
|
||||
{
|
||||
if ((false === empty($this->GetAttrsBlackList()))
|
||||
&& (in_array($sAttributeName, $this->GetAttrsBlackList(), true))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (array_key_exists($sAttributeName, $this->GetAttrsWhiteList())) {
|
||||
return preg_match($this->GetAttrsWhiteList()[$sAttributeName], $sValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function CleanStyle($sStyle)
|
||||
{
|
||||
if (empty($this->GetStylesWhiteList())) {
|
||||
return $sStyle;
|
||||
}
|
||||
|
||||
$aAllowedStyles = array();
|
||||
$aItems = explode(';', $sStyle);
|
||||
{
|
||||
foreach ($aItems as $sItem) {
|
||||
$aElements = explode(':', trim($sItem));
|
||||
if (in_array(trim(strtolower($aElements[0])), $this->GetStylesWhiteList())) {
|
||||
$aAllowedStyles[] = trim($sItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return implode(';', $aAllowedStyles);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
|
||||
class HTMLDOMSanitizer extends DOMSanitizer
|
||||
{
|
||||
protected $oDoc;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @see https://www.itophub.io/wiki/page?id=2_6_0%3Aadmin%3Arich_text_limitations
|
||||
* @var array
|
||||
*/
|
||||
protected static $aTagsWhiteList = array(
|
||||
'html' => array(),
|
||||
@@ -214,8 +333,8 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @see https://www.itophub.io/wiki/page?id=2_6_0%3Aadmin%3Arich_text_limitations
|
||||
* @var array
|
||||
*/
|
||||
protected static $aStylesWhiteList = array(
|
||||
'background-color',
|
||||
@@ -239,164 +358,200 @@ class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
'white-space',
|
||||
);
|
||||
|
||||
public function __construct()
|
||||
public function __construct($sInlineImageClassName = InlineImage::class)
|
||||
{
|
||||
parent::__construct();
|
||||
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))
|
||||
{
|
||||
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+\$_.-]*))?)?)';
|
||||
$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 = $sUrlPattern.'|'.$sMailtoPattern.'|'.$sPlaceholderPattern;
|
||||
$sPattern = '/'.str_replace('/', '\/', $sPattern).'/i';
|
||||
self::$aAttrsWhiteList['href'] = $sPattern;
|
||||
}
|
||||
}
|
||||
|
||||
public function DoSanitize($sHTML)
|
||||
public function GetTagsWhiteList()
|
||||
{
|
||||
$this->oDoc = new DOMDocument();
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
return static::$aTagsWhiteList;
|
||||
}
|
||||
|
||||
// MS outlook implements empty lines by the mean of <p><o:p></o:p></p>
|
||||
// We have to transform that into <p><br></p> (which is how Thunderbird implements empty lines)
|
||||
// Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace)
|
||||
// therefore we have to do the transformation upfront
|
||||
$sHTML = preg_replace('@<o:p>(\s| )*</o:p>@', '<br>', $sHTML);
|
||||
// Replace badly encoded non breaking space
|
||||
$sHTML = preg_replace('~\xc2\xa0~', ' ', $sHTML);
|
||||
public function GetTagsBlackList()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function GetAttrsWhiteList()
|
||||
{
|
||||
return static::$aAttrsWhiteList;
|
||||
}
|
||||
|
||||
public function GetAttrsBlackList()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function GetStylesWhiteList()
|
||||
{
|
||||
return static::$aStylesWhiteList;
|
||||
}
|
||||
|
||||
public function LoadDoc($sHTML)
|
||||
{
|
||||
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
|
||||
|
||||
$this->CleanNode($this->oDoc);
|
||||
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
}
|
||||
|
||||
public function PrintDoc()
|
||||
{
|
||||
$oXPath = new DOMXPath($this->oDoc);
|
||||
$sXPath = "//body";
|
||||
$oNodesList = $oXPath->query($sXPath);
|
||||
|
||||
if ($oNodesList->length == 0)
|
||||
{
|
||||
|
||||
if ($oNodesList->length == 0) {
|
||||
// No body, save the whole document
|
||||
$sCleanHtml = $this->oDoc->saveHTML();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Export only the content of the body tag
|
||||
$sCleanHtml = $this->oDoc->saveHTML($oNodesList->item(0));
|
||||
// remove the body tag itself
|
||||
$sCleanHtml = str_replace( array('<body>', '</body>'), '', $sCleanHtml);
|
||||
$sCleanHtml = str_replace(array('<body>', '</body>'), '', $sCleanHtml);
|
||||
}
|
||||
|
||||
|
||||
return $sCleanHtml;
|
||||
}
|
||||
|
||||
protected function CleanNode(DOMNode $oElement)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 2.6.5 2.7.6 3.0.0 N°4360
|
||||
*/
|
||||
class SVGDOMSanitizer extends DOMSanitizer
|
||||
{
|
||||
public function GetTagsWhiteList()
|
||||
{
|
||||
$aAttrToRemove = array();
|
||||
// Gather the attributes to remove
|
||||
if ($oElement->hasAttributes())
|
||||
{
|
||||
foreach($oElement->attributes as $oAttr)
|
||||
{
|
||||
$sAttr = strtolower($oAttr->name);
|
||||
if (!in_array($sAttr, self::$aTagsWhiteList[strtolower($oElement->tagName)]))
|
||||
{
|
||||
// Forbidden (or unknown) attribute
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
}
|
||||
else if (!$this->IsValidAttributeContent($sAttr, $oAttr->value))
|
||||
{
|
||||
// Invalid content
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
}
|
||||
else if ($sAttr == 'style')
|
||||
{
|
||||
// Special processing for style tags
|
||||
$sCleanStyle = $this->CleanStyle($oAttr->value);
|
||||
if ($sCleanStyle == '')
|
||||
{
|
||||
// Invalid content
|
||||
$aAttrToRemove[] = $oAttr->name;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oElement->setAttribute($oAttr->name, $sCleanStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aAttrToRemove as $sName)
|
||||
{
|
||||
$oElement->removeAttribute($sName);
|
||||
}
|
||||
}
|
||||
|
||||
if ($oElement->hasChildNodes())
|
||||
{
|
||||
$aChildElementsToRemove = array();
|
||||
// Gather the child noes to remove
|
||||
foreach($oElement->childNodes as $oNode)
|
||||
{
|
||||
if (($oNode instanceof DOMElement) && (!array_key_exists(strtolower($oNode->tagName), self::$aTagsWhiteList)))
|
||||
{
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
}
|
||||
else if ($oNode instanceof DOMComment)
|
||||
{
|
||||
$aChildElementsToRemove[] = $oNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Recurse
|
||||
$this->CleanNode($oNode);
|
||||
if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img'))
|
||||
{
|
||||
InlineImage::ProcessImageTag($oNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aChildElementsToRemove as $oDomElement)
|
||||
{
|
||||
$oElement->removeChild($oDomElement);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function CleanStyle($sStyle)
|
||||
/**
|
||||
* @return string[]
|
||||
* @link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script
|
||||
*/
|
||||
public function GetTagsBlackList()
|
||||
{
|
||||
$aAllowedStyles = array();
|
||||
$aItems = explode(';', $sStyle);
|
||||
{
|
||||
foreach($aItems as $sItem)
|
||||
{
|
||||
$aElements = explode(':', trim($sItem));
|
||||
if (in_array(trim(strtolower($aElements[0])), static::$aStylesWhiteList))
|
||||
{
|
||||
$aAllowedStyles[] = trim($sItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return implode(';', $aAllowedStyles);
|
||||
return [
|
||||
'script',
|
||||
];
|
||||
}
|
||||
|
||||
protected function IsValidAttributeContent($sAttributeName, $sValue)
|
||||
|
||||
public function GetAttrsWhiteList()
|
||||
{
|
||||
if (array_key_exists($sAttributeName, self::$aAttrsWhiteList))
|
||||
{
|
||||
return preg_match(self::$aAttrsWhiteList[$sAttributeName], $sValue);
|
||||
}
|
||||
return true;
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Events#document_event_attributes
|
||||
*/
|
||||
public function GetAttrsBlackList()
|
||||
{
|
||||
return [
|
||||
'onbegin',
|
||||
'onbegin',
|
||||
'onrepeat',
|
||||
'onabort',
|
||||
'onerror',
|
||||
'onerror',
|
||||
'onscroll',
|
||||
'onunload',
|
||||
'oncopy',
|
||||
'oncut',
|
||||
'onpaste',
|
||||
'oncancel',
|
||||
'oncanplay',
|
||||
'oncanplaythrough',
|
||||
'onchange',
|
||||
'onclick',
|
||||
'onclose',
|
||||
'oncuechange',
|
||||
'ondblclick',
|
||||
'ondrag',
|
||||
'ondragend',
|
||||
'ondragenter',
|
||||
'ondragleave',
|
||||
'ondragover',
|
||||
'ondragstart',
|
||||
'ondrop',
|
||||
'ondurationchange',
|
||||
'onemptied',
|
||||
'onended',
|
||||
'onerror',
|
||||
'onfocus',
|
||||
'oninput',
|
||||
'oninvalid',
|
||||
'onkeydown',
|
||||
'onkeypress',
|
||||
'onkeyup',
|
||||
'onload',
|
||||
'onloadeddata',
|
||||
'onloadedmetadata',
|
||||
'onloadstart',
|
||||
'onmousedown',
|
||||
'onmouseenter',
|
||||
'onmouseleave',
|
||||
'onmousemove',
|
||||
'onmouseout',
|
||||
'onmouseover',
|
||||
'onmouseup',
|
||||
'onmousewheel',
|
||||
'onpause',
|
||||
'onplay',
|
||||
'onplaying',
|
||||
'onprogress',
|
||||
'onratechange',
|
||||
'onreset',
|
||||
'onresize',
|
||||
'onscroll',
|
||||
'onseeked',
|
||||
'onseeking',
|
||||
'onselect',
|
||||
'onshow',
|
||||
'onstalled',
|
||||
'onsubmit',
|
||||
'onsuspend',
|
||||
'ontimeupdate',
|
||||
'ontoggle',
|
||||
'onvolumechange',
|
||||
'onwaiting',
|
||||
'onactivate',
|
||||
'onfocusin',
|
||||
'onfocusout',
|
||||
];
|
||||
}
|
||||
|
||||
public function GetStylesWhiteList()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function LoadDoc($sHTML)
|
||||
{
|
||||
@$this->oDoc->loadXml($sHTML, LIBXML_NOBLANKS);
|
||||
}
|
||||
|
||||
public function PrintDoc()
|
||||
{
|
||||
return $this->oDoc->saveXML();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
*/
|
||||
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Parser;
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\PrettyPrinter\Standard;
|
||||
|
||||
@@ -80,38 +82,49 @@ class iTopConfigParser
|
||||
* @param \PhpParser\Parser $oParser
|
||||
* @param $sConfig
|
||||
*
|
||||
* @return \Combodo\iTop\Config\Validator\ConfigNodesVisitor
|
||||
* @return void
|
||||
*/
|
||||
private function BrowseFile(\PhpParser\Parser $oParser, $sConfig)
|
||||
private function BrowseFile(Parser $oParser, $sConfig)
|
||||
{
|
||||
$prettyPrinter = new Standard();
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
$aNodes = $oParser->parse($sConfig);
|
||||
}
|
||||
catch (\Error $e)
|
||||
{
|
||||
catch (\Error $e) {
|
||||
$sMessage = Dict::Format('config-parse-error', $e->getMessage(), $e->getLine());
|
||||
$this->oException = new \Exception($sMessage, 0, $e);
|
||||
}
|
||||
|
||||
foreach ($aNodes as $oAssignation)
|
||||
{
|
||||
if (! $oAssignation instanceof Assign)
|
||||
{
|
||||
foreach ($aNodes as $sKey => $oNode) {
|
||||
// With PhpParser 3 we had an Assign node at root
|
||||
// In PhpParser 4 the root node is now an Expression
|
||||
|
||||
if (false === ($oNode instanceof \PhpParser\Node\Stmt\Expression)) {
|
||||
continue;
|
||||
}
|
||||
/** @var \PhpParser\Node\Stmt\Expression $oNode */
|
||||
|
||||
if (false === ($oNode->expr instanceof Assign)) {
|
||||
continue;
|
||||
}
|
||||
/** @var Assign $oAssignation */
|
||||
$oAssignation = $oNode->expr;
|
||||
|
||||
if (false === ($oAssignation->var instanceof Variable)) {
|
||||
continue;
|
||||
}
|
||||
if (false === ($oAssignation->expr instanceof PhpParser\Node\Expr\Array_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sCurrentRootVar = $oAssignation->var->name;
|
||||
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap))
|
||||
{
|
||||
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap)) {
|
||||
continue;
|
||||
}
|
||||
$aCurrentRootVarMap =& $this->aVarsMap[$sCurrentRootVar];
|
||||
|
||||
foreach ($oAssignation->expr->items as $oItem)
|
||||
{
|
||||
foreach ($oAssignation->expr->items as $oItem) {
|
||||
$sValue = $prettyPrinter->prettyPrintExpr($oItem->value);
|
||||
$aCurrentRootVarMap[$oItem->key->value] = $sValue;
|
||||
}
|
||||
|
||||
@@ -176,26 +176,30 @@ class InlineImage extends DBObject
|
||||
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
|
||||
$oSearch = DBObjectSearch::FromOQL($sOQL);
|
||||
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
|
||||
while($oInlineImage = $oSet->Fetch())
|
||||
{
|
||||
$aInlineImagesId = array();
|
||||
while ($oInlineImage = $oSet->Fetch()) {
|
||||
$aInlineImagesId[] = $oInlineImage->GetKey();
|
||||
$oInlineImage->SetItem($oObject);
|
||||
$oInlineImage->Set('temp_id', '');
|
||||
$oInlineImage->DBUpdate();
|
||||
}
|
||||
IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', LogChannels::INLINE_IMAGE, array(
|
||||
'$sObjectClass' => get_class($oObject),
|
||||
'$sTransactionId' => $iTransactionId,
|
||||
'$sTempId' => $sTempId,
|
||||
'$aInlineImagesId' => $aInlineImagesId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
}
|
||||
// For tracing issues with Inline Images... but beware not all updates are interactive, so this trace happens when creating objects non-interactively (REST, Synchro...)
|
||||
// else
|
||||
// {
|
||||
// IssueLog::Error('InlineImage: Error during FinalizeInlineImages(), no transaction ID for object '.get_class($oObject).'#'.$oObject->GetKey().'.');
|
||||
//
|
||||
// IssueLog::Error('|- Call stack:');
|
||||
// $oException = new Exception();
|
||||
// $sStackTrace = $oException->getTraceAsString();
|
||||
// IssueLog::Error($sStackTrace);
|
||||
//
|
||||
// IssueLog::Error('|- POST vars:');
|
||||
// IssueLog::Error(print_r($_POST, true));
|
||||
// }
|
||||
else {
|
||||
IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', LogChannels::INLINE_IMAGE, array(
|
||||
'$sObjectClass' => get_class($oObject),
|
||||
'$sTransactionId' => $iTransactionId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,10 +212,18 @@ class InlineImage extends DBObject
|
||||
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
|
||||
$oSearch = DBObjectSearch::FromOQL($sOQL);
|
||||
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
|
||||
$aInlineImagesId = array();
|
||||
while($oInlineImage = $oSet->Fetch())
|
||||
{
|
||||
$aInlineImagesId[] = $oInlineImage->GetKey();
|
||||
$oInlineImage->DBDelete();
|
||||
}
|
||||
IssueLog::Trace('OnFormCancel', LogChannels::INLINE_IMAGE, array(
|
||||
'$sTempId' => $sTempId,
|
||||
'$aInlineImagesId' => $aInlineImagesId,
|
||||
'$sUser' => UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -548,19 +560,72 @@ EOF
|
||||
JS
|
||||
;
|
||||
}
|
||||
|
||||
protected function AfterInsert()
|
||||
{
|
||||
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
));
|
||||
|
||||
parent::AfterInsert();
|
||||
}
|
||||
|
||||
protected function AfterUpdate()
|
||||
{
|
||||
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
));
|
||||
|
||||
parent::AfterUpdate();
|
||||
}
|
||||
|
||||
protected function AfterDelete()
|
||||
{
|
||||
IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array(
|
||||
'id' => $this->GetKey(),
|
||||
'expire' => $this->Get('expire'),
|
||||
'temp_id' => $this->Get('temp_id'),
|
||||
'item_class' => $this->Get('item_class'),
|
||||
'item_id' => $this->Get('item_id'),
|
||||
'item_org_id' => $this->Get('item_org_id'),
|
||||
'secret' => $this->Get('secret'),
|
||||
'user' => $sUser = UserRights::GetUser(),
|
||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||
));
|
||||
|
||||
parent::AfterDelete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Garbage collector for cleaning "old" temporary InlineImages (and Attachments).
|
||||
* This background process runs every hour and deletes all temporary InlineImages and Attachments
|
||||
* whic are are older than one hour.
|
||||
*/
|
||||
class InlineImageGC implements iBackgroundProcess
|
||||
{
|
||||
public function GetPeriodicity()
|
||||
{
|
||||
return 1; // Runs every 8 hours
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -593,6 +658,9 @@ class InlineImageGC implements iBackgroundProcess
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove $sClass instance based on their `expire` field value.
|
||||
* This `expire` field contains current time + draft_attachments_lifetime config parameter, it is initialized on object creation.
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param int $iTimeLimit
|
||||
* @param string $sDateLimit
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
//
|
||||
// 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\Core\Kpi\KpiLogData;
|
||||
use Combodo\iTop\Service\Module\ModuleService;
|
||||
|
||||
|
||||
/**
|
||||
@@ -30,6 +32,8 @@ class ExecutionKPI
|
||||
static protected $m_bEnabled_Memory = false;
|
||||
static protected $m_bBlameCaller = false;
|
||||
static protected $m_sAllowedUser = '*';
|
||||
static protected $m_bGenerateLegacyReport = true;
|
||||
static protected $m_fSlowQueries = 0;
|
||||
|
||||
static protected $m_aStats = array(); // Recurrent operations
|
||||
static protected $m_aExecData = array(); // One shot operations
|
||||
@@ -77,14 +81,39 @@ class ExecutionKPI
|
||||
return false;
|
||||
}
|
||||
|
||||
static public function SetGenerateLegacyReport($bReportExtensionsOnly)
|
||||
{
|
||||
self::$m_bGenerateLegacyReport = $bReportExtensionsOnly;
|
||||
}
|
||||
|
||||
static public function SetSlowQueries($fSlowQueries)
|
||||
{
|
||||
self::$m_fSlowQueries = $fSlowQueries;
|
||||
}
|
||||
|
||||
static public function GetDescription()
|
||||
{
|
||||
$aFeatures = array();
|
||||
if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration';
|
||||
if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage';
|
||||
$sFeatures = implode(', ', $aFeatures);
|
||||
$sFeatures = 'Measures: '.implode(', ', $aFeatures);
|
||||
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
|
||||
return "KPI logging is active for $sFor. Measures: $sFeatures";
|
||||
$sSlowQueries = '';
|
||||
if (self::$m_fSlowQueries > 0) {
|
||||
$sSlowQueries = ". Slow Queries: ".self::$m_fSlowQueries."s";
|
||||
}
|
||||
|
||||
$aExtensions = [];
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
$aExtensions[] = ModuleService::GetInstance()->GetModuleNameFromObject($oExtensionInstance);
|
||||
}
|
||||
$sExtensions = '';
|
||||
if (count($aExtensions) > 0) {
|
||||
$sExtensions = '. KPI Extensions: ['.implode(', ', $aExtensions).']';
|
||||
}
|
||||
|
||||
return "KPI logging is active for $sFor. $sFeatures$sSlowQueries$sExtensions";
|
||||
}
|
||||
|
||||
static public function ReportStats()
|
||||
@@ -92,7 +121,28 @@ class ExecutionKPI
|
||||
if (!self::IsEnabled()) return;
|
||||
|
||||
global $fItopStarted;
|
||||
global $iItopInitialMemory;
|
||||
$sExecId = microtime(); // id to differentiate the hrefs!
|
||||
$sRequest = $_SERVER['REQUEST_URI'].' ('.$_SERVER['REQUEST_METHOD'].')';
|
||||
if (isset($_POST['operation'])) {
|
||||
$sRequest .= ' operation: '.$_POST['operation'];
|
||||
}
|
||||
|
||||
$fStop = MyHelpers::getmicrotime();
|
||||
if (($fStop - $fItopStarted) > self::$m_fSlowQueries) {
|
||||
// Invoke extensions to log the KPI operation
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
$iCurrentMemory = self::memory_get_usage();
|
||||
$iPeakMemory = self::memory_get_peak_usage();
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
$oKPILogData = new KpiLogData(KpiLogData::TYPE_REQUEST, 'Page', $sRequest, $fItopStarted, $fStop, '', $iItopInitialMemory, $iCurrentMemory, $iPeakMemory);
|
||||
$oExtensionInstance->LogOperation($oKPILogData);
|
||||
}
|
||||
}
|
||||
|
||||
if (!self::$m_bGenerateLegacyReport) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aBeginTimes = array();
|
||||
foreach (self::$m_aExecData as $aOpStats)
|
||||
@@ -105,7 +155,7 @@ class ExecutionKPI
|
||||
|
||||
self::Report("<hr/>");
|
||||
self::Report("<div style=\"background-color: grey; padding: 10px;\">");
|
||||
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>");
|
||||
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - $sRequest</h3>");
|
||||
self::Report("<p>".date('Y-m-d H:i:s', $fItopStarted)."</p>");
|
||||
self::Report("<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>");
|
||||
self::Report("<div>");
|
||||
@@ -200,8 +250,6 @@ class ExecutionKPI
|
||||
|
||||
self::Report("<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>");
|
||||
|
||||
$fSlowQueries = MetaModel::GetConfig()->Get('log_kpi_slow_queries');
|
||||
|
||||
// Report operation details
|
||||
foreach (self::$m_aStats as $sOperation => $aOpStats)
|
||||
{
|
||||
@@ -245,7 +293,7 @@ class ExecutionKPI
|
||||
$sTotalInter = round($fTotalInter, 3);
|
||||
$sMinInter = round($fMinInter, 3);
|
||||
$sMaxInter = round($fMaxInter, 3);
|
||||
if (($fTotalInter >= $fSlowQueries))
|
||||
if (($fTotalInter >= self::$m_fSlowQueries))
|
||||
{
|
||||
if ($bDisplayHeader)
|
||||
{
|
||||
@@ -271,11 +319,19 @@ class ExecutionKPI
|
||||
self::Report('<a name="end-'.md5($sExecId).'"> </a>');
|
||||
}
|
||||
|
||||
public static function InitStats()
|
||||
{
|
||||
// Invoke extensions to initialize the KPI statistics
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
$oExtensionInstance->InitStats();
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->ResetCounters();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the duration since startup, and reset the counter for the next measure
|
||||
//
|
||||
@@ -283,8 +339,14 @@ class ExecutionKPI
|
||||
{
|
||||
global $fItopStarted;
|
||||
|
||||
if (!self::IsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$aNewEntry = null;
|
||||
|
||||
$fStarted = $this->m_fStarted;
|
||||
$fStopped = $this->m_fStarted;
|
||||
if (self::$m_bEnabled_Duration)
|
||||
{
|
||||
$fStopped = MyHelpers::getmicrotime();
|
||||
@@ -297,6 +359,9 @@ class ExecutionKPI
|
||||
$this->m_fStarted = $fStopped;
|
||||
}
|
||||
|
||||
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
|
||||
$iCurrentMemory = 0;
|
||||
$iPeakMemory = 0;
|
||||
if (self::$m_bEnabled_Memory)
|
||||
{
|
||||
$iCurrentMemory = self::memory_get_usage();
|
||||
@@ -306,41 +371,102 @@ class ExecutionKPI
|
||||
}
|
||||
$aNewEntry['mem_begin'] = $this->m_iInitialMemory;
|
||||
$aNewEntry['mem_end'] = $iCurrentMemory;
|
||||
if (function_exists('memory_get_peak_usage'))
|
||||
{
|
||||
$aNewEntry['mem_peak'] = memory_get_peak_usage();
|
||||
}
|
||||
$iPeakMemory = self::memory_get_peak_usage();
|
||||
$aNewEntry['mem_peak'] = $iPeakMemory;
|
||||
// Reset for the next operation (if the object is recycled)
|
||||
$this->m_iInitialMemory = $iCurrentMemory;
|
||||
}
|
||||
|
||||
if (!is_null($aNewEntry))
|
||||
if (self::$m_bEnabled_Duration || self::$m_bEnabled_Memory) {
|
||||
// Invoke extensions to log the KPI operation
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
|
||||
$oKPILogData = new KpiLogData(
|
||||
KpiLogData::TYPE_REPORT,
|
||||
'Step',
|
||||
$sOperationDesc,
|
||||
$fStarted,
|
||||
$fStopped,
|
||||
$sExtension,
|
||||
$iInitialMemory,
|
||||
$iCurrentMemory,
|
||||
$iPeakMemory);
|
||||
$oExtensionInstance->LogOperation($oKPILogData);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($aNewEntry) && self::$m_bGenerateLegacyReport)
|
||||
{
|
||||
self::$m_aExecData[] = $aNewEntry;
|
||||
}
|
||||
$this->ResetCounters();
|
||||
}
|
||||
|
||||
public function ComputeStatsForExtension($object, $sMethod)
|
||||
{
|
||||
if (!self::IsEnabled()) {
|
||||
return;
|
||||
}
|
||||
$sSignature = ModuleService::GetInstance()->GetModuleMethodSignature($object, $sMethod);
|
||||
if (utils::StartsWith($sSignature, '[')) {
|
||||
$this->ComputeStats('Extension', $sSignature);
|
||||
}
|
||||
}
|
||||
|
||||
public function ComputeStats($sOperation, $sArguments)
|
||||
{
|
||||
if (!self::IsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self::$m_bEnabled_Duration)
|
||||
{
|
||||
$fStopped = MyHelpers::getmicrotime();
|
||||
$fDuration = $fStopped - $this->m_fStarted;
|
||||
if (self::$m_bBlameCaller)
|
||||
{
|
||||
self::$m_aStats[$sOperation][$sArguments][] = array(
|
||||
'time' => $fDuration,
|
||||
'callers' => MyHelpers::get_callstack(1),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_aStats[$sOperation][$sArguments][] = array(
|
||||
'time' => $fDuration
|
||||
);
|
||||
}
|
||||
}
|
||||
$aCallstack = [];
|
||||
if (self::$m_bGenerateLegacyReport) {
|
||||
if (self::$m_bBlameCaller) {
|
||||
$aCallstack = MyHelpers::get_callstack(1);
|
||||
self::$m_aStats[$sOperation][$sArguments][] = [
|
||||
'time' => $fDuration,
|
||||
'callers' => $aCallstack,
|
||||
];
|
||||
} else {
|
||||
self::$m_aStats[$sOperation][$sArguments][] = [
|
||||
'time' => $fDuration
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
|
||||
$iCurrentMemory = 0;
|
||||
$iPeakMemory = 0;
|
||||
if (self::$m_bEnabled_Memory)
|
||||
{
|
||||
$iCurrentMemory = self::memory_get_usage();
|
||||
$iPeakMemory = self::memory_get_peak_usage();
|
||||
}
|
||||
|
||||
// Invoke extensions to log the KPI operation
|
||||
/** @var \iKPILoggerExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
|
||||
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
|
||||
$oKPILogData = new KpiLogData(
|
||||
KpiLogData::TYPE_STATS,
|
||||
$sOperation,
|
||||
$sArguments,
|
||||
$this->m_fStarted,
|
||||
$fStopped,
|
||||
$sExtension,
|
||||
$iInitialMemory,
|
||||
$iCurrentMemory,
|
||||
$iPeakMemory,
|
||||
$aCallstack);
|
||||
$oExtensionInstance->LogOperation($oKPILogData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function ResetCounters()
|
||||
@@ -370,35 +496,7 @@ class ExecutionKPI
|
||||
|
||||
static protected function memory_get_usage()
|
||||
{
|
||||
if (function_exists('memory_get_usage'))
|
||||
{
|
||||
return memory_get_usage(true);
|
||||
}
|
||||
|
||||
// Copied from the PHP manual
|
||||
//
|
||||
//If its Windows
|
||||
//Tested on Win XP Pro SP2. Should work on Win 2003 Server too
|
||||
//Doesn't work for 2000
|
||||
//If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642
|
||||
if (substr(PHP_OS,0,3) == 'WIN')
|
||||
{
|
||||
$output = array();
|
||||
exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output);
|
||||
|
||||
return preg_replace( '/[\D]/', '', $output[5] ) * 1024;
|
||||
}
|
||||
else
|
||||
{
|
||||
//We now assume the OS is UNIX
|
||||
//Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
|
||||
//This should work on most UNIX systems
|
||||
$pid = getmypid();
|
||||
exec("ps -eo%mem,rss,pid | grep $pid", $output);
|
||||
$output = explode(" ", $output[0]);
|
||||
//rss is given in 1024 byte units
|
||||
return $output[1] * 1024;
|
||||
}
|
||||
return memory_get_usage(true);
|
||||
}
|
||||
|
||||
static public function memory_get_peak_usage($bRealUsage = false)
|
||||
|
||||
@@ -2602,5 +2602,18 @@ class DBObjectSearch extends DBSearch
|
||||
return $oExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aAttCodes array of attCodes to search into
|
||||
* @param string $sNeedle one word to be searched
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function AddCondition_FullTextOnAttributes(array $aAttCodes, $sNeedle)
|
||||
{
|
||||
}
|
||||
|
||||
public function ListParameters()
|
||||
{
|
||||
return $this->GetCriteria()->ListParameters();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ class DailyRotatingLogFileNameBuilder extends RotatingLogFileNameBuilder
|
||||
public function GetCronProcessNextOccurrence(DateTime $oNow)
|
||||
{
|
||||
$oOccurrence = clone $oNow;
|
||||
$oOccurrence->modify('tomorrow');
|
||||
$oOccurrence->modify('tomorrow midnight');
|
||||
|
||||
return $oOccurrence;
|
||||
}
|
||||
@@ -359,8 +359,7 @@ class WeeklyRotatingLogFileNameBuilder extends RotatingLogFileNameBuilder
|
||||
public function GetCronProcessNextOccurrence(DateTime $oNow)
|
||||
{
|
||||
$oOccurrence = clone $oNow;
|
||||
$oOccurrence->modify('Monday next week');
|
||||
$oOccurrence->setTime(0, 0, 0);
|
||||
$oOccurrence->modify('Monday next week midnight');
|
||||
|
||||
return $oOccurrence;
|
||||
}
|
||||
@@ -399,10 +398,10 @@ class MonthlyRotatingLogFileNameBuilder extends RotatingLogFileNameBuilder
|
||||
*/
|
||||
protected function GetFileSuffix($oDate)
|
||||
{
|
||||
$sWeekYear = $oDate->format('o');
|
||||
$sWeekNumber = $oDate->format('m');
|
||||
$sMonthYear = $oDate->format('o');
|
||||
$sMonthNumber = $oDate->format('m');
|
||||
|
||||
return $sWeekYear.'-month'.$sWeekNumber;
|
||||
return $sMonthYear.'-month'.$sMonthNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,8 +410,7 @@ class MonthlyRotatingLogFileNameBuilder extends RotatingLogFileNameBuilder
|
||||
public function GetCronProcessNextOccurrence(DateTime $oNow)
|
||||
{
|
||||
$oOccurrence = clone $oNow;
|
||||
$oOccurrence->modify('first day of next month');
|
||||
$oOccurrence->setTime(0, 0, 0);
|
||||
$oOccurrence->modify('first day of next month midnight');
|
||||
|
||||
return $oOccurrence;
|
||||
}
|
||||
@@ -504,6 +502,7 @@ class FileLog
|
||||
protected function Write($sText, $sLevel = '', $sChannel = '', $aContext = array())
|
||||
{
|
||||
$sTextPrefix = empty($sLevel) ? '' : (str_pad($sLevel, 7).' | ');
|
||||
$sTextPrefix .= str_pad(UserRights::GetUserId(), 5)." | ";
|
||||
$sTextSuffix = empty($sChannel) ? '' : " | $sChannel";
|
||||
$sText = "{$sTextPrefix}{$sText}{$sTextSuffix}";
|
||||
$sLogFilePath = $this->oFileNameBuilder->GetLogFilePath();
|
||||
@@ -518,12 +517,9 @@ class FileLog
|
||||
{
|
||||
flock($hLogFile, LOCK_EX);
|
||||
$sDate = date('Y-m-d H:i:s');
|
||||
if (empty($aContext))
|
||||
{
|
||||
if (empty($aContext)) {
|
||||
fwrite($hLogFile, "$sDate | $sText\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sContext = var_export($aContext, true);
|
||||
fwrite($hLogFile, "$sDate | $sText\n$sContext\n");
|
||||
}
|
||||
@@ -534,6 +530,44 @@ class FileLog
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple enum like class to factorize channels values as constants
|
||||
* Channels are used especially as parameters in {@see \LogAPI} methods
|
||||
*
|
||||
* @since 2.7.5 3.0.0 N°4012
|
||||
*/
|
||||
class LogChannels
|
||||
{
|
||||
const APC = 'apc';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 2.7.7 N°4558 use this new channel when logging DB transactions
|
||||
*/
|
||||
const CMDB_SOURCE = 'cmdbsource';
|
||||
|
||||
const DEADLOCK = 'DeadLock';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 2.7.9
|
||||
*/
|
||||
const EXPORT = 'export';
|
||||
|
||||
const INLINE_IMAGE = 'InlineImage';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.1 N°4849
|
||||
* @since 2.7.7 N°4635
|
||||
*/
|
||||
const NOTIFICATIONS = 'notifications';
|
||||
|
||||
const PORTAL = 'portal';
|
||||
}
|
||||
|
||||
|
||||
abstract class LogAPI
|
||||
{
|
||||
const CHANNEL_DEFAULT = '';
|
||||
@@ -544,6 +578,12 @@ abstract class LogAPI
|
||||
const LEVEL_OK = 'Ok';
|
||||
const LEVEL_DEBUG = 'Debug';
|
||||
const LEVEL_TRACE = 'Trace';
|
||||
/**
|
||||
* @see GetMinLogLevel
|
||||
* @var string default log level, can be overrided
|
||||
* @since 2.7.1 N°2977
|
||||
*/
|
||||
const LEVEL_DEFAULT = self::LEVEL_OK;
|
||||
|
||||
protected static $aLevelsPriority = array(
|
||||
self::LEVEL_ERROR => 400,
|
||||
@@ -600,36 +640,29 @@ abstract class LogAPI
|
||||
|
||||
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array())
|
||||
{
|
||||
if (! static::$m_oFileLog)
|
||||
{
|
||||
if (!static::$m_oFileLog) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset(self::$aLevelsPriority[$sLevel]))
|
||||
{
|
||||
if (!isset(self::$aLevelsPriority[$sLevel])) {
|
||||
IssueLog::Error("invalid log level '{$sLevel}'");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($sChannel))
|
||||
{
|
||||
if (is_null($sChannel)) {
|
||||
$sChannel = static::CHANNEL_DEFAULT;
|
||||
}
|
||||
|
||||
$sMinLogLevel = self::GetMinLogLevel($sChannel);
|
||||
|
||||
if ($sMinLogLevel === false || $sMinLogLevel === 'false')
|
||||
{
|
||||
if ($sMinLogLevel === false || $sMinLogLevel === 'false') {
|
||||
return;
|
||||
}
|
||||
if (is_string($sMinLogLevel))
|
||||
{
|
||||
if (! isset(self::$aLevelsPriority[$sMinLogLevel]))
|
||||
{
|
||||
if (is_string($sMinLogLevel)) {
|
||||
if (!isset(self::$aLevelsPriority[$sMinLogLevel])) {
|
||||
throw new Exception("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority)));
|
||||
}
|
||||
elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel])
|
||||
{
|
||||
} elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel]) {
|
||||
//priority too low regarding the conf, do not log this
|
||||
return;
|
||||
}
|
||||
@@ -641,21 +674,22 @@ abstract class LogAPI
|
||||
/**
|
||||
* @param $sChannel
|
||||
*
|
||||
* @return mixed|null
|
||||
* @return string one of the LEVEL_* const value
|
||||
* @uses \LogAPI::LEVEL_DEFAULT
|
||||
*/
|
||||
private static function GetMinLogLevel($sChannel)
|
||||
{
|
||||
$oConfig = (static::$m_oMockMetaModelConfig !== null) ? static::$m_oMockMetaModelConfig : \MetaModel::GetConfig();
|
||||
if (!$oConfig instanceof Config)
|
||||
{
|
||||
return self::LEVEL_OK;
|
||||
return static::LEVEL_DEFAULT;
|
||||
}
|
||||
|
||||
$sLogLevelMin = $oConfig->Get('log_level_min');
|
||||
|
||||
if (empty($sLogLevelMin))
|
||||
{
|
||||
return self::LEVEL_OK;
|
||||
return static::LEVEL_DEFAULT;
|
||||
}
|
||||
|
||||
if (!is_array($sLogLevelMin))
|
||||
@@ -670,10 +704,10 @@ abstract class LogAPI
|
||||
|
||||
if (isset($sLogLevelMin[static::CHANNEL_DEFAULT]))
|
||||
{
|
||||
return $sLogLevelMin[$sChannel];
|
||||
return $sLogLevelMin[static::CHANNEL_DEFAULT];
|
||||
}
|
||||
|
||||
return self::LEVEL_OK;
|
||||
return static::LEVEL_DEFAULT;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -681,6 +715,12 @@ abstract class LogAPI
|
||||
class SetupLog extends LogAPI
|
||||
{
|
||||
const CHANNEL_DEFAULT = 'SetupLog';
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* As this object is used during setup, without any conf file available, customizing the level can be done by changing this constant !
|
||||
*/
|
||||
const LEVEL_DEFAULT = self::LEVEL_INFO;
|
||||
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
@@ -699,6 +739,66 @@ class ToolsLog extends LogAPI
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \CMDBSource::LogDeadLock()
|
||||
* @since 2.7.1 PR #139
|
||||
*
|
||||
* @link https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks.html
|
||||
*/
|
||||
class DeadLockLog extends LogAPI
|
||||
{
|
||||
const CHANNEL_WAIT_TIMEOUT = 'Deadlock-WaitTimeout';
|
||||
const CHANNEL_DEADLOCK_FOUND = 'Deadlock-Found';
|
||||
const CHANNEL_DEFAULT = self::CHANNEL_WAIT_TIMEOUT;
|
||||
|
||||
/** @var \FileLog we want our own instance ! */
|
||||
protected static $m_oFileLog = null;
|
||||
|
||||
public static function Enable($sTargetFile = null)
|
||||
{
|
||||
if (empty($sTargetFile))
|
||||
{
|
||||
$sTargetFile = APPROOT.'log/deadlocks.log';
|
||||
}
|
||||
parent::Enable($sTargetFile);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnreachableStatementInspection */
|
||||
private static function GetChannelFromMysqlErrorNo($iMysqlErrorNo)
|
||||
{
|
||||
switch ($iMysqlErrorNo)
|
||||
{
|
||||
case CMDBSource::MYSQL_ERRNO_WAIT_TIMEOUT:
|
||||
return self::CHANNEL_WAIT_TIMEOUT;
|
||||
break;
|
||||
case CMDBSource::MYSQL_ERRNO_DEADLOCK:
|
||||
return self::CHANNEL_DEADLOCK_FOUND;
|
||||
break;
|
||||
default:
|
||||
return self::CHANNEL_DEFAULT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sLevel
|
||||
* @param string $sMessage
|
||||
* @param int $iMysqlErrorNumber will be converted to channel using {@link GetChannelFromMysqlErrorNo}
|
||||
* @param array $aContext
|
||||
*
|
||||
* @throws \Exception
|
||||
* @noinspection PhpParameterNameChangedDuringInheritanceInspection
|
||||
*
|
||||
* @since 2.7.1 method creation
|
||||
* @since 2.7.5 3.0.0 rename param names and fix phpdoc (thanks Hipska !)
|
||||
*/
|
||||
public static function Log($sLevel, $sMessage, $iMysqlErrorNumber = null, $aContext = array())
|
||||
{
|
||||
$sChannel = self::GetChannelFromMysqlErrorNo($iMysqlErrorNumber);
|
||||
parent::Log($sLevel, $sMessage, $sChannel, $aContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LogFileRotationProcess implements iScheduledProcess
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user