mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-14 16:04:10 +01:00
Compare commits
1317 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c78acbe25 | ||
|
|
fe887f01c0 | ||
|
|
9109138127 | ||
|
|
51b19c03b1 | ||
|
|
8cb584abdd | ||
|
|
3480d478b0 | ||
|
|
3a34417a7d | ||
|
|
6ca3ca108b | ||
|
|
037036ce6a | ||
|
|
9d2dab5eba | ||
|
|
3169b39952 | ||
|
|
ba33b01d84 | ||
|
|
7db69ed158 | ||
|
|
a6f6d536e0 | ||
|
|
880faf2021 | ||
|
|
f96902c03e | ||
|
|
6e810322f7 | ||
|
|
dd9984be7f | ||
|
|
98f3f88ea2 | ||
|
|
8be0c36859 | ||
|
|
8c5a65d836 | ||
|
|
db47973063 | ||
|
|
028e815bfa | ||
|
|
caf07affa6 | ||
|
|
bdef9e59de | ||
|
|
4bf001d108 | ||
|
|
a095208e53 | ||
|
|
2429ec4eec | ||
|
|
55655e3fca | ||
|
|
7e98b04ed3 | ||
|
|
59c6c0250d | ||
|
|
18a5afb8a6 | ||
|
|
4b6258dfa1 | ||
|
|
44b9f23117 | ||
|
|
2ddefcd2fc | ||
|
|
39b15fec50 | ||
|
|
c180dd9863 | ||
|
|
1172130d7c | ||
|
|
8172e14345 | ||
|
|
6e4836f5e0 | ||
|
|
4ee6e6f915 | ||
|
|
e5b318b94f | ||
|
|
c081b89f03 | ||
|
|
f237a98c1d | ||
|
|
fd2a41aee6 | ||
|
|
25b1a0d8af | ||
|
|
985b366cc7 | ||
|
|
d1e2bc9b2b | ||
|
|
757b3d1cc3 | ||
|
|
8c0bbadbfe | ||
|
|
cbd3d2c165 | ||
|
|
b286bd48c7 | ||
|
|
c4efbe2891 | ||
|
|
58506441cf | ||
|
|
d575ee50ee | ||
|
|
997fa3a856 | ||
|
|
70abd8027e | ||
|
|
4c6e6ffc9c | ||
|
|
b268df7bb4 | ||
|
|
7aaa35f88e | ||
|
|
4bb91b819c | ||
|
|
656cae3e66 | ||
|
|
292735b4b2 | ||
|
|
dcc4061261 | ||
|
|
ff7ac731df | ||
|
|
c784afdb06 | ||
|
|
5e60e8d725 | ||
|
|
564bf04647 | ||
|
|
17d18eb75f | ||
|
|
a9a6460747 | ||
|
|
1fa1a053e4 | ||
|
|
01e151632f | ||
|
|
c17d0b1027 | ||
|
|
517d16e3d9 | ||
|
|
696a5d1a48 | ||
|
|
96288db97b | ||
|
|
e6072cd8b6 | ||
|
|
db8bb7d94a | ||
|
|
1b2dbf751c | ||
|
|
b6805af20e | ||
|
|
4cc6290c88 | ||
|
|
c79d17516d | ||
|
|
a2154d6418 | ||
|
|
f820d3bd81 | ||
|
|
0a914f527a | ||
|
|
a8c749a46a | ||
|
|
e58c8afeab | ||
|
|
005b0eab1d | ||
|
|
55b34b5d11 | ||
|
|
7b4f00ee45 | ||
|
|
cc99cec250 | ||
|
|
105dc46f46 | ||
|
|
770fbe157c | ||
|
|
21d44c664f | ||
|
|
b318cbe211 | ||
|
|
7106501812 | ||
|
|
567dd39270 | ||
|
|
b292621f82 | ||
|
|
46f0b48add | ||
|
|
9a5f16bd3e | ||
|
|
6224ec7a37 | ||
|
|
c460edcd5e | ||
|
|
b839b42cd4 | ||
|
|
fabc8b91d7 | ||
|
|
4c38cd570c | ||
|
|
c4fd15ae90 | ||
|
|
bbca491118 | ||
|
|
0d04bd8b0e | ||
|
|
3bcb3a2154 | ||
|
|
aa863c40d3 | ||
|
|
5d7ad9f6dd | ||
|
|
72cb41dd7e | ||
|
|
6ef39c9f5b | ||
|
|
614b948c82 | ||
|
|
7c0715ad59 | ||
|
|
31819497ae | ||
|
|
c86e2c6c79 | ||
|
|
1d54b66de9 | ||
|
|
f932765e19 | ||
|
|
11b4085566 | ||
|
|
12afcd7cce | ||
|
|
666c95a656 | ||
|
|
fde9c28263 | ||
|
|
52c3ff0406 | ||
|
|
9f4d07aaa5 | ||
|
|
70561d6331 | ||
|
|
ae8311e224 | ||
|
|
e09fa0ffc1 | ||
|
|
5b6a20048f | ||
|
|
d86c211b73 | ||
|
|
8499babcb3 | ||
|
|
2d44cbbb60 | ||
|
|
3d89ef9076 | ||
|
|
e896f8af3d | ||
|
|
23268e8969 | ||
|
|
421e12debd | ||
|
|
389848cef4 | ||
|
|
7da4423346 | ||
|
|
2a770b9dc4 | ||
|
|
8df12e64f2 | ||
|
|
341261c14e | ||
|
|
bfc7c73e18 | ||
|
|
9a6c4ba7bb | ||
|
|
dc47c34981 | ||
|
|
3ac5131a19 | ||
|
|
134a9aa684 | ||
|
|
a98c0d32ae | ||
|
|
38af2b85c4 | ||
|
|
c9c84735c4 | ||
|
|
646972838a | ||
|
|
ce92241593 | ||
|
|
715eeff627 | ||
|
|
651de821df | ||
|
|
2233ea5f54 | ||
|
|
39e6915e05 | ||
|
|
6279f4ac70 | ||
|
|
badf10e74e | ||
|
|
2e9cd6a342 | ||
|
|
cef70ee9a2 | ||
|
|
37e15706b2 | ||
|
|
4b210273d2 | ||
|
|
951e8e03b0 | ||
|
|
c016ca364d | ||
|
|
4611442665 | ||
|
|
c7e7976607 | ||
|
|
0ca9fac04b | ||
|
|
7edc79f398 | ||
|
|
c110206264 | ||
|
|
b3ad2030cb | ||
|
|
0938ba939c | ||
|
|
bca393def3 | ||
|
|
796ac55ff4 | ||
|
|
66ae89e91d | ||
|
|
361cca465a | ||
|
|
7462d66643 | ||
|
|
a177b9b1d4 | ||
|
|
48f6635917 | ||
|
|
d6707743a9 | ||
|
|
6f474686ad | ||
|
|
affd8ea8a6 | ||
|
|
abd2748a09 | ||
|
|
6a1c9b9f26 | ||
|
|
ac04aa3359 | ||
|
|
051d68c902 | ||
|
|
ce81f7abb6 | ||
|
|
03fbde3403 | ||
|
|
59ddab9f94 | ||
|
|
225cbb450e | ||
|
|
7cace73844 | ||
|
|
2562d33997 | ||
|
|
a1e5b32e49 | ||
|
|
0c5df83a95 | ||
|
|
cf79456ff3 | ||
|
|
a0a378a91a | ||
|
|
9cf42f6cc8 | ||
|
|
c022d12a0a | ||
|
|
3d72060bf5 | ||
|
|
43709576c0 | ||
|
|
8ca6610e75 | ||
|
|
b02d347541 | ||
|
|
a39789cf89 | ||
|
|
58534dca39 | ||
|
|
538e9c11e8 | ||
|
|
cba6e8d8a3 | ||
|
|
fd9fe85ef3 | ||
|
|
7ed3e10d0f | ||
|
|
fe98b850d1 | ||
|
|
b1494d0dd9 | ||
|
|
0655904683 | ||
|
|
bad5aff764 | ||
|
|
440f2639b5 | ||
|
|
5f2245595f | ||
|
|
03db4e7a14 | ||
|
|
04030e07e3 | ||
|
|
80832877eb | ||
|
|
aa63847502 | ||
|
|
4171c5c35f | ||
|
|
8e06a95502 | ||
|
|
1c84934777 | ||
|
|
0294f50339 | ||
|
|
33098943a1 | ||
|
|
b953a6ab3e | ||
|
|
7287cab6f4 | ||
|
|
df4cad3ff1 | ||
|
|
66b6206daf | ||
|
|
6f40459215 | ||
|
|
24430e630f | ||
|
|
3420db26a5 | ||
|
|
c27cafb02f | ||
|
|
09a2dbc185 | ||
|
|
015c5b7a96 | ||
|
|
4710ae6eb7 | ||
|
|
c6b98dd5c1 | ||
|
|
61d3a4e48f | ||
|
|
be4c1e2c1c | ||
|
|
6f499dd36e | ||
|
|
03661158f1 | ||
|
|
b9866008b2 | ||
|
|
2557d6e7d5 | ||
|
|
79582b55a4 | ||
|
|
46b5293867 | ||
|
|
54ca6ad3d9 | ||
|
|
2ab8161182 | ||
|
|
3c7f5db5e7 | ||
|
|
e72bc65df8 | ||
|
|
c039414adc | ||
|
|
d45cbb36f7 | ||
|
|
ba5dd94240 | ||
|
|
fe84cbd978 | ||
|
|
f8ddcc7b70 | ||
|
|
c0d7737872 | ||
|
|
97e6c53add | ||
|
|
68ac5d7300 | ||
|
|
e0eebc0b4e | ||
|
|
fb28de971c | ||
|
|
ae637c7c96 | ||
|
|
6bc24b6064 | ||
|
|
db60dfb707 | ||
|
|
ee2aa67959 | ||
|
|
eff42f5d3b | ||
|
|
3a1810bc42 | ||
|
|
1f1ab2b10c | ||
|
|
d0d9b1ce50 | ||
|
|
307145502c | ||
|
|
5c84703cf0 | ||
|
|
cb2745be24 | ||
|
|
b613b9b302 | ||
|
|
624f6bcfc5 | ||
|
|
ce2f1edaac | ||
|
|
140efb4240 | ||
|
|
e3847ac24b | ||
|
|
b583bd2edc | ||
|
|
c654a2067d | ||
|
|
3531d5c5b7 | ||
|
|
e31fa066fc | ||
|
|
bc476295cb | ||
|
|
98781fac6d | ||
|
|
efeee395d1 | ||
|
|
b17505f86e | ||
|
|
f89dd77dbc | ||
|
|
f8b6fb51c2 | ||
|
|
d80c2293dc | ||
|
|
3cecdeff1c | ||
|
|
3f819eaa19 | ||
|
|
01de060093 | ||
|
|
7c39a8baf1 | ||
|
|
ebcaaa089a | ||
|
|
121635d636 | ||
|
|
68885496dd | ||
|
|
9a56c3acfd | ||
|
|
a0259636b1 | ||
|
|
0844beca79 | ||
|
|
dbe3e94d5c | ||
|
|
9a43083b3b | ||
|
|
c6e472b98b | ||
|
|
0834e36b38 | ||
|
|
f7cf825975 | ||
|
|
189c03dfea | ||
|
|
990be7a105 | ||
|
|
813c80499c | ||
|
|
495cedc04f | ||
|
|
cbf2919dcd | ||
|
|
7cbdfaa5d4 | ||
|
|
374ce20d0c | ||
|
|
1dbc2051cc | ||
|
|
7e38d4be50 | ||
|
|
8451ffdfb4 | ||
|
|
c2b9716e28 | ||
|
|
b15621639b | ||
|
|
4ca998ce91 | ||
|
|
71ec3da4ac | ||
|
|
43b8522b85 | ||
|
|
8b820ce403 | ||
|
|
4301a5d39d | ||
|
|
51519a3659 | ||
|
|
2693e81bad | ||
|
|
eca746f558 | ||
|
|
01865ed7f9 | ||
|
|
14dfe04714 | ||
|
|
befa0b4429 | ||
|
|
68ff589f9c | ||
|
|
04e1f32860 | ||
|
|
636356f479 | ||
|
|
313ea72017 | ||
|
|
98b4a0178f | ||
|
|
602be73d0b | ||
|
|
9bfc9a0a76 | ||
|
|
7e6a040983 | ||
|
|
905d47cab5 | ||
|
|
2118a5da71 | ||
|
|
c2c0221535 | ||
|
|
ba0a9709f4 | ||
|
|
80121b89c3 | ||
|
|
3b48428897 | ||
|
|
018d6a98e9 | ||
|
|
522108dc69 | ||
|
|
5451ced894 | ||
|
|
944c24e18f | ||
|
|
248f8d6fd4 | ||
|
|
ebe467b77a | ||
|
|
e9b7ccd475 | ||
|
|
b213f2baea | ||
|
|
50970810d2 | ||
|
|
17fbc504e2 | ||
|
|
237980097d | ||
|
|
44b53e40a0 | ||
|
|
6c6ad0a45b | ||
|
|
623fa8ec63 | ||
|
|
02c79fd0a2 | ||
|
|
05bb797768 | ||
|
|
096cfdc529 | ||
|
|
5354b0b32b | ||
|
|
88ec528071 | ||
|
|
5584d42813 | ||
|
|
b696b140b2 | ||
|
|
1de6ac1765 | ||
|
|
d2d895fdf5 | ||
|
|
a8ad3004ea | ||
|
|
b707db9364 | ||
|
|
36c53249a0 | ||
|
|
f9ed88a084 | ||
|
|
294964b227 | ||
|
|
8a6fba1981 | ||
|
|
1fe0ce5640 | ||
|
|
7bbe907edc | ||
|
|
6f7d364826 | ||
|
|
ea2681e08c | ||
|
|
c66728e478 | ||
|
|
ba9c6bd8b3 | ||
|
|
b23b468c0f | ||
|
|
f5144c2bb1 | ||
|
|
ee95dd2480 | ||
|
|
c1805ce47f | ||
|
|
23556e3a43 | ||
|
|
e70e1de75e | ||
|
|
091b989715 | ||
|
|
b238283104 | ||
|
|
de2eed5187 | ||
|
|
8e046cafda | ||
|
|
f02df401ff | ||
|
|
28eba2512a | ||
|
|
e3931274ae | ||
|
|
a55245d203 | ||
|
|
7b0acbdad3 | ||
|
|
9cc583c47b | ||
|
|
fa2fd6dcdf | ||
|
|
9d242e1623 | ||
|
|
ec6a8537e9 | ||
|
|
402e95b9e2 | ||
|
|
2fcf50bb88 | ||
|
|
2e8c629195 | ||
|
|
9c5b3818eb | ||
|
|
c9bb27a3ff | ||
|
|
f96a2092e7 | ||
|
|
5dafb92dd3 | ||
|
|
63f0a0ef11 | ||
|
|
bb9236deae | ||
|
|
1a66a6491a | ||
|
|
1528d85f5f | ||
|
|
3773a88cc1 | ||
|
|
856a73fb87 | ||
|
|
d0bc9a0bb8 | ||
|
|
751b7c8560 | ||
|
|
4fccae157b | ||
|
|
85dc7c9452 | ||
|
|
1cf25e49ae | ||
|
|
ab6cd49fcb | ||
|
|
5411e60c81 | ||
|
|
15da430459 | ||
|
|
5adb0fd556 | ||
|
|
2876d7e08c | ||
|
|
59c3f565a3 | ||
|
|
86191b36d8 | ||
|
|
b737252076 | ||
|
|
e688d04511 | ||
|
|
76fd5c0b5f | ||
|
|
d944f386ce | ||
|
|
9f9fd8550f | ||
|
|
772954711a | ||
|
|
3644553886 | ||
|
|
c073611597 | ||
|
|
802d20d554 | ||
|
|
38c12104b3 | ||
|
|
399662ef99 | ||
|
|
3daf4c62db | ||
|
|
9937f62f9d | ||
|
|
cf17e197ce | ||
|
|
2f8b5e5eeb | ||
|
|
d22fb83443 | ||
|
|
af093ba86a | ||
|
|
020f47ab2c | ||
|
|
8f0095f751 | ||
|
|
b7fc2e4012 | ||
|
|
1dcf830141 | ||
|
|
a10cdaba74 | ||
|
|
435a177283 | ||
|
|
d22d657886 | ||
|
|
1da5618204 | ||
|
|
318c069f3d | ||
|
|
d0267f60ae | ||
|
|
ea3c7703c4 | ||
|
|
95e04178ea | ||
|
|
cf0792cd64 | ||
|
|
e41a8833e2 | ||
|
|
0b2ce4289d | ||
|
|
a13270ef91 | ||
|
|
64ca121f5b | ||
|
|
0a5708272d | ||
|
|
fec15ffe66 | ||
|
|
0b0340bf21 | ||
|
|
cf716ce2a3 | ||
|
|
5ebcb41224 | ||
|
|
2d9c0e16b9 | ||
|
|
20e0ab3d6e | ||
|
|
d94ccc441a | ||
|
|
fdff9e048b | ||
|
|
64e1d7e276 | ||
|
|
5f82c78dc7 | ||
|
|
b3a0c6119e | ||
|
|
342ac0444c | ||
|
|
d9469360fe | ||
|
|
0df420cd17 | ||
|
|
ab44522016 | ||
|
|
bc875653d2 | ||
|
|
ee41204d6c | ||
|
|
c4d1113bb8 | ||
|
|
6582569150 | ||
|
|
69d0ceaf5b | ||
|
|
60165d5216 | ||
|
|
76ca7dc9e8 | ||
|
|
fcc5342775 | ||
|
|
b4534f455a | ||
|
|
0d3203476c | ||
|
|
9fa6157c37 | ||
|
|
2ee674f11b | ||
|
|
820d7108e4 | ||
|
|
9a6a562ed2 | ||
|
|
f3f9e98aa4 | ||
|
|
fc375da128 | ||
|
|
172ceba464 | ||
|
|
ecae7ea983 | ||
|
|
a204a786c0 | ||
|
|
79a2ab8abd | ||
|
|
af13b42eab | ||
|
|
75721091f2 | ||
|
|
6e327e245b | ||
|
|
6bfead405d | ||
|
|
a4f5620076 | ||
|
|
134a427901 | ||
|
|
27c3facb2b | ||
|
|
fa9848d1be | ||
|
|
1afd7d2ae4 | ||
|
|
c622efe95c | ||
|
|
c0949421ad | ||
|
|
0e21edcc77 | ||
|
|
df85186407 | ||
|
|
403ecf7fba | ||
|
|
d143f0880b | ||
|
|
c185e1fc4f | ||
|
|
68c180f7eb | ||
|
|
c903fc2246 | ||
|
|
7d5898f302 | ||
|
|
a4bdd3aaf4 | ||
|
|
5bae9deecc | ||
|
|
ffbd666aca | ||
|
|
4ec3aeec92 | ||
|
|
c5a00a1bf1 | ||
|
|
1b1e91f0dc | ||
|
|
21fc272674 | ||
|
|
5716c11450 | ||
|
|
f7e77e9fa6 | ||
|
|
ad8c3db6b4 | ||
|
|
d5c3b8d8e2 | ||
|
|
6ac6aea29f | ||
|
|
7ce06c0797 | ||
|
|
53bf1f424a | ||
|
|
b793cded34 | ||
|
|
47ec6d4917 | ||
|
|
e586ba8d6e | ||
|
|
8a913a18cf | ||
|
|
a09e579451 | ||
|
|
79c5dc2ce2 | ||
|
|
95d7bc5319 | ||
|
|
d3f5d05063 | ||
|
|
b30a35ceb5 | ||
|
|
fac22c9729 | ||
|
|
44e329c38d | ||
|
|
e48ad8cb61 | ||
|
|
359dc73526 | ||
|
|
8169e81e9a | ||
|
|
f62f087bcc | ||
|
|
4eb0b3086d | ||
|
|
1c81650572 | ||
|
|
59e3367da8 | ||
|
|
2b4b0fed83 | ||
|
|
fe6ae6f2eb | ||
|
|
79d994acf7 | ||
|
|
9dbbb8e012 | ||
|
|
17fafbf85b | ||
|
|
f3a6455ed8 | ||
|
|
5336d9e965 | ||
|
|
97d11ba910 | ||
|
|
2fa68a3abc | ||
|
|
a88365ca49 | ||
|
|
f3053c39c2 | ||
|
|
c7ac39b86a | ||
|
|
88eece7188 | ||
|
|
b7d101cdfb | ||
|
|
56ade2b44c | ||
|
|
2a38eb757d | ||
|
|
954ba60611 | ||
|
|
f9a984f3e4 | ||
|
|
8fd9acb2ce | ||
|
|
915a88afd4 | ||
|
|
c755b66d8c | ||
|
|
2f8dc0fa0f | ||
|
|
a34747f893 | ||
|
|
4dd6c813b1 | ||
|
|
8e96094977 | ||
|
|
dd41ebf861 | ||
|
|
f732df751c | ||
|
|
acfab8fb63 | ||
|
|
23193153c6 | ||
|
|
a908dcd752 | ||
|
|
b8a80cb267 | ||
|
|
ad1412c7e2 | ||
|
|
4bf51515be | ||
|
|
05f97dd75f | ||
|
|
90cab29a3c | ||
|
|
445b488603 | ||
|
|
7141e0f4b0 | ||
|
|
ffc756e4a4 | ||
|
|
8bed267feb | ||
|
|
13933e488c | ||
|
|
d48f76e965 | ||
|
|
a025c95054 | ||
|
|
b2fa9c468f | ||
|
|
8feef7fd8a | ||
|
|
8330f3d498 | ||
|
|
88e7bd225c | ||
|
|
1b0f818c4b | ||
|
|
32dc1225ca | ||
|
|
2b35cf5047 | ||
|
|
df66e28545 | ||
|
|
0ab060c958 | ||
|
|
dfceef4ca6 | ||
|
|
49f82e6377 | ||
|
|
c5957f1146 | ||
|
|
b357e7d7d6 | ||
|
|
e7342b0eb8 | ||
|
|
f33f4e3406 | ||
|
|
3d57c720e0 | ||
|
|
ef5d156b98 | ||
|
|
8722447f2f | ||
|
|
e6047dcbf5 | ||
|
|
df1d10f1cb | ||
|
|
d1dd60f928 | ||
|
|
7bea59fea1 | ||
|
|
17703ce572 | ||
|
|
6d556249aa | ||
|
|
16b5db448b | ||
|
|
8ef7f073b3 | ||
|
|
22c647e3f8 | ||
|
|
bf94dfa894 | ||
|
|
43d3cfefb5 | ||
|
|
e0855093ea | ||
|
|
c147062aaa | ||
|
|
480c2fab04 | ||
|
|
53fb619da1 | ||
|
|
c0a7bbaa72 | ||
|
|
12b27778f5 | ||
|
|
babcd6a92a | ||
|
|
d80d24c348 | ||
|
|
2c78a91a00 | ||
|
|
109e5dfe2c | ||
|
|
93ff327b54 | ||
|
|
359c188089 | ||
|
|
6682eafb4d | ||
|
|
b64c79d34e | ||
|
|
e553c0bbe1 | ||
|
|
18f15ba9cc | ||
|
|
f4e45b6c8d | ||
|
|
1013cbc22f | ||
|
|
12c64bd6e5 | ||
|
|
c209b75f6b | ||
|
|
775ed7d437 | ||
|
|
dee3d55af2 | ||
|
|
0d48e40c18 | ||
|
|
a0965c2a52 | ||
|
|
daccb122ae | ||
|
|
0a8532e27a | ||
|
|
e7adf6559f | ||
|
|
07db5855a2 | ||
|
|
db47b2d05c | ||
|
|
a2eab87b7b | ||
|
|
396c4564b4 | ||
|
|
8582f6da70 | ||
|
|
fc7a10ff03 | ||
|
|
7330154dd0 | ||
|
|
beb53fd9dc | ||
|
|
9dbad63d6f | ||
|
|
21e5ca484c | ||
|
|
41bee2b9b2 | ||
|
|
41556ba00b | ||
|
|
33b46483a9 | ||
|
|
970f75d5e2 | ||
|
|
3a25916f00 | ||
|
|
c44284dc3c | ||
|
|
0ac9e21e5e | ||
|
|
17abb729e1 | ||
|
|
00d131e3fc | ||
|
|
9d05c1c79c | ||
|
|
71f3313070 | ||
|
|
18de167d5e | ||
|
|
c177264113 | ||
|
|
31cafcf2dd | ||
|
|
1e6ab3bdf0 | ||
|
|
0ab344edee | ||
|
|
f82b5833aa | ||
|
|
daea9f0925 | ||
|
|
ebd0ae85a4 | ||
|
|
3b1886a435 | ||
|
|
eabc2a4eab | ||
|
|
29cd969d49 | ||
|
|
31a375f640 | ||
|
|
218a2e2f01 | ||
|
|
94295f11da | ||
|
|
d7b58a7730 | ||
|
|
04133a8853 | ||
|
|
619fe22a15 | ||
|
|
8071962bf9 | ||
|
|
9b87d15f9a | ||
|
|
3c9072bb65 | ||
|
|
fa2c234a43 | ||
|
|
a0d16b868e | ||
|
|
2773419faa | ||
|
|
f89fa885d2 | ||
|
|
b34ea69cb3 | ||
|
|
b54f78ab1a | ||
|
|
963a09ba6f | ||
|
|
feebe3f9a9 | ||
|
|
636cd646aa | ||
|
|
9b774d3f72 | ||
|
|
12857ceba1 | ||
|
|
ff5a96f92d | ||
|
|
c4660f1caf | ||
|
|
dc2ee3472b | ||
|
|
ce6ed190aa | ||
|
|
52309bb1e5 | ||
|
|
2e71aee720 | ||
|
|
41cb94bcf0 | ||
|
|
5b9d2182dd | ||
|
|
e6fefcc361 | ||
|
|
181e14f48e | ||
|
|
8446fb028d | ||
|
|
d88249eabc | ||
|
|
304080d74d | ||
|
|
f74afac781 | ||
|
|
066e7bedc1 | ||
|
|
899ea36bcc | ||
|
|
8ed94c4609 | ||
|
|
5dc86ebc5d | ||
|
|
4f7cc48fd4 | ||
|
|
dfa855b4f7 | ||
|
|
49aeeb0202 | ||
|
|
c9a0d2bc80 | ||
|
|
4a63989237 | ||
|
|
bd4aca770e | ||
|
|
a4adf0b9ba | ||
|
|
e48a1b1645 | ||
|
|
09fad78952 | ||
|
|
2a62b43848 | ||
|
|
e8fb1cf236 | ||
|
|
ac2492958c | ||
|
|
ddfc20fb7d | ||
|
|
3147f36d96 | ||
|
|
1d379245ee | ||
|
|
cb0fa2a5c8 | ||
|
|
4c59d64025 | ||
|
|
c92b0ea8f7 | ||
|
|
7ebb0c40f3 | ||
|
|
b24c2b8455 | ||
|
|
281925755d | ||
|
|
f5ae5f7c42 | ||
|
|
80789ccaa9 | ||
|
|
54a40c42cd | ||
|
|
2c5d95a638 | ||
|
|
10ca60893b | ||
|
|
f86c4bb8fa | ||
|
|
3edf777aa6 | ||
|
|
8a2fbdfd56 | ||
|
|
07056013c2 | ||
|
|
ae61a1e5eb | ||
|
|
e0909766fd | ||
|
|
e9dde4ee58 | ||
|
|
0050de641a | ||
|
|
445349d2d9 | ||
|
|
d026c86c50 | ||
|
|
1fb346da67 | ||
|
|
df466faddf | ||
|
|
43106d3a77 | ||
|
|
9fc3b52b24 | ||
|
|
026edb523c | ||
|
|
f640558349 | ||
|
|
ae52521a3f | ||
|
|
13ef1a4084 | ||
|
|
a8f67116ea | ||
|
|
af9bd61bb7 | ||
|
|
fdb0a8116c | ||
|
|
ffcc6ded74 | ||
|
|
6152277910 | ||
|
|
62b47556bc | ||
|
|
c9bfeedb50 | ||
|
|
0dcff56969 | ||
|
|
16d0df2553 | ||
|
|
564b8a9726 | ||
|
|
3830d2414c | ||
|
|
2e6e6d52ca | ||
|
|
22f73506a2 | ||
|
|
fc4c3c9bb9 | ||
|
|
461153967a | ||
|
|
c4e20ea0fe | ||
|
|
1ea66646b6 | ||
|
|
201f5118b3 | ||
|
|
4bebcdc63a | ||
|
|
f6b185388a | ||
|
|
b97b9bf1d5 | ||
|
|
eab0320ef0 | ||
|
|
93adab0644 | ||
|
|
adc70103e0 | ||
|
|
cf3d0bde5b | ||
|
|
4187b074a9 | ||
|
|
1a5637352b | ||
|
|
1e719b97d8 | ||
|
|
2299d23099 | ||
|
|
a91723befe | ||
|
|
e1ea466053 | ||
|
|
2c4ccd2302 | ||
|
|
930e51f94c | ||
|
|
534eef1f72 | ||
|
|
89fa0365fd | ||
|
|
9f8ed3ada3 | ||
|
|
047fd088cc | ||
|
|
db010e4ddc | ||
|
|
bc6f73b9ec | ||
|
|
7761404755 | ||
|
|
7a6e47f067 | ||
|
|
409ca87f8c | ||
|
|
6ce0940f67 | ||
|
|
127d2a3295 | ||
|
|
f4ff96a552 | ||
|
|
64b3e12258 | ||
|
|
7aa1495c4a | ||
|
|
4a1ec12cba | ||
|
|
f4ab48a2d4 | ||
|
|
eeb0d5c98c | ||
|
|
1ebafb0566 | ||
|
|
4a81d70bf6 | ||
|
|
ccc0c7e7aa | ||
|
|
788f7bb029 | ||
|
|
70774f1923 | ||
|
|
c914344a32 | ||
|
|
3b38388c73 | ||
|
|
d9a5b85c67 | ||
|
|
1aacf1adae | ||
|
|
88866ac199 | ||
|
|
316d1f9b14 | ||
|
|
6b465688a2 | ||
|
|
6e8ee09399 | ||
|
|
754604b009 | ||
|
|
7790f770a7 | ||
|
|
72b4c549c7 | ||
|
|
1515178500 | ||
|
|
165dbaf245 | ||
|
|
3c112eb078 | ||
|
|
5540fdb7db | ||
|
|
6e074f5486 | ||
|
|
636140bfdd | ||
|
|
69165396d4 | ||
|
|
dab860cfbd | ||
|
|
7380f56a50 | ||
|
|
d0d761236b | ||
|
|
760f3a788e | ||
|
|
73274ec461 | ||
|
|
291a5847f0 | ||
|
|
2720f6e54b | ||
|
|
58b571f08a | ||
|
|
14a2d9960f | ||
|
|
88c46813d9 | ||
|
|
bcb5e4304a | ||
|
|
793d4f814d | ||
|
|
1693f73742 | ||
|
|
4c9edf04dd | ||
|
|
9b11b12b07 | ||
|
|
6297809716 | ||
|
|
6540c547a4 | ||
|
|
9d8a2cb7bb | ||
|
|
35c0bfea1c | ||
|
|
748c1853ec | ||
|
|
0e5c2c3e80 | ||
|
|
cc0019c090 | ||
|
|
e00667c2e4 | ||
|
|
c1a4c0185b | ||
|
|
1c997a5973 | ||
|
|
236de6ce34 | ||
|
|
681e07ca73 | ||
|
|
e388e4b163 | ||
|
|
1018dc6e74 | ||
|
|
06075805e0 | ||
|
|
37a6a5183d | ||
|
|
ebd89194ee | ||
|
|
243dee4312 | ||
|
|
d677a20c96 | ||
|
|
ccddf1d4f0 | ||
|
|
1d3ab23699 | ||
|
|
0182822f76 | ||
|
|
c911ce38a6 | ||
|
|
89328e1662 | ||
|
|
a618f2804b | ||
|
|
242f7785e6 | ||
|
|
3335d0a453 | ||
|
|
1621f2ba31 | ||
|
|
45ddc7f71b | ||
|
|
ae22bbbc81 | ||
|
|
3e1607047e | ||
|
|
da69985970 | ||
|
|
6999458de6 | ||
|
|
37a8db125a | ||
|
|
3d74c1ccaa | ||
|
|
6fae298c0c | ||
|
|
e85c6ca0c5 | ||
|
|
aa788a7aad | ||
|
|
3c4845cf99 | ||
|
|
2b12a86fa8 | ||
|
|
dc5040c1d2 | ||
|
|
b02e163ecc | ||
|
|
0e25c9a7a1 | ||
|
|
79f73256d7 | ||
|
|
2513f0489c | ||
|
|
3579f557d1 | ||
|
|
668e822fc6 | ||
|
|
dd41dc05f5 | ||
|
|
f247b89342 | ||
|
|
5386662146 | ||
|
|
7d4e9ce069 | ||
|
|
9fd07125e2 | ||
|
|
5d5b61d956 | ||
|
|
4d91e92344 | ||
|
|
75b32f2552 | ||
|
|
8eba9ae714 | ||
|
|
b318d27b19 | ||
|
|
90cdd28bc8 | ||
|
|
e51a6f8ff2 | ||
|
|
585a73e641 | ||
|
|
2a835e5be4 | ||
|
|
0386c53a6a | ||
|
|
e262dbfcf2 | ||
|
|
8834e1b49c | ||
|
|
c9c6b2f7d5 | ||
|
|
7abb048b7c | ||
|
|
e27d61a525 | ||
|
|
f436cece4a | ||
|
|
e7eb1ec7e3 | ||
|
|
21564ff340 | ||
|
|
c32ef34307 | ||
|
|
2d05b110b8 | ||
|
|
25287a8c04 | ||
|
|
e877ec431f | ||
|
|
272051ea99 | ||
|
|
725c7d45d1 | ||
|
|
b991f0a6c6 | ||
|
|
ed035b3699 | ||
|
|
e9f57fd9e2 | ||
|
|
fd124ef53b | ||
|
|
0259071bdd | ||
|
|
32ce26aa7d | ||
|
|
3997ea3a23 | ||
|
|
f41c4f80f8 | ||
|
|
3eacf2e7fa | ||
|
|
c3f804bb29 | ||
|
|
1784653678 | ||
|
|
447fc85867 | ||
|
|
f3773f6047 | ||
|
|
b741532231 | ||
|
|
f01bd61692 | ||
|
|
a5d3208599 | ||
|
|
f3cc54fe8d | ||
|
|
70e0fab267 | ||
|
|
7868c4364c | ||
|
|
2a5ca467fd | ||
|
|
2ab12d9d11 | ||
|
|
0104c3fe41 | ||
|
|
847c1d2736 | ||
|
|
922354320b | ||
|
|
462af27157 | ||
|
|
ea31d71d16 | ||
|
|
2150682a92 | ||
|
|
24fcb20927 | ||
|
|
414b94405b | ||
|
|
5328feb58b | ||
|
|
bc7176f07e | ||
|
|
8f4a8fc7be | ||
|
|
81317d4df9 | ||
|
|
af87ef3623 | ||
|
|
c201ae4147 | ||
|
|
37e3cb6285 | ||
|
|
4b7fb20eaf | ||
|
|
92d9c778e5 | ||
|
|
1c90cd2312 | ||
|
|
4006fce0f2 | ||
|
|
a31be78cbd | ||
|
|
f29af948be | ||
|
|
44ba3d7bf8 | ||
|
|
7ea5176b56 | ||
|
|
e6887ab317 | ||
|
|
67c92ab946 | ||
|
|
daa090d4fe | ||
|
|
ced87e71cb | ||
|
|
e26eed3142 | ||
|
|
d33dad51f8 | ||
|
|
37f6c6ed7d | ||
|
|
7e3d526de3 | ||
|
|
53029f9fc3 | ||
|
|
22ccb317d6 | ||
|
|
ad91dc14b8 | ||
|
|
6bd89f31d3 | ||
|
|
608e94a613 | ||
|
|
9c16b08e22 | ||
|
|
63b6b95f71 | ||
|
|
17127a5157 | ||
|
|
bfadbc4098 | ||
|
|
08c6bb5c5e | ||
|
|
4e24e9899e | ||
|
|
c72bdae8d7 | ||
|
|
3687657dd7 | ||
|
|
21f0adb41b | ||
|
|
e0fad5e0e6 | ||
|
|
77f8129fac | ||
|
|
064ae11ba8 | ||
|
|
d7a69118bc | ||
|
|
f2fabe4eec | ||
|
|
dc92e40429 | ||
|
|
9c080d51f7 | ||
|
|
cf0541c93e | ||
|
|
54be542355 | ||
|
|
225ace0d02 | ||
|
|
ca24ae9632 | ||
|
|
9f69fd0811 | ||
|
|
b978a5d219 | ||
|
|
e7759aa79a | ||
|
|
1f4ca07b5f | ||
|
|
3ecd768982 | ||
|
|
3cfcbeb654 | ||
|
|
e1409ba39c | ||
|
|
172e255cc2 | ||
|
|
ef6299c6b4 | ||
|
|
8a99b09e83 | ||
|
|
9da19de860 | ||
|
|
b8af72b402 | ||
|
|
764c551f0f | ||
|
|
410c47178d | ||
|
|
f53ce84f5d | ||
|
|
ab0d425d93 | ||
|
|
95ca14b05c | ||
|
|
61e2f97d6c | ||
|
|
9e6c024beb | ||
|
|
879f5d89b9 | ||
|
|
161041d379 | ||
|
|
6d23d64e8f | ||
|
|
8c4e84dfaf | ||
|
|
706940769b | ||
|
|
3fe2aa3b1d | ||
|
|
1f4d7f2a32 | ||
|
|
57f0cce318 | ||
|
|
e95d0a4722 | ||
|
|
f37030fe26 | ||
|
|
3be0bc8ca8 | ||
|
|
65a7a8ee56 | ||
|
|
ab38ce63a5 | ||
|
|
e92c6e5298 | ||
|
|
76df404c8d | ||
|
|
d3a2841fef | ||
|
|
d60a4aa740 | ||
|
|
6538d7af1e | ||
|
|
c69279ee20 | ||
|
|
a16e746aa1 | ||
|
|
b1f62c8409 | ||
|
|
4a85f7f12b | ||
|
|
818be68c2d | ||
|
|
7511391aed | ||
|
|
d0a50adf32 | ||
|
|
c9576c696a | ||
|
|
908b442b26 | ||
|
|
9687e9985e | ||
|
|
07e4b18d06 | ||
|
|
93654dc656 | ||
|
|
4be5334829 | ||
|
|
0f4301af01 | ||
|
|
b071c47674 | ||
|
|
05e9f394f0 | ||
|
|
2027becad2 | ||
|
|
e7170755d8 | ||
|
|
d0a0d3c93c | ||
|
|
2db785477c | ||
|
|
31ec3152f9 | ||
|
|
7b8b469f5a | ||
|
|
bc4737ac23 | ||
|
|
6dc190d369 | ||
|
|
9980d2e72a | ||
|
|
2d34510f50 | ||
|
|
ef57f870ac | ||
|
|
7105b7a5fa | ||
|
|
1992adfac2 | ||
|
|
c2e8eca577 | ||
|
|
0cc466dd7e | ||
|
|
3eec1d358c | ||
|
|
1cc38fb58e | ||
|
|
91479bba53 | ||
|
|
1d4a3e780d | ||
|
|
af9a419e84 | ||
|
|
b311e924cd | ||
|
|
5c9b221b4c | ||
|
|
e94282459e | ||
|
|
77a0c0a7c6 | ||
|
|
c8fa3870db | ||
|
|
4261923126 | ||
|
|
554a462809 | ||
|
|
1206cc42bc | ||
|
|
48ab835646 | ||
|
|
bcd9141db6 | ||
|
|
e1fd65fe47 | ||
|
|
b2fe3cb033 | ||
|
|
df9cb7f0d4 | ||
|
|
07fdeb9284 | ||
|
|
6d04633daf | ||
|
|
2d95c131fc | ||
|
|
853c96478b | ||
|
|
86a7d133f3 | ||
|
|
1a6559efde | ||
|
|
c7cf8a9f74 | ||
|
|
8593f00917 | ||
|
|
6fd2c81315 | ||
|
|
3cbb0e974e | ||
|
|
02aa8339f8 | ||
|
|
7f64982fc0 | ||
|
|
d2e78d0292 | ||
|
|
11b768dace | ||
|
|
972c94bff7 | ||
|
|
489820cfe7 | ||
|
|
a3c4454090 | ||
|
|
bc6acee1f1 | ||
|
|
49a189c920 | ||
|
|
a35488b540 | ||
|
|
6b7071726b | ||
|
|
7d0282e59d | ||
|
|
f26bcd812c | ||
|
|
33762796b8 | ||
|
|
38b6582080 | ||
|
|
cd3122d597 | ||
|
|
b28a4c029c | ||
|
|
85899e6ac0 | ||
|
|
e21656c550 | ||
|
|
8fec8b7f80 | ||
|
|
8b45928d11 | ||
|
|
98150db0b4 | ||
|
|
96a4b83e31 | ||
|
|
84c31da226 | ||
|
|
cad5e703f8 | ||
|
|
62959a89bc | ||
|
|
c29f2eccaf | ||
|
|
664cfbf014 | ||
|
|
e1acce6e6e | ||
|
|
5fa83c84d3 | ||
|
|
1bb2d168fa | ||
|
|
52ad33f5b2 | ||
|
|
d9adcf01cd | ||
|
|
81d19c8804 | ||
|
|
5cbcebb79e | ||
|
|
40990020b1 | ||
|
|
5153139581 | ||
|
|
c852cd8e09 | ||
|
|
680104109b | ||
|
|
5f0938d01b | ||
|
|
1e533b24d1 | ||
|
|
7fa99cedee | ||
|
|
09cbf63c5a | ||
|
|
be3bce26ed | ||
|
|
5425f55af7 | ||
|
|
b6341741c3 | ||
|
|
a4f1a8f5ff | ||
|
|
aa2ab1118a | ||
|
|
71048be499 | ||
|
|
cba724d676 | ||
|
|
6f1d186287 | ||
|
|
4674658cfa | ||
|
|
4cfcb60e59 | ||
|
|
a230861afa | ||
|
|
e2207dc74c | ||
|
|
16b68ee154 | ||
|
|
1331f91061 | ||
|
|
138423aeec | ||
|
|
20815f1a91 | ||
|
|
35c57bb10c | ||
|
|
5fd653dae5 | ||
|
|
5a9b8a7bb0 | ||
|
|
5277a9eb38 | ||
|
|
b1887ae431 | ||
|
|
a3aed6aafc | ||
|
|
6903a36298 | ||
|
|
ea4c654af8 | ||
|
|
ec61417e39 | ||
|
|
3ba2c3d657 | ||
|
|
8b5faf6b66 | ||
|
|
76149633a1 | ||
|
|
d8113a3304 | ||
|
|
3fc19bf160 | ||
|
|
a30cb0b4c4 | ||
|
|
d29e9b525d | ||
|
|
7c8a348ead | ||
|
|
712931b728 | ||
|
|
2f0b122101 | ||
|
|
628b7644b7 | ||
|
|
88717ac9ab | ||
|
|
e5e90b1faf | ||
|
|
1a970d1372 | ||
|
|
b87b33c955 | ||
|
|
cfe9675709 | ||
|
|
b5f75271b9 | ||
|
|
90d5f5b8cf | ||
|
|
f84f17a5be | ||
|
|
ea8b254bd9 | ||
|
|
cb5f6e1ada | ||
|
|
fa94dd257a | ||
|
|
2a9ae8335d | ||
|
|
567317386a | ||
|
|
24a54f146c | ||
|
|
742abab420 | ||
|
|
c1c3cd3dc9 | ||
|
|
5e5739e37e | ||
|
|
69c0bcd4ca | ||
|
|
d994bbffd0 | ||
|
|
8c5b020961 | ||
|
|
a426cf07e9 | ||
|
|
d64641127a | ||
|
|
cbc0e36057 | ||
|
|
26405f8299 | ||
|
|
606e462b53 | ||
|
|
d424addb4c | ||
|
|
5427d6a466 | ||
|
|
b04298916c | ||
|
|
d3990ee2be | ||
|
|
4e567585af | ||
|
|
3bafb01202 | ||
|
|
dd5454591a | ||
|
|
df9f25dc3c | ||
|
|
a6b74d6538 | ||
|
|
0b045e5dd0 | ||
|
|
7a139dddc0 | ||
|
|
baf54a7c02 | ||
|
|
20e4dbfc1d | ||
|
|
77388bed29 | ||
|
|
cf5adc5ae7 | ||
|
|
1070283349 | ||
|
|
4ee78ea59c | ||
|
|
b8f0ecb134 | ||
|
|
efec6f6ec9 | ||
|
|
7f460eda5a | ||
|
|
678786c76c | ||
|
|
9917d6355c | ||
|
|
7f65e9fd5e | ||
|
|
9f92e5e0be | ||
|
|
6e92438282 | ||
|
|
f25980bb0d | ||
|
|
dd7861c5b4 | ||
|
|
35a4112840 | ||
|
|
2982f9cc9b | ||
|
|
bbd83fba30 | ||
|
|
cd5e5da526 | ||
|
|
f8df72b329 | ||
|
|
19e5130441 | ||
|
|
9ba1914524 | ||
|
|
586ec4515d | ||
|
|
4c2543d6f4 | ||
|
|
1aa489890c | ||
|
|
7a5bbd0613 | ||
|
|
d9fcd83370 | ||
|
|
73cd1274a5 | ||
|
|
35e58f8cd2 | ||
|
|
0769b2c481 | ||
|
|
80c0312219 | ||
|
|
3949632339 | ||
|
|
1eb4b0cec4 | ||
|
|
a1ba5bec17 | ||
|
|
7ca7cb39ae | ||
|
|
d1a74589b1 | ||
|
|
3e6896b8e6 | ||
|
|
3595434a05 | ||
|
|
7f1f1337fa | ||
|
|
7077879194 | ||
|
|
f314036cef | ||
|
|
af2835e505 | ||
|
|
d63b4ef6d1 | ||
|
|
f69109bc43 | ||
|
|
255df92a30 | ||
|
|
95defedf08 | ||
|
|
ec97e6d2e0 | ||
|
|
161a92fef2 | ||
|
|
da7ae0660e | ||
|
|
ca794b421d | ||
|
|
520ccd361c | ||
|
|
aa93fde347 | ||
|
|
fedde33be1 | ||
|
|
dc356ae7b6 | ||
|
|
fa333504c6 | ||
|
|
7c210f4d1c | ||
|
|
df47e2a9e9 | ||
|
|
87a3b73024 | ||
|
|
eb379662ce | ||
|
|
7176d5a19c | ||
|
|
ff1514dc75 | ||
|
|
59ebc262a3 | ||
|
|
26eb4c7083 | ||
|
|
ef8888c679 | ||
|
|
34ff5d6ac4 | ||
|
|
e64b6d1d98 | ||
|
|
b9b5287b37 | ||
|
|
5df6009f08 | ||
|
|
cca4737b91 | ||
|
|
bf1812ae83 | ||
|
|
0000cfd234 | ||
|
|
a876cd2186 | ||
|
|
d9b1d0faf3 | ||
|
|
2856d53967 | ||
|
|
9772b58333 | ||
|
|
b74ab0614e | ||
|
|
61a21520d1 | ||
|
|
ebfc9aa1e0 | ||
|
|
ff54d6dd6c | ||
|
|
828e4d6297 | ||
|
|
030b4fa380 | ||
|
|
328a5e8077 | ||
|
|
e8cbb2d39d | ||
|
|
887e73ea1d | ||
|
|
e210996839 | ||
|
|
2ba3ab3057 | ||
|
|
3cf0fa3ee2 | ||
|
|
8b36699893 | ||
|
|
166f5ce73f | ||
|
|
92baec128e | ||
|
|
4919ca88ec | ||
|
|
fa0d408664 | ||
|
|
444d9e36c6 | ||
|
|
95fc4d867d | ||
|
|
6524a40eaa | ||
|
|
f53943e78c | ||
|
|
528a8901df | ||
|
|
acd6d9679a | ||
|
|
f7c7fc5dc4 | ||
|
|
d575c48579 | ||
|
|
930d833e1b | ||
|
|
f7f77911be | ||
|
|
508f82946f | ||
|
|
6bb9754628 | ||
|
|
44fad50031 | ||
|
|
ed2cd2cea3 | ||
|
|
eaf74a3f23 | ||
|
|
1a99146b7a | ||
|
|
4a8e9e71f4 | ||
|
|
6d2d0ff701 | ||
|
|
af3c93051f | ||
|
|
f594190005 | ||
|
|
546d181ea9 | ||
|
|
143cefe4e3 | ||
|
|
ece152173f | ||
|
|
b08de31b3c | ||
|
|
0f967a41df | ||
|
|
4c3bf70cc4 |
@@ -405,12 +405,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
protected $m_aAdmins = array(); // id -> bool, true if the user has the well-known admin profile
|
||||
protected $m_aPortalUsers = array(); // id -> bool, true if the user has the well-known portal user profile
|
||||
|
||||
protected $m_aProfiles; // id -> object
|
||||
protected $m_aUserProfiles = array(); // userid,profileid -> object
|
||||
protected $m_aUserOrgs = array(); // userid -> array of orgid
|
||||
|
||||
// Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read)
|
||||
@@ -458,114 +452,65 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
return $this->m_aUserOrgs[$iUser];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and cache profiles of the given user
|
||||
*/
|
||||
protected function GetUserProfiles($iUser)
|
||||
{
|
||||
if (!array_key_exists($iUser, $this->m_aUserProfiles))
|
||||
{
|
||||
$oSearch = new DBObjectSearch('URP_UserProfile');
|
||||
$oSearch->AllowAllData();
|
||||
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
|
||||
$oSearch->AddConditionExpression($oCondition);
|
||||
|
||||
$this->m_aUserProfiles[$iUser] = array();
|
||||
$oUserProfileSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
|
||||
while ($oUserProfile = $oUserProfileSet->Fetch())
|
||||
{
|
||||
$this->m_aUserProfiles[$iUser][$oUserProfile->Get('profileid')] = $oUserProfile;
|
||||
}
|
||||
}
|
||||
return $this->m_aUserProfiles[$iUser];
|
||||
|
||||
}
|
||||
|
||||
public function ResetCache()
|
||||
{
|
||||
// Loaded by Load cache
|
||||
$this->m_aProfiles = null;
|
||||
$this->m_aUserProfiles = array();
|
||||
$this->m_aUserOrgs = array();
|
||||
|
||||
$this->m_aAdmins = array();
|
||||
$this->m_aPortalUsers = array();
|
||||
|
||||
// Cache
|
||||
$this->m_aObjectActionGrants = array();
|
||||
}
|
||||
|
||||
public function LoadCache()
|
||||
{
|
||||
if (!is_null($this->m_aProfiles)) return;
|
||||
// Could be loaded in a shared memory (?)
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
|
||||
if (self::HasSharing())
|
||||
static $bSharedObjectInitialized = false;
|
||||
if (!$bSharedObjectInitialized)
|
||||
{
|
||||
SharedObject::InitSharedClassProperties();
|
||||
$bSharedObjectInitialized = true;
|
||||
if (self::HasSharing())
|
||||
{
|
||||
SharedObject::InitSharedClassProperties();
|
||||
}
|
||||
}
|
||||
|
||||
$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles"));
|
||||
$this->m_aProfiles = array();
|
||||
while ($oProfile = $oProfileSet->Fetch())
|
||||
{
|
||||
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
|
||||
}
|
||||
|
||||
$oKPI->ComputeAndReport('Load of user management cache (excepted Action Grants)');
|
||||
|
||||
/*
|
||||
echo "<pre>\n";
|
||||
print_r($this->m_aProfiles);
|
||||
print_r($this->m_aUserProfiles);
|
||||
print_r($this->m_aUserOrgs);
|
||||
echo "</pre>\n";
|
||||
exit;
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oUser User
|
||||
* @return array
|
||||
*/
|
||||
public function IsAdministrator($oUser)
|
||||
{
|
||||
//$this->LoadCache();
|
||||
$iUser = $oUser->GetKey();
|
||||
if (!array_key_exists($iUser, $this->m_aAdmins))
|
||||
{
|
||||
$bIsAdmin = false;
|
||||
foreach($this->GetUserProfiles($iUser) as $oUserProfile)
|
||||
{
|
||||
if ($oUserProfile->Get('profile') == ADMIN_PROFILE_NAME)
|
||||
{
|
||||
$bIsAdmin = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->m_aAdmins[$iUser] = $bIsAdmin;
|
||||
}
|
||||
return $this->m_aAdmins[$iUser];
|
||||
// UserRights caches the list for us
|
||||
return UserRights::HasProfile(ADMIN_PROFILE_NAME, $oUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $oUser User
|
||||
* @return array
|
||||
*/
|
||||
public function IsPortalUser($oUser)
|
||||
{
|
||||
//$this->LoadCache();
|
||||
$iUser = $oUser->GetKey();
|
||||
if (!array_key_exists($iUser, $this->m_aPortalUsers))
|
||||
{
|
||||
$bIsPortalUser = false;
|
||||
foreach($this->GetUserProfiles($iUser) as $oUserProfile)
|
||||
{
|
||||
if ($oUserProfile->Get('profile') == PORTAL_PROFILE_NAME)
|
||||
{
|
||||
$bIsPortalUser = true;
|
||||
break;
|
||||
}
|
||||
// UserRights caches the list for us
|
||||
return UserRights::HasProfile(PORTAL_PROFILE_NAME, $oUser);
|
||||
}
|
||||
$this->m_aPortalUsers[$iUser] = $bIsPortalUser;
|
||||
/**
|
||||
* @param $oUser User
|
||||
* @return bool
|
||||
*/
|
||||
public function ListProfiles($oUser)
|
||||
{
|
||||
$aRet = array();
|
||||
$oSearch = new DBObjectSearch('URP_UserProfile');
|
||||
$oSearch->AllowAllData();
|
||||
$oSearch->NoContextParameters();
|
||||
$oSearch->Addcondition('userid', $oUser->GetKey(), '=');
|
||||
$oProfiles = new DBObjectSet($oSearch);
|
||||
while ($oUserProfile = $oProfiles->Fetch())
|
||||
{
|
||||
$aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname');
|
||||
}
|
||||
return $this->m_aPortalUsers[$iUser];
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
public function GetSelectFilter($oUser, $sClass, $aSettings = array())
|
||||
@@ -621,8 +566,8 @@ exit;
|
||||
$sAction = self::$m_aActionCodes[$iActionCode];
|
||||
|
||||
$bStatus = null;
|
||||
$aAttributes = array();
|
||||
foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile)
|
||||
// Call the API of UserRights because it caches the list for us
|
||||
foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
|
||||
{
|
||||
$bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
|
||||
if (!is_null($bGrant))
|
||||
@@ -645,12 +590,11 @@ exit;
|
||||
|
||||
$aRes = array(
|
||||
'permission' => $iPermission,
|
||||
// 'attributes' => $aAttributes,
|
||||
);
|
||||
$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes;
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
|
||||
public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null)
|
||||
{
|
||||
$this->LoadCache();
|
||||
@@ -752,7 +696,8 @@ exit;
|
||||
// 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;
|
||||
foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile)
|
||||
// Call the API of UserRights because it caches the list for us
|
||||
foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
|
||||
{
|
||||
$bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
|
||||
if (!is_null($bGrant))
|
||||
|
||||
328
application/Html2Text.php
Normal file
328
application/Html2Text.php
Normal file
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
namespace Html2Text;
|
||||
|
||||
if (!function_exists('mb_split'))
|
||||
{
|
||||
function mb_split($pattern, $subject, $limit = -1)
|
||||
{
|
||||
return preg_split('/'.$pattern.'/', $subject, $limit);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Replace all occurrences of the search string with the replacement string.
|
||||
*
|
||||
* @author Sean Murphy <sean@iamseanmurphy.com>
|
||||
* @copyright Copyright 2012 Sean Murphy. All rights reserved.
|
||||
* @license http://creativecommons.org/publicdomain/zero/1.0/
|
||||
* @link http://php.net/manual/function.str-replace.php
|
||||
*
|
||||
* @param mixed $search
|
||||
* @param mixed $replace
|
||||
* @param mixed $subject
|
||||
* @param int $count
|
||||
* @return mixed
|
||||
*/
|
||||
function mb_str_replace($search, $replace, $subject, &$count = 0) {
|
||||
if (!is_array($subject)) {
|
||||
// Normalize $search and $replace so they are both arrays of the same length
|
||||
$searches = is_array($search) ? array_values($search) : array($search);
|
||||
$replacements = is_array($replace) ? array_values($replace) : array($replace);
|
||||
$replacements = array_pad($replacements, count($searches), '');
|
||||
foreach ($searches as $key => $search) {
|
||||
$parts = mb_split(preg_quote($search), $subject);
|
||||
$count += count($parts) - 1;
|
||||
$subject = implode($replacements[$key], $parts);
|
||||
}
|
||||
} else {
|
||||
// Call mb_str_replace for each subject in array, recursively
|
||||
foreach ($subject as $key => $value) {
|
||||
$subject[$key] = mb_str_replace($search, $replace, $value, $count);
|
||||
}
|
||||
}
|
||||
return $subject;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2010 Jevon Wright and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* or
|
||||
*
|
||||
* LGPL which is available at http://www.gnu.org/licenses/lgpl.html
|
||||
*
|
||||
*
|
||||
* Contributors:
|
||||
* Jevon Wright - initial API and implementation
|
||||
* Denis Flaven - some fixes for properly handling UTF-8 characters
|
||||
****************************************************************************/
|
||||
|
||||
class Html2Text {
|
||||
|
||||
/**
|
||||
* Tries to convert the given HTML into a plain text format - best suited for
|
||||
* e-mail display, etc.
|
||||
*
|
||||
* <p>In particular, it tries to maintain the following features:
|
||||
* <ul>
|
||||
* <li>Links are maintained, with the 'href' copied over
|
||||
* <li>Information in the <head> is lost
|
||||
* </ul>
|
||||
*
|
||||
* @param string html the input HTML
|
||||
* @return string the HTML converted, as best as possible, to text
|
||||
* @throws Html2TextException if the HTML could not be loaded as a {@link DOMDocument}
|
||||
*/
|
||||
static function convert($html) {
|
||||
// replace with spaces
|
||||
|
||||
$html = str_replace(" ", " ", $html);
|
||||
$html = mb_str_replace("\xc2\xa0", " ", $html); // DO NOT USE str_replace since it breaks the "à" character which is \xc3 \xa0 in UTF-8
|
||||
|
||||
$html = static::fixNewlines($html);
|
||||
|
||||
$doc = new \DOMDocument();
|
||||
if (!@$doc->loadHTML('<?xml encoding="UTF-8">'.$html)) // Forces the UTF-8 character set for HTML fragments
|
||||
{
|
||||
throw new Html2TextException("Could not load HTML - badly formed?", $html);
|
||||
}
|
||||
|
||||
$output = static::iterateOverNode($doc);
|
||||
|
||||
// remove leading and trailing spaces on each line
|
||||
$output = preg_replace("/[ \t]*\n[ \t]*/im", "\n", $output);
|
||||
$output = preg_replace("/ *\t */im", "\t", $output);
|
||||
|
||||
// remove unnecessary empty lines
|
||||
$output = preg_replace("/\n\n\n*/im", "\n\n", $output);
|
||||
|
||||
// remove leading and trailing whitespace
|
||||
$output = trim($output);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unify newlines; in particular, \r\n becomes \n, and
|
||||
* then \r becomes \n. This means that all newlines (Unix, Windows, Mac)
|
||||
* all become \ns.
|
||||
*
|
||||
* @param string text text with any number of \r, \r\n and \n combinations
|
||||
* @return string the fixed text
|
||||
*/
|
||||
static function fixNewlines($text) {
|
||||
// replace \r\n to \n
|
||||
$text = str_replace("\r\n", "\n", $text);
|
||||
// remove \rs
|
||||
$text = str_replace("\r", "\n", $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
static function nextChildName($node) {
|
||||
// get the next child
|
||||
$nextNode = $node->nextSibling;
|
||||
while ($nextNode != null) {
|
||||
if ($nextNode instanceof \DOMElement) {
|
||||
break;
|
||||
}
|
||||
$nextNode = $nextNode->nextSibling;
|
||||
}
|
||||
$nextName = null;
|
||||
if ($nextNode instanceof \DOMElement && $nextNode != null) {
|
||||
$nextName = strtolower($nextNode->nodeName);
|
||||
}
|
||||
|
||||
return $nextName;
|
||||
}
|
||||
|
||||
static function prevChildName($node) {
|
||||
// get the previous child
|
||||
$nextNode = $node->previousSibling;
|
||||
while ($nextNode != null) {
|
||||
if ($nextNode instanceof \DOMElement) {
|
||||
break;
|
||||
}
|
||||
$nextNode = $nextNode->previousSibling;
|
||||
}
|
||||
$nextName = null;
|
||||
if ($nextNode instanceof \DOMElement && $nextNode != null) {
|
||||
$nextName = strtolower($nextNode->nodeName);
|
||||
}
|
||||
|
||||
return $nextName;
|
||||
}
|
||||
|
||||
static function iterateOverNode($node) {
|
||||
if ($node instanceof \DOMText) {
|
||||
// Replace whitespace characters with a space (equivilant to \s)
|
||||
return preg_replace("/[\\t\\n\\f\\r ]+/im", " ", $node->wholeText);
|
||||
}
|
||||
if ($node instanceof \DOMDocumentType) {
|
||||
// ignore
|
||||
return "";
|
||||
}
|
||||
|
||||
$nextName = static::nextChildName($node);
|
||||
$prevName = static::prevChildName($node);
|
||||
|
||||
$name = strtolower($node->nodeName);
|
||||
|
||||
// start whitespace
|
||||
switch ($name) {
|
||||
case "hr":
|
||||
return "---------------------------------------------------------------\n";
|
||||
|
||||
case "style":
|
||||
case "head":
|
||||
case "title":
|
||||
case "meta":
|
||||
case "script":
|
||||
// ignore these tags
|
||||
return "";
|
||||
|
||||
case "h1":
|
||||
case "h2":
|
||||
case "h3":
|
||||
case "h4":
|
||||
case "h5":
|
||||
case "h6":
|
||||
case "ol":
|
||||
case "ul":
|
||||
// add two newlines, second line is added below
|
||||
$output = "\n";
|
||||
break;
|
||||
|
||||
case "td":
|
||||
case "th":
|
||||
// add tab char to separate table fields
|
||||
$output = "\t";
|
||||
break;
|
||||
|
||||
case "tr":
|
||||
case "p":
|
||||
case "div":
|
||||
// add one line
|
||||
$output = "\n";
|
||||
break;
|
||||
|
||||
case "li":
|
||||
$output = "- ";
|
||||
break;
|
||||
|
||||
default:
|
||||
// print out contents of unknown tags
|
||||
$output = "";
|
||||
break;
|
||||
}
|
||||
|
||||
// debug
|
||||
//$output .= "[$name,$nextName]";
|
||||
|
||||
if (isset($node->childNodes)) {
|
||||
for ($i = 0; $i < $node->childNodes->length; $i++) {
|
||||
$n = $node->childNodes->item($i);
|
||||
|
||||
$text = static::iterateOverNode($n);
|
||||
|
||||
$output .= $text;
|
||||
}
|
||||
}
|
||||
|
||||
// end whitespace
|
||||
switch ($name) {
|
||||
case "h1":
|
||||
case "h2":
|
||||
case "h3":
|
||||
case "h4":
|
||||
case "h5":
|
||||
case "h6":
|
||||
$output .= "\n";
|
||||
break;
|
||||
|
||||
case "p":
|
||||
case "br":
|
||||
// add one line
|
||||
if ($nextName != "div")
|
||||
$output .= "\n";
|
||||
break;
|
||||
|
||||
case "div":
|
||||
// add one line only if the next child isn't a div
|
||||
if ($nextName != "div" && $nextName != null)
|
||||
$output .= "\n";
|
||||
break;
|
||||
|
||||
case "a":
|
||||
// links are returned in [text](link) format
|
||||
$href = $node->getAttribute("href");
|
||||
|
||||
$output = trim($output);
|
||||
|
||||
// remove double [[ ]] s from linking images
|
||||
if (substr($output, 0, 1) == "[" && substr($output, -1) == "]") {
|
||||
$output = substr($output, 1, strlen($output) - 2);
|
||||
|
||||
// for linking images, the title of the <a> overrides the title of the <img>
|
||||
if ($node->getAttribute("title")) {
|
||||
$output = $node->getAttribute("title");
|
||||
}
|
||||
}
|
||||
|
||||
// if there is no link text, but a title attr
|
||||
if (!$output && $node->getAttribute("title")) {
|
||||
$output = $node->getAttribute("title");
|
||||
}
|
||||
|
||||
if ($href == null) {
|
||||
// it doesn't link anywhere
|
||||
if ($node->getAttribute("name") != null) {
|
||||
$output = "[$output]";
|
||||
}
|
||||
} else {
|
||||
if ($href == $output || $href == "mailto:$output" || $href == "http://$output" || $href == "https://$output") {
|
||||
// link to the same address: just use link
|
||||
$output;
|
||||
} else {
|
||||
// replace it
|
||||
if ($output) {
|
||||
$output = "[$output]($href)";
|
||||
} else {
|
||||
// empty string
|
||||
$output = $href;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// does the next node require additional whitespace?
|
||||
switch ($nextName) {
|
||||
case "h1": case "h2": case "h3": case "h4": case "h5": case "h6":
|
||||
$output .= "\n";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "img":
|
||||
if ($node->getAttribute("title")) {
|
||||
$output = "[" . $node->getAttribute("title") . "]";
|
||||
} elseif ($node->getAttribute("alt")) {
|
||||
$output = "[" . $node->getAttribute("alt") . "]";
|
||||
} else {
|
||||
$output = "";
|
||||
}
|
||||
break;
|
||||
|
||||
case "li":
|
||||
$output .= "\n";
|
||||
break;
|
||||
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
}
|
||||
28
application/Html2TextException.php
Normal file
28
application/Html2TextException.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2010 Jevon Wright and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* or
|
||||
*
|
||||
* LGPL which is available at http://www.gnu.org/licenses/lgpl.html
|
||||
*
|
||||
*
|
||||
* Contributors:
|
||||
* Jevon Wright - initial API and implementation
|
||||
****************************************************************************/
|
||||
|
||||
namespace Html2Text;
|
||||
|
||||
class Html2TextException extends \Exception {
|
||||
var $more_info;
|
||||
|
||||
public function __construct($message = "", $more_info = "") {
|
||||
parent::__construct($message);
|
||||
$this->more_info = $more_info;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2014 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
* Simple web page with no includes, header or fancy formatting, useful to
|
||||
* generate HTML fragments when called by an AJAX method
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -42,7 +42,10 @@ class ajax_page extends WebPage implements iTabbedPage
|
||||
*/
|
||||
function __construct($s_title)
|
||||
{
|
||||
parent::__construct($s_title);
|
||||
$sPrintable = utils::ReadParam('printable', '0');
|
||||
$bPrintable = ($sPrintable == '1');
|
||||
|
||||
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");
|
||||
@@ -50,7 +53,9 @@ class ajax_page extends WebPage implements iTabbedPage
|
||||
$this->sContentType = 'text/html';
|
||||
$this->sContentDisposition = 'inline';
|
||||
$this->m_sMenu = "";
|
||||
}
|
||||
|
||||
utils::InitArchiveMode();
|
||||
}
|
||||
|
||||
public function AddTabContainer($sTabContainer, $sPrefix = '')
|
||||
{
|
||||
@@ -197,34 +202,18 @@ EOF
|
||||
);
|
||||
}
|
||||
// Render the tabs in the page (if any)
|
||||
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content);
|
||||
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
|
||||
|
||||
// Additional UI widgets to be activated inside the ajax fragment ??
|
||||
if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) )
|
||||
// Additional UI widgets to be activated inside the ajax fragment
|
||||
// Important: Testing the content type is not enough because some ajax handlers have not correctly positionned the flag (e.g json response corrupted by the script)
|
||||
if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) )
|
||||
{
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
$(".datetime-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd 00:00:00',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
PrepareWidgets();
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
$s_captured_output = $this->ob_get_clean_safe();
|
||||
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))
|
||||
{
|
||||
@@ -249,7 +238,6 @@ EOF
|
||||
echo "<script type=\"text/javascript\">\n";
|
||||
echo "$('#inner_menu').html($('#accordion_temp_$uid').html());\n";
|
||||
echo "$('#accordion_temp_$uid').remove();\n";
|
||||
echo "$('#accordion').accordion({ header: 'h3', navigation: true, autoHeight: false, collapsible: false, icons: false });\n";
|
||||
echo "\n</script>\n";
|
||||
}
|
||||
|
||||
@@ -278,9 +266,9 @@ EOF
|
||||
echo self::FilterXSS($s_captured_output);
|
||||
}
|
||||
|
||||
if (class_exists('MetaModel'))
|
||||
if (class_exists('DBSearch'))
|
||||
{
|
||||
MetaModel::RecordQueryTrace();
|
||||
DBSearch::RecordQueryTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
require_once(APPROOT.'/application/applicationcontext.class.inc.php');
|
||||
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
|
||||
require_once(APPROOT.'/application/displayblock.class.inc.php');
|
||||
require_once(APPROOT.'/application/sqlblock.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.category.class.inc.php');
|
||||
require_once(APPROOT.'/application/audit.rule.class.inc.php');
|
||||
require_once(APPROOT.'/application/query.class.inc.php');
|
||||
|
||||
@@ -118,8 +118,7 @@ class ApplicationContext
|
||||
$oSearchFilter = new DBObjectSearch('Organization');
|
||||
$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
|
||||
$oSet = new CMDBObjectSet($oSearchFilter);
|
||||
$iCount = $oSet->Count();
|
||||
if ($iCount == 1)
|
||||
if ($oSet->Count(2) == 1)
|
||||
{
|
||||
// Only one possible value for org_id, set it in the context
|
||||
$oOrg = $oSet->Fetch();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -308,6 +308,43 @@ interface iPopupMenuExtension
|
||||
* $param is null
|
||||
*/
|
||||
const MENU_USER_ACTIONS = 5;
|
||||
/**
|
||||
* Insert an item into the Action menu on an object item in an objects list in the portal
|
||||
*
|
||||
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object on the current line)
|
||||
*/
|
||||
const PORTAL_OBJLISTITEM_ACTIONS = 7;
|
||||
/**
|
||||
* Insert an item into the Action menu on an object details page in the portal
|
||||
*
|
||||
* $param is an array('portal_id' => $sPortalId, 'object' => $oObject) containing the portal id and a DBObject instance (the object currently displayed)
|
||||
*/
|
||||
const PORTAL_OBJDETAILS_ACTIONS = 8;
|
||||
|
||||
/**
|
||||
* Insert an item into the Actions menu of a list in the portal
|
||||
* Note: This is not implemented yet !
|
||||
*
|
||||
* $param is an array('portal_id' => $sPortalId, 'object_set' => $oSet) containing DBObjectSet containing the list of objects
|
||||
* @todo
|
||||
*/
|
||||
const PORTAL_OBJLIST_ACTIONS = 6;
|
||||
/**
|
||||
* Insert an item into the user menu of the portal
|
||||
* Note: This is not implemented yet !
|
||||
*
|
||||
* $param is the portal id
|
||||
* @todo
|
||||
*/
|
||||
const PORTAL_USER_ACTIONS = 9;
|
||||
/**
|
||||
* Insert an item into the navigation menu of the portal
|
||||
* Note: This is not implemented yet !
|
||||
*
|
||||
* $param is the portal id
|
||||
* @todo
|
||||
*/
|
||||
const PORTAL_MENU_ACTIONS = 10;
|
||||
|
||||
/**
|
||||
* Get the list of items to be added to a menu.
|
||||
@@ -334,17 +371,21 @@ abstract class ApplicationPopupMenuItem
|
||||
protected $sUID;
|
||||
/** @ignore */
|
||||
protected $sLabel;
|
||||
/** @ignore */
|
||||
protected $aCssClasses;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @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 $sLabel The display label of the menu (must be localized)
|
||||
* @param array $aCssClasses The CSS classes to add to the menu
|
||||
*/
|
||||
public function __construct($sUID, $sLabel)
|
||||
{
|
||||
$this->sUID = $sUID;
|
||||
$this->sLabel = $sLabel;
|
||||
$this->aCssClasses = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,6 +409,35 @@ abstract class ApplicationPopupMenuItem
|
||||
{
|
||||
return $this->sLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CSS classes
|
||||
*
|
||||
* @return array
|
||||
* @ignore
|
||||
*/
|
||||
public function GetCssClasses()
|
||||
{
|
||||
return $this->aCssClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $aCssClasses
|
||||
*/
|
||||
public function SetCssClasses($aCssClasses)
|
||||
{
|
||||
$this->aCssClasses = $aCssClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a CSS class to the CSS classes that will be put on the menu item
|
||||
*
|
||||
* @param $sCssClass
|
||||
*/
|
||||
public function AddCssClass($sCssClass)
|
||||
{
|
||||
$this->aCssClasses[] = $sCssClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the components to create a popup menu item in HTML
|
||||
@@ -415,7 +485,7 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
|
||||
/** @ignore */
|
||||
public function GetMenuItem()
|
||||
{
|
||||
return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget);
|
||||
return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget, 'css_classes' => $this->aCssClasses);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,7 +521,7 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
|
||||
public function GetMenuItem()
|
||||
{
|
||||
// Note: the semicolumn is a must here!
|
||||
return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode.'; return false;', 'url' => '#');
|
||||
return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode.'; return false;', 'url' => '#', 'css_classes' => $this->aCssClasses);
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
@@ -483,10 +553,34 @@ class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
|
||||
/** @ignore */
|
||||
public function GetMenuItem()
|
||||
{
|
||||
return array ('label' => '<hr class="menu-separator">', 'url' => '');
|
||||
return array ('label' => '<hr class="menu-separator">', 'url' => '', 'css_classes' => $this->aCssClasses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for adding an item as a button that browses to the given URL
|
||||
*
|
||||
* @package Extensibility
|
||||
* @api
|
||||
* @since 2.0
|
||||
*/
|
||||
class URLButtonItem extends URLPopupMenuItem
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for adding an item as a button that runs some JS code
|
||||
*
|
||||
* @package Extensibility
|
||||
* @api
|
||||
* @since 2.0
|
||||
*/
|
||||
class JSButtonItem extends JSPopupMenuItem
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add content to any iTopWebPage
|
||||
*
|
||||
@@ -528,6 +622,128 @@ interface iPageUIExtension
|
||||
public function GetBannerHtml(iTopWebPage $oPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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!
|
||||
*
|
||||
* @package Extensibility
|
||||
* @api
|
||||
* @since 2.4
|
||||
*/
|
||||
interface iPortalUIExtension
|
||||
{
|
||||
const ENUM_PORTAL_EXT_UI_BODY = 'Body';
|
||||
const ENUM_PORTAL_EXT_UI_NAVIGATION_MENU = 'NavigationMenu';
|
||||
const ENUM_PORTAL_EXT_UI_MAIN_CONTENT = 'MainContent';
|
||||
|
||||
/**
|
||||
* Returns an array of CSS file urls
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return array
|
||||
*/
|
||||
public function GetCSSFiles(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns inline (raw) CSS
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return string
|
||||
*/
|
||||
public function GetCSSInline(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns an array of JS file urls
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return array
|
||||
*/
|
||||
public function GetJSFiles(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns raw JS code
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return string
|
||||
*/
|
||||
public function GetJSInline(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the <body> tag
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return string
|
||||
*/
|
||||
public function GetBodyHTML(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the #main-wrapper element
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return string
|
||||
*/
|
||||
public function GetMainContentHTML(\Silex\Application $oApp);
|
||||
/**
|
||||
* Returns raw HTML code to put at the end of the #topbar and #sidebar elements
|
||||
*
|
||||
* @param \Silex\Application $oApp
|
||||
* @return string
|
||||
*/
|
||||
public function GetNavigationMenuHTML(\Silex\Application $oApp);
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now!
|
||||
*/
|
||||
abstract class AbstractPortalUIExtension implements iPortalUIExtension
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetCSSFiles(\Silex\Application $oApp)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetCSSInline(\Silex\Application $oApp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetJSFiles(\Silex\Application $oApp)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetJSInline(\Silex\Application $oApp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetBodyHTML(\Silex\Application $oApp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetMainContentHTML(\Silex\Application $oApp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function GetNavigationMenuHTML(\Silex\Application $oApp)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to add new operations to the REST/JSON web service
|
||||
*
|
||||
@@ -781,8 +997,12 @@ class RestUtils
|
||||
$oSearch = new DBObjectSearch($sClass);
|
||||
foreach ($oCriteria as $sAttCode => $value)
|
||||
{
|
||||
$realValue = self::MakeValue($sClass, $sAttCode, $value);
|
||||
$realValue = static::MakeValue($sClass, $sAttCode, $value);
|
||||
$oSearch->AddCondition($sAttCode, $realValue, '=');
|
||||
if (is_object($value) || is_array($value))
|
||||
{
|
||||
$value = json_encode($value);
|
||||
}
|
||||
$aCriteriaReport[] = "$sAttCode: $value ($realValue)";
|
||||
}
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
@@ -814,7 +1034,7 @@ class RestUtils
|
||||
{
|
||||
if (is_object($key))
|
||||
{
|
||||
$res = self::FindObjectFromCriteria($sClass, $key);
|
||||
$res = static::FindObjectFromCriteria($sClass, $key);
|
||||
}
|
||||
elseif (is_numeric($key))
|
||||
{
|
||||
@@ -878,7 +1098,7 @@ class RestUtils
|
||||
$oSearch = new DBObjectSearch($sClass);
|
||||
foreach ($key as $sAttCode => $value)
|
||||
{
|
||||
$realValue = self::MakeValue($sClass, $sAttCode, $value);
|
||||
$realValue = static::MakeValue($sClass, $sAttCode, $value);
|
||||
$oSearch->AddCondition($sAttCode, $realValue, '=');
|
||||
}
|
||||
}
|
||||
@@ -922,7 +1142,7 @@ class RestUtils
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeExternalKey)
|
||||
{
|
||||
$oExtKeyObject = self::FindObjectFromKey($oAttDef->GetTargetClass(), $value, true /* allow null */);
|
||||
$oExtKeyObject = static::FindObjectFromKey($oAttDef->GetTargetClass(), $value, true /* allow null */);
|
||||
$value = ($oExtKeyObject != null) ? $oExtKeyObject->GetKey() : 0;
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeLinkedSet)
|
||||
@@ -935,7 +1155,7 @@ class RestUtils
|
||||
$aLinks = array();
|
||||
foreach($value as $oValues)
|
||||
{
|
||||
$oLnk = self::MakeObjectFromFields($sLnkClass, $oValues);
|
||||
$oLnk = static::MakeObjectFromFields($sLnkClass, $oValues);
|
||||
$aLinks[] = $oLnk;
|
||||
}
|
||||
$value = DBObjectSet::FromArray($sLnkClass, $aLinks);
|
||||
@@ -966,7 +1186,7 @@ class RestUtils
|
||||
$oObject = MetaModel::NewObject($sClass);
|
||||
foreach ($aFields as $sAttCode => $value)
|
||||
{
|
||||
$realValue = self::MakeValue($sClass, $sAttCode, $value);
|
||||
$realValue = static::MakeValue($sClass, $sAttCode, $value);
|
||||
try
|
||||
{
|
||||
$oObject->Set($sAttCode, $realValue);
|
||||
@@ -993,7 +1213,7 @@ class RestUtils
|
||||
$sClass = get_class($oObject);
|
||||
foreach ($aFields as $sAttCode => $value)
|
||||
{
|
||||
$realValue = self::MakeValue($sClass, $sAttCode, $value);
|
||||
$realValue = static::MakeValue($sClass, $sAttCode, $value);
|
||||
try
|
||||
{
|
||||
$oObject->Set($sAttCode, $realValue);
|
||||
|
||||
@@ -47,7 +47,7 @@ class AuditCategory extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeOQL("definition_set", array("allowed_values"=>null, "sql"=>"definition_set", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", array("linked_class"=>"AuditRule", "ext_key_to_me"=>"category_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(), "edit_mode" => LINKSET_EDITMODE_INPLACE)));
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", array("linked_class"=>"AuditRule", "ext_key_to_me"=>"category_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(), "edit_mode" => LINKSET_EDITMODE_INPLACE, "tracking_level" => LINKSET_TRACKING_ALL)));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('name', 'description', 'definition_set', 'rules_list')); // Attributes to be displayed for the complete details
|
||||
|
||||
84
application/capturewebpage.class.inc.php
Normal file
84
application/capturewebpage.class.inc.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 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/>
|
||||
|
||||
/**
|
||||
* Adapter class: when an API requires WebPage and you want to produce something else
|
||||
*
|
||||
* @copyright Copyright (C) 2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT."/application/webpage.class.inc.php");
|
||||
|
||||
class CaptureWebPage extends WebPage
|
||||
{
|
||||
protected $aReadyScripts;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
parent::__construct('capture web page');
|
||||
$this->aReadyScripts = array();
|
||||
}
|
||||
|
||||
public function GetHtml()
|
||||
{
|
||||
$trash = $this->ob_get_clean_safe();
|
||||
return $this->s_content;
|
||||
}
|
||||
|
||||
public function GetJS()
|
||||
{
|
||||
$sRet = implode("\n", $this->a_scripts);
|
||||
if (!empty($this->s_deferred_content))
|
||||
{
|
||||
$sRet .= "\n\$('body').append('".addslashes(str_replace("\n", '', $this->s_deferred_content))."');";
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetReadyJS()
|
||||
{
|
||||
return "\$(document).ready(function() {\n".implode("\n", $this->aReadyScripts)."\n});";
|
||||
}
|
||||
|
||||
public function GetCSS()
|
||||
{
|
||||
return $this->a_styles;
|
||||
}
|
||||
|
||||
public function GetJSFiles()
|
||||
{
|
||||
return $this->a_linked_scripts;
|
||||
}
|
||||
|
||||
public function GetCSSFiles()
|
||||
{
|
||||
return $this->a_linked_stylesheets;
|
||||
}
|
||||
|
||||
public function output()
|
||||
{
|
||||
throw new Exception(__method__.' should not be called');
|
||||
}
|
||||
|
||||
public function add_ready_script($sScript)
|
||||
{
|
||||
$this->aReadyScripts[] = $sScript;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* CLI page
|
||||
* The page adds the content-type text/XML and the encoding into the headers
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -35,9 +35,9 @@ class CLIPage implements Page
|
||||
|
||||
public function output()
|
||||
{
|
||||
if (class_exists('MetaModel'))
|
||||
if (class_exists('DBSearch'))
|
||||
{
|
||||
MetaModel::RecordQueryTrace();
|
||||
DBSearch::RecordQueryTrace();
|
||||
}
|
||||
if (class_exists('ExecutionKPI'))
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2014 Combodo SARL
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* Simple web page with no includes or fancy formatting, useful to generateXML documents
|
||||
* The page adds the content-type text/XML and the encoding into the headers
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -51,9 +51,9 @@ class CSVPage extends WebPage
|
||||
echo trim($this->s_content);
|
||||
echo "\n";
|
||||
|
||||
if (class_exists('MetaModel'))
|
||||
if (class_exists('DBSearch'))
|
||||
{
|
||||
MetaModel::RecordQueryTrace();
|
||||
DBSearch::RecordQueryTrace();
|
||||
}
|
||||
if (class_exists('ExecutionKPI'))
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -23,7 +23,7 @@ require_once(APPROOT.'core/modelreflection.class.inc.php');
|
||||
/**
|
||||
* A user editable dashboard page
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
abstract class Dashboard
|
||||
@@ -312,7 +312,7 @@ abstract class Dashboard
|
||||
|
||||
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
|
||||
{
|
||||
$oPage->add('<h1>'.Dict::S($this->sTitle).'</h1>');
|
||||
$oPage->add('<h1>'.htmlentities(Dict::S($this->sTitle), ENT_QUOTES, 'UTF-8', false).'</h1>');
|
||||
$oLayout = new $this->sLayoutClass;
|
||||
$oLayout->Render($oPage, $this->aCells, $bEditMode, $aExtraParams);
|
||||
if (!$bEditMode)
|
||||
@@ -515,8 +515,6 @@ class RuntimeDashboard extends Dashboard
|
||||
// Assuming there is at most one couple {user, menu}!
|
||||
$oUserDashboard = $oUDSet->Fetch();
|
||||
$oUserDashboard->Set('contents', $sXml);
|
||||
|
||||
$oUserDashboard->DBUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -525,9 +523,10 @@ class RuntimeDashboard extends Dashboard
|
||||
$oUserDashboard->Set('user_id', UserRights::GetUserId());
|
||||
$oUserDashboard->Set('menu_code', $this->sId);
|
||||
$oUserDashboard->Set('contents', $sXml);
|
||||
|
||||
$oUserDashboard->DBInsert();
|
||||
}
|
||||
}
|
||||
utils::PushArchiveMode(false);
|
||||
$oUserDashboard->DBWrite();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
|
||||
public function Revert()
|
||||
@@ -540,13 +539,17 @@ class RuntimeDashboard extends Dashboard
|
||||
{
|
||||
// Assuming there is at most one couple {user, menu}!
|
||||
$oUserDashboard = $oUDSet->Fetch();
|
||||
utils::PushArchiveMode(false);
|
||||
$oUserDashboard->DBDelete();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
|
||||
public function RenderEditionTools($oPage)
|
||||
{
|
||||
$sEditMenu = "<td><span id=\"DashboardMenu\"><ul><li><img src=\"../images/edit.png\"><ul>";
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.iframe-transport.js');
|
||||
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.fileupload.js');
|
||||
$sEditMenu = "<td><span id=\"DashboardMenu\"><ul><li><img src=\"../images/pencil-menu.png\"><ul>";
|
||||
|
||||
$aActions = array();
|
||||
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:Edit'), "return EditDashboard('{$this->sId}')");
|
||||
@@ -752,33 +755,65 @@ EOF
|
||||
|
||||
public static function GetDashletCreationForm($sOQL = null)
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContextMenuId = $oAppContext->GetCurrentValue('menu', null);
|
||||
|
||||
$oForm = new DesignerForm();
|
||||
|
||||
// Get the list of all 'dashboard' menus in which we can insert a dashlet
|
||||
$aAllMenus = ApplicationMenu::ReflectionMenuNodes();
|
||||
$sRootMenuId = ApplicationMenu::GetRootMenuId($sContextMenuId);
|
||||
$aAllowedDashboards = array();
|
||||
foreach($aAllMenus as $idx => $aMenu)
|
||||
$sDefaultDashboard = null;
|
||||
|
||||
// Store the parent menus for acces check
|
||||
$aParentMenus = array();
|
||||
foreach($aAllMenus as $idx => $aMenu)
|
||||
{
|
||||
/** @var MenuNode $oMenu */
|
||||
$oMenu = $aMenu['node'];
|
||||
if (count(ApplicationMenu::GetChildren($oMenu->GetIndex())) > 0)
|
||||
{
|
||||
$aParentMenus[$oMenu->GetMenuId()] = $aMenu;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($aAllMenus as $idx => $aMenu)
|
||||
{
|
||||
$oMenu = $aMenu['node'];
|
||||
$sParentId = $aMenu['parent'];
|
||||
if ($oMenu instanceof DashboardMenuNode)
|
||||
{
|
||||
$sMenuLabel = $oMenu->GetTitle();
|
||||
$sParentLabel = Dict::S('Menu:'.$sParentId);
|
||||
if ($sParentLabel != $sMenuLabel)
|
||||
{
|
||||
$aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel;
|
||||
}
|
||||
}
|
||||
if ($oMenu instanceof DashboardMenuNode)
|
||||
{
|
||||
// Get the root parent for access check
|
||||
$sParentId = $aMenu['parent'];
|
||||
$aParentMenu = $aParentMenus[$sParentId];
|
||||
while (isset($aParentMenus[$aParentMenu['parent']]))
|
||||
{
|
||||
// grand parent exists
|
||||
$sParentId = $aParentMenu['parent'];
|
||||
$aParentMenu = $aParentMenus[$sParentId];
|
||||
}
|
||||
$oParentMenu = $aParentMenu['node'];
|
||||
if ($oMenu->IsEnabled() && $oParentMenu->IsEnabled())
|
||||
{
|
||||
$sMenuLabel = $oMenu->GetTitle();
|
||||
$sParentLabel = Dict::S('Menu:'.$sParentId);
|
||||
if ($sParentLabel != $sMenuLabel)
|
||||
{
|
||||
$aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel;
|
||||
}
|
||||
if (empty($sDefaultDashboard) && ($sRootMenuId == ApplicationMenu::GetRootMenuId($oMenu->GetMenuId())))
|
||||
{
|
||||
$sDefaultDashboard = $oMenu->GetMenuId();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
asort($aAllowedDashboards);
|
||||
|
||||
$aKeys = array_keys($aAllowedDashboards); // Select the first one by default
|
||||
$sDefaultDashboard = $aKeys[0];
|
||||
$oField = new DesignerComboField('menu_id', Dict::S('UI:DashletCreation:Dashboard'), $sDefaultDashboard);
|
||||
$oField->SetAllowedValues($aAllowedDashboards);
|
||||
$oField->SetMandatory(true);
|
||||
@@ -839,7 +874,7 @@ EOF
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#dashlet_creation_dlg').dialog({
|
||||
width: 400,
|
||||
width: 600,
|
||||
modal: true,
|
||||
title: '$sDialogTitle',
|
||||
buttons: [
|
||||
|
||||
@@ -104,7 +104,7 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
|
||||
// Trim the list of cells to remove the invisible/empty ones at the end of the array
|
||||
$aCells = $this->TrimCellsArray($aCells);
|
||||
|
||||
$oPage->add('<table style="width:100%"><tbody>');
|
||||
$oPage->add('<table style="width:100%;table-layout:fixed;"><tbody>');
|
||||
$iCellIdx = 0;
|
||||
$fColSize = 100 / $this->iNbCols;
|
||||
$sStyle = $bEditMode ? 'border: 1px #ccc dashed; width:'.$fColSize.'%;' : 'width: '.$fColSize.'%;';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2012-2013 Combodo SARL
|
||||
// Copyright (C) 2012-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@ require_once(APPROOT.'application/forms.class.inc.php');
|
||||
/**
|
||||
* Base class for all 'dashlets' (i.e. widgets to be inserted into a dashboard)
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
abstract class Dashlet
|
||||
@@ -530,7 +530,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$this->sGroupByAttCode = $sGroupBy;
|
||||
$this->sFunction = null;
|
||||
}
|
||||
if ($this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode))
|
||||
if (($sClass != '') && $this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode))
|
||||
{
|
||||
$sAttLabel = $this->oModelReflection->GetLabel($sClass, $this->sGroupByAttCode);
|
||||
if (!is_null($this->sFunction))
|
||||
@@ -583,6 +583,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
|
||||
// First perform the query - if the OQL is not ok, it will generate an exception : no need to go further
|
||||
$oFilter = DBObjectSearch::FromOQL($sQuery);
|
||||
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
|
||||
$sClass = $oFilter->GetClass();
|
||||
$sClassAlias = $oFilter->GetClassAlias();
|
||||
@@ -596,7 +597,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
switch($sStyle)
|
||||
{
|
||||
case 'bars':
|
||||
$sType = 'open_flash_chart';
|
||||
$sType = 'chart';
|
||||
$aExtraParams = array(
|
||||
'chart_type' => 'bars',
|
||||
'chart_title' => $sTitle,
|
||||
@@ -607,7 +608,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
break;
|
||||
|
||||
case 'pie':
|
||||
$sType = 'open_flash_chart';
|
||||
$sType = 'chart';
|
||||
$aExtraParams = array(
|
||||
'chart_type' => 'pie',
|
||||
'chart_title' => $sTitle,
|
||||
@@ -685,7 +686,7 @@ abstract class DashletGroupBy extends Dashlet
|
||||
}
|
||||
foreach ($aValues as $sValue)
|
||||
{
|
||||
$aDisplayValues[] = array('label' => $sValue, 'count' => (int)rand(1, 15));
|
||||
$aDisplayValues[] = array('label' => $sValue, 'value' => (int)rand(1, 15));
|
||||
}
|
||||
}
|
||||
elseif (is_subclass_of($sAttributeType, 'AttributeEnum') || $sAttributeType == 'AttributeEnum')
|
||||
@@ -698,16 +699,16 @@ abstract class DashletGroupBy extends Dashlet
|
||||
$iCount = (int) rand(2, 100);
|
||||
$aDisplayValues[] = array(
|
||||
'label' => $sValueLabel,
|
||||
'count' => $iCount
|
||||
'value' => $iCount
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aDisplayValues[] = array('label' => 'a', 'count' => 123);
|
||||
$aDisplayValues[] = array('label' => 'b', 'count' => 321);
|
||||
$aDisplayValues[] = array('label' => 'c', 'count' => 456);
|
||||
$aDisplayValues[] = array('label' => 'a', 'value' => 123);
|
||||
$aDisplayValues[] = array('label' => 'b', 'value' => 321);
|
||||
$aDisplayValues[] = array('label' => 'c', 'value' => 456);
|
||||
}
|
||||
}
|
||||
return $aDisplayValues;
|
||||
@@ -733,7 +734,8 @@ abstract class DashletGroupBy extends Dashlet
|
||||
if (is_subclass_of($sAttType, 'AttributeFriendlyName')) continue;
|
||||
if ($sAttType == 'AttributeExternalField') continue;
|
||||
if (is_subclass_of($sAttType, 'AttributeExternalField')) continue;
|
||||
|
||||
if ($sAttType == 'AttributeOneWayPassword') continue;
|
||||
|
||||
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
|
||||
$aGroupBy[$sAttCode] = $sLabel;
|
||||
|
||||
@@ -904,82 +906,42 @@ class DashletGroupByPie extends DashletGroupBy
|
||||
{
|
||||
$sTitle = $this->aProperties['title'];
|
||||
|
||||
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
|
||||
|
||||
$HTMLsTitle = ($sTitle != '') ? '<h1 style="text-align:center">'.htmlentities($sTitle, ENT_QUOTES, 'UTF-8').'</h1>' : '';
|
||||
$oPage->add("<div style=\"background-color:#fff;padding:0.25em;\">$HTMLsTitle<div id=\"$sBlockId\" style=\"background-color:#fff;\"></div></div>");
|
||||
|
||||
$aDisplayValues = $this->MakeSimulatedData();
|
||||
|
||||
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
|
||||
$oChart = new open_flash_chart();
|
||||
|
||||
$aGroupBy = array();
|
||||
$aLabels = array();
|
||||
foreach($aDisplayValues as $iRow => $aDisplayData)
|
||||
$aColumns = array();
|
||||
$aNames = array();
|
||||
foreach($aDisplayValues as $idx => $aValue)
|
||||
{
|
||||
$aLabels[$iRow] = $aDisplayData['label'];
|
||||
$aGroupBy[$iRow] = (int) $aDisplayData['count'];
|
||||
$aColumns[] = array('series_'.$idx, (int)$aValue['value']);
|
||||
$aNames['series_'.$idx] = $aValue['label'];
|
||||
}
|
||||
|
||||
$oChartElement = new pie();
|
||||
$oChartElement->set_start_angle( 35 );
|
||||
$oChartElement->set_animate( true );
|
||||
$oChartElement->set_tooltip( '#label# - #val# (#percent#)' );
|
||||
$oChartElement->set_colours( array('#FF8A00', '#909980', '#2C2B33', '#CCC08D', '#596664') );
|
||||
|
||||
$aData = array();
|
||||
foreach($aGroupBy as $iRow => $iCount)
|
||||
{
|
||||
$sFlashLabel = html_entity_decode($aLabels[$iRow], ENT_QUOTES, 'UTF-8');
|
||||
$PieValue = new pie_value($iCount, $sFlashLabel);
|
||||
$aData[] = $PieValue;
|
||||
}
|
||||
|
||||
$oChartElement->set_values($aData);
|
||||
$oChart->x_axis = null;
|
||||
|
||||
if (!empty($sTitle))
|
||||
{
|
||||
// The title has been given in an url, and urlencoded...
|
||||
// and urlencode transforms utf-8 into something similar to ISO-8859-1
|
||||
// Example: é (C3A9 becomes %E9)
|
||||
// As a consequence, json_encode (called within open-flash-chart.php)
|
||||
// was returning 'null' and the graph was not displayed at all
|
||||
// To make sure that the graph is displayed AND to get a correct title
|
||||
// (at least for european characters) let's transform back into utf-8 !
|
||||
$sTitle = iconv("ISO-8859-1", "UTF-8//IGNORE", $sTitle);
|
||||
|
||||
// If the title is a dictionnary entry, fetch it
|
||||
$sTitle = $this->oModelReflection->DictString($sTitle);
|
||||
|
||||
$oTitle = new title($sTitle);
|
||||
$oChart->set_title($oTitle);
|
||||
$oTitle->set_style("{font-size: 16px; font-family: Tahoma; font-weight: bold; text-align: center;}");
|
||||
}
|
||||
$oChart->set_bg_colour('#FFFFFF');
|
||||
$oChart->add_element($oChartElement);
|
||||
|
||||
$sData = $oChart->toPrettyString();
|
||||
$sData = json_encode($sData);
|
||||
$oPage->add_script(
|
||||
<<< EOF
|
||||
function ofc_get_data_dashlet_{$this->sId}()
|
||||
{
|
||||
return $sData;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
$oPage->add("<div id=\"dashlet_chart_{$this->sId}\">If the chart does not display, <a href=\"http://get.adobe.com/flash/\" target=\"_blank\">install Flash</a></div>\n");
|
||||
$oPage->add('</div>');
|
||||
|
||||
// $oPage->add_script("function ofc_resize(left, width, top, height) { /* do nothing special */ }");
|
||||
$sJSColumns = json_encode($aColumns);
|
||||
$sJSNames = json_encode($aNames);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
swfobject.embedSWF( "../images/open-flash-chart.swf",
|
||||
"dashlet_chart_{$this->sId}",
|
||||
"100%", "300","9.0.0",
|
||||
"expressInstall.swf",
|
||||
{"get-data":"ofc_get_data_dashlet_{$this->sId}", "id":"dashlet_chart_{$this->sId}"},
|
||||
{'wmode': 'transparent'}
|
||||
);
|
||||
window.setTimeout(function() {
|
||||
var chart = c3.generate({
|
||||
bindto: '#{$sBlockId}',
|
||||
data: {
|
||||
columns: $sJSColumns,
|
||||
type: 'pie',
|
||||
names: $sJSNames,
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
},
|
||||
tooltip: {
|
||||
format: {
|
||||
value: function (value, ratio, id) { return value; }
|
||||
}
|
||||
}
|
||||
});}, 100);
|
||||
EOF
|
||||
);
|
||||
}
|
||||
@@ -1007,106 +969,69 @@ class DashletGroupByBars extends DashletGroupBy
|
||||
{
|
||||
$sTitle = $this->aProperties['title'];
|
||||
|
||||
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
|
||||
|
||||
$HTMLsTitle = ($sTitle != '') ? '<h1 style="text-align:center">'.htmlentities($sTitle, ENT_QUOTES, 'UTF-8').'</h1>' : '';
|
||||
$oPage->add("<div style=\"background-color:#fff;padding:0.25em;\">$HTMLsTitle<div id=\"$sBlockId\" style=\"background-color:#fff;\"></div></div>");
|
||||
|
||||
$aDisplayValues = $this->MakeSimulatedData();
|
||||
|
||||
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
|
||||
$oChart = new open_flash_chart();
|
||||
|
||||
$aGroupBy = array();
|
||||
$aLabels = array();
|
||||
foreach($aDisplayValues as $iRow => $aDisplayData)
|
||||
$aNames = array();
|
||||
foreach($aDisplayValues as $idx => $aValue)
|
||||
{
|
||||
$aLabels[$iRow] = $aDisplayData['label'];
|
||||
$aGroupBy[$iRow] = (int) $aDisplayData['count'];
|
||||
$aNames[$idx] = $aValue['label'];
|
||||
}
|
||||
|
||||
$oChartElement = new bar_glass();
|
||||
|
||||
$aData = array();
|
||||
$aChartLabels = array();
|
||||
$maxValue = 0;
|
||||
foreach($aGroupBy as $iRow => $iCount)
|
||||
{
|
||||
$oBarValue = new bar_value($iCount);
|
||||
$aData[] = $oBarValue;
|
||||
if ($iCount > $maxValue) $maxValue = $iCount;
|
||||
$aChartLabels[] = html_entity_decode($aLabels[$iRow], ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
$oYAxis = new y_axis();
|
||||
$aMagicValues = array(1,2,5,10);
|
||||
$iMultiplier = 1;
|
||||
$index = 0;
|
||||
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
|
||||
while($maxValue > $iTop)
|
||||
{
|
||||
$index++;
|
||||
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
|
||||
if (($index % count($aMagicValues)) == 0)
|
||||
{
|
||||
$iMultiplier = $iMultiplier * 10;
|
||||
}
|
||||
}
|
||||
//echo "oYAxis->set_range(0, $iTop, $iMultiplier);\n";
|
||||
$oYAxis->set_range(0, $iTop, $iMultiplier);
|
||||
$oChart->set_y_axis( $oYAxis );
|
||||
|
||||
$oChartElement->set_values( $aData );
|
||||
$oXAxis = new x_axis();
|
||||
$oXLabels = new x_axis_labels();
|
||||
// set them vertical
|
||||
$oXLabels->set_vertical();
|
||||
// set the label text
|
||||
$oXLabels->set_labels($aChartLabels);
|
||||
// Add the X Axis Labels to the X Axis
|
||||
$oXAxis->set_labels( $oXLabels );
|
||||
$oChart->set_x_axis( $oXAxis );
|
||||
|
||||
if (!empty($sTitle))
|
||||
{
|
||||
// The title has been given in an url, and urlencoded...
|
||||
// and urlencode transforms utf-8 into something similar to ISO-8859-1
|
||||
// Example: é (C3A9 becomes %E9)
|
||||
// As a consequence, json_encode (called within open-flash-chart.php)
|
||||
// was returning 'null' and the graph was not displayed at all
|
||||
// To make sure that the graph is displayed AND to get a correct title
|
||||
// (at least for european characters) let's transform back into utf-8 !
|
||||
$sTitle = iconv("ISO-8859-1", "UTF-8//IGNORE", $sTitle);
|
||||
$sJSNames = json_encode($aNames);
|
||||
|
||||
// If the title is a dictionnary entry, fetch it
|
||||
$sTitle = $this->oModelReflection->DictString($sTitle);
|
||||
|
||||
$oTitle = new title($sTitle);
|
||||
$oChart->set_title($oTitle);
|
||||
$oTitle->set_style("{font-size: 16px; font-family: Tahoma; font-weight: bold; text-align: center;}");
|
||||
}
|
||||
$oChart->set_bg_colour('#FFFFFF');
|
||||
$oChart->add_element($oChartElement);
|
||||
|
||||
$sData = $oChart->toPrettyString();
|
||||
$sData = json_encode($sData);
|
||||
$oPage->add_script(
|
||||
<<< EOF
|
||||
function ofc_get_data_dashlet_{$this->sId}()
|
||||
{
|
||||
return $sData;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
$oPage->add("<div id=\"dashlet_chart_{$this->sId}\">If the chart does not display, <a href=\"http://get.adobe.com/flash/\" target=\"_blank\">install Flash</a></div>\n");
|
||||
$oPage->add('</div>');
|
||||
|
||||
// $oPage->add_script("function ofc_resize(left, width, top, height) { /* do nothing special */ }");
|
||||
$sJson = json_encode($aDisplayValues);
|
||||
$sJSCount = json_encode(Dict::S('UI:GroupBy:Count'));
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
swfobject.embedSWF( "../images/open-flash-chart.swf",
|
||||
"dashlet_chart_{$this->sId}",
|
||||
"100%", "300","9.0.0",
|
||||
"expressInstall.swf",
|
||||
{"get-data":"ofc_get_data_dashlet_{$this->sId}", "id":"dashlet_chart_{$this->sId}"},
|
||||
{'wmode': 'transparent'}
|
||||
);
|
||||
window.setTimeout(function() {
|
||||
var chart = c3.generate({
|
||||
bindto: '#{$sBlockId}',
|
||||
data: {
|
||||
json: $sJson,
|
||||
keys: {
|
||||
x: 'label',
|
||||
value: ["value"]
|
||||
},
|
||||
selection: {
|
||||
enabled: true
|
||||
},
|
||||
type: 'bar'
|
||||
},
|
||||
axis: {
|
||||
x: {
|
||||
tick: {
|
||||
culling: {max: 25}, // Maximum 24 labels on x axis (2 years).
|
||||
centered: true,
|
||||
rotate: 90,
|
||||
multiline: false
|
||||
},
|
||||
type: 'category' // this needed to load string x value
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
y: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
grouped: false,
|
||||
format: {
|
||||
title: function() { return '' },
|
||||
name: function (name, ratio, id, index) {
|
||||
var aNames = $sJSNames;
|
||||
return aNames[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
EOF
|
||||
);
|
||||
}
|
||||
@@ -1137,7 +1062,7 @@ class DashletGroupByTable extends DashletGroupBy
|
||||
$iTotal = 0;
|
||||
foreach($aDisplayValues as $iRow => $aDisplayData)
|
||||
{
|
||||
$iTotal += $aDisplayData['count'];
|
||||
$iTotal += $aDisplayData['value'];
|
||||
}
|
||||
|
||||
$oPage->add('<div class="dashlet-content">');
|
||||
@@ -1158,7 +1083,7 @@ class DashletGroupByTable extends DashletGroupBy
|
||||
{
|
||||
$oPage->add('<tr class="even">');
|
||||
$oPage->add('<td class=""><span title="Active">'.$aDisplayData['label'].'</span></td>');
|
||||
$oPage->add('<td class=""><a>'.$aDisplayData['count'].'</a></td>');
|
||||
$oPage->add('<td class=""><a>'.$aDisplayData['value'].'</a></td>');
|
||||
$oPage->add('</tr>');
|
||||
}
|
||||
$oPage->add('</tbody>');
|
||||
@@ -1570,7 +1495,7 @@ class DashletBadge extends Dashlet
|
||||
$oPage->add('<p>');
|
||||
$oPage->add(' <a>'.Dict::Format('UI:ClickToCreateNew', $sClassLabel).'</a>');
|
||||
$oPage->add(' <br/>');
|
||||
$oPage->add(' <a>Search for Server objects</a>');
|
||||
$oPage->add(' <a>'.Dict::Format('UI:SearchFor_Class', $sClassLabel).'</a>');
|
||||
$oPage->add('</p>');
|
||||
$oPage->add('</div>');
|
||||
|
||||
|
||||
22
application/datamodel.application.xml
Normal file
22
application/datamodel.application.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design>
|
||||
<portals>
|
||||
<portal id="legacy_portal" _delta="define">
|
||||
<url>portal/index.php</url>
|
||||
<rank>1.0</rank>
|
||||
<handler/>
|
||||
<allow>
|
||||
</allow>
|
||||
<deny/>
|
||||
</portal>
|
||||
<portal id="backoffice" _delta="define">
|
||||
<url>pages/UI.php</url>
|
||||
<rank>2.0</rank>
|
||||
<handler/>
|
||||
<allow/>
|
||||
<deny>
|
||||
<profile id="Portal user"/>
|
||||
</deny>
|
||||
</portal>
|
||||
</portals>
|
||||
</itop_design>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -18,7 +18,7 @@
|
||||
/**
|
||||
* Data Table to display a set of objects in a tabular manner in HTML
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,8 @@ class DataTable
|
||||
protected $iNbObjects; // Total number of objects inthe set
|
||||
protected $bUseCustomSettings; // Whether or not the current display uses custom settings
|
||||
protected $oDefaultSettings; // the default settings for displaying such a list
|
||||
|
||||
protected $bShowObsoleteData;
|
||||
|
||||
/**
|
||||
* @param $iListId mixed Unique ID for this div/table in the page
|
||||
* @param $oSet DBObjectSet The set of data to display
|
||||
@@ -47,6 +48,7 @@ class DataTable
|
||||
$this->iNbObjects = $oSet->Count();
|
||||
$this->bUseCustomSettings = false;
|
||||
$this->oDefaultSettings = null;
|
||||
$this->bShowObsoleteData = $oSet->GetShowObsoleteData();
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage, DataTableSettings $oSettings, $bActionsMenu, $sSelectMode, $bViewLink, $aExtraParams)
|
||||
@@ -145,7 +147,9 @@ class DataTable
|
||||
$sHtml .= "<tr><td class=\"datacontents\">$sDataTable</td></tr>";
|
||||
$sHtml .= "</table>\n";
|
||||
$oPage->add_at_the_end($sConfigDlg);
|
||||
|
||||
|
||||
$aExtraParams['show_obsolete_data'] = $this->bShowObsoleteData;
|
||||
|
||||
$aOptions = array(
|
||||
'sPersistentId' => '',
|
||||
'sFilter' => $this->oSet->GetFilter()->serialize(),
|
||||
@@ -290,17 +294,24 @@ EOF;
|
||||
|
||||
protected function GetToolkitMenu(WebPage $oPage, $aExtraParams)
|
||||
{
|
||||
$sMenuTitle = Dict::S('UI:ConfigureThisList');
|
||||
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><img src="../images/toolkit_menu.png"><ul>';
|
||||
|
||||
$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
|
||||
$aActions = array(
|
||||
$oMenuItem1->GetUID() => $oMenuItem1->GetMenuItem(),
|
||||
);
|
||||
$this->oSet->Rewind();
|
||||
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $this->oSet, $aActions, $this->sTableId, $this->iListId);
|
||||
$this->oSet->Rewind();
|
||||
$sHtml .= $oPage->RenderPopupMenuItems($aActions);
|
||||
if (!$oPage->IsPrintableVersion())
|
||||
{
|
||||
$sMenuTitle = Dict::S('UI:ConfigureThisList');
|
||||
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><img src="../images/toolkit_menu.png?itopversion='.ITOP_VERSION.'"><ul>';
|
||||
|
||||
$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
|
||||
$aActions = array(
|
||||
$oMenuItem1->GetUID() => $oMenuItem1->GetMenuItem(),
|
||||
);
|
||||
$this->oSet->Rewind();
|
||||
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $this->oSet, $aActions, $this->sTableId, $this->iListId);
|
||||
$this->oSet->Rewind();
|
||||
$sHtml .= $oPage->RenderPopupMenuItems($aActions);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHtml = '';
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -479,6 +490,7 @@ EOF;
|
||||
{
|
||||
$aExtraParams['query_params'][$sName] = $sValue;
|
||||
}
|
||||
$aExtraParams['show_obsolete_data'] = $this->bShowObsoleteData;
|
||||
|
||||
$sHtml .= "<tr><td>";
|
||||
$sHtml .= $oPage->GetTable($aAttribs, $aValues);
|
||||
@@ -606,6 +618,34 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified version of the data table with less "decoration" (and no paging)
|
||||
* which is optimized for printing
|
||||
*/
|
||||
class PrintableDataTable extends DataTable
|
||||
{
|
||||
public function GetAsHTML(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex, $aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams)
|
||||
{
|
||||
return $this->GetHTMLTable($oPage, $aColumns, $sSelectMode, -1, $bViewLink, $aExtraParams);
|
||||
}
|
||||
|
||||
public function GetHTMLTable(WebPage $oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
|
||||
{
|
||||
$iNbPages = ($iPageSize < 1) ? 1 : ceil($this->iNbObjects / $iPageSize);
|
||||
if ($iPageSize < 1)
|
||||
{
|
||||
$iPageSize = -1; // convention: no pagination
|
||||
}
|
||||
$aAttribs = $this->GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink);
|
||||
|
||||
$aValues = $this->GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
|
||||
|
||||
$sHtml = $oPage->GetTable($aAttribs, $aValues);
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
}
|
||||
|
||||
class DataTableSettings implements Serializable
|
||||
{
|
||||
public $aClassAliases;
|
||||
@@ -713,6 +753,12 @@ class DataTableSettings implements Serializable
|
||||
{
|
||||
$sSort = $aSortOrder['friendlyname'] ? 'asc' : 'desc';
|
||||
}
|
||||
$sNormalizedFName = MetaModel::NormalizeFieldSpec($sClass, 'friendlyname');
|
||||
if(array_key_exists($sNormalizedFName, $aSortOrder))
|
||||
{
|
||||
$sSort = $aSortOrder[$sNormalizedFName] ? 'asc' : 'desc';
|
||||
}
|
||||
|
||||
$aColumns[$sAlias]['_key_'] = $oSettings->GetFieldData($sAlias, '_key_', null, true /* bChecked */, $sSort);
|
||||
}
|
||||
foreach($aList as $sAttCode)
|
||||
@@ -887,8 +933,15 @@ class DataTableSettings implements Serializable
|
||||
}
|
||||
else if ($oAttDef->IsExternalField())
|
||||
{
|
||||
$oExtAttDef = $oAttDef->GetExtAttDef();
|
||||
$sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel());
|
||||
if ($oAttDef->IsFriendlyName())
|
||||
{
|
||||
$sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel());
|
||||
}
|
||||
else
|
||||
{
|
||||
$oExtAttDef = $oAttDef->GetExtAttDef();
|
||||
$sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel());
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef instanceof AttributeFriendlyName)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -339,7 +339,7 @@ EOF
|
||||
return '</tr>';
|
||||
}
|
||||
|
||||
public function RenderAsDialog($oPage, $sDialogId, $sDialogTitle, $iDialogWidth, $sOkButtonLabel, $sIntroduction = null)
|
||||
public function RenderAsDialog($oPage, $sDialogId, $sDialogTitle, $iDialogWidth, $sOkButtonLabel, $sIntroduction = null, $bAutoOpen = true)
|
||||
{
|
||||
$this->SetPrefix('dlg_'); // To make sure that the controls have different IDs that the property sheet which may be displayed at the same time
|
||||
|
||||
@@ -355,12 +355,15 @@ EOF
|
||||
$this->Render($oPage);
|
||||
$oPage->add('</div>');
|
||||
|
||||
$sAutoOpen = $bAutoOpen ? 'true' : 'false';
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$sDialogId').dialog({
|
||||
height: 'auto',
|
||||
maxHeight: $(window).height() - 8,
|
||||
width: $iDialogWidth,
|
||||
modal: true,
|
||||
autoOpen: $sAutoOpen,
|
||||
title: '$sDialogTitle',
|
||||
buttons: [
|
||||
{ text: "$sOkButtonLabel", click: function() {
|
||||
@@ -529,7 +532,7 @@ EOF
|
||||
|
||||
public function GetFieldId($sCode)
|
||||
{
|
||||
return $this->GetPrefix().'attr_'.$sCode;
|
||||
return $this->GetPrefix().'attr_'.utils::GetSafeId($sCode.$this->GetSuffix());
|
||||
}
|
||||
|
||||
public function GetFieldName($sCode)
|
||||
@@ -867,13 +870,11 @@ class DesignerTextField extends DesignerFormField
|
||||
{
|
||||
protected $sValidationPattern;
|
||||
protected $aForbiddenValues;
|
||||
protected $sExplainForbiddenValues;
|
||||
public function __construct($sCode, $sLabel = '', $defaultValue = '')
|
||||
{
|
||||
parent::__construct($sCode, $sLabel, $defaultValue);
|
||||
$this->sValidationPattern = '';
|
||||
$this->aForbiddenValues = null;
|
||||
$this->sExplainForbiddenValues = null;
|
||||
$this->aForbiddenValues = array();
|
||||
}
|
||||
|
||||
public function SetValidationPattern($sValidationPattern)
|
||||
@@ -881,19 +882,19 @@ class DesignerTextField extends DesignerFormField
|
||||
$this->sValidationPattern = $sValidationPattern;
|
||||
}
|
||||
|
||||
public function SetForbiddenValues($aValues, $sExplain)
|
||||
public function SetForbiddenValues($aValues, $sExplain, $bCaseSensitive = true)
|
||||
{
|
||||
$this->aForbiddenValues = $aValues;
|
||||
$aForbiddenValues = $aValues;
|
||||
|
||||
$iDefaultKey = array_search($this->defaultValue, $this->aForbiddenValues);
|
||||
$iDefaultKey = array_search($this->defaultValue, $aForbiddenValues);
|
||||
if ($iDefaultKey !== false)
|
||||
{
|
||||
// The default (current) value is always allowed...
|
||||
unset($this->aForbiddenValues[$iDefaultKey]);
|
||||
unset($aForbiddenValues[$iDefaultKey]);
|
||||
|
||||
}
|
||||
|
||||
$this->sExplainForbiddenValues = $sExplain;
|
||||
$this->aForbiddenValues[] = array('values' => $aForbiddenValues, 'message' => $sExplain, 'case_sensitive' => $bCaseSensitive);
|
||||
}
|
||||
|
||||
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
|
||||
@@ -911,17 +912,15 @@ class DesignerTextField extends DesignerFormField
|
||||
if (is_array($this->aForbiddenValues))
|
||||
{
|
||||
$sForbiddenValues = json_encode($this->aForbiddenValues);
|
||||
$sExplainForbiddenValues = addslashes($this->sExplainForbiddenValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sForbiddenValues = 'null';
|
||||
$sExplainForbiddenValues = 'null';
|
||||
$sForbiddenValues = '[]'; //Empty JS array
|
||||
}
|
||||
$sMandatory = $this->bMandatory ? 'true' : 'false';
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues, '$sExplainForbiddenValues'); } );
|
||||
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues); } );
|
||||
{
|
||||
var myTimer = null;
|
||||
$('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
|
||||
@@ -941,8 +940,8 @@ EOF
|
||||
public function ReadParam(&$aValues)
|
||||
{
|
||||
parent::ReadParam($aValues);
|
||||
|
||||
if (($this->sValidationPattern != '') &&(!preg_match('/'.$this->sValidationPattern.'/', $aValues[$this->sCode])) )
|
||||
$sPattern = '/'.str_replace('/', '\/', $this->sValidationPattern).'/'; // Escape the forward slashes since they are used as delimiters for preg_match
|
||||
if (($this->sValidationPattern != '') && (!preg_match($sPattern, $aValues[$this->sCode])) )
|
||||
{
|
||||
$aValues[$this->sCode] = $this->defaultValue;
|
||||
}
|
||||
@@ -964,30 +963,35 @@ class DesignerLongTextField extends DesignerTextField
|
||||
if (is_array($this->aForbiddenValues))
|
||||
{
|
||||
$sForbiddenValues = json_encode($this->aForbiddenValues);
|
||||
$sExplainForbiddenValues = addslashes($this->sExplainForbiddenValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sForbiddenValues = 'null';
|
||||
$sExplainForbiddenValues = 'null';
|
||||
$sForbiddenValues = '[]'; //Empty JS array
|
||||
}
|
||||
$sMandatory = $this->bMandatory ? 'true' : 'false';
|
||||
$sReadOnly = $this->IsReadOnly() ? 'readonly' : '';
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues, '$sExplainForbiddenValues'); } );
|
||||
{
|
||||
var myTimer = null;
|
||||
$('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$sCSSClasses = '';
|
||||
if (count($this->aCSSClasses) > 0)
|
||||
{
|
||||
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
|
||||
}
|
||||
return array('label' => $this->sLabel, 'value' => "<textarea $sCSSClasses id=\"$sId\" $sReadOnly name=\"$sName\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</textarea>");
|
||||
if (!$this->IsReadOnly())
|
||||
{
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues); } );
|
||||
{
|
||||
var myTimer = null;
|
||||
$('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$sValue = "<textarea $sCSSClasses id=\"$sId\" name=\"$sName\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</textarea>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = "<div $sCSSClasses id=\"$sId\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</div>";
|
||||
}
|
||||
return array('label' => $this->sLabel, 'value' => $sValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1065,6 +1069,7 @@ class DesignerComboField extends DesignerFormField
|
||||
protected $bMultipleSelection;
|
||||
protected $bOtherChoices;
|
||||
protected $sNullLabel;
|
||||
protected $bSorted;
|
||||
|
||||
public function __construct($sCode, $sLabel = '', $defaultValue = '')
|
||||
{
|
||||
@@ -1075,6 +1080,7 @@ class DesignerComboField extends DesignerFormField
|
||||
$this->sNullLabel = Dict::S('UI:SelectOne');
|
||||
|
||||
$this->bAutoApply = true;
|
||||
$this->bSorted = true; // Sorted by default
|
||||
}
|
||||
|
||||
public function SetAllowedValues($aAllowedValues)
|
||||
@@ -1100,6 +1106,16 @@ class DesignerComboField extends DesignerFormField
|
||||
$this->sNullLabel = $sLabel;
|
||||
}
|
||||
|
||||
public function IsSorted()
|
||||
{
|
||||
return $this->bSorted;
|
||||
}
|
||||
|
||||
public function SetSorted($bSorted)
|
||||
{
|
||||
$this->bSorted = $bSorted;
|
||||
}
|
||||
|
||||
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
|
||||
{
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
@@ -1107,6 +1123,10 @@ class DesignerComboField extends DesignerFormField
|
||||
$sChecked = $this->defaultValue ? 'checked' : '';
|
||||
$sMandatory = $this->bMandatory ? 'true' : 'false';
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
|
||||
if ($this->IsSorted())
|
||||
{
|
||||
asort($this->aAllowedValues);
|
||||
}
|
||||
$sCSSClasses = '';
|
||||
if (count($this->aCSSClasses) > 0)
|
||||
{
|
||||
@@ -1319,7 +1339,7 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = '<img src="'.$this->MakeFileUrl($this->defaultValue).'" />';
|
||||
$sValue = '<span style="display:inline-block;line-height:48px;height:48px;"><span><img style="vertical-align:middle" src="'.$this->aAllowedValues[$idx]['icon'].'" /> '.htmlentities($this->aAllowedValues[$idx]['label'], ENT_QUOTES, 'UTF-8').'</span></span>';
|
||||
}
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled' : '';
|
||||
return array('label' => $this->sLabel, 'value' => $sValue);
|
||||
@@ -1328,14 +1348,19 @@ EOF
|
||||
|
||||
class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
{
|
||||
static $aAllIcons = array();
|
||||
|
||||
public function __construct($sCode, $sLabel = '', $defaultValue = '')
|
||||
{
|
||||
parent::__construct($sCode, $sLabel, $defaultValue);
|
||||
|
||||
$aAllIcons = self::FindIconsOnDisk(APPROOT.'env-'.utils::GetCurrentEnvironment());
|
||||
ksort($aAllIcons);
|
||||
if (count(self::$aAllIcons) == 0)
|
||||
{
|
||||
self::$aAllIcons = self::FindIconsOnDisk(APPROOT.'env-'.utils::GetCurrentEnvironment());
|
||||
ksort(self::$aAllIcons);
|
||||
}
|
||||
$aValues = array();
|
||||
foreach($aAllIcons as $sFilePath)
|
||||
foreach(self::$aAllIcons as $sFilePath)
|
||||
{
|
||||
$aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
|
||||
}
|
||||
@@ -1343,6 +1368,36 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
}
|
||||
|
||||
static protected function FindIconsOnDisk($sBaseDir, $sDir = '')
|
||||
{
|
||||
$aFiles = null;
|
||||
$sKey = $sBaseDir.'/'.$sDir;
|
||||
$sShortKey = abs(crc32($sKey));
|
||||
$sCacheFile = utils::GetCachePath().'available-icons-'.$sShortKey.'.php';
|
||||
$sCacheClass = 'AvailableIcons_'.$sShortKey;
|
||||
if (file_exists($sCacheFile))
|
||||
{
|
||||
require_once($sCacheFile);
|
||||
if ($sCacheClass::$sKey === $sKey) // crc32 collision detection
|
||||
{
|
||||
$aFiles = $sCacheClass::$aIconFiles;
|
||||
}
|
||||
}
|
||||
if ($aFiles === null)
|
||||
{
|
||||
$aFiles = self::_FindIconsOnDisk($sBaseDir, $sDir);
|
||||
$sAvailableIcons = '<?php'.PHP_EOL;
|
||||
$sAvailableIcons .= '// Generated and used by '.__METHOD__.PHP_EOL;
|
||||
$sAvailableIcons .= 'class '.$sCacheClass.PHP_EOL;
|
||||
$sAvailableIcons .= '{'.PHP_EOL;
|
||||
$sAvailableIcons .= ' static $sKey = '.var_export($sKey, true).';'.PHP_EOL;
|
||||
$sAvailableIcons .= ' static $aIconFiles = '.var_export($aFiles, true).';'.PHP_EOL;
|
||||
$sAvailableIcons .= '}'.PHP_EOL;
|
||||
file_put_contents($sCacheFile, $sAvailableIcons, LOCK_EX);
|
||||
}
|
||||
return $aFiles;
|
||||
}
|
||||
|
||||
static protected function _FindIconsOnDisk($sBaseDir, $sDir = '')
|
||||
{
|
||||
$aResult = array();
|
||||
// Populate automatically the list of icon files
|
||||
@@ -1354,7 +1409,7 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
|
||||
{
|
||||
$sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
|
||||
$aResult = array_merge($aResult, self::FindIconsOnDisk($sBaseDir, $sDirSubPath));
|
||||
$aResult = array_merge($aResult, self::_FindIconsOnDisk($sBaseDir, $sDirSubPath));
|
||||
}
|
||||
if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
|
||||
{
|
||||
@@ -1384,8 +1439,12 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
|
||||
|
||||
public function GetDefaultValue($sClass = 'Contact')
|
||||
{
|
||||
$sIconPath = MetaModel::GetClassIcon($sClass, false);
|
||||
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIconPath);
|
||||
$sIcon = '';
|
||||
if (MetaModel::IsValidClass($sClass))
|
||||
{
|
||||
$sIconPath = MetaModel::GetClassIcon($sClass, false);
|
||||
$sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIconPath);
|
||||
}
|
||||
return $sIcon;
|
||||
}
|
||||
}
|
||||
@@ -1428,13 +1487,37 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
{
|
||||
protected $aSubForms;
|
||||
protected $defaultRealValue; // What's stored as default value is actually the index
|
||||
protected $bSorted;
|
||||
|
||||
public function __construct($sCode, $sLabel = '', $defaultValue = '')
|
||||
{
|
||||
parent::__construct($sCode, $sLabel, 0);
|
||||
$this->defaultRealValue = $defaultValue;
|
||||
$this->aSubForms = array();
|
||||
$this->bSorted = true;
|
||||
}
|
||||
|
||||
public function IsSorted()
|
||||
{
|
||||
return $this->bSorted;
|
||||
}
|
||||
|
||||
public function SetSorted($bSorted)
|
||||
{
|
||||
$this->bSorted = $bSorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for sorting an array of $aFormData based ont he labels of the subforms
|
||||
* @param unknown $aItem1
|
||||
* @param unknown $aItem2
|
||||
* @return number
|
||||
*/
|
||||
static function SortOnFormLabel($aItem1, $aItem2)
|
||||
{
|
||||
return strcasecmp($aItem1['label'], $aItem2['label']);
|
||||
}
|
||||
|
||||
public function GetWidgetClass()
|
||||
{
|
||||
return 'selector_property_field';
|
||||
@@ -1465,6 +1548,10 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
|
||||
}
|
||||
|
||||
if ($this->IsSorted())
|
||||
{
|
||||
uasort($this->aSubForms, array(get_class($this), 'SortOnFormLabel'));
|
||||
}
|
||||
|
||||
if ($this->IsReadOnly())
|
||||
{
|
||||
@@ -1490,9 +1577,10 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
|
||||
foreach($this->aSubForms as $iKey => $aFormData)
|
||||
{
|
||||
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');;
|
||||
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
|
||||
$sValue = htmlentities($aFormData['value'], ENT_QUOTES, 'UTF-8');
|
||||
$sSelected = ($iKey == $this->defaultValue) ? 'selected' : '';
|
||||
$sHtml .= "<option value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
|
||||
$sHtml .= "<option data-value=\"$sValue\" value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
|
||||
}
|
||||
$sHtml .= "</select>";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2014 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class iTopWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2013 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -34,31 +34,58 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
{
|
||||
private $m_sMenu;
|
||||
// private $m_currentOrganization;
|
||||
private $m_sMessage;
|
||||
private $m_aMessages;
|
||||
private $m_sInitScript;
|
||||
protected $m_oTabs;
|
||||
protected $bBreadCrumbEnabled;
|
||||
protected $sBreadCrumbEntryId;
|
||||
protected $sBreadCrumbEntryLabel;
|
||||
protected $sBreadCrumbEntryDescription;
|
||||
protected $sBreadCrumbEntryUrl;
|
||||
protected $sBreadCrumbEntryIcon;
|
||||
protected $oCtx;
|
||||
|
||||
public function __construct($sTitle)
|
||||
public function __construct($sTitle, $bPrintable = false)
|
||||
{
|
||||
parent::__construct($sTitle);
|
||||
parent::__construct($sTitle, $bPrintable);
|
||||
$this->m_oTabs = new TabManager();
|
||||
$this->oCtx = new ContextTag('GUI:Console');
|
||||
|
||||
ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker');
|
||||
|
||||
if ((count($_POST) == 0) || (array_key_exists('loginop', $_POST)))
|
||||
{
|
||||
// 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
|
||||
{
|
||||
$this->bBreadCrumbEnabled = false;
|
||||
}
|
||||
|
||||
utils::InitArchiveMode();
|
||||
|
||||
$this->m_sMenu = "";
|
||||
$this->m_sMessage = '';
|
||||
$this->m_aMessages = array();
|
||||
$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
|
||||
$this->add_header("Content-type: text/html; charset=utf-8");
|
||||
$this->add_header("Cache-control: no-cache");
|
||||
$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");
|
||||
$this->add_linked_stylesheet("../css/fg.menu.css");
|
||||
$this->add_linked_stylesheet("../css/jquery.multiselect.css");
|
||||
$this->add_linked_stylesheet("../css/magnific-popup.css");
|
||||
$this->add_linked_stylesheet("../css/c3.min.css");
|
||||
$this->add_linked_stylesheet("../css/font-awesome/css/font-awesome.min.css");
|
||||
|
||||
$this->add_linked_script('../js/jquery.layout.min.js');
|
||||
$this->add_linked_script('../js/jquery.ba-bbq.min.js');
|
||||
$this->add_linked_script("../js/jquery.treeview.js");
|
||||
$this->add_linked_script("../js/jquery.autocomplete.js");
|
||||
$this->add_linked_script("../js/date.js");
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon.js");
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js");
|
||||
$this->add_linked_script("../js/jquery.blockUI.js");
|
||||
$this->add_linked_script("../js/utils.js");
|
||||
$this->add_linked_script("../js/swfobject.js");
|
||||
@@ -69,35 +96,27 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$this->add_linked_script('../js/fg.menu.js');
|
||||
$this->add_linked_script('../js/icon_select.js');
|
||||
$this->add_linked_script('../js/raphael-min.js');
|
||||
$this->add_linked_script('../js/g.raphael.js');
|
||||
$this->add_linked_script('../js/g.pie.js');
|
||||
$this->add_linked_script('../js/g.dot.js');
|
||||
$this->add_linked_script('../js/charts.js');
|
||||
$this->add_linked_script('../js/jquery.multiselect.min.js');
|
||||
$this->add_linked_script('../js/d3.js');
|
||||
$this->add_linked_script('../js/c3.js');
|
||||
$this->add_linked_script('../js/jquery.multiselect.js');
|
||||
$this->add_linked_script('../js/ajaxfileupload.js');
|
||||
$this->add_linked_script('../js/jquery.mousewheel.js');
|
||||
$this->add_linked_script('../js/jquery.magnific-popup.min.js');
|
||||
$this->add_linked_script('../js/breadcrumb.js');
|
||||
$this->add_linked_script('../js/moment.min.js');
|
||||
|
||||
|
||||
$aMultiselectOptions = array(
|
||||
'header' => true,
|
||||
'checkAllText' => Dict::S('UI:SearchValue:CheckAll'),
|
||||
'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'),
|
||||
'noneSelectedText' => Dict::S('UI:SearchValue:Any'),
|
||||
'selectedText' => Dict::S('UI:SearchValue:NbSelected'),
|
||||
'selectedList' => 1,
|
||||
);
|
||||
$sJSMultiselectOptions = json_encode($aMultiselectOptions);
|
||||
$sSearchAny = addslashes(Dict::S('UI:SearchValue:Any'));
|
||||
$sSearchNbSelected = addslashes(Dict::S('UI:SearchValue:NbSelected'));
|
||||
$this->add_dict_entry('UI:FillAllMandatoryFields');
|
||||
|
||||
$bForceMenuPane = utils::ReadParam('force_menu_pane', null);
|
||||
$sInitClosed = '';
|
||||
if (($bForceMenuPane !== null) && ($bForceMenuPane == 0))
|
||||
{
|
||||
$sInitClosed = 'initClosed: true,';
|
||||
}
|
||||
$this->add_dict_entry('UI:Button:Cancel');
|
||||
$this->add_dict_entry('UI:Button:Done');
|
||||
|
||||
$this->add_script(
|
||||
<<<EOF
|
||||
if (!$this->IsPrintableVersion())
|
||||
{
|
||||
$this->PrepareLayout();
|
||||
$this->add_script(
|
||||
<<<EOF
|
||||
function ShowAboutBox()
|
||||
{
|
||||
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'about_box'}, function(data){
|
||||
@@ -105,26 +124,146 @@ function ShowAboutBox()
|
||||
});
|
||||
return false;
|
||||
}
|
||||
function ArchiveMode(bEnable)
|
||||
{
|
||||
var sPrevUrl = StripArchiveArgument(window.location.search);
|
||||
if (bEnable)
|
||||
{
|
||||
window.location.search = sPrevUrl + '&with-archive=1';
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.search = sPrevUrl + '&with-archive=0';
|
||||
}
|
||||
}
|
||||
function StripArchiveArgument(sUrl)
|
||||
{
|
||||
var res = sUrl.replace(/&with-archive=[01]/g, '');
|
||||
return res;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function IsMenuPaneVisible()
|
||||
{
|
||||
$bLeftPaneOpen = true;
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
// Leave the pane opened
|
||||
}
|
||||
else
|
||||
{
|
||||
if (utils::ReadParam('force_menu_pane', null) === 0)
|
||||
{
|
||||
$bLeftPaneOpen = false;
|
||||
}
|
||||
elseif (appUserPreferences::GetPref('menu_pane', 'open') == 'closed')
|
||||
{
|
||||
$bLeftPaneOpen = false;
|
||||
}
|
||||
}
|
||||
return $bLeftPaneOpen;
|
||||
}
|
||||
|
||||
protected function PrepareLayout()
|
||||
{
|
||||
if (MetaModel::GetConfig()->Get('demo_mode'))
|
||||
{
|
||||
// No pin button
|
||||
$sConfigureWestPane = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sConfigureWestPane =
|
||||
<<<EOF
|
||||
if (GetUserPreference('menu_pane', 'open') == 'closed')
|
||||
{
|
||||
myLayout.close('west');
|
||||
}
|
||||
myLayout.addPinBtn( "#tPinMenu", "west" );
|
||||
EOF;
|
||||
}
|
||||
$sInitClosed = $this->IsMenuPaneVisible() ? '' : 'initClosed: true,';
|
||||
|
||||
$sJSDisconnectedMessage = json_encode(Dict::S('UI:DisconnectedDlgMessage'));
|
||||
$sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle'));
|
||||
$sJSLoginAgain = json_encode(Dict::S('UI:LoginAgain'));
|
||||
$sJSStayOnThePage = json_encode(Dict::S('UI:StayOnThePage'));
|
||||
$aDaysMin = array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
|
||||
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min'));
|
||||
$aMonthsShort = array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
|
||||
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short'));
|
||||
$sTimeFormat = AttributeDateTime::GetFormat()->ToTimeFormat();
|
||||
$oTimeFormat = new DateTimeFormat($sTimeFormat);
|
||||
$sJSLangShort = json_encode(strtolower(substr(Dict::GetUserLanguage(), 0, 2)));
|
||||
|
||||
// Date picker options
|
||||
$aPickerOptions = array(
|
||||
'showOn' => 'button',
|
||||
'buttonImage' => '../images/calendar.png',
|
||||
'buttonImageOnly' => true,
|
||||
'dateFormat' => AttributeDate::GetFormat()->ToDatePicker(),
|
||||
'constrainInput' => false,
|
||||
'changeMonth' => true,
|
||||
'changeYear' => true,
|
||||
'dayNamesMin' => $aDaysMin,
|
||||
'monthNamesShort' => $aMonthsShort,
|
||||
'firstDay' => (int) Dict::S('Calendar-FirstDayOfWeek'),
|
||||
);
|
||||
$sJSDatePickerOptions = json_encode($aPickerOptions);
|
||||
|
||||
// Time picker additional options
|
||||
$aPickerOptions['showOn'] = '';
|
||||
$aPickerOptions['buttonImage'] = null;
|
||||
$aPickerOptions['timeFormat'] = $oTimeFormat->ToDatePicker();
|
||||
$aPickerOptions['controlType'] = 'select';
|
||||
$aPickerOptions['closeText'] = Dict::S('UI:Button:Ok');
|
||||
$sJSDateTimePickerOptions = json_encode($aPickerOptions);
|
||||
if ($sJSLangShort != '"en"')
|
||||
{
|
||||
// More options that cannot be passed via json_encode since they must be evaluated client-side
|
||||
$aMoreJSOptions = ",
|
||||
'timeText': $.timepicker.regional[$sJSLangShort].timeText,
|
||||
'hourText': $.timepicker.regional[$sJSLangShort].hourText,
|
||||
'minuteText': $.timepicker.regional[$sJSLangShort].minuteText,
|
||||
'secondText': $.timepicker.regional[$sJSLangShort].secondText,
|
||||
'currentText': $.timepicker.regional[$sJSLangShort].currentText
|
||||
}";
|
||||
$sJSDateTimePickerOptions = substr($sJSDateTimePickerOptions, 0, -1).$aMoreJSOptions;
|
||||
}
|
||||
$this->add_script(
|
||||
<<< EOF
|
||||
function PrepareWidgets()
|
||||
{
|
||||
// note: each action implemented here must be idempotent,
|
||||
// because this helper function might be called several times on a given page
|
||||
|
||||
// Note: Trigger image is wrapped in a span so we can display it we want
|
||||
$(".date-pick").datepicker($sJSDatePickerOptions)
|
||||
.next("img").wrap("<span>");
|
||||
|
||||
// Hack for the date and time picker addon issue on Chrome (see #1305)
|
||||
// The workaround is to instantiate the widget on demand
|
||||
// It relies on the same markup, thus reverting to the original implementation should be straightforward
|
||||
$(".datetime-pick:not(.is-widget-ready)").each(function(){
|
||||
var oInput = this;
|
||||
$(oInput).addClass('is-widget-ready');
|
||||
$('<span><img class="datetime-pick-button" src="../images/calendar.png"></span>')
|
||||
.insertAfter($(this))
|
||||
.on('click', function(){
|
||||
$(oInput)
|
||||
.datetimepicker($sJSDateTimePickerOptions)
|
||||
.datetimepicker('show')
|
||||
.datetimepicker('option', 'onClose', function(dateText,inst){
|
||||
$(oInput).datetimepicker('destroy');
|
||||
})
|
||||
.on('click keypress', function(){
|
||||
$(oInput).datetimepicker('hide');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->m_sInitScript =
|
||||
<<< EOF
|
||||
try
|
||||
@@ -135,11 +274,12 @@ EOF;
|
||||
paneSize = GetUserPreference('menu_size', 300)
|
||||
myLayout = $('body').layout({
|
||||
west : {
|
||||
$sInitClosed minSize: 200, size: paneSize, spacing_open: 16, spacing_close: 16, slideTrigger_open: "mouseover", hideTogglerOnSlide: true, enableCursorHotkey: false,
|
||||
$sInitClosed minSize: 200, size: paneSize, spacing_open: 16, spacing_close: 16, slideTrigger_open: "click", hideTogglerOnSlide: true, enableCursorHotkey: false,
|
||||
onclose_end: function(name, elt, state, options, layout)
|
||||
{
|
||||
if (state.isSliding == false)
|
||||
{
|
||||
$('.menu-pane-exclusive').show();
|
||||
SetUserPreference('menu_pane', 'closed', true);
|
||||
}
|
||||
},
|
||||
@@ -155,6 +295,7 @@ EOF;
|
||||
{
|
||||
if (state.isSliding == false)
|
||||
{
|
||||
$('.menu-pane-exclusive').hide();
|
||||
SetUserPreference('menu_pane', 'open', true);
|
||||
}
|
||||
}
|
||||
@@ -171,6 +312,7 @@ EOF;
|
||||
var innerWidth = $(this).innerWidth() - 10;
|
||||
$(this).find('.item').width(innerWidth);
|
||||
});
|
||||
$('.panel-resized').trigger('resized');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -181,17 +323,14 @@ EOF;
|
||||
$sConfigureWestPane
|
||||
|
||||
$('#left-pane').layout({ resizable: false, spacing_open: 0, south: { size: 94 }, enableCursorHotkey: false });
|
||||
|
||||
// Accordion Menu
|
||||
$("#accordion").accordion({ header: "h3", navigation: true, heightStyle: "content", collapsible: false, icons: false }); // collapsible will be enabled once the item will be selected
|
||||
|
||||
|
||||
// Tabs, using JQuery BBQ to store the history
|
||||
// The "tab widgets" to handle.
|
||||
var tabs = $('div[id^=tabbedContent]');
|
||||
|
||||
// This selector will be reused when selecting actual tab widget A elements.
|
||||
var tab_a_selector = 'ul.ui-tabs-nav a';
|
||||
|
||||
|
||||
// Ugly patch for a change in the behavior of jQuery UI:
|
||||
// Before jQuery UI 1.9, tabs were always considered as "local" (opposed to Ajax)
|
||||
// when their href was beginning by #. Starting with 1.9, a <base> tag in the page
|
||||
@@ -226,9 +365,7 @@ EOF;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('.multiselect').multiselect($sJSMultiselectOptions);
|
||||
|
||||
|
||||
$('.resizable').filter(':visible').resizable();
|
||||
}
|
||||
catch(err)
|
||||
@@ -297,6 +434,21 @@ EOF
|
||||
$.bbq.pushState( state );
|
||||
});
|
||||
|
||||
// refresh the hash when the tab is changed (from a JS script)
|
||||
$('body').on( 'tabsactivate', '.ui-tabs', function(event, ui) {
|
||||
var state = {};
|
||||
|
||||
// Get the id of this tab widget.
|
||||
var id = $(ui.newTab).closest( 'div[id^=tabbedContent]' ).attr( 'id' );
|
||||
|
||||
// Get the index of this tab.
|
||||
var idx = $(ui.newTab).prevAll().length;
|
||||
|
||||
// Set the state!
|
||||
state[ id ] = idx;
|
||||
$.bbq.pushState( state );
|
||||
});
|
||||
|
||||
// Bind an event to window.onhashchange that, when the history state changes,
|
||||
// iterates over all tab widgets, changing the current tab as necessary.
|
||||
$(window).bind( 'hashchange', function(e)
|
||||
@@ -347,24 +499,7 @@ EOF
|
||||
|
||||
// End of Tabs handling
|
||||
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
$(".datetime-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd 00:00:00',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
PrepareWidgets();
|
||||
|
||||
// Make sortable, everything that claims to be sortable
|
||||
$('.sortable').sortable( {axis: 'y', cursor: 'move', handle: '.drag_handle', stop: function()
|
||||
@@ -379,13 +514,39 @@ EOF
|
||||
}
|
||||
});
|
||||
docWidth = $(document).width();
|
||||
$('#ModalDlg').dialog({ autoOpen: false, modal: true, width: 0.8*docWidth }); // JQuery UI dialogs
|
||||
$('#ModalDlg').dialog({ autoOpen: false, modal: true, width: 0.8*docWidth, height: 'auto', maxHeight: $(window).height() - 50 }); // JQuery UI dialogs
|
||||
ShowDebug();
|
||||
$('#logOffBtn>ul').popupmenu();
|
||||
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry').toggle(); });
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry,.caselog_entry_html').toggle(); });
|
||||
|
||||
$(document).ajaxSend(function(event, jqxhr, options) {
|
||||
jqxhr.setRequestHeader('X-Combodo-Ajax', 'true');
|
||||
});
|
||||
$(document).ajaxError(function(event, jqxhr, options) {
|
||||
if (jqxhr.status == 401)
|
||||
{
|
||||
$('<div>'+$sJSDisconnectedMessage+'</div>').dialog({
|
||||
modal:true,
|
||||
title: $sJSTitle,
|
||||
close: function() { $(this).remove(); },
|
||||
minWidth: 400,
|
||||
buttons: [
|
||||
{ text: $sJSLoginAgain, click: function() { window.location.href= GetAbsoluteUrlAppRoot()+'pages/UI.php' } },
|
||||
{ text: $sJSStayOnThePage, click: function() { $(this).dialog('close'); } }
|
||||
]
|
||||
});
|
||||
}
|
||||
});
|
||||
EOF
|
||||
);
|
||||
$this->add_ready_script(InlineImage::FixImagesWidth());
|
||||
/*
|
||||
* Not used since the sorting of the tables is always performed server-side
|
||||
AttributeDateTime::InitTableSorter($this, 'custom_date_time');
|
||||
AttributeDate::InitTableSorter($this, 'custom_date');
|
||||
*/
|
||||
|
||||
$sUserPrefs = appUserPreferences::GetAsJSON();
|
||||
$this->add_script(
|
||||
<<<EOF
|
||||
@@ -406,12 +567,17 @@ EOF
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
function BackToDetails(sClass, id, sDefaultUrl)
|
||||
function BackToDetails(sClass, id, sDefaultUrl, sOwnershipToken)
|
||||
{
|
||||
window.bInCancel = true;
|
||||
if (id > 0)
|
||||
{
|
||||
window.location.href = AddAppContext(GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=details&class='+sClass+'&id='+id);
|
||||
sToken = '';
|
||||
if (sOwnershipToken != undefined)
|
||||
{
|
||||
sToken = '&token='+sOwnershipToken;
|
||||
}
|
||||
window.location.href = AddAppContext(GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=release_lock_and_details&class='+sClass+'&id='+id+sToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -419,7 +585,6 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function BackToList(sClass)
|
||||
{
|
||||
window.location.href = AddAppContext(GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=search_oql&oql_class='+sClass+'&oql_clause=WHERE id=0');
|
||||
@@ -458,6 +623,36 @@ EOF
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sId Identifies the item, to search after it in the current breadcrumb
|
||||
* @param string $sLabel Label of the breadcrumb item
|
||||
* @param string $sDescription More information, displayed as a tooltip
|
||||
* @param string $sUrl Specify a URL if the current URL as perceived on the browser side is not relevant
|
||||
* @param string $sIcon Icon (relative or absolute) path that will be displayed next to the label
|
||||
*/
|
||||
public function SetBreadCrumbEntry($sId, $sLabel, $sDescription, $sUrl = '', $sIcon = '')
|
||||
{
|
||||
$this->bBreadCrumbEnabled = true;
|
||||
$this->sBreadCrumbEntryId = $sId;
|
||||
$this->sBreadCrumbEntryLabel = $sLabel;
|
||||
$this->sBreadCrumbEntryDescription = $sDescription;
|
||||
$this->sBreadCrumbEntryUrl = $sUrl;
|
||||
$this->sBreadCrumbEntryIcon = $sIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* State that there will be no breadcrumb item for the current page
|
||||
*/
|
||||
public function DisableBreadCrumb()
|
||||
{
|
||||
$this->bBreadCrumbEnabled = false;
|
||||
$this->sBreadCrumbEntryId = null;
|
||||
$this->sBreadCrumbEntryLabel = null;
|
||||
$this->sBreadCrumbEntryDescription = null;
|
||||
$this->sBreadCrumbEntryUrl = null;
|
||||
$this->sBreadCrumbEntryIcon = null;
|
||||
}
|
||||
|
||||
public function AddToMenu($sHtml)
|
||||
{
|
||||
$this->m_sMenu .= $sHtml;
|
||||
@@ -513,7 +708,7 @@ EOF
|
||||
array('iFieldSize' => 20, 'iMinChars' => MetaModel::GetConfig()->Get('min_autocomplete_chars'), 'sDefaultValue' => Dict::S('UI:AllOrganizations')),
|
||||
null, 'select', false /* bSearchMultiple */);
|
||||
$this->add_ready_script('$("#org_id").bind("extkeychange", function() { $("#SiloSelection form").submit(); } )');
|
||||
$this->add_ready_script("$('#label_org_id').click( function() { $(this).val(''); $('#org_id').val(''); return true; } );\n");
|
||||
$this->add_ready_script("$('#label_org_id').click( function() { if ($('#org_id').val() == '') { $(this).val(''); } } );\n");
|
||||
// Add other dimensions/context information to this form
|
||||
$oAppContext->Reset('org_id'); // org_id is handled above and we want to be able to change it here !
|
||||
$oAppContext->Reset('menu'); // don't pass the menu, since a menu may expect more parameters
|
||||
@@ -559,7 +754,7 @@ EOF
|
||||
|
||||
if (UserRights::IsAdministrator() && ExecutionKPI::IsEnabled())
|
||||
{
|
||||
$sNorthPane .= '<div id="admin-banner"><span style="padding:5px;">'.ExecutionKPI::GetDescription().'<span></div>';
|
||||
$sNorthPane .= '<div class="app-message"><span style="padding:5px;">'.ExecutionKPI::GetDescription().'<span></div>';
|
||||
}
|
||||
|
||||
//$sSouthPane = '<p>Peak memory Usage: '.sprintf('%.3f MB', memory_get_peak_usage(true) / (1024*1024)).'</p>';
|
||||
@@ -568,8 +763,20 @@ EOF
|
||||
{
|
||||
$sSouthPane .= $oExtensionInstance->GetSouthPaneHtml($this);
|
||||
}
|
||||
|
||||
// Render the tabs in the page (if any)
|
||||
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
|
||||
|
||||
// Put here the 'ready scripts' that must be executed after all others
|
||||
$aMultiselectOptions = array(
|
||||
'header' => true,
|
||||
'checkAllText' => Dict::S('UI:SearchValue:CheckAll'),
|
||||
'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'),
|
||||
'noneSelectedText' => Dict::S('UI:SearchValue:Any'),
|
||||
'selectedText' => Dict::S('UI:SearchValue:NbSelected'),
|
||||
'selectedList' => 1,
|
||||
);
|
||||
$sJSMultiselectOptions = json_encode($aMultiselectOptions);
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
// Since the event is only triggered when the hash changes, we need to trigger
|
||||
@@ -578,8 +785,42 @@ EOF
|
||||
|
||||
// Some table are sort-able, some are not, let's fix this
|
||||
$('table.listResults').each( function() { FixTableSorter($(this)); } );
|
||||
|
||||
$('.multiselect').multiselect($sJSMultiselectOptions);
|
||||
|
||||
FixSearchFormsDisposition();
|
||||
EOF
|
||||
);
|
||||
|
||||
$iBreadCrumbMaxCount = utils::GetConfig()->Get('breadcrumb.max_count');
|
||||
if ($iBreadCrumbMaxCount > 1)
|
||||
{
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
$siTopInstanceId = json_encode(utils::GetAbsoluteUrlAppRoot().'==='.$oConfig->GetDBHost().'/'.$oConfig->GetDBName().'/'.$oConfig->GetDBSubname());
|
||||
if ($this->bBreadCrumbEnabled)
|
||||
{
|
||||
if (is_null($this->sBreadCrumbEntryId))
|
||||
{
|
||||
$this->sBreadCrumbEntryId = $this->s_title;
|
||||
$this->sBreadCrumbEntryLabel = $this->s_title;
|
||||
$this->sBreadCrumbEntryDescription = $this->s_title;
|
||||
$this->sBreadCrumbEntryUrl = '';
|
||||
$this->sBreadCrumbEntryIcon = utils::GetAbsoluteUrlAppRoot().'images/wrench.png';
|
||||
}
|
||||
$sNewEntry = json_encode(array('id' => $this->sBreadCrumbEntryId, 'url' => $this->sBreadCrumbEntryUrl, 'label' => htmlentities($this->sBreadCrumbEntryLabel, ENT_QUOTES, 'UTF-8'), 'description' => htmlentities($this->sBreadCrumbEntryDescription, ENT_QUOTES, 'UTF-8'), 'icon' => $this->sBreadCrumbEntryIcon));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sNewEntry = 'null';
|
||||
}
|
||||
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
$('#itop-breadcrumb').breadcrumb({itop_instance_id: $siTopInstanceId, new_entry: $sNewEntry, max_count: $iBreadCrumbMaxCount});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
foreach($this->a_headers as $s_header)
|
||||
@@ -600,19 +841,27 @@ EOF
|
||||
// jQuery scripts may face some spurious problems (like failing on a 'reload')
|
||||
foreach($this->a_linked_stylesheets as $a_stylesheet)
|
||||
{
|
||||
if (strpos($a_stylesheet['link'], '?') === false)
|
||||
{
|
||||
$s_stylesheet = $a_stylesheet['link']."?itopversion=".ITOP_VERSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
$s_stylesheet = $a_stylesheet['link']."&itopversion=".ITOP_VERSION;
|
||||
}
|
||||
if ($a_stylesheet['condition'] != "")
|
||||
{
|
||||
$sHtml .= "<!--[if {$a_stylesheet['condition']}]>\n";
|
||||
}
|
||||
$sHtml .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$a_stylesheet['link']}\" />\n";
|
||||
$sHtml .= "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$s_stylesheet}\" />\n";
|
||||
if ($a_stylesheet['condition'] != "")
|
||||
{
|
||||
$sHtml .= "<![endif]-->\n";
|
||||
}
|
||||
}
|
||||
// special stylesheet for printing, hides the navigation gadgets
|
||||
$sHtml .= "<link rel=\"stylesheet\" media=\"print\" type=\"text/css\" href=\"../css/print.css\" />\n";
|
||||
|
||||
$sHtml .= "<link rel=\"stylesheet\" media=\"print\" type=\"text/css\" href=\"../css/print.css?itopversion=".ITOP_VERSION."\" />\n";
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
$sHtml .= $this->output_dict_entries(true); // before any script so that they can benefit from the translations
|
||||
@@ -630,7 +879,40 @@ EOF
|
||||
}
|
||||
$sHtml .= "<script type=\"text/javascript\" src=\"$s_script\"></script>\n";
|
||||
}
|
||||
$this->add_script("var iPaneVisWatchDog = window.setTimeout('FixPaneVis()',5000);\n\$(document).ready(function() {\n{$this->m_sInitScript};\nwindow.setTimeout('onDelayedReady()',10)\n});");
|
||||
if (!$this->IsPrintableVersion())
|
||||
{
|
||||
$this->add_script("var iPaneVisWatchDog = window.setTimeout('FixPaneVis()',5000);");
|
||||
}
|
||||
$this->add_script("\$(document).ready(function() {\n{$this->m_sInitScript};\nwindow.setTimeout('onDelayedReady()',10)\n});");
|
||||
if ($this->IsPrintableVersion())
|
||||
{
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
var sHiddeableChapters = '<div class="light ui-tabs ui-widget ui-widget-content ui-corner-all">';
|
||||
sHiddeableChapters += '<ul role="tablist" class="ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all">';
|
||||
for (sId in oHiddeableChapters)
|
||||
{
|
||||
sHiddeableChapters += '<li tabindex="-1" role="tab" class="ui-state-default ui-corner-top hideable-chapter" chapter-id="'+sId+'"><span class="tab ui-tabs-anchor">' + oHiddeableChapters[sId] + '</span></li>';
|
||||
//alert(oHiddeableChapters[sId]);
|
||||
}
|
||||
sHiddeableChapters += '</ul></div>';
|
||||
$('#hiddeable_chapters').html(sHiddeableChapters);
|
||||
$('.hideable-chapter').click(function(){
|
||||
var sChapterId = $(this).attr('chapter-id');
|
||||
$('#'+sChapterId).toggle();
|
||||
$(this).toggleClass('strikethrough');
|
||||
});
|
||||
$('fieldset').each(function() {
|
||||
var jLegend = $(this).find('legend');
|
||||
jLegend.remove();
|
||||
$(this).wrapInner('<span></span>').prepend(jLegend);
|
||||
});
|
||||
$('legend').css('cursor', 'pointer').click(function(){
|
||||
$(this).parent('fieldset').toggleClass('not-printable strikethrough');
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
if (count($this->m_aReadyScripts)>0)
|
||||
{
|
||||
$this->add_script("\nonDelayedReady = function() {\n".implode("\n", $this->m_aReadyScripts)."\n}\n");
|
||||
@@ -656,38 +938,53 @@ EOF
|
||||
$sHtml .= "</style>\n";
|
||||
}
|
||||
$sHtml .= "<link rel=\"search\" type=\"application/opensearchdescription+xml\" title=\"iTop\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/opensearch.xml.php\" />\n";
|
||||
$sHtml .= "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico\" />\n";
|
||||
$sHtml .= "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico?itopversion=".ITOP_VERSION."\" />\n";
|
||||
|
||||
$sHtml .= "</head>\n";
|
||||
$sHtml .= "<body>\n";
|
||||
$sBodyClass = "";
|
||||
if ($this->IsPrintableVersion())
|
||||
{
|
||||
$sBodyClass = 'printable-version';
|
||||
}
|
||||
$sHtml .= "<body class=\"$sBodyClass\">\n";
|
||||
if ($this->IsPrintableVersion())
|
||||
{
|
||||
$sHtml .= "<div class=\"explain-printable not-printable\">";
|
||||
$sHtml .= '<p>'.Dict::Format('UI:ExplainPrintable', '<img src="../images/eye-open-555.png" style="vertical-align:middle">').'</p>';
|
||||
$sHtml .= "<div id=\"hiddeable_chapters\"></div>";
|
||||
$sHtml .= '<button onclick="window.print()">'.htmlentities(Dict::S('UI:Button:GoPrint'), ENT_QUOTES, 'UTF-8').'</button>';
|
||||
$sHtml .= ' ';
|
||||
$sHtml .= '<button onclick="window.close()">'.htmlentities(Dict::S('UI:Button:Cancel'), ENT_QUOTES, 'UTF-8').'</button>';
|
||||
$sHtml .= "</div>";
|
||||
}
|
||||
|
||||
// Render the revision number
|
||||
if (ITOP_REVISION == '$WCREV$')
|
||||
{
|
||||
// This is NOT a version built using the buil system, just display the main version
|
||||
$sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
|
||||
$sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a build made from SVN, let display the full information
|
||||
$sVersionString = Dict::Format('UI:iTopVersion:Long', ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
|
||||
$sVersionString = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
|
||||
}
|
||||
|
||||
// Render the text of the global search form
|
||||
$sText = htmlentities(utils::ReadParam('text', '', false, 'raw_data'), ENT_QUOTES, 'UTF-8');
|
||||
$sOnClick = "";
|
||||
$sOnClick = " onclick=\"if ($('#global-search-input').val() != '') { $('#global-search form').submit(); } \"";
|
||||
if (empty($sText))
|
||||
{
|
||||
// if no search text is supplied then
|
||||
// 1) the search text is filled with "your search"
|
||||
// 2) clicking on it will erase it
|
||||
$sText = Dict::S("UI:YourSearch");
|
||||
$sOnClick = " onclick=\"this.value='';this.onclick=null;\"";
|
||||
}
|
||||
// Render the tabs in the page (if any)
|
||||
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content);
|
||||
|
||||
if ($this->GetOutputFormat() == 'html')
|
||||
if ($this->IsPrintableVersion())
|
||||
{
|
||||
$sHtml .= ' <!-- Beginning of page content -->';
|
||||
$sHtml .= self::FilterXSS($this->s_content);
|
||||
$sHtml .= ' <!-- End of page content -->';
|
||||
}
|
||||
elseif ($this->GetOutputFormat() == 'html')
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
|
||||
@@ -701,16 +998,46 @@ EOF
|
||||
{
|
||||
$sLogonMessage = Dict::Format('UI:LoggedAsMessage', $sUserName);
|
||||
}
|
||||
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li><img src=\"../images/onOffBtn.png\"><ul>";
|
||||
$sLogOffMenu = "<span id=\"logOffBtn\"><ul><li><img src=\"../images/on-off-menu.png\"><ul>";
|
||||
$sLogOffMenu .= "<li><span>$sLogonMessage</span></li>\n";
|
||||
$aActions = array();
|
||||
|
||||
$aAllowedPortals = UserRights::GetAllowedPortals();
|
||||
if(count($aAllowedPortals) > 1)
|
||||
{
|
||||
// Adding portals
|
||||
foreach($aAllowedPortals as $aAllowedPortal)
|
||||
{
|
||||
if($aAllowedPortal['id'] !== 'backoffice')
|
||||
{
|
||||
$oPortalMenuItem = new URLPopupMenuItem('portal:'.$aAllowedPortal['id'], Dict::S($aAllowedPortal['label']), $aAllowedPortal['url'], '_blank');
|
||||
$aActions[$oPortalMenuItem->GetUID()] = $oPortalMenuItem->GetMenuItem();
|
||||
}
|
||||
}
|
||||
// Adding a separator
|
||||
$oPortalSeparatorMenuItem = new SeparatorPopupMenuItem();
|
||||
$aActions[$oPortalSeparatorMenuItem->GetUID()] = $oPortalSeparatorMenuItem->GetMenuItem();
|
||||
}
|
||||
|
||||
$oPrefs = new URLPopupMenuItem('UI:Preferences', Dict::S('UI:Preferences'), utils::GetAbsoluteUrlAppRoot()."pages/preferences.php?".$oAppContext->GetForLink());
|
||||
$aActions[$oPrefs->GetUID()] = $oPrefs->GetMenuItem();
|
||||
|
||||
|
||||
if (utils::IsArchiveMode())
|
||||
{
|
||||
$oExitArchive = new JSPopupMenuItem('UI:ArchiveModeOff', Dict::S('UI:ArchiveModeOff'), 'return ArchiveMode(false);');
|
||||
$aActions[$oExitArchive->GetUID()] = $oExitArchive->GetMenuItem();
|
||||
|
||||
$sIcon = '<span class="fa fa-lock fa-1x"></span>';
|
||||
$this->AddApplicationMessage(Dict::S('UI:ArchiveMode:Banner'), $sIcon, Dict::S('UI:ArchiveMode:Banner+'));
|
||||
}
|
||||
elseif (UserRights::CanBrowseArchive())
|
||||
{
|
||||
$oBrowseArchive = new JSPopupMenuItem('UI:ArchiveModeOn', Dict::S('UI:ArchiveModeOn'), 'return ArchiveMode(true);');
|
||||
$aActions[$oBrowseArchive->GetUID()] = $oBrowseArchive->GetMenuItem();
|
||||
}
|
||||
if (utils::CanLogOff())
|
||||
{
|
||||
$oLogOff = new URLPopupMenuItem('UI:LogOffMenu', Dict::S('UI:LogOffMenu'), utils::GetAbsoluteUrlAppRoot().'pages/logoff.php');
|
||||
$oLogOff = new URLPopupMenuItem('UI:LogOffMenu', Dict::S('UI:LogOffMenu'), utils::GetAbsoluteUrlAppRoot().'pages/logoff.php?operation=do_logoff');
|
||||
$aActions[$oLogOff->GetUID()] = $oLogOff->GetMenuItem();
|
||||
}
|
||||
if (UserRights::CanChangePassword())
|
||||
@@ -739,30 +1066,38 @@ EOF
|
||||
$sRestrictions = Dict::S('UI:AccessRO-Users');
|
||||
}
|
||||
|
||||
$sApplicationBanner = '';
|
||||
if (strlen($sRestrictions) > 0)
|
||||
{
|
||||
$sIcon =
|
||||
<<<EOF
|
||||
<span class="fa-stack fa-sm">
|
||||
<i class="fa fa-pencil fa-flip-horizontal fa-stack-1x"></i>
|
||||
<i class="fa fa-ban fa-stack-2x text-danger"></i>
|
||||
</span>
|
||||
EOF;
|
||||
|
||||
$sAdminMessage = trim(MetaModel::GetConfig()->Get('access_message'));
|
||||
$sApplicationBanner .= '<div id="admin-banner">';
|
||||
$sApplicationBanner .= '<img src="../images/locked.png" style="vertical-align:middle;">';
|
||||
$sApplicationBanner .= ' <b>'.$sRestrictions.'</b>';
|
||||
if (strlen($sAdminMessage) > 0)
|
||||
{
|
||||
$sApplicationBanner .= ' <b>'.$sAdminMessage.'</b>';
|
||||
$sRestrictions .= ' '.$sAdminMessage;
|
||||
}
|
||||
$sApplicationBanner .= '</div>';
|
||||
$this->AddApplicationMessage($sRestrictions, $sIcon);
|
||||
}
|
||||
|
||||
if(strlen($this->m_sMessage))
|
||||
$sApplicationMessages = '';
|
||||
foreach ($this->m_aMessages as $aMessage)
|
||||
{
|
||||
$sApplicationBanner .= '<div id="admin-banner"><span style="padding:5px;">'.$this->m_sMessage.'<span></div>';
|
||||
$sHtmlIcon = $aMessage['icon'] ? $aMessage['icon'] : '';
|
||||
$sHtmlMessage = $aMessage['message'];
|
||||
$sTitleAttr = $aMessage['tip'] ? 'title="'.htmlentities($aMessage['tip'], ENT_QUOTES, 'UTF-8').'"' : '';
|
||||
$sApplicationMessages .= '<div class="app-message" '.$sTitleAttr.'><span class="app-message-icon">'.$sHtmlIcon.'</span><span class="app-message-body">'.$sHtmlMessage.'</div></span>';
|
||||
}
|
||||
|
||||
$sApplicationBanner .= $sBannerExtraHtml;
|
||||
$sApplicationBanner = "<div class=\"app-banner ui-helper-clearfix\">$sApplicationMessages$sBannerExtraHtml</div>";
|
||||
|
||||
if (!empty($sNorthPane))
|
||||
{
|
||||
$sNorthPane = '<div id="bottom-pane" class="ui-layout-north">'.$sNorthPane.'</div>';
|
||||
$sNorthPane = '<div id="top-pane" class="ui-layout-north">'.$sNorthPane.'</div>';
|
||||
}
|
||||
|
||||
if (!empty($sSouthPane))
|
||||
@@ -774,10 +1109,10 @@ EOF
|
||||
$sOnlineHelpUrl = MetaModel::GetConfig()->Get('online_help');
|
||||
//$sLogOffMenu = "<span id=\"logOffBtn\" style=\"height:55px;padding:0;margin:0;\"><img src=\"../images/onOffBtn.png\"></span>";
|
||||
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/itop-logo.png';
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/itop-logo.png?itopversion='.ITOP_VERSION;
|
||||
if (file_exists(MODULESROOT.'branding/main-logo.png'))
|
||||
{
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/main-logo.png';
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/main-logo.png?itopversion='.ITOP_VERSION;
|
||||
}
|
||||
|
||||
$sHtml .= $sNorthPane;
|
||||
@@ -804,18 +1139,45 @@ EOF
|
||||
$sHtml .= ' </div>';
|
||||
$sHtml .= ' </div> <!-- /inner menu -->';
|
||||
$sHtml .= ' </div> <!-- /menu -->';
|
||||
$sHtml .= ' <div class="footer ui-layout-south"><div id="combodo_logo"><a href="http://www.combodo.com" title="www.combodo.com" target="_blank"><img src="../images/logo-combodo.png"/></a></div></div>';
|
||||
$sHtml .= ' <div class="footer ui-layout-south"><div id="combodo_logo"><a href="http://www.combodo.com" title="www.combodo.com" target="_blank"><img src="../images/logo-combodo.png?itopversion='.ITOP_VERSION.'"/></a></div></div>';
|
||||
$sHtml .= '<!-- End of the left pane -->';
|
||||
$sHtml .= '</div>';
|
||||
|
||||
$sHtml .= '<div class="ui-layout-center">';
|
||||
$sHtml .= ' <div id="top-bar" style="width:100%">';
|
||||
$sHtml .= ' <div id="top-bar" class="ui-helper-clearfix" style="width:100%">';
|
||||
$sHtml .= self::FilterXSS($sApplicationBanner);
|
||||
$sHtml .= ' <div id="global-search"><form action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php"><table><tr><td></td><td id="g-search-input"><input type="text" name="text" value="'.$sText.'"'.$sOnClick.'/></td>';
|
||||
$sHtml .= '<td><input type="image" src="../images/searchBtn.png"/></a></td>';
|
||||
$sHtml .= '<td><a style="background:transparent;" href="'.$sOnlineHelpUrl.'" target="_blank"><img style="border:0;padding-left:20px;padding-right:10px;" title="'.Dict::S('UI:Help').'" src="../images/help.png"/></td>';
|
||||
$sHtml .= '<td style="padding-right:20px;padding-left:10px;">'.self::FilterXSS($sLogOffMenu).'</td><td><input type="hidden" name="operation" value="full_text"/></td></tr></table></form></div>';
|
||||
//echo '<td> <input type="hidden" name="operation" value="full_text"/></td></tr></table></form></div>';
|
||||
|
||||
$GoHomeInitialStyle = $this->IsMenuPaneVisible() ? 'display: none;' : '';
|
||||
|
||||
$sHtml .= ' <table id="top-bar-table">';
|
||||
$sHtml .= ' <tr>';
|
||||
$sHtml .= ' <td id="open-left-pane" class="menu-pane-exclusive" style="'.$GoHomeInitialStyle.'" onclick="$(\'body\').layout().open(\'west\');">';
|
||||
$sHtml .= ' <img src="../images/menu.png">';
|
||||
$sHtml .= ' </td>';
|
||||
$sHtml .= ' <td id="go-home" class="menu-pane-exclusive" style="'.$GoHomeInitialStyle.'">';
|
||||
$sHtml .= ' <a href="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php"><img src="../images/home.png"></a>';
|
||||
$sHtml .= ' </td>';
|
||||
$sHtml .= ' <td class="top-bar-spacer menu-pane-exclusive" style="'.$GoHomeInitialStyle.'">';
|
||||
$sHtml .= ' </td>';
|
||||
$sHtml .= ' <td id="top-bar-table-breadcrumb">';
|
||||
$sHtml .= ' <div id="itop-breadcrumb"></div>';
|
||||
$sHtml .= ' </td>';
|
||||
$sHtml .= ' <td id="top-bar-table-search">';
|
||||
$sHtml .= ' <div id="global-search"><form action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php">';
|
||||
$sHtml .= ' <table id="top-left-buttons-area"><tr>';
|
||||
$sHtml .= ' <td id="top-left-global-search-cell"><div id="global-search-area"><input id="global-search-input" type="text" name="text" placeholder="'.$sText.'"></input><div '.$sOnClick.' id="global-search-image"><input type="hidden" name="operation" value="full_text"/></div></div></td>';
|
||||
$sHtml .= ' <td id="top-left-help-cell"><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank"><img title="'.Dict::S('UI:Help').'" src="../images/help.png?itopversion='.ITOP_VERSION.'"/></td>';
|
||||
$sHtml .= ' <td id="top-left-logoff-cell">'.self::FilterXSS($sLogOffMenu).'</td>';
|
||||
$sHtml .= ' </tr></table></form></div>';
|
||||
$sHtml .= ' </td>';
|
||||
$sHtml .= ' </tr>';
|
||||
$sHtml .= ' </table>';
|
||||
|
||||
// $sHtml .= ' <div id="global-search"><form action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php"><table><tr><td></td><td><div id="global-search-area"><input id="global-search-input" type="text" name="text" placeholder="'.$sText.'"></input><div '.$sOnClick.' id="global-search-image"></div></div></td>';
|
||||
// $sHtml .= '<td><a id="help-link" href="'.$sOnlineHelpUrl.'" target="_blank"><img title="'.Dict::S('UI:Help').'" src="../images/help.png?itopversion='.ITOP_VERSION.'"/></td>';
|
||||
// $sHtml .= '<td>'.self::FilterXSS($sLogOffMenu).'</td><td><input type="hidden" name="operation" value="full_text"/></td></tr></table></form></div>';
|
||||
// $sHtml .= ' <div id="itop-breadcrumb"></div>';
|
||||
|
||||
$sHtml .= ' </div>';
|
||||
$sHtml .= ' <div class="ui-layout-content" style="overflow:auto;">';
|
||||
$sHtml .= ' <!-- Beginning of page content -->';
|
||||
@@ -851,28 +1213,31 @@ EOF
|
||||
}
|
||||
else if ($this->GetOutputFormat() == 'pdf' && $this->IsOutputFormatAvailable('pdf') )
|
||||
{
|
||||
require_once(APPROOT.'lib/MPDF/mpdf.php');
|
||||
$oMPDF = new mPDF('c');
|
||||
$oMPDF->mirroMargins = false;
|
||||
if ($this->a_base['href'] != '')
|
||||
if (@is_readable(APPROOT.'lib/MPDF/mpdf.php'))
|
||||
{
|
||||
$oMPDF->setBasePath($this->a_base['href']); // Seems that the <BASE> tag is not recognized by mPDF...
|
||||
require_once(APPROOT.'lib/MPDF/mpdf.php');
|
||||
$oMPDF = new mPDF('c');
|
||||
$oMPDF->mirroMargins = false;
|
||||
if ($this->a_base['href'] != '')
|
||||
{
|
||||
$oMPDF->setBasePath($this->a_base['href']); // Seems that the <BASE> tag is not recognized by mPDF...
|
||||
}
|
||||
$oMPDF->showWatermarkText = true;
|
||||
if ($this->GetOutputOption('pdf', 'template_path'))
|
||||
{
|
||||
$oMPDF->setImportUse(); // Allow templates
|
||||
$oMPDF->SetDocTemplate ($this->GetOutputOption('pdf', 'template_path'), 1);
|
||||
}
|
||||
$oMPDF->WriteHTML($sHtml);
|
||||
$sOutputName = $this->s_title.'.pdf';
|
||||
if ($this->GetOutputOption('pdf', 'output_name'))
|
||||
{
|
||||
$sOutputName = $this->GetOutputOption('pdf', 'output_name');
|
||||
}
|
||||
$oMPDF->Output($sOutputName, 'I');
|
||||
}
|
||||
$oMPDF->showWatermarkText = true;
|
||||
if ($this->GetOutputOption('pdf', 'template_path'))
|
||||
{
|
||||
$oMPDF->setImportUse(); // Allow templates
|
||||
$oMPDF->SetDocTemplate ($this->GetOutputOption('pdf', 'template_path'), 1);
|
||||
}
|
||||
$oMPDF->WriteHTML($sHtml);
|
||||
$sOutputName = $this->s_title.'.pdf';
|
||||
if ($this->GetOutputOption('pdf', 'output_name'))
|
||||
{
|
||||
$sOutputName = $this->GetOutputOption('pdf', 'output_name');
|
||||
}
|
||||
$oMPDF->Output($sOutputName, 'I');
|
||||
}
|
||||
MetaModel::RecordQueryTrace();
|
||||
DBSearch::RecordQueryTrace();
|
||||
ExecutionKPI::ReportStats();
|
||||
}
|
||||
|
||||
@@ -1031,10 +1396,26 @@ EOF
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message to be displayed in the 'admin-banner' section at the top of the page
|
||||
* Set the message to be displayed in the 'app-banner' section at the top of the page
|
||||
*/
|
||||
public function SetMessage($sMessage)
|
||||
public function SetMessage($sHtmlMessage)
|
||||
{
|
||||
$this->m_sMessage = $sMessage;
|
||||
$sHtmlIcon = '<span class="fa fa-comment fa-1x"></span>';
|
||||
$this->AddApplicationMessage($sHtmlMessage, $sHtmlIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add message to be displayed in the 'app-banner' section at the top of the page
|
||||
*/
|
||||
public function AddApplicationMessage($sHtmlMessage, $sHtmlIcon = null, $sTip = null)
|
||||
{
|
||||
if (strlen($sHtmlMessage))
|
||||
{
|
||||
$this->m_aMessages[] = array(
|
||||
'icon' => $sHtmlIcon,
|
||||
'message' => $sHtmlMessage,
|
||||
'tip' => $sTip
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,11 +20,12 @@
|
||||
/**
|
||||
* Class LoginWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2013 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT."/application/nicewebpage.class.inc.php");
|
||||
require_once(APPROOT.'/application/portaldispatcher.class.inc.php');
|
||||
/**
|
||||
* Web page used for displaying the login form
|
||||
*/
|
||||
@@ -55,8 +56,13 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
protected static $m_sLoginFailedMessage = '';
|
||||
|
||||
public function __construct($sTitle = 'iTop Login')
|
||||
public function __construct($sTitle = null)
|
||||
{
|
||||
if($sTitle === null)
|
||||
{
|
||||
$sTitle = Dict::S('UI:Login:Title');
|
||||
}
|
||||
|
||||
parent::__construct($sTitle);
|
||||
$this->SetStyleSheet();
|
||||
$this->add_header("Cache-control: no-cache");
|
||||
@@ -64,7 +70,7 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
public function SetStyleSheet()
|
||||
{
|
||||
$this->add_linked_stylesheet("../css/login.css");
|
||||
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/login.css');
|
||||
}
|
||||
|
||||
public static function SetLoginFailedMessage($sMessage)
|
||||
@@ -89,12 +95,12 @@ class LoginWebPage extends NiceWebPage
|
||||
$sLogo = 'itop-logo-external.png';
|
||||
$sBrandingLogo = 'login-logo.png';
|
||||
}
|
||||
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION);
|
||||
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
|
||||
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo;
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?itopversion='.ITOP_VERSION;
|
||||
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
|
||||
{
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo;
|
||||
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?itopversion='.ITOP_VERSION;
|
||||
}
|
||||
$this->add("<div id=\"login-logo\"><a href=\"".htmlentities($sIconUrl, ENT_QUOTES, 'UTF-8')."\"><img title=\"$sVersionShort\" src=\"$sDisplayIcon\"></a></div>\n");
|
||||
}
|
||||
@@ -111,7 +117,7 @@ class LoginWebPage extends NiceWebPage
|
||||
|
||||
case 'basic':
|
||||
case 'url':
|
||||
$this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
$this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION));
|
||||
$this->add_header('HTTP/1.0 401 Unauthorized');
|
||||
$this->add_header('Content-type: text/html; charset=iso-8859-1');
|
||||
// Note: displayed when the user will click on Cancel
|
||||
@@ -305,16 +311,20 @@ class LoginWebPage extends NiceWebPage
|
||||
{
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
|
||||
}
|
||||
elseif ($oUser->Get('reset_pwd_token') != $sToken)
|
||||
{
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."</p>\n");
|
||||
|
||||
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
|
||||
$this->add_script(
|
||||
$oEncryptedToken = $oUser->Get('reset_pwd_token');
|
||||
|
||||
if (!$oEncryptedToken->CheckPassword($sToken))
|
||||
{
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."</p>\n");
|
||||
|
||||
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
|
||||
$this->add_script(
|
||||
<<<EOF
|
||||
function DoCheckPwd()
|
||||
{
|
||||
@@ -326,18 +336,19 @@ function DoCheckPwd()
|
||||
return true;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$this->add("<form method=\"post\">\n");
|
||||
$this->add("<table>\n");
|
||||
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
|
||||
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
|
||||
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
|
||||
$this->add("</table>\n");
|
||||
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_reset_pwd\" />\n");
|
||||
$this->add("<input type=\"hidden\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" />\n");
|
||||
$this->add("<input type=\"hidden\" name=\"token\" value=\"".htmlentities($sToken, ENT_QUOTES, 'UTF-8')."\" />\n");
|
||||
$this->add("</form>\n");
|
||||
$this->add("</div\n");
|
||||
);
|
||||
$this->add("<form method=\"post\">\n");
|
||||
$this->add("<table>\n");
|
||||
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
|
||||
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
|
||||
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
|
||||
$this->add("</table>\n");
|
||||
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_reset_pwd\" />\n");
|
||||
$this->add("<input type=\"hidden\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" />\n");
|
||||
$this->add("<input type=\"hidden\" name=\"token\" value=\"".htmlentities($sToken, ENT_QUOTES, 'UTF-8')."\" />\n");
|
||||
$this->add("</form>\n");
|
||||
$this->add("</div\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,21 +368,25 @@ EOF
|
||||
{
|
||||
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
|
||||
}
|
||||
elseif ($oUser->Get('reset_pwd_token') != $sToken)
|
||||
{
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Trash the token and change the password
|
||||
$oUser->Set('reset_pwd_token', '');
|
||||
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
|
||||
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
$this->add("<p><a href=\"$sUrl\">".Dict::S('UI:ResetPwd-Login')."</a></p>");
|
||||
$oEncryptedPassword = $oUser->Get('reset_pwd_token');
|
||||
if (!$oEncryptedPassword->CheckPassword($sToken))
|
||||
{
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Trash the token and change the password
|
||||
$oUser->Set('reset_pwd_token', '');
|
||||
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
|
||||
|
||||
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
$this->add("<p><a href=\"$sUrl\">".Dict::S('UI:ResetPwd-Login')."</a></p>");
|
||||
}
|
||||
$this->add("</div\n");
|
||||
}
|
||||
$this->add("</div\n");
|
||||
}
|
||||
|
||||
public function DisplayChangePwdForm($bFailedLogin = false)
|
||||
@@ -417,17 +432,12 @@ EOF
|
||||
|
||||
static function ResetSession()
|
||||
{
|
||||
if (isset($_SESSION['login_mode']))
|
||||
{
|
||||
$sPreviousLoginMode = $_SESSION['login_mode'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sPreviousLoginMode = '';
|
||||
}
|
||||
// Unset all of the session variables.
|
||||
unset($_SESSION['auth_user']);
|
||||
unset($_SESSION['login_mode']);
|
||||
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!
|
||||
}
|
||||
@@ -475,178 +485,189 @@ EOF
|
||||
{
|
||||
//echo "User: ".$_SESSION['auth_user']."\n";
|
||||
// Already authentified
|
||||
UserRights::Login($_SESSION['auth_user']); // Login & set the user's language
|
||||
return self::EXIT_CODE_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
$index = 0;
|
||||
$sLoginMode = '';
|
||||
$sAuthentication = 'internal';
|
||||
while(($sLoginMode == '') && ($index < count($aAllowedLoginTypes)))
|
||||
$bRet = UserRights::Login($_SESSION['auth_user']); // Login & set the user's language
|
||||
if ($bRet)
|
||||
{
|
||||
$sLoginType = $aAllowedLoginTypes[$index];
|
||||
switch($sLoginType)
|
||||
{
|
||||
case 'cas':
|
||||
utils::InitCASClient();
|
||||
// check CAS authentication
|
||||
if (phpCAS::isAuthenticated())
|
||||
{
|
||||
$sAuthUser = phpCAS::getUser();
|
||||
$sAuthPwd = '';
|
||||
$sLoginMode = 'cas';
|
||||
$sAuthentication = 'external';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'form':
|
||||
// iTop standard mode: form based authentication
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, false, 'raw_data');
|
||||
if (($sAuthUser != '') && ($sAuthPwd !== null))
|
||||
{
|
||||
$sLoginMode = 'form';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'basic':
|
||||
// Standard PHP authentication method, works with Apache...
|
||||
// Case 1) Apache running in CGI mode + rewrite rules in .htaccess
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
|
||||
$sLoginMode = 'basic';
|
||||
}
|
||||
else if (isset($_SERVER['PHP_AUTH_USER']))
|
||||
{
|
||||
$sAuthUser = $_SERVER['PHP_AUTH_USER'];
|
||||
// Unfortunately, the RFC is not clear about the encoding...
|
||||
// IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8
|
||||
// So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base
|
||||
if (!self::LooksLikeUTF8($sAuthUser))
|
||||
{
|
||||
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
|
||||
// Supposed to be harmless in case of a plain ASCII string...
|
||||
$sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser);
|
||||
}
|
||||
$sAuthPwd = $_SERVER['PHP_AUTH_PW'];
|
||||
if (!self::LooksLikeUTF8($sAuthPwd))
|
||||
{
|
||||
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
|
||||
// Supposed to be harmless in case of a plain ASCII string...
|
||||
$sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd);
|
||||
}
|
||||
$sLoginMode = 'basic';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'external':
|
||||
// Web server supplied authentication
|
||||
$bExternalAuth = false;
|
||||
$sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ?
|
||||
eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value
|
||||
if ($sAuthUser && (strlen($sAuthUser) > 0))
|
||||
{
|
||||
$sAuthPwd = ''; // No password in this case the web server already authentified the user...
|
||||
$sLoginMode = 'external';
|
||||
$sAuthentication = 'external';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
// Credentials passed directly in the url
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
if (($sAuthUser != '') && ($sAuthPwd !== null))
|
||||
{
|
||||
$sLoginMode = 'url';
|
||||
}
|
||||
break;
|
||||
}
|
||||
$index++;
|
||||
return self::EXIT_CODE_OK;
|
||||
}
|
||||
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
|
||||
if ($sLoginMode == '')
|
||||
// The user account is no longer valid/enabled
|
||||
static::ResetSession();
|
||||
}
|
||||
|
||||
$index = 0;
|
||||
$sLoginMode = '';
|
||||
$sAuthentication = 'internal';
|
||||
while(($sLoginMode == '') && ($index < count($aAllowedLoginTypes)))
|
||||
{
|
||||
$sLoginType = $aAllowedLoginTypes[$index];
|
||||
switch($sLoginType)
|
||||
{
|
||||
// First connection
|
||||
$sDesiredLoginMode = utils::ReadParam('login_mode');
|
||||
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
|
||||
case 'cas':
|
||||
utils::InitCASClient();
|
||||
// check CAS authentication
|
||||
if (phpCAS::isAuthenticated())
|
||||
{
|
||||
$sLoginMode = $sDesiredLoginMode;
|
||||
$sAuthUser = phpCAS::getUser();
|
||||
$sAuthPwd = '';
|
||||
$sLoginMode = 'cas';
|
||||
$sAuthentication = 'external';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'form':
|
||||
// iTop standard mode: form based authentication
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, false, 'raw_data');
|
||||
if (($sAuthUser != '') && ($sAuthPwd !== null))
|
||||
{
|
||||
$sLoginMode = 'form';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'basic':
|
||||
// Standard PHP authentication method, works with Apache...
|
||||
// Case 1) Apache running in CGI mode + rewrite rules in .htaccess
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
|
||||
{
|
||||
list($sAuthUser, $sAuthPwd) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
|
||||
$sLoginMode = 'basic';
|
||||
}
|
||||
else if (isset($_SERVER['PHP_AUTH_USER']))
|
||||
{
|
||||
$sAuthUser = $_SERVER['PHP_AUTH_USER'];
|
||||
// Unfortunately, the RFC is not clear about the encoding...
|
||||
// IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8
|
||||
// So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base
|
||||
if (!self::LooksLikeUTF8($sAuthUser))
|
||||
{
|
||||
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
|
||||
// Supposed to be harmless in case of a plain ASCII string...
|
||||
$sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser);
|
||||
}
|
||||
$sAuthPwd = $_SERVER['PHP_AUTH_PW'];
|
||||
if (!self::LooksLikeUTF8($sAuthPwd))
|
||||
{
|
||||
// Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8
|
||||
// Supposed to be harmless in case of a plain ASCII string...
|
||||
$sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd);
|
||||
}
|
||||
$sLoginMode = 'basic';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'external':
|
||||
// Web server supplied authentication
|
||||
$bExternalAuth = false;
|
||||
$sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ?
|
||||
eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value
|
||||
if ($sAuthUser && (strlen($sAuthUser) > 0))
|
||||
{
|
||||
$sAuthPwd = ''; // No password in this case the web server already authentified the user...
|
||||
$sLoginMode = 'external';
|
||||
$sAuthentication = 'external';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
// Credentials passed directly in the url
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
if (($sAuthUser != '') && ($sAuthPwd !== null))
|
||||
{
|
||||
$sLoginMode = 'url';
|
||||
}
|
||||
break;
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
//echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)";
|
||||
if ($sLoginMode == '')
|
||||
{
|
||||
// First connection
|
||||
$sDesiredLoginMode = utils::ReadParam('login_mode');
|
||||
if (in_array($sDesiredLoginMode, $aAllowedLoginTypes))
|
||||
{
|
||||
$sLoginMode = $sDesiredLoginMode;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
|
||||
}
|
||||
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
|
||||
{
|
||||
// X-Combodo-Ajax is a special header automatically added to all ajax requests
|
||||
// Let's reply that we're currently logged-out
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
exit;
|
||||
}
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION));
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('Content-type: text/html; charset=iso-8859-1');
|
||||
exit;
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
if (($sAuthUser !== '') && ($sAuthPwd === null))
|
||||
{
|
||||
return self::EXIT_CODE_MISSINGPASSWORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sLoginMode = $aAllowedLoginTypes[0]; // First in the list...
|
||||
return self::EXIT_CODE_MISSINGLOGIN;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
|
||||
{
|
||||
//echo "Check Credentials returned false for user $sAuthUser!";
|
||||
self::ResetSession();
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION));
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('Content-type: text/html; charset=iso-8859-1');
|
||||
exit;
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
if (($sAuthUser !== '') && ($sAuthPwd === null))
|
||||
{
|
||||
return self::EXIT_CODE_MISSINGPASSWORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::EXIT_CODE_MISSINGLOGIN;
|
||||
}
|
||||
return self::EXIT_CODE_WRONGCREDENTIALS;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */);
|
||||
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
|
||||
// User is Ok, let's save it in the session and proceed with normal login
|
||||
UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
|
||||
|
||||
if (MetaModel::GetConfig()->Get('log_usage'))
|
||||
{
|
||||
//echo "Check Credentials returned false for user $sAuthUser!";
|
||||
self::ResetSession();
|
||||
if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic'))
|
||||
{
|
||||
header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION));
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
header('Content-type: text/html; charset=iso-8859-1');
|
||||
exit;
|
||||
}
|
||||
else if($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
return self::EXIT_CODE_WRONGCREDENTIALS;
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
$oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// User is Ok, let's save it in the session and proceed with normal login
|
||||
UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language
|
||||
|
||||
if (MetaModel::GetConfig()->Get('log_usage'))
|
||||
{
|
||||
$oLog = new EventLoginUsage();
|
||||
$oLog->Set('userinfo', UserRights::GetUser());
|
||||
$oLog->Set('user_id', UserRights::GetUserObject()->GetKey());
|
||||
$oLog->Set('message', 'Successful login');
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$_SESSION['login_mode'] = $sLoginMode;
|
||||
$oLog = new EventLoginUsage();
|
||||
$oLog->Set('userinfo', UserRights::GetUser());
|
||||
$oLog->Set('user_id', UserRights::GetUserObject()->GetKey());
|
||||
$oLog->Set('message', 'Successful login');
|
||||
$oLog->DBInsertNoReload();
|
||||
}
|
||||
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$_SESSION['login_mode'] = $sLoginMode;
|
||||
UserRights::_InitSessionCache();
|
||||
}
|
||||
}
|
||||
return self::EXIT_CODE_OK;
|
||||
@@ -654,12 +675,22 @@ EOF
|
||||
|
||||
/**
|
||||
* Overridable: depending on the user, head toward a dedicated portal
|
||||
* @param bool $bIsAllowedToPortalUsers Whether or not the current page is considered as part of the portal
|
||||
* @param string|null $sRequestedPortalId
|
||||
* @param int $iOnExit How to complete the call: redirect or return a code
|
||||
*/
|
||||
protected static function ChangeLocation($bIsAllowedToPortalUsers, $iOnExit = self::EXIT_PROMPT)
|
||||
protected static function ChangeLocation($sRequestedPortalId = null, $iOnExit = self::EXIT_PROMPT)
|
||||
{
|
||||
if ( (!$bIsAllowedToPortalUsers) && (UserRights::IsPortalUser()))
|
||||
$fStart = microtime(true);
|
||||
$ret = call_user_func(array(self::$sHandlerClass, 'Dispatch'), $sRequestedPortalId);
|
||||
if ($ret === true)
|
||||
{
|
||||
return self::EXIT_CODE_OK;
|
||||
}
|
||||
else if($ret === false)
|
||||
{
|
||||
throw new Exception('Nowhere to go??');
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
@@ -668,16 +699,11 @@ EOF
|
||||
else
|
||||
{
|
||||
// No rights to be here, redirect to the portal
|
||||
header('Location: '.utils::GetAbsoluteUrlAppRoot().'portal/index.php');
|
||||
header('Location: '.$ret);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return self::EXIT_CODE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if the user is already authentified, if yes, then performs some additional validations:
|
||||
* - if $bMustBeAdmin is true, then the user must be an administrator, otherwise an error is displayed
|
||||
@@ -688,9 +714,56 @@ EOF
|
||||
*/
|
||||
static function DoLogin($bMustBeAdmin = false, $bIsAllowedToPortalUsers = false, $iOnExit = self::EXIT_PROMPT)
|
||||
{
|
||||
$sMessage = ''; // In case we need to return a message to the calling web page
|
||||
$sRequestedPortalId = $bIsAllowedToPortalUsers ? 'legacy_portal' : 'backoffice';
|
||||
return self::DoLoginEx($sRequestedPortalId, $bMustBeAdmin, $iOnExit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is already authentified, if yes, then performs some additional validations to redirect towards the desired "portal"
|
||||
* @param string|null $sRequestedPortalId The requested "portal" interface, null for any
|
||||
* @param bool $bMustBeAdmin Whether or not the user must be an admin to access the current page
|
||||
* @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...)
|
||||
*/
|
||||
static function DoLoginEx($sRequestedPortalId = null, $bMustBeAdmin = false, $iOnExit = self::EXIT_PROMPT)
|
||||
{
|
||||
$operation = utils::ReadParam('loginop', '');
|
||||
|
||||
|
||||
$sMessage = self::HandleOperations($operation); // May exit directly
|
||||
|
||||
$iRet = self::Login($iOnExit);
|
||||
|
||||
if ($iRet == self::EXIT_CODE_OK)
|
||||
{
|
||||
if ($bMustBeAdmin && !UserRights::IsAdministrator())
|
||||
{
|
||||
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->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();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $sRequestedPortalId, $iOnExit);
|
||||
}
|
||||
if ($iOnExit == self::EXIT_RETURN)
|
||||
{
|
||||
return $iRet;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sMessage;
|
||||
}
|
||||
}
|
||||
protected static function HandleOperations($operation)
|
||||
{
|
||||
$sMessage = ''; // most of the operations never return, but some can return a message to be displayed
|
||||
if ($operation == 'logoff')
|
||||
{
|
||||
if (isset($_SESSION['login_mode']))
|
||||
@@ -714,7 +787,7 @@ EOF
|
||||
$oPage->DisplayLoginForm( $sLoginMode, false /* not a failed attempt */);
|
||||
$oPage->output();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else if ($operation == 'forgot_pwd')
|
||||
{
|
||||
$oPage = self::NewLoginWebPage();
|
||||
@@ -767,36 +840,29 @@ EOF
|
||||
}
|
||||
$sMessage = Dict::S('UI:Login:PasswordChanged');
|
||||
}
|
||||
return $sMessage;
|
||||
}
|
||||
|
||||
protected static function Dispatch($sRequestedPortalId)
|
||||
{
|
||||
if ($sRequestedPortalId === null) return true; // allowed to any portal => return true
|
||||
|
||||
$iRet = self::Login($iOnExit);
|
||||
|
||||
if ($iRet == self::EXIT_CODE_OK)
|
||||
$aPortalsConf = PortalDispatcherData::GetData();
|
||||
$aDispatchers = array();
|
||||
foreach($aPortalsConf as $sPortalId => $aConf)
|
||||
{
|
||||
if ($bMustBeAdmin && !UserRights::IsAdministrator())
|
||||
{
|
||||
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->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();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $bIsAllowedToPortalUsers, $iOnExit);
|
||||
$sHandlerClass = $aConf['handler'];
|
||||
$aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId);
|
||||
}
|
||||
if ($iOnExit == self::EXIT_RETURN)
|
||||
|
||||
if (array_key_exists($sRequestedPortalId, $aDispatchers) && $aDispatchers[$sRequestedPortalId]->IsUserAllowed())
|
||||
{
|
||||
return $iRet;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
foreach($aDispatchers as $sPortalId => $oDispatcher)
|
||||
{
|
||||
return $sMessage;
|
||||
if ($oDispatcher->IsUserAllowed()) return $oDispatcher->GetUrl();
|
||||
}
|
||||
}
|
||||
return false; // nothing matched !!
|
||||
}
|
||||
} // End of class
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Construction and display of the application's main menu
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -148,6 +148,7 @@ class ApplicationMenu
|
||||
// the menu already exists, let's combine the conditions that make it visible
|
||||
self::$aMenusIndex[$index]['node']->AddCondition($oMenuNode);
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
@@ -174,7 +175,7 @@ class ApplicationMenu
|
||||
{
|
||||
$oMenuNode = self::GetMenuNode($aMenu['index']);
|
||||
if (!$oMenuNode->IsEnabled()) continue; // Don't display a non-enabled menu
|
||||
$oPage->AddToMenu('<h3>'.$oMenuNode->GetTitle().'</h3>');
|
||||
$oPage->AddToMenu('<h3 id="'.utils::GetSafeId('AccordionMenu_'.$oMenuNode->GetMenuID()).'">'.$oMenuNode->GetTitle().'</h3>');
|
||||
$oPage->AddToMenu('<div>');
|
||||
$aChildren = self::GetChildren($aMenu['index']);
|
||||
if (count($aChildren) > 0)
|
||||
@@ -184,9 +185,12 @@ class ApplicationMenu
|
||||
$oPage->AddToMenu('</ul>');
|
||||
if ($bActive)
|
||||
{
|
||||
//$oPage->add_ready_script("$('#accordion').accordion('activate', $iAccordion);");
|
||||
// $oPage->add_ready_script("$('#accordion').accordion('option', {collapsible: true});"); // Make it auto-collapsible once it has been opened properly
|
||||
$oPage->add_ready_script("$('#accordion').accordion('option', {collapsible: true, active: $iAccordion});"); // Make it auto-collapsible once it has been opened properly
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
// Accordion Menu
|
||||
$("#accordion").css({display:'block'}).accordion({ header: "h3", navigation: true, heightStyle: "content", collapsible: true, active: $iAccordion, icons: false, animate:true }); // collapsible will be enabled once the item will be selected
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
$oPage->AddToMenu('</div>');
|
||||
@@ -214,11 +218,11 @@ class ApplicationMenu
|
||||
$sHyperlink = $oMenu->GetHyperlink($aExtraParams);
|
||||
if ($sHyperlink != '')
|
||||
{
|
||||
$oPage->AddToMenu('<li'.$sCSSClass.'><a href="'.$oMenu->GetHyperlink($aExtraParams).'">'.$oMenu->GetTitle().'</a></li>');
|
||||
$oPage->AddToMenu('<li id="'.utils::GetSafeId('AccordionMenu_'.$oMenu->GetMenuID()).'"'.$sCSSClass.'><a href="'.$oMenu->GetHyperlink($aExtraParams).'">'.$oMenu->GetTitle().'</a></li>');
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage->AddToMenu('<li'.$sCSSClass.'>'.$oMenu->GetTitle().'</li>');
|
||||
$oPage->AddToMenu('<li id="'.utils::GetSafeId('AccordionMenu_'.$oMenu->GetMenuID()).'"'.$sCSSClass.'>'.$oMenu->GetTitle().'</li>');
|
||||
}
|
||||
$aCurrentMenu = self::$aMenusIndex[$index];
|
||||
if ($iActiveMenu == $index)
|
||||
@@ -296,6 +300,16 @@ class ApplicationMenu
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
|
||||
if ($sMenuId === null)
|
||||
{
|
||||
$sMenuId = self::GetDefaultMenuId();
|
||||
}
|
||||
return $sMenuId;
|
||||
}
|
||||
|
||||
static public function GetDefaultMenuId()
|
||||
{
|
||||
static $sDefaultMenuId = null;
|
||||
if (is_null($sDefaultMenuId))
|
||||
{
|
||||
// Make sure the root menu is sorted on 'rank'
|
||||
usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
|
||||
@@ -303,10 +317,25 @@ class ApplicationMenu
|
||||
$aChildren = self::$aMenusIndex[$oFirstGroup->GetIndex()]['children'];
|
||||
usort($aChildren, array('ApplicationMenu', 'CompareOnRank'));
|
||||
$oMenuNode = self::GetMenuNode($aChildren[0]['index']);
|
||||
$sMenuId = $oMenuNode->GetMenuId();
|
||||
$sDefaultMenuId = $oMenuNode->GetMenuId();
|
||||
}
|
||||
return $sMenuId;
|
||||
}
|
||||
return $sDefaultMenuId;
|
||||
}
|
||||
|
||||
static public function GetRootMenuId($sMenuId)
|
||||
{
|
||||
$iMenuIndex = self::GetMenuIndexById($sMenuId);
|
||||
if ($iMenuIndex == -1)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
$oMenu = ApplicationMenu::GetMenuNode($iMenuIndex);
|
||||
while ($oMenu->GetParentIndex() != -1)
|
||||
{
|
||||
$oMenu = ApplicationMenu::GetMenuNode($oMenu->GetParentIndex());
|
||||
}
|
||||
return $oMenu->GetMenuId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,6 +368,7 @@ abstract class MenuNode
|
||||
{
|
||||
protected $sMenuId;
|
||||
protected $index;
|
||||
protected $iParentIndex;
|
||||
|
||||
/**
|
||||
* Properties reflecting how the node has been declared
|
||||
@@ -379,6 +409,7 @@ abstract class MenuNode
|
||||
public function __construct($sMenuId, $iParentIndex = -1, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
{
|
||||
$this->sMenuId = $sMenuId;
|
||||
$this->iParentIndex = $iParentIndex;
|
||||
$this->aReflectionProperties = array();
|
||||
if (strlen($sEnableClass) > 0)
|
||||
{
|
||||
@@ -403,7 +434,12 @@ abstract class MenuNode
|
||||
{
|
||||
return $this->sMenuId;
|
||||
}
|
||||
|
||||
|
||||
public function GetParentIndex()
|
||||
{
|
||||
return $this->iParentIndex;
|
||||
}
|
||||
|
||||
public function GetTitle()
|
||||
{
|
||||
return Dict::S("Menu:$this->sMenuId", str_replace('_', ' ', $this->sMenuId));
|
||||
@@ -411,7 +447,21 @@ abstract class MenuNode
|
||||
|
||||
public function GetLabel()
|
||||
{
|
||||
return Dict::S("Menu:$this->sMenuId+", "");
|
||||
$sRet = Dict::S("Menu:$this->sMenuId+", "");
|
||||
if ($sRet === '')
|
||||
{
|
||||
if ($this->iParentIndex != -1)
|
||||
{
|
||||
$oParentMenu = ApplicationMenu::GetMenuNode($this->iParentIndex);
|
||||
$sRet = $oParentMenu->GetTitle().' / '.$this->GetTitle();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = $this->GetTitle();
|
||||
}
|
||||
//$sRet = $this->GetTitle();
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetIndex()
|
||||
@@ -592,6 +642,7 @@ class OQLMenuNode extends MenuNode
|
||||
protected $sPageTitle;
|
||||
protected $sOQL;
|
||||
protected $bSearch;
|
||||
protected $bSearchFormOpen;
|
||||
|
||||
/**
|
||||
* Extra parameters to be passed to the display block to fine tune its appearence
|
||||
@@ -611,12 +662,20 @@ class OQLMenuNode extends MenuNode
|
||||
* @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them...
|
||||
* @return MenuNode
|
||||
*/
|
||||
public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
|
||||
public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null, $bSearchFormOpen = null)
|
||||
{
|
||||
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
|
||||
$this->sPageTitle = "Menu:$sMenuId+";
|
||||
$this->sOQL = $sOQL;
|
||||
$this->bSearch = $bSearch;
|
||||
if ($bSearchFormOpen == null)
|
||||
{
|
||||
$this->bSearchFormOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->bSearchFormOpen = $bSearchFormOpen;
|
||||
}
|
||||
$this->m_aParams = array();
|
||||
$this->aReflectionProperties['oql'] = $sOQL;
|
||||
$this->aReflectionProperties['do_search'] = $bSearch;
|
||||
@@ -645,13 +704,14 @@ class OQLMenuNode extends MenuNode
|
||||
Dict::S($this->sPageTitle),
|
||||
'Menu_'.$this->GetMenuId(),
|
||||
$this->bSearch, // Search pane
|
||||
true, // Search open
|
||||
$this->bSearchFormOpen, // Search open
|
||||
$oPage,
|
||||
array_merge($this->m_aParams, $aExtraParams)
|
||||
array_merge($this->m_aParams, $aExtraParams),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = array())
|
||||
public static function RenderOQLSearch($sOql, $sTitle, $sUsageId, $bSearchPane, $bSearchOpen, WebPage $oPage, $aExtraParams = array(), $bEnableBreadcrumb = false)
|
||||
{
|
||||
$sUsageId = utils::GetSafeId($sUsageId);
|
||||
$oSearch = DBObjectSearch::FromOQL($sOql);
|
||||
@@ -669,6 +729,15 @@ class OQLMenuNode extends MenuNode
|
||||
$aParams = array_merge(array('table_id' => $sUsageId), $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams);
|
||||
$oBlock->Display($oPage, $sUsageId);
|
||||
|
||||
if ($bEnableBreadcrumb && ($oPage instanceof iTopWebPage))
|
||||
{
|
||||
// Breadcrumb
|
||||
//$iCount = $oBlock->GetDisplayedCount();
|
||||
$sPageId = "ui-search-".$oSearch->GetClass();
|
||||
$sLabel = MetaModel::GetName($oSearch->GetClass());
|
||||
$oPage->SetBreadCrumbEntry($sPageId, $sLabel, $sTitle, '', '../images/breadcrumb-search.png');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,9 +768,11 @@ class SearchMenuNode extends MenuNode
|
||||
$this->sClass = $sClass;
|
||||
$this->aReflectionProperties['class'] = $sClass;
|
||||
}
|
||||
|
||||
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', utils::GetAbsoluteUrlAppRoot().'images/search.png');
|
||||
|
||||
$oSearch = new DBObjectSearch($this->sClass);
|
||||
$aParams = array_merge(array('open' => true, 'table_id' => 'Menu_'.utils::GetSafeId($this->GetMenuId())), $aExtraParams);
|
||||
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
|
||||
@@ -920,6 +991,29 @@ EOF
|
||||
$sId = addslashes($this->sMenuId);
|
||||
$oPage->add_ready_script("EditDashboard('$sId');");
|
||||
}
|
||||
else
|
||||
{
|
||||
$oParentMenu = ApplicationMenu::GetMenuNode($this->iParentIndex);
|
||||
$sParentTitle = $oParentMenu->GetTitle();
|
||||
$sThisTitle = $this->GetTitle();
|
||||
if ($sParentTitle != $sThisTitle)
|
||||
{
|
||||
$sDescription = $sParentTitle.' / '.$sThisTitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDescription = $sThisTitle;
|
||||
}
|
||||
if ($this->sMenuId == ApplicationMenu::GetDefaultMenuId())
|
||||
{
|
||||
$sIcon = '../images/breadcrumb_home.png';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sIcon = '../images/breadcrumb-dashboard.png';
|
||||
}
|
||||
$oPage->SetBreadCrumbEntry("ui-dashboard-".$this->sMenuId, $this->GetTitle(), $sDescription, '', $sIcon);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -950,7 +1044,7 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
$oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
|
||||
throw new Exception("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class NiceWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -33,23 +33,23 @@ class NiceWebPage extends WebPage
|
||||
var $m_aReadyScripts;
|
||||
var $m_sRootUrl;
|
||||
|
||||
public function __construct($s_title)
|
||||
public function __construct($s_title, $bPrintable = false)
|
||||
{
|
||||
parent::__construct($s_title);
|
||||
parent::__construct($s_title, $bPrintable);
|
||||
$this->m_aReadyScripts = array();
|
||||
$this->add_linked_script("../js/jquery-1.10.0.min.js");
|
||||
$this->add_linked_script("../js/jquery-migrate-1.2.1.min.js"); // Needed since many other plugins still rely on oldies like $.browser
|
||||
$this->add_linked_stylesheet('../css/ui-lightness/jquery-ui-1.10.3.custom.min.css');
|
||||
$this->add_linked_script('../js/jquery-ui-1.10.3.custom.min.js');
|
||||
$this->add_linked_script("../js/hovertip.js");
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-1.10.0.min.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate-1.2.1.min.js'); // Needed since many other plugins still rely on oldies like $.browser
|
||||
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/ui-lightness/jquery-ui-1.10.3.custom.min.css');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-1.10.3.custom.min.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/hovertip.js');
|
||||
// table sorting
|
||||
$this->add_linked_script("../js/jquery.tablesorter.js");
|
||||
$this->add_linked_script("../js/jquery.tablesorter.pager.js");
|
||||
$this->add_linked_script("../js/jquery.tablehover.js");
|
||||
$this->add_linked_script('../js/field_sorter.js');
|
||||
$this->add_linked_script('../js/datatable.js');
|
||||
$this->add_linked_script("../js/jquery.positionBy.js");
|
||||
$this->add_linked_script("../js/jquery.popupmenu.js");
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.pager.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablehover.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_sorter.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/datatable.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.positionBy.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.popupmenu.js');
|
||||
$this->add_ready_script(
|
||||
<<< EOF
|
||||
//add new widget called TruncatedList to properly display truncated lists when they are sorted
|
||||
@@ -94,7 +94,7 @@ class NiceWebPage extends WebPage
|
||||
$("table.listResults").tableHover(); // hover tables
|
||||
EOF
|
||||
);
|
||||
$this->add_linked_stylesheet("../css/light-grey.css");
|
||||
$this->add_saas("css/light-grey.scss");
|
||||
|
||||
$this->m_sRootUrl = $this->GetAbsoluteUrlAppRoot();
|
||||
$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
|
||||
|
||||
194
application/pdfpage.class.inc.php
Normal file
194
application/pdfpage.class.inc.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
require_once(APPROOT.'lib/tcpdf/tcpdf.php');
|
||||
|
||||
/**
|
||||
* Custom class derived from TCPDF for providing custom headers and footers
|
||||
* @author denis
|
||||
*
|
||||
*/
|
||||
class iTopPDF extends TCPDF
|
||||
{
|
||||
protected $sDocumentTitle;
|
||||
|
||||
public function SetDocumentTitle($sDocumentTitle)
|
||||
{
|
||||
$this->sDocumentTitle = $sDocumentTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the custom header. Called for each new page.
|
||||
* @see TCPDF::Header()
|
||||
*/
|
||||
public function Header()
|
||||
{
|
||||
// Title
|
||||
// Set font
|
||||
$this->SetFont('dejavusans', 'B', 10);
|
||||
|
||||
$iPageNumberWidth = 25;
|
||||
$aMargins = $this->getMargins();
|
||||
|
||||
// Display the title (centered)
|
||||
$this->SetXY($aMargins['left'] + $iPageNumberWidth, 0);
|
||||
$this->MultiCell($this->getPageWidth() - $aMargins['left'] - $aMargins['right'] - 2*$iPageNumberWidth, 15, $this->sDocumentTitle, 0, 'C', false, 0 /* $ln */, '', '', true, 0, false, true, 15, 'M' /* $valign */);
|
||||
$this->SetFont('dejavusans', '', 10);
|
||||
|
||||
// Display the page number (right aligned)
|
||||
// Warning: the 'R'ight alignment does not work when using placeholders like $this->getAliasNumPage() or $this->getAliasNbPages()
|
||||
$this->MultiCell($iPageNumberWidth, 15, 'Page '.$this->page, 0, 'R', false, 0 /* $ln */, '', '', true, 0, false, true, 15, 'M' /* $valign */);
|
||||
|
||||
// Branding logo
|
||||
$sBrandingIcon = APPROOT.'images/itop-logo.png';
|
||||
if (file_exists(MODULESROOT.'branding/main-logo.png'))
|
||||
{
|
||||
$sBrandingIcon = MODULESROOT.'branding/main-logo.png';
|
||||
}
|
||||
$this->Image($sBrandingIcon, $aMargins['left'], 5, 0, 10);
|
||||
}
|
||||
|
||||
// Page footer
|
||||
public function Footer()
|
||||
{
|
||||
// No footer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special class of WebPage for printing into a PDF document
|
||||
*/
|
||||
class PDFPage extends WebPage
|
||||
{
|
||||
/**
|
||||
* Instance of the TCPDF object for creating the PDF
|
||||
* @var TCPDF
|
||||
*/
|
||||
protected $oPdf;
|
||||
|
||||
public function __construct($s_title, $sPageFormat = 'A4', $sPageOrientation = 'L')
|
||||
{
|
||||
parent::__construct($s_title);
|
||||
define(K_PATH_FONTS, APPROOT.'lib/tcpdf/fonts');
|
||||
$this->oPdf = new iTopPDF($sPageOrientation, 'mm', $sPageFormat, true, 'UTF-8', false);
|
||||
|
||||
// set document information
|
||||
$this->oPdf->SetCreator(PDF_CREATOR);
|
||||
$this->oPdf->SetAuthor('iTop');
|
||||
$this->oPdf->SetTitle($s_title);
|
||||
$this->oPdf->SetDocumentTitle($s_title);
|
||||
|
||||
$this->oPdf->setFontSubsetting(true);
|
||||
|
||||
// Set font
|
||||
// dejavusans is a UTF-8 Unicode font. Standard PDF fonts like helvetica or times new roman are NOT UTF-8
|
||||
$this->oPdf->SetFont('dejavusans', '', 10, '', true);
|
||||
|
||||
// set auto page breaks
|
||||
$this->oPdf->SetAutoPageBreak(true, 15); // 15 mm break margin at the bottom
|
||||
$this->oPdf->SetTopMargin(15);
|
||||
|
||||
// Add a page, we're ready to start
|
||||
$this->oPdf->AddPage();
|
||||
|
||||
$this->SetContentDisposition('inline', $s_title.'.pdf');
|
||||
$this->SetDefaultStyle();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a default style (suitable for printing) to be included each time $this->oPdf->writeHTML() is called
|
||||
*/
|
||||
protected function SetDefaultStyle()
|
||||
{
|
||||
$this->add_style(
|
||||
<<<EOF
|
||||
table {
|
||||
padding: 2pt;
|
||||
}
|
||||
table.listResults td {
|
||||
border: 0.5pt solid #000 ;
|
||||
}
|
||||
table.listResults th {
|
||||
background-color: #eee;
|
||||
border: 0.5pt solid #000 ;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
table.section td {
|
||||
vertical-align: middle;
|
||||
font-size: 10pt;
|
||||
background-color:#eee;
|
||||
}
|
||||
td.icon {
|
||||
width: 30px;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access to the underlying TCPDF object
|
||||
* @return TCPDF
|
||||
*/
|
||||
public function get_tcpdf()
|
||||
{
|
||||
$this->flush();
|
||||
return $this->oPdf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the currently buffered HTML content into the PDF. This can be useful:
|
||||
* - to sync the flow in case you want to access the underlying TCPDF object for some specific/graphic output
|
||||
* - to process the HTML by smaller chunks instead of processing the whole page at once for performance reasons
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
if (strlen($this->s_content) > 0)
|
||||
{
|
||||
$sHtml = '';
|
||||
if (count($this->a_styles) > 0)
|
||||
{
|
||||
$sHtml .= "<style>\n".implode("\n", $this->a_styles)."\n</style>\n";
|
||||
}
|
||||
$sHtml .= $this->s_content;
|
||||
$this->oPdf->writeHTML($sHtml); // The style(s) must be supplied each time we call writeHtml
|
||||
$this->s_content = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the page is a PDF page
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_pdf()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the PDF document and returns the PDF content as a string
|
||||
* @return string
|
||||
* @see WebPage::output()
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
$this->add_header('Content-type: application/x-pdf');
|
||||
if (!empty($this->sContentDisposition))
|
||||
{
|
||||
$this->add_header('Content-Disposition: '.$this->sContentDisposition.'; filename="'.$this->sContentFileName.'"');
|
||||
}
|
||||
foreach($this->a_headers as $s_header)
|
||||
{
|
||||
header($s_header);
|
||||
}
|
||||
$this->flush();
|
||||
echo $this->oPdf->Output($this->s_title.'.pdf', 'S');
|
||||
}
|
||||
|
||||
public function get_pdf()
|
||||
{
|
||||
$this->flush();
|
||||
return $this->oPdf->Output($this->s_title.'.pdf', 'S');
|
||||
}
|
||||
}
|
||||
66
application/portaldispatcher.class.inc.php
Normal file
66
application/portaldispatcher.class.inc.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
class PortalDispatcher
|
||||
{
|
||||
protected $sPortalid;
|
||||
protected $aData;
|
||||
|
||||
public function __construct($sPortalId)
|
||||
{
|
||||
$this->sPortalid = $sPortalId;
|
||||
$this->aData = PortalDispatcherData::GetData($sPortalId);
|
||||
}
|
||||
|
||||
public function IsUserAllowed()
|
||||
{
|
||||
$bRet = true;
|
||||
$aProfiles = UserRights::ListProfiles();
|
||||
|
||||
foreach($this->aData['deny'] as $sDeniedProfile)
|
||||
{
|
||||
// If one denied profile is present, it's enough => return false
|
||||
if (in_array($sDeniedProfile, $aProfiles))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// If there are some "allow" profiles, then by default the result is false
|
||||
// since the user must have at least one of the profiles to be allowed
|
||||
if (count($this->aData['allow']) > 0)
|
||||
{
|
||||
$bRet = false;
|
||||
}
|
||||
foreach($this->aData['allow'] as $sAllowProfile)
|
||||
{
|
||||
// If one "allow" profile is present, it's enough => return true
|
||||
if (in_array($sAllowProfile, $aProfiles))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
public function GetURL()
|
||||
{
|
||||
$aOverloads = MetaModel::GetConfig()->Get('portal_dispatch_urls');
|
||||
if (array_key_exists($this->sPortalid, $aOverloads))
|
||||
{
|
||||
$sRet = $aOverloads[$this->sPortalid];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = utils::GetAbsoluteUrlAppRoot().$this->aData['url'];
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetLabel()
|
||||
{
|
||||
return Dict::S('portal:'.$this->sPortalid);
|
||||
}
|
||||
|
||||
public function GetRank()
|
||||
{
|
||||
return $this->aData['rank'];
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Class PortalWebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2013 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -49,9 +49,12 @@ class PortalWebPage extends NiceWebPage
|
||||
*/
|
||||
protected $m_sWelcomeMsg;
|
||||
protected $m_aMenuButtons;
|
||||
protected $m_oCtx;
|
||||
|
||||
|
||||
public function __construct($sTitle, $sAlternateStyleSheet = '')
|
||||
{
|
||||
$this->m_oCtx = new ContextTag('GUI:Portal');
|
||||
$this->m_sWelcomeMsg = '';
|
||||
$this->m_aMenuButtons = array();
|
||||
parent::__construct($sTitle);
|
||||
@@ -88,8 +91,94 @@ class PortalWebPage extends NiceWebPage
|
||||
$this->add_linked_script("../js/forms-json-utils.js");
|
||||
$this->add_linked_script("../js/swfobject.js");
|
||||
$this->add_linked_script("../js/jquery.qtip-1.0.min.js");
|
||||
$this->add_linked_script('../js/jquery.multiselect.min.js');
|
||||
$this->add_linked_script('../js/jquery.multiselect.js');
|
||||
$this->add_linked_script("../js/ajaxfileupload.js");
|
||||
$this->add_linked_script("../js/ckeditor/ckeditor.js");
|
||||
$this->add_linked_script("../js/ckeditor/adapters/jquery.js");
|
||||
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon.js");
|
||||
$this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js");
|
||||
$this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css");
|
||||
|
||||
$sJSDisconnectedMessage = json_encode(Dict::S('UI:DisconnectedDlgMessage'));
|
||||
$sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle'));
|
||||
$sJSLoginAgain = json_encode(Dict::S('UI:LoginAgain'));
|
||||
$sJSStayOnThePage = json_encode(Dict::S('UI:StayOnThePage'));
|
||||
$aDaysMin = array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'),
|
||||
Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min'));
|
||||
$aMonthsShort = array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
|
||||
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short'));
|
||||
$sTimeFormat = AttributeDateTime::GetFormat()->ToTimeFormat();
|
||||
$oTimeFormat = new DateTimeFormat($sTimeFormat);
|
||||
$sJSLangShort = json_encode(strtolower(substr(Dict::GetUserLanguage(), 0, 2)));
|
||||
|
||||
// Date picker options
|
||||
$aPickerOptions = array(
|
||||
'showOn' => 'button',
|
||||
'buttonImage' => '../images/calendar.png',
|
||||
'buttonImageOnly' => true,
|
||||
'dateFormat' => AttributeDate::GetFormat()->ToDatePicker(),
|
||||
'constrainInput' => false,
|
||||
'changeMonth' => true,
|
||||
'changeYear' => true,
|
||||
'dayNamesMin' => $aDaysMin,
|
||||
'monthNamesShort' => $aMonthsShort,
|
||||
'firstDay' => (int) Dict::S('Calendar-FirstDayOfWeek'),
|
||||
);
|
||||
$sJSDatePickerOptions = json_encode($aPickerOptions);
|
||||
|
||||
// Time picker additional options
|
||||
$aPickerOptions['showOn'] = '';
|
||||
$aPickerOptions['buttonImage'] = null;
|
||||
$aPickerOptions['timeFormat'] = $oTimeFormat->ToDatePicker();
|
||||
$aPickerOptions['controlType'] = 'select';
|
||||
$aPickerOptions['closeText'] = Dict::S('UI:Button:Ok');
|
||||
$sJSDateTimePickerOptions = json_encode($aPickerOptions);
|
||||
if ($sJSLangShort != '"en"')
|
||||
{
|
||||
// More options that cannot be passed via json_encode since they must be evaluated client-side
|
||||
$aMoreJSOptions = ",
|
||||
'timeText': $.timepicker.regional[$sJSLangShort].timeText,
|
||||
'hourText': $.timepicker.regional[$sJSLangShort].hourText,
|
||||
'minuteText': $.timepicker.regional[$sJSLangShort].minuteText,
|
||||
'secondText': $.timepicker.regional[$sJSLangShort].secondText,
|
||||
'currentText': $.timepicker.regional[$sJSLangShort].currentText
|
||||
}";
|
||||
$sJSDateTimePickerOptions = substr($sJSDateTimePickerOptions, 0, -1).$aMoreJSOptions;
|
||||
}
|
||||
$this->add_script(
|
||||
<<< EOF
|
||||
function PrepareWidgets()
|
||||
{
|
||||
// note: each action implemented here must be idempotent,
|
||||
// because this helper function might be called several times on a given page
|
||||
|
||||
$(".date-pick").datepicker($sJSDatePickerOptions);
|
||||
|
||||
// Hack for the date and time picker addon issue on Chrome (see #1305)
|
||||
// The workaround is to instantiate the widget on demand
|
||||
// It relies on the same markup, thus reverting to the original implementation should be straightforward
|
||||
$(".datetime-pick:not(.is-widget-ready)").each(function(){
|
||||
var oInput = this;
|
||||
$(oInput).addClass('is-widget-ready');
|
||||
$('<img class="datetime-pick-button" src="../images/calendar.png">')
|
||||
.insertAfter($(this))
|
||||
.on('click', function(){
|
||||
$(oInput)
|
||||
.datetimepicker($sJSDateTimePickerOptions)
|
||||
.datetimepicker('show')
|
||||
.datetimepicker('option', 'onClose', function(dateText,inst){
|
||||
$(oInput).datetimepicker('destroy');
|
||||
})
|
||||
.on('click keypress', function(){
|
||||
$(oInput).datetimepicker('hide');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
EOF
|
||||
);
|
||||
|
||||
$this->add_ready_script(
|
||||
<<<EOF
|
||||
try
|
||||
@@ -130,28 +219,29 @@ try
|
||||
}
|
||||
});
|
||||
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
|
||||
$(".datetime-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd 00:00:00',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
PrepareWidgets();
|
||||
|
||||
//$('.resizable').resizable(); // Make resizable everything that claims to be resizable !
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry').toggle(); });
|
||||
$('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry,.caselog_entry_html').toggle(); });
|
||||
|
||||
$(document).ajaxSend(function(event, jqxhr, options) {
|
||||
jqxhr.setRequestHeader('X-Combodo-Ajax', 'true');
|
||||
});
|
||||
$(document).ajaxError(function(event, jqxhr, options) {
|
||||
if (jqxhr.status == 401)
|
||||
{
|
||||
$('<div>'+$sJSDisconnectedMessage+'</div>').dialog({
|
||||
modal:true,
|
||||
title: $sJSTitle,
|
||||
close: function() { $(this).remove(); },
|
||||
minWidth: 400,
|
||||
buttons: [
|
||||
{ text: $sJSLoginAgain, click: function() { window.location.href= GetAbsoluteUrlAppRoot()+'pages/UI.php' } },
|
||||
{ text: $sJSStayOnThePage, click: function() { $(this).dialog('close'); } }
|
||||
]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
@@ -222,7 +312,7 @@ EOF
|
||||
{
|
||||
var form = $('FORM');
|
||||
form.unbind('submit'); // De-activate validation
|
||||
window.location.href = '?operation=';
|
||||
window.location.href = window.location.href.replace(/operation=[^&]*&?/, '');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -231,6 +321,20 @@ EOF
|
||||
var next_step = $('input[id=next_step]');
|
||||
next_step.val(sStep);
|
||||
}
|
||||
|
||||
// For disabling the CKEditor at init time when the corresponding textarea is disabled !
|
||||
CKEDITOR.plugins.add( 'disabler',
|
||||
{
|
||||
init : function( editor )
|
||||
{
|
||||
editor.on( 'instanceReady', function(e)
|
||||
{
|
||||
e.removeListener();
|
||||
$('#'+ editor.name).trigger('update');
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
EOF
|
||||
);
|
||||
|
||||
@@ -283,7 +387,7 @@ EOF
|
||||
{
|
||||
$sReadOnly = Dict::S('UI:AccessRO-Users');
|
||||
$sAdminMessage = trim(MetaModel::GetConfig()->Get('access_message'));
|
||||
$sApplicationBanner .= '<div id="admin-banner">';
|
||||
$sApplicationBanner .= '<div class="app-message">';
|
||||
$sApplicationBanner .= '<img src="../images/locked.png" style="vertical-align:middle;">';
|
||||
$sApplicationBanner .= ' <b>'.$sReadOnly.'</b>';
|
||||
if (strlen($sAdminMessage) > 0)
|
||||
@@ -296,7 +400,7 @@ EOF
|
||||
$sMenu = '';
|
||||
if ($this->m_bEnableDisconnectButton)
|
||||
{
|
||||
$this->AddMenuButton('logoff', 'Portal:Disconnect', utils::GetAbsoluteUrlAppRoot().'pages/logoff.php'); // This menu is always present and is the last one
|
||||
$this->AddMenuButton('logoff', 'Portal:Disconnect', utils::GetAbsoluteUrlAppRoot().'pages/logoff.php?operation=do_logoff'); // This menu is always present and is the last one
|
||||
}
|
||||
foreach($this->m_aMenuButtons as $aMenuItem)
|
||||
{
|
||||
@@ -712,7 +816,7 @@ EOF
|
||||
$sClass = get_class($oObj);
|
||||
|
||||
$sStimulus = trim(utils::ReadPostedParam('apply_stimulus', ''));
|
||||
$sTargetState = '';
|
||||
$aExpectedAttributes = array();
|
||||
if (!empty($sStimulus))
|
||||
{
|
||||
// Compute the target state
|
||||
@@ -722,10 +826,10 @@ EOF
|
||||
{
|
||||
throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel()));
|
||||
}
|
||||
$sTargetState = $aTransitions[$sStimulus]['target_state'];
|
||||
}
|
||||
$aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /*, current state*/);
|
||||
}
|
||||
|
||||
$oObj->UpdateObjectFromPostedForm('' /* form prefix */, $aAttList, $sTargetState);
|
||||
$oObj->UpdateObjectFromPostedForm('' /* form prefix */, $aAttList, $aExpectedAttributes);
|
||||
|
||||
// Optional: apply a stimulus
|
||||
//
|
||||
@@ -755,6 +859,17 @@ EOF
|
||||
|
||||
$this->p("<h1>".Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())."</h1>\n");
|
||||
}
|
||||
$bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled');
|
||||
if ($bLockEnabled)
|
||||
{
|
||||
// Release the concurrent lock, if any
|
||||
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data');
|
||||
if ($sOwnershipToken !== null)
|
||||
{
|
||||
// We're done, let's release the lock
|
||||
iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -826,7 +941,7 @@ EOF
|
||||
}
|
||||
|
||||
$sStepHistory = implode(',', $aPreviousSteps);
|
||||
$this->add("<input type=\"hidden\" id=\"step_history\" name=\"step_history\" value=\"$sStepHistory\">");
|
||||
$this->add("<input type=\"hidden\" id=\"step_history\" name=\"step_history\" value=\"".htmlentities($sStepHistory, ENT_QUOTES, 'UTF-8')."\">");
|
||||
|
||||
if (!is_null($sNextStep))
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -22,7 +22,7 @@
|
||||
* Application internal events
|
||||
* There is also a file log
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -92,7 +92,18 @@ class QueryOQL extends Query
|
||||
|
||||
if (!$bEditMode)
|
||||
{
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?format=spreadsheet&login_mode=basic&query='.$this->GetKey();
|
||||
$sFields = trim($this->Get('fields'));
|
||||
$bExportV1Recommended = ($sFields == '');
|
||||
if ($bExportV1Recommended)
|
||||
{
|
||||
$oFieldAttDef = MetaModel::GetAttributeDef('QueryOQL', 'fields');
|
||||
$oPage->add('<div class="message message_error" style="padding-left: 30px;"><div style="padding: 10px;">'.Dict::Format('UI:Query:UrlV1', $oFieldAttDef->GetLabel()).'</div></div>');
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?format=spreadsheet&login_mode=basic&query='.$this->GetKey();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php?format=spreadsheet&login_mode=basic&date_format='.urlencode((string)AttributeDateTime::GetFormat()).'&query='.$this->GetKey();
|
||||
}
|
||||
$sOql = $this->Get('oql');
|
||||
$sMessage = null;
|
||||
try
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* Persistent class Shortcut and derived
|
||||
* Shortcuts of any kind
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -200,7 +200,7 @@ class ShortcutOQL extends Shortcut
|
||||
$bSearchOpen = false;
|
||||
try
|
||||
{
|
||||
OQLMenuNode::RenderOQLSearch($this->Get('oql'), $this->Get('name'), 'shortcut_'.$this->GetKey(), $bSearchPane, $bSearchOpen, $oPage, $aExtraParams);
|
||||
OQLMenuNode::RenderOQLSearch($this->Get('oql'), $this->Get('name'), 'shortcut_'.$this->GetKey(), $bSearchPane, $bSearchOpen, $oPage, $aExtraParams, true);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
|
||||
@@ -1,531 +0,0 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 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/>
|
||||
|
||||
/**
|
||||
* SqlBlock - display tables or charts, given an SQL query - use cautiously!
|
||||
*
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
require_once(APPROOT.'/application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
|
||||
require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php');
|
||||
|
||||
/**
|
||||
* Helper class to design optimized dashboards, based on an SQL query
|
||||
*
|
||||
*/
|
||||
class SqlBlock
|
||||
{
|
||||
protected $m_sQuery;
|
||||
protected $m_aColumns;
|
||||
protected $m_sTitle;
|
||||
protected $m_sType;
|
||||
protected $m_aParams;
|
||||
|
||||
public function __construct($sQuery, $aColumns, $sTitle, $sType, $aParams = array())
|
||||
{
|
||||
$this->m_sQuery = $sQuery;
|
||||
$this->m_aColumns = $aColumns;
|
||||
$this->m_sTitle = $sTitle;
|
||||
$this->m_sType = $sType;
|
||||
$this->m_aParams = $aParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SqlBlock object from an XML template
|
||||
/*
|
||||
*
|
||||
* <sqlblock>
|
||||
* <sql>SELECT date_format(start_date, '%d') AS Date, count(*) AS Count FROM ticket WHERE DATE_SUB(NOW(), INTERVAL 15 DAY) < start_date AND finalclass = 'UserIssue' GROUP BY date_format(start_date, '%d') AND $CONDITION(param1, ticket.org_id)$</sql>
|
||||
* <type>table</type>
|
||||
* <title>UserRequest:Overview-Title</title>
|
||||
* <parameter>
|
||||
* <name>param1</name>
|
||||
* <type>context</type>
|
||||
* <mapping>org_id</mapping>
|
||||
* </parameter>
|
||||
* <column>
|
||||
* <name>Date</name>
|
||||
* <label>UserRequest:Overview-Date</label>
|
||||
* <drilldown></drilldown>
|
||||
* </column>
|
||||
* <column>
|
||||
* <name>Count</name>
|
||||
* <label>UserRequest:Overview-Count</label>
|
||||
* <drilldown>SELECT UserIssue WHERE date_format(start_date, '%d') = :Date</drilldown>
|
||||
* </column>
|
||||
* </sqlblock>
|
||||
*
|
||||
* Tags
|
||||
* - sql: a (My)SQL query. Do not forget to use html entities (e.g. < for <)
|
||||
* - type: table (default), bars or pie. If bars or pie is selected only the two first columns are taken into account.
|
||||
* - title: optional title, typed in clear or given as a dictionnary entry
|
||||
* - parameter: specifies how to map the context parameters (namely org_id) to a given named parameter in the query.
|
||||
* The expression $CONDITION(<param_name>, <sql_column_name>) will be automatically replaced by:
|
||||
* either the string "1" if there is no restriction on the organisation in iTop
|
||||
* or the string "(<sql_column_name>=<value_of_org_id>)" if there is a limitation to one organizations in iTop
|
||||
* or the string "(<sql_column_name> IN (<values_of_org_id>))" if there is a limitation to a given set of organizations in iTop
|
||||
* - column: specification of a column (not displayed if omitted)
|
||||
* - column / name: name of the column in the SQL query (use aliases)
|
||||
* - column / label: label, typed in clear or given as a dictionnary entry
|
||||
* - column / drilldown: NOT IMPLEMENTED YET - OQL with parameters corresponding to column names (in the query)
|
||||
*
|
||||
* @param $sTemplate string The XML template
|
||||
* @return DisplayBlock The DisplayBlock object, or null if the template is invalid
|
||||
*/
|
||||
public static function FromTemplate($sTemplate)
|
||||
{
|
||||
$oXml = simplexml_load_string('<root>'.$sTemplate.'</root>', 'SimpleXMLElement', LIBXML_NOCDATA);
|
||||
if (false)
|
||||
{
|
||||
// Debug
|
||||
echo "<pre>\n";
|
||||
print_r($oXml);
|
||||
echo "</pre>\n";
|
||||
}
|
||||
|
||||
if (isset($oXml->title))
|
||||
{
|
||||
$sTitle = (string)$oXml->title;
|
||||
}
|
||||
if (isset($oXml->type))
|
||||
{
|
||||
$sType = (string)$oXml->type;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sType = 'table';
|
||||
}
|
||||
if (!isset($oXml->sql))
|
||||
{
|
||||
throw new Exception('Missing tag "sql" in sqlblock');
|
||||
}
|
||||
$sQuery = (string)$oXml->sql;
|
||||
|
||||
$aColumns = array();
|
||||
if (isset($oXml->column))
|
||||
{
|
||||
foreach ($oXml->column AS $oColumnData)
|
||||
{
|
||||
if (!isset($oColumnData->name))
|
||||
{
|
||||
throw new Exception("Missing tag 'name' in sqlblock/column");
|
||||
}
|
||||
$sName = (string) $oColumnData->name;
|
||||
if (strlen($sName) == 0)
|
||||
{
|
||||
throw new Exception("Empty tag 'name' in sqlblock/column");
|
||||
}
|
||||
|
||||
$aColumns[$sName] = array();
|
||||
if (isset($oColumnData->label))
|
||||
{
|
||||
$sLabel = (string)$oColumnData->label;
|
||||
if (strlen($sLabel) > 0)
|
||||
{
|
||||
$aColumns[$sName]['label'] = Dict::S($sLabel);
|
||||
}
|
||||
}
|
||||
if (isset($oColumnData->drilldown))
|
||||
{
|
||||
$sDrillDown = (string)$oColumnData->drilldown;
|
||||
if (strlen($sDrillDown) > 0)
|
||||
{
|
||||
$aColumns[$sName]['drilldown'] = $sDrillDown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$aParams = array();
|
||||
if (isset($oXml->parameter))
|
||||
{
|
||||
foreach ($oXml->parameter AS $oParamData)
|
||||
{
|
||||
if (!isset($oParamData->name))
|
||||
{
|
||||
throw new Exception("Missing tag 'name' for parameter in sqlblock/column");
|
||||
}
|
||||
$sName = (string) $oParamData->name;
|
||||
if (strlen($sName) == 0)
|
||||
{
|
||||
throw new Exception("Empty tag 'name' for parameter in sqlblock/column");
|
||||
}
|
||||
if (!isset($oParamData->mapping))
|
||||
{
|
||||
throw new Exception("Missing tag 'mapping' for parameter in sqlblock/column");
|
||||
}
|
||||
$sMapping = (string) $oParamData->mapping;
|
||||
if (strlen($sMapping) == 0)
|
||||
{
|
||||
throw new Exception("Empty tag 'mapping' for parameter in sqlblock/column");
|
||||
}
|
||||
|
||||
if (isset($oParamData->type))
|
||||
{
|
||||
$sParamType = $oParamData->type;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sParamType = 'context';
|
||||
}
|
||||
$aParams[$sName] = array('mapping' => $sMapping, 'type' => $sParamType);
|
||||
}
|
||||
}
|
||||
|
||||
return new SqlBlock($sQuery, $aColumns, $sTitle, $sType, $aParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the defined parameters into the SQL query
|
||||
* @return string the SQL query to execute
|
||||
*/
|
||||
public function BuildQuery()
|
||||
{
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sQuery = $this->m_sQuery;
|
||||
$sQuery = str_replace('$DB_PREFIX$', MetaModel::GetConfig()->GetDBSubname(), $sQuery); // put the tables DB prefix (if any)
|
||||
foreach($this->m_aParams as $sName => $aParam)
|
||||
{
|
||||
if ($aParam['type'] == 'context')
|
||||
{
|
||||
$sSearchPattern = '/\$CONDITION\('.$sName.',([^\)]+)\)\$/';
|
||||
$value = $oAppContext->GetCurrentValue($aParam['mapping']);
|
||||
if (empty($value))
|
||||
{
|
||||
$sSQLExpr = '(1)';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Special case for managing the hierarchy of organizations
|
||||
if (($aParam['mapping'] == 'org_id') && ( MetaModel::IsValidClass('Organization')))
|
||||
{
|
||||
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
|
||||
if ($sHierarchicalKeyCode != false)
|
||||
{
|
||||
// organizations are in hierarchy... gather all the orgs below the given one...
|
||||
$sOQL = "SELECT Organization AS node JOIN Organization AS root ON node.$sHierarchicalKeyCode BELOW root.id WHERE root.id = :value";
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('value' => $value));
|
||||
$aOrgIds = array();
|
||||
while($oOrg = $oSet->Fetch())
|
||||
{
|
||||
$aOrgIds[]= $oOrg->GetKey();
|
||||
}
|
||||
$sSQLExpr = '($1 IN('.implode(',', $aOrgIds).'))';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
|
||||
}
|
||||
}
|
||||
$sQuery = preg_replace($sSearchPattern, $sSQLExpr, $sQuery);
|
||||
}
|
||||
}
|
||||
return $sQuery;
|
||||
}
|
||||
|
||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||
{
|
||||
if (empty($aExtraParams['currentId']))
|
||||
{
|
||||
$sId = 'sqlblock_'.$oPage->GetUniqueId(); // Works only if the page is not an Ajax one !
|
||||
}
|
||||
else
|
||||
{
|
||||
$sId = $aExtraParams['currentId'];
|
||||
}
|
||||
// $oPage->add($this->GetRenderContent($oPage, $aExtraParams, $sId));
|
||||
|
||||
$sQuery = $this->BuildQuery();
|
||||
$res = CMDBSource::Query($sQuery);
|
||||
$aQueryCols = CMDBSource::GetColumns($res);
|
||||
|
||||
// Prepare column definitions (check + give default values)
|
||||
//
|
||||
foreach($this->m_aColumns as $sName => $aColumnData)
|
||||
{
|
||||
if (!in_array($sName, $aQueryCols))
|
||||
{
|
||||
throw new Exception("Unknown column name '$sName' in sqlblock column");
|
||||
}
|
||||
if (!isset($aColumnData['label']))
|
||||
{
|
||||
$this->m_aColumns[$sName]['label'] = $sName;
|
||||
}
|
||||
if (isset($aColumnData['drilldown']) && !empty($aColumnData['drilldown']))
|
||||
{
|
||||
// Check if the OQL is valid
|
||||
try
|
||||
{
|
||||
$this->m_aColumns[$sName]['filter'] = DBObjectSearch::FromOQL($aColumnData['drilldown']);
|
||||
}
|
||||
catch(OQLException $e)
|
||||
{
|
||||
unset($aColumnData['drilldown']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen($this->m_sTitle) > 0)
|
||||
{
|
||||
$oPage->add("<h2>".Dict::S($this->m_sTitle)."</h2>\n");
|
||||
}
|
||||
|
||||
switch ($this->m_sType)
|
||||
{
|
||||
case 'bars':
|
||||
case 'pie':
|
||||
$aColNames = array_keys($this->m_aColumns);
|
||||
$sXColName = $aColNames[0];
|
||||
$sYColName = $aColNames[1];
|
||||
$aData = array();
|
||||
$aRows = array();
|
||||
while($aRow = CMDBSource::FetchArray($res))
|
||||
{
|
||||
$aData[$aRow[$sXColName]] = $aRow[$sYColName];
|
||||
$aRows[$aRow[$sXColName]] = $aRow;
|
||||
}
|
||||
$this->RenderChart($oPage, $sId, $aData, $this->m_aColumns[$sYColName]['drilldown'], $aRows);
|
||||
break;
|
||||
|
||||
default:
|
||||
case 'table':
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
if (!empty($sContext))
|
||||
{
|
||||
$sContext = '&'.$sContext;
|
||||
}
|
||||
$aDisplayConfig = array();
|
||||
foreach($this->m_aColumns as $sName => $aColumnData)
|
||||
{
|
||||
$aDisplayConfig[$sName] = array('label' => $aColumnData['label'], 'description' => '');
|
||||
}
|
||||
|
||||
$aDisplayData = array();
|
||||
while($aRow = CMDBSource::FetchArray($res))
|
||||
{
|
||||
$aSQLColNames = array_keys($aRow);
|
||||
$aDisplayRow = array();
|
||||
foreach($this->m_aColumns as $sName => $aColumnData)
|
||||
{
|
||||
if (isset($aColumnData['filter']))
|
||||
{
|
||||
$sFilter = $aColumnData['drilldown'];
|
||||
$sClass = $aColumnData['filter']->GetClass();
|
||||
$sFilter = str_replace('SELECT '.$sClass, '', $sFilter);
|
||||
foreach($aSQLColNames as $sColName)
|
||||
{
|
||||
$sFilter = str_replace(':'.$sColName, "'".addslashes( $aRow[$sColName] )."'", $sFilter);
|
||||
}
|
||||
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_oql&search_form=0&oql_class='.$sClass.'&oql_clause='.urlencode($sFilter).'&format=html'.$sContext;
|
||||
$aDisplayRow[$sName] = '<a href="'.$sURL.'">'.$aRow[$sName]."</a>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$aDisplayRow[$sName] = $aRow[$sName];
|
||||
}
|
||||
}
|
||||
$aDisplayData[] = $aDisplayRow;
|
||||
}
|
||||
$oPage->table($aDisplayConfig, $aDisplayData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
|
||||
{
|
||||
$sHtml = '';
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
protected function RenderChart($oPage, $sId, $aValues, $sDrillDown = '', $aRows = array())
|
||||
{
|
||||
// 1- Compute Open Flash Chart data
|
||||
//
|
||||
$aValueKeys = array();
|
||||
$index = 0;
|
||||
if ((count($aValues) > 0) && ($sDrillDown != ''))
|
||||
{
|
||||
$oFilter = DBObjectSearch::FromOQL($sDrillDown);
|
||||
$sClass = $oFilter->GetClass();
|
||||
$sOQLClause = str_replace('SELECT '.$sClass, '', $sDrillDown);
|
||||
$aSQLColNames = array_keys(current($aRows)); // Read the list of columns from the current (i.e. first) element of the array
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_oql&search_form=0&oql_class='.$sClass.'&format=html&'.$oAppContext->GetForLink().'&oql_clause=';
|
||||
}
|
||||
$aURLs = array();
|
||||
foreach($aValues as $key => $value)
|
||||
{
|
||||
// Make sure that values are integers (so that max() will work....)
|
||||
// and build an array of STRING with the keys (numeric keys are transformed into string by PHP :-(
|
||||
$aValues[$key] = (int)$value;
|
||||
$aValueKeys[] = (string)$key;
|
||||
|
||||
// Build the custom query for the 'drill down' on each element
|
||||
if ($sDrillDown != '')
|
||||
{
|
||||
$sFilter = $sOQLClause;
|
||||
foreach($aSQLColNames as $sColName)
|
||||
{
|
||||
$sFilter = str_replace(':'.$sColName, "'".addslashes( $aRows[$key][$sColName] )."'", $sFilter);
|
||||
$aURLs[$index] = $sURL.urlencode($sFilter);
|
||||
}
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
|
||||
$oChart = new open_flash_chart();
|
||||
|
||||
if ($this->m_sType == 'bars')
|
||||
{
|
||||
$oChartElement = new bar_glass();
|
||||
|
||||
if (count($aValues) > 0)
|
||||
{
|
||||
$maxValue = max($aValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
$maxValue = 1;
|
||||
}
|
||||
$oYAxis = new y_axis();
|
||||
$aMagicValues = array(1,2,5,10);
|
||||
$iMultiplier = 1;
|
||||
$index = 0;
|
||||
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
|
||||
while($maxValue > $iTop)
|
||||
{
|
||||
$index++;
|
||||
$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
|
||||
if (($index % count($aMagicValues)) == 0)
|
||||
{
|
||||
$iMultiplier = $iMultiplier * 10;
|
||||
}
|
||||
}
|
||||
//echo "oYAxis->set_range(0, $iTop, $iMultiplier);\n";
|
||||
$oYAxis->set_range(0, $iTop, $iMultiplier);
|
||||
$oChart->set_y_axis( $oYAxis );
|
||||
$aBarValues = array();
|
||||
foreach($aValues as $iValue)
|
||||
{
|
||||
$oBarValue = new bar_value($iValue);
|
||||
$oBarValue->on_click("ofc_drilldown_{$sId}");
|
||||
$aBarValues[] = $oBarValue;
|
||||
}
|
||||
$oChartElement->set_values($aBarValues);
|
||||
//$oChartElement->set_values(array_values($aValues));
|
||||
$oXAxis = new x_axis();
|
||||
$oXLabels = new x_axis_labels();
|
||||
// set them vertical
|
||||
$oXLabels->set_vertical();
|
||||
// set the label text
|
||||
$oXLabels->set_labels($aValueKeys);
|
||||
// Add the X Axis Labels to the X Axis
|
||||
$oXAxis->set_labels( $oXLabels );
|
||||
$oChart->set_x_axis( $oXAxis );
|
||||
}
|
||||
else
|
||||
{
|
||||
$oChartElement = new pie();
|
||||
$oChartElement->set_start_angle( 35 );
|
||||
$oChartElement->set_animate( true );
|
||||
$oChartElement->set_tooltip( '#label# - #val# (#percent#)' );
|
||||
$oChartElement->set_colours( array('#FF8A00', '#909980', '#2C2B33', '#CCC08D', '#596664') );
|
||||
|
||||
$aData = array();
|
||||
foreach($aValues as $sValue => $iValue)
|
||||
{
|
||||
$oPieValue = new pie_value($iValue, $sValue); //@@ BUG: not passed via ajax !!!
|
||||
$oPieValue->on_click("ofc_drilldown_{$sId}");
|
||||
$aData[] = $oPieValue;
|
||||
}
|
||||
|
||||
$oChartElement->set_values( $aData );
|
||||
$oChart->x_axis = null;
|
||||
}
|
||||
|
||||
// Title given in HTML
|
||||
//$oTitle = new title($this->m_sTitle);
|
||||
//$oChart->set_title($oTitle);
|
||||
$oChart->set_bg_colour('#FFFFFF');
|
||||
$oChart->add_element( $oChartElement );
|
||||
|
||||
$sData = $oChart->toPrettyString();
|
||||
$sData = json_encode($sData);
|
||||
|
||||
// 2- Declare the Javascript function that will render the chart data\
|
||||
//
|
||||
$oPage->add_script(
|
||||
<<< EOF
|
||||
function ofc_get_data_{$sId}()
|
||||
{
|
||||
return $sData;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
|
||||
if (count($aURLs) > 0)
|
||||
{
|
||||
$sURLList = '';
|
||||
foreach($aURLs as $index => $sURL)
|
||||
{
|
||||
$sURLList .= "\taURLs[$index] = '".addslashes($sURL)."';\n";
|
||||
}
|
||||
|
||||
$oPage->add_script(
|
||||
<<< EOF
|
||||
function ofc_drilldown_{$sId}(index)
|
||||
{
|
||||
var aURLs = new Array();
|
||||
{$sURLList}
|
||||
var sURL = aURLs[index];
|
||||
|
||||
window.location.href = sURL; // Navigate !
|
||||
}
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
// 3- Insert the Open Flash chart
|
||||
//
|
||||
$oPage->add("<div id=\"$sId\"><div>\n");
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
swfobject.embedSWF( "../images/open-flash-chart.swf",
|
||||
"{$sId}",
|
||||
"100%", "300","9.0.0",
|
||||
"expressInstall.swf",
|
||||
{"get-data":"ofc_get_data_{$sId}", "id":"{$sId}"},
|
||||
{'wmode': 'transparent'}
|
||||
);
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,18 +20,20 @@
|
||||
/**
|
||||
* File to include to initialize the datamodel in memory
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
|
||||
require_once(APPROOT.'/application/utils.inc.php');
|
||||
require_once(APPROOT.'/core/contexttag.class.inc.php');
|
||||
session_name('itop-'.md5(APPROOT));
|
||||
session_start();
|
||||
if (isset($_REQUEST['switch_env']))
|
||||
$sSwitchEnv = utils::ReadParam('switch_env', null);
|
||||
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)))
|
||||
{
|
||||
$sEnv = $_REQUEST['switch_env'];
|
||||
$_SESSION['itop_env'] = $sEnv;
|
||||
$_SESSION['itop_env'] = $sSwitchEnv;
|
||||
$sEnv = $sSwitchEnv;
|
||||
// TODO: reset the credentials as well ??
|
||||
}
|
||||
else if (isset($_SESSION['itop_env']))
|
||||
@@ -44,6 +46,4 @@ else
|
||||
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
|
||||
}
|
||||
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
|
||||
MetaModel::Startup($sConfigFile);
|
||||
|
||||
?>
|
||||
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class DisplayTemplate
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -353,11 +353,15 @@ class ObjectDetailsTemplate extends DisplayTemplate
|
||||
if ($iFlags & OPT_ATT_SLAVE)
|
||||
{
|
||||
$iSynchroFlags = $this->m_oObj->GetSynchroReplicaFlags($sAttCode, $aReasons);
|
||||
$sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sSynchroIcon = " <img id=\"synchro_$iInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
|
||||
$sTip = '';
|
||||
foreach($aReasons as $aRow)
|
||||
{
|
||||
$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
|
||||
$sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8');
|
||||
$sDescription = str_replace(array("\r\n", "\n"), "<br/>", $sDescription);
|
||||
$sTip .= "<div class='synchro-source'>";
|
||||
$sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>";
|
||||
$sTip .= "<div class='synchro-source-description'>$sDescription</div>";
|
||||
}
|
||||
$oPage->add_ready_script("$('#synchro_$iInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,8 @@
|
||||
/**
|
||||
* This class records the pending "transactions" corresponding to forms that have not been
|
||||
* submitted yet, in order to prevent double submissions. When created a transaction remains valid
|
||||
* until the user's session expires
|
||||
* until the user's session expires. This class is actually a wrapper to the underlying implementation
|
||||
* which choice is configured via the parameter 'transaction_storage'
|
||||
*
|
||||
* @package iTop
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
@@ -28,6 +29,81 @@
|
||||
|
||||
|
||||
class privUITransaction
|
||||
{
|
||||
/**
|
||||
* Create a new transaction id, store it in the session and return its id
|
||||
* @param void
|
||||
* @return int The identifier of the new transaction
|
||||
*/
|
||||
public static function GetNewTransactionId()
|
||||
{
|
||||
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
|
||||
if (!$bTransactionsEnabled)
|
||||
{
|
||||
return 'notransactions'; // Any value will do
|
||||
}
|
||||
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
|
||||
if (!class_exists($sClass, false))
|
||||
{
|
||||
IssueLog::Error("Incorrect value '".MetaModel::GetConfig()->Get('transaction_storage')."' for 'transaction_storage', the class '$sClass' does not exists. Using privUITransactionSession instead for storing sessions.");
|
||||
$sClass = 'privUITransactionSession';
|
||||
}
|
||||
|
||||
return (string)$sClass::GetNewTransactionId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static function IsTransactionValid($id, $bRemoveTransaction = true)
|
||||
{
|
||||
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
|
||||
if (!$bTransactionsEnabled)
|
||||
{
|
||||
return true; // All values are valid
|
||||
}
|
||||
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
|
||||
if (!class_exists($sClass, false))
|
||||
{
|
||||
$sClass = 'privUITransactionSession';
|
||||
}
|
||||
|
||||
return $sClass::IsTransactionValid($id, $bRemoveTransaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the transaction specified by its id
|
||||
* @param int $id The Identifier (as returned by GetNewTranscationId) of the transaction to be removed.
|
||||
* @return void
|
||||
*/
|
||||
public static function RemoveTransaction($id)
|
||||
{
|
||||
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
|
||||
if (!$bTransactionsEnabled)
|
||||
{
|
||||
return; // Nothing to do
|
||||
}
|
||||
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
|
||||
if (!class_exists($sClass, false))
|
||||
{
|
||||
$sClass = 'privUITransactionSession';
|
||||
}
|
||||
|
||||
$sClass::RemoveTransaction($id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The original (and by default) mechanism for storing transaction information
|
||||
* as an array in the $_SESSION variable
|
||||
*
|
||||
*/
|
||||
class privUITransactionSession
|
||||
{
|
||||
/**
|
||||
* Create a new transaction id, store it in the session and return its id
|
||||
@@ -99,4 +175,178 @@ class privUITransaction
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
/**
|
||||
* An alternate implementation for storing the transactions as temporary files
|
||||
* Useful when using an in-memory storage for the session which do not
|
||||
* guarantee mutual exclusion for writing
|
||||
*/
|
||||
class privUITransactionFile
|
||||
{
|
||||
/**
|
||||
* Create a new transaction id, store it in the session and return its id
|
||||
* @param void
|
||||
* @return int The identifier of the new transaction
|
||||
*/
|
||||
public static function GetNewTransactionId()
|
||||
{
|
||||
if (!is_dir(APPROOT.'data/transactions'))
|
||||
{
|
||||
if (!is_writable(APPROOT.'data'))
|
||||
{
|
||||
throw new Exception('The directory "'.APPROOT.'data" must be writable to the application.');
|
||||
}
|
||||
if (!@mkdir(APPROOT.'data/transactions'))
|
||||
{
|
||||
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', self::GetUserPrefix()));
|
||||
self::Info('GetNewTransactionId: Created transaction: '.$id);
|
||||
|
||||
return (string)$id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static function IsTransactionValid($id, $bRemoveTransaction = true)
|
||||
{
|
||||
$sFilepath = APPROOT.'data/transactions/'.$id;
|
||||
clearstatcache(true, $sFilepath);
|
||||
$bResult = file_exists($sFilepath);
|
||||
if ($bResult)
|
||||
{
|
||||
if ($bRemoveTransaction)
|
||||
{
|
||||
$bResult = @unlink($sFilepath);
|
||||
if (!$bResult)
|
||||
{
|
||||
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
|
||||
*/
|
||||
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()));
|
||||
}
|
||||
$bSuccess = @unlink($sFilepath);
|
||||
if (!$bSuccess)
|
||||
{
|
||||
self::Error('RemoveTransaction: FAILED to remove transaction '.$id);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::Info('RemoveTransaction: OK '.$id);
|
||||
}
|
||||
return $bSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
$iLimit = time() - 24*3600;
|
||||
clearstatcache();
|
||||
$aTransactions = glob(APPROOT.'data/transactions/*-*');
|
||||
foreach($aTransactions as $sFileName)
|
||||
{
|
||||
if (filemtime($sFileName) < $iLimit)
|
||||
{
|
||||
@unlink($sFileName);
|
||||
self::Info('CleanupOldTransactions: Deleted transaction: '.$sFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For debugging purposes: gets the pending transactions of the current user
|
||||
* as an array, with the date of the creation of the transaction file
|
||||
*/
|
||||
protected static function GetPendingTransactions()
|
||||
{
|
||||
clearstatcache();
|
||||
$aResult = array();
|
||||
$aTransactions = glob(APPROOT.'data/transactions/'.self::GetUserPrefix().'*');
|
||||
foreach($aTransactions as $sFileName)
|
||||
{
|
||||
$aResult[] = date('Y-m-d H:i:s', filemtime($sFileName)).' - '.basename($sFileName);
|
||||
}
|
||||
sort($aResult);
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
protected static function GetUserPrefix()
|
||||
{
|
||||
$sPrefix = substr(UserRights::GetUser(), 0, 10);
|
||||
$sPrefix = preg_replace('/[^a-zA-Z0-9-_]/', '_', $sPrefix);
|
||||
return $sPrefix.'-';
|
||||
}
|
||||
|
||||
protected static function Info($sText)
|
||||
{
|
||||
self::Write('Info | '.$sText);
|
||||
}
|
||||
|
||||
protected static function Warning($sText)
|
||||
{
|
||||
self::Write('Warning | '.$sText);
|
||||
}
|
||||
|
||||
protected static function Error($sText)
|
||||
{
|
||||
self::Write('Error | '.$sText);
|
||||
}
|
||||
|
||||
protected static function Write($sText)
|
||||
{
|
||||
$bLogEnabled = MetaModel::GetConfig()->Get('log_transactions');
|
||||
if ($bLogEnabled)
|
||||
{
|
||||
$hLogFile = @fopen(APPROOT.'log/transactions.log', 'a');
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -54,7 +54,7 @@
|
||||
* | | +--------+ +-----+ | |
|
||||
* | +--------------------------------------------+ |
|
||||
* +------------------------------------------------+
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -101,7 +101,7 @@ class UIExtKeyWidget
|
||||
* @param Hash $aArgs Extra context arguments
|
||||
* @return string The HTML fragment to be inserted into the page
|
||||
*/
|
||||
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
|
||||
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
|
||||
{
|
||||
if (!is_null($bSearchMode))
|
||||
{
|
||||
@@ -111,12 +111,12 @@ class UIExtKeyWidget
|
||||
$oPage->add_linked_script('../js/extkeywidget.js');
|
||||
$oPage->add_linked_script('../js/forms-json-utils.js');
|
||||
|
||||
$bCreate = (!$this->bSearchMode) && (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
|
||||
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
|
||||
$bExtensions = true;
|
||||
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
||||
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
|
||||
|
||||
$sHTMLValue = "<span style=\"white-space:nowrap\">"; // no wrap
|
||||
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey\">";
|
||||
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
|
||||
if($this->bSearchMode)
|
||||
{
|
||||
@@ -141,16 +141,22 @@ class UIExtKeyWidget
|
||||
{
|
||||
throw new Exception('Implementation: null value for allowed values definition');
|
||||
}
|
||||
elseif ($oAllowedValues->Count() < $iMaxComboLength)
|
||||
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
// Don't automatically launch the search if the table is huge
|
||||
$bDoSearch = !utils::IsHighCardinality($this->sTargetClass);
|
||||
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
|
||||
|
||||
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
|
||||
if ($oAllowedValues->Count($iMaxComboLength * 2) < $iMaxComboLength)
|
||||
{
|
||||
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
|
||||
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
|
||||
switch($sDisplayStyle)
|
||||
{
|
||||
case 'radio':
|
||||
case 'radio_horizontal':
|
||||
case 'radio_vertical':
|
||||
$sValidationField = "<span id=\"v_{$this->iId}\"></span>";
|
||||
$sHTMLValue = '';
|
||||
$sValidationField = null;
|
||||
|
||||
$bVertical = ($sDisplayStyle != 'radio_horizontal');
|
||||
$bExtensions = false;
|
||||
$oAllowedValues->Rewind();
|
||||
@@ -159,34 +165,37 @@ class UIExtKeyWidget
|
||||
{
|
||||
$aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
|
||||
}
|
||||
$sHTMLValue = $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", $bMandatory, $bVertical, $sValidationField);
|
||||
$sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField);
|
||||
$aEventsList[] ='change';
|
||||
break;
|
||||
|
||||
case 'select':
|
||||
case 'list':
|
||||
default:
|
||||
$sSelectMode = 'true';
|
||||
|
||||
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
|
||||
|
||||
$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
|
||||
|
||||
if ($this->bSearchMode)
|
||||
{
|
||||
if ($bSearchMultiple)
|
||||
{
|
||||
$sHTMLValue = "<select class=\"multiselect\" multiple title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"$this->iId\">\n";
|
||||
$sHTMLValue .= "<select class=\"multiselect\" multiple title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"$this->iId\">\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
||||
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
||||
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
|
||||
$sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
||||
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
|
||||
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
|
||||
}
|
||||
|
||||
$oAllowedValues->Rewind();
|
||||
while($oObj = $oAllowedValues->Fetch())
|
||||
{
|
||||
@@ -205,6 +214,8 @@ class UIExtKeyWidget
|
||||
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
|
||||
}
|
||||
$sHTMLValue .= "</select>\n";
|
||||
$sHTMLValue .= "</div>\n";
|
||||
|
||||
if (($this->bSearchMode) && $bSearchMultiple)
|
||||
{
|
||||
$aOptions = array(
|
||||
@@ -220,7 +231,7 @@ class UIExtKeyWidget
|
||||
}
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
|
||||
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
|
||||
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
|
||||
$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
|
||||
$('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } );
|
||||
@@ -252,20 +263,20 @@ EOF
|
||||
$sDisplayValue = $this->GetObjectName($value);
|
||||
}
|
||||
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
|
||||
$iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 30; //@@@ $this->oAttDef->GetMaxSize();
|
||||
$iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 20; //@@@ $this->oAttDef->GetMaxSize();
|
||||
|
||||
// the input for the auto-complete
|
||||
$sHTMLValue = "<input count=\"".$oAllowedValues->Count()."\" type=\"text\" id=\"label_$this->iId\" size=\"$iFieldSize\" value=\"$sDisplayValue\"/> ";
|
||||
$sHTMLValue .= "<img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif\" onClick=\"oACWidget_{$this->iId}.Search();\"/> ";
|
||||
$sHTMLValue .= "<input class=\"field_autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
|
||||
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.Search();\"/></span>";
|
||||
|
||||
// another hidden input to store & pass the object's Id
|
||||
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"$value\" />\n";
|
||||
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
|
||||
|
||||
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
|
||||
// Scripts to start the autocomplete and bind some events to it
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
|
||||
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
|
||||
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
|
||||
$('#label_$this->iId').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter',bSearchMode:$JSSearchMode, json: function() { return $sWizHelperJSON; } }});
|
||||
$('#label_$this->iId').keyup(function() { if ($(this).val() == '') { $('#$this->iId').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly !
|
||||
@@ -280,7 +291,7 @@ EOF
|
||||
}
|
||||
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
|
||||
{
|
||||
$sHTMLValue .= "<img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/> ";
|
||||
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/></span>";
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
if ($('#ac_tree_{$this->iId}').length == 0)
|
||||
@@ -292,7 +303,9 @@ EOF
|
||||
}
|
||||
if ($bCreate && $bExtensions)
|
||||
{
|
||||
$sHTMLValue .= "<img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif\" onClick=\"oACWidget_{$this->iId}.CreateObject();\"/> ";
|
||||
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
|
||||
|
||||
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif?itopversion=".ITOP_VERSION."\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"/></span>";
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
if ($('#ajax_{$this->iId}').length == 0)
|
||||
@@ -302,11 +315,14 @@ EOF
|
||||
EOF
|
||||
);
|
||||
}
|
||||
if ($sDisplayStyle == 'select')
|
||||
{
|
||||
$sHTMLValue .= "<span id=\"v_{$this->iId}\"></span>";
|
||||
}
|
||||
$sHTMLValue .= "</span>"; // end of no wrap
|
||||
$sHTMLValue .= "</div>";
|
||||
|
||||
// Note: This test is no longer necessary as we changed the markup to extract validation decoration in the standard .field_input_xxx container
|
||||
//if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
|
||||
//{
|
||||
$sHTMLValue .= "<span class=\"form_validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
|
||||
//}
|
||||
|
||||
return $sHTMLValue;
|
||||
}
|
||||
|
||||
@@ -327,9 +343,10 @@ EOF
|
||||
$aParams = array();
|
||||
$oFilter = new DBObjectSearch($this->sTargetClass);
|
||||
}
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open') || utils::IsHighCardinality($this->sTargetClass);
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
|
||||
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => true, 'currentId' => $this->iId));
|
||||
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => $bOpen, 'currentId' => $this->iId));
|
||||
$sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n";
|
||||
$sHTML .= "<div id=\"dr_{$this->iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
|
||||
$sHTML .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
|
||||
@@ -370,7 +387,11 @@ EOF
|
||||
$oFilter->ChangeClass($sRemoteClass);
|
||||
}
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj)));
|
||||
|
||||
// Current extkey value, so we can display event if it is not available anymore (eg. archived).
|
||||
$iCurrentExtKeyId = (is_null($oObj)) ? 0 : $oObj->Get($this->sAttCode);
|
||||
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId)));
|
||||
$oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'table_id' => 'select_'.$this->sAttCode)); // Don't display the 'Actions' menu on the results
|
||||
}
|
||||
|
||||
@@ -387,15 +408,40 @@ EOF
|
||||
{
|
||||
throw new Exception('Implementation: null value for allowed values definition');
|
||||
}
|
||||
|
||||
// Current extkey value, so we can display event if it is not available anymore (eg. archived).
|
||||
$iCurrentExtKeyId = (is_null($oObj) || $this->sAttCode === '') ? 0 : $oObj->Get($this->sAttCode);
|
||||
|
||||
$oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
|
||||
$iMax = 150;
|
||||
$oValuesSet->SetLimit($iMax);
|
||||
$oValuesSet->SetSort(false);
|
||||
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$aValues = $oValuesSet->GetValues(array('this' => $oObj), $sContains);
|
||||
foreach($aValues as $sKey => $sFriendlyName)
|
||||
|
||||
$aValuesEquals = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals_start_with');
|
||||
//asort($aValuesEquals);
|
||||
foreach($aValuesEquals as $sKey => $sFriendlyName)
|
||||
{
|
||||
$oP->add(trim($sFriendlyName)."\t".$sKey."\n");
|
||||
$iMax--;
|
||||
}
|
||||
if ($iMax <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$oValuesSet->SetLimit($iMax);
|
||||
$aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
|
||||
asort($aValuesContains);
|
||||
foreach($aValuesContains as $sKey => $sFriendlyName)
|
||||
{
|
||||
if (!isset($aValuesEquals[$sKey]))
|
||||
{
|
||||
$oP->add(trim($sFriendlyName)."\t".$sKey."\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the display name of the selected object, to fill back the autocomplete
|
||||
*/
|
||||
@@ -414,7 +460,49 @@ EOF
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the form to select a leaf class from the $this->sTargetClass (that should be abstract)
|
||||
* Note: Inspired from UILinksWidgetDirect::GetObjectCreationDialog()
|
||||
*
|
||||
* @param WebPage $oPage
|
||||
*/
|
||||
public function GetClassSelectionForm(WebPage $oPage)
|
||||
{
|
||||
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
|
||||
// and that the current user is allowed to create objects of this class
|
||||
$aSubClasses = MetaModel::EnumChildClasses($this->sTargetClass);
|
||||
$aPossibleClasses = array();
|
||||
foreach($aSubClasses as $sCandidateClass)
|
||||
{
|
||||
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
|
||||
{
|
||||
$aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass);
|
||||
}
|
||||
}
|
||||
|
||||
$sDialogTitle = '';
|
||||
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
|
||||
$oPage->add('<form>');
|
||||
|
||||
$sClassLabel = MetaModel::GetName($this->sTargetClass);
|
||||
$oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel));
|
||||
$oPage->add('<nobr><select name="class">');
|
||||
asort($aPossibleClasses);
|
||||
foreach($aPossibleClasses as $sClassName => $sClassLabel)
|
||||
{
|
||||
$oPage->add("<option value=\"$sClassName\">$sClassLabel</option>");
|
||||
}
|
||||
$oPage->add('</select>');
|
||||
$oPage->add(' <button type="submit" class="action" style="margin-top:15px;"><span>' . Dict::S('UI:Button:Ok') . '</span></button></nobr></p>');
|
||||
|
||||
$oPage->add('</form>');
|
||||
$oPage->add('</div></div></div>');
|
||||
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
|
||||
$oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form to create a new object of the 'target' class
|
||||
*/
|
||||
@@ -450,7 +538,17 @@ EOF
|
||||
$sDialogTitle = '';
|
||||
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
|
||||
$oPage->add("<h1>".MetaModel::GetClassIcon($this->sTargetClass)." ".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."</h1>\n");
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true));
|
||||
$aFieldsFlags = array();
|
||||
$aFieldsComments = array();
|
||||
foreach(MetaModel::ListAttributeDefs($this->sTargetClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if (($oAttDef instanceof AttributeBlob) || (false))
|
||||
{
|
||||
$aFieldsFlags[$sAttCode] = OPT_ATT_READONLY;
|
||||
$aFieldsComments[$sAttCode] = ' <img src="../images/transp-lock.png" style="vertical-align:middle" title="'.htmlentities(Dict::S('UI:UploadNotSupportedInThisMode')).'"/>';
|
||||
}
|
||||
}
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments));
|
||||
$oPage->add('</div></div></div>');
|
||||
// $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
|
||||
@@ -472,9 +570,9 @@ EOF
|
||||
}
|
||||
try
|
||||
{
|
||||
$oFilter = DBObjectSearch::FromOQL($sFilter);
|
||||
$oFilter = DBObjectSearch::FromOQL($sFilter);
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj));
|
||||
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj, 'current_extkey_id' => $currValue));
|
||||
}
|
||||
catch(MissingQueryArgument $e)
|
||||
{
|
||||
@@ -485,6 +583,7 @@ EOF
|
||||
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
|
||||
$oSet = new DBObjectSet($oFilter);
|
||||
}
|
||||
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
|
||||
$sHKAttCode = MetaModel::IsHierarchicalClass($this->sTargetClass);
|
||||
$this->DumpTree($oPage, $oSet, $sHKAttCode, $currValue);
|
||||
@@ -504,16 +603,23 @@ EOF
|
||||
*/
|
||||
public function DoCreateObject($oPage)
|
||||
{
|
||||
$oObj = MetaModel::NewObject($this->sTargetClass);
|
||||
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
|
||||
if (count($aErrors) == 0)
|
||||
try
|
||||
{
|
||||
$oObj->DBInsert();
|
||||
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
|
||||
$oObj = MetaModel::NewObject($this->sTargetClass);
|
||||
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
|
||||
if (count($aErrors) == 0)
|
||||
{
|
||||
$oObj->DBInsert();
|
||||
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
|
||||
}
|
||||
else
|
||||
{
|
||||
return array('error' => implode(' ', $aErrors), 'id' => 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch(Exception $e)
|
||||
{
|
||||
return array('name' => implode(' ', $aErrors), 'id' => 0);
|
||||
return array('error' => $e->getMessage(), 'id' => 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,4 +687,3 @@ EOF
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,14 +20,15 @@
|
||||
* Class UIHTMLEditorWidget
|
||||
* UI wdiget for displaying and editing one-way encrypted passwords
|
||||
*
|
||||
* @author Phil Eddies
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @author Romain Quetiez
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class UIHTMLEditorWidget
|
||||
{
|
||||
protected $m_iId;
|
||||
protected $m_oAttDef;
|
||||
protected $m_sAttCode;
|
||||
protected $m_sNameSuffix;
|
||||
protected $m_sFieldPrefix;
|
||||
@@ -36,10 +37,11 @@ class UIHTMLEditorWidget
|
||||
protected $m_sValue;
|
||||
protected $m_sMandatory;
|
||||
|
||||
public function __construct($iInputId, $sAttCode, $sNameSuffix, $sFieldPrefix, $sHelpText, $sValidationField, $sValue, $sMandatory)
|
||||
public function __construct($iInputId, $oAttDef, $sNameSuffix, $sFieldPrefix, $sHelpText, $sValidationField, $sValue, $sMandatory)
|
||||
{
|
||||
$this->m_iId = $iInputId;
|
||||
$this->m_sAttCode = $sAttCode;
|
||||
$this->m_oAttDef = $oAttDef;
|
||||
$this->m_sAttCode = $oAttDef->GetCode();
|
||||
$this->m_sNameSuffix = $sNameSuffix;
|
||||
$this->m_sHelpText = $sHelpText;
|
||||
$this->m_sValidationField = $sValidationField;
|
||||
@@ -62,14 +64,30 @@ class UIHTMLEditorWidget
|
||||
$sHelpText = $this->m_sHelpText;
|
||||
$sValidationField = $this->m_sValidationField;
|
||||
|
||||
$sHtmlValue = "<table><tr><td><textarea class=\"htmlEditor\" title=\"$sHelpText\" name=\"attr_{$this->m_sFieldPrefix}{$sCode}\" rows=\"10\" cols=\"10\" id=\"$iId\">$sValue</textarea></td><td>$sValidationField</td></tr></table>";
|
||||
$sHtmlValue = "<div class=\"field_input_zone field_input_html\"><textarea class=\"htmlEditor\" title=\"$sHelpText\" name=\"attr_{$this->m_sFieldPrefix}{$sCode}\" rows=\"10\" cols=\"10\" id=\"$iId\">$sValue</textarea></div>$sValidationField";
|
||||
|
||||
// Replace the text area with CKEditor
|
||||
// To change the default settings of the editor,
|
||||
// a) edit the file /js/ckeditor/config.js
|
||||
// b) or override some of the configuration settings, using the second parameter of ckeditor()
|
||||
$aConfig = array();
|
||||
$sLanguage = strtolower(trim(UserRights::GetUserLanguage()));
|
||||
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, { language : '$sLanguage' , contentsLanguage : '$sLanguage', extraPlugins: 'disabler' });"); // Transform $iId into a CKEdit
|
||||
$aConfig['language'] = $sLanguage;
|
||||
$aConfig['contentsLanguage'] = $sLanguage;
|
||||
$aConfig['extraPlugins'] = 'disabler';
|
||||
$sWidthSpec = addslashes(trim($this->m_oAttDef->GetWidth()));
|
||||
if ($sWidthSpec != '')
|
||||
{
|
||||
$aConfig['width'] = $sWidthSpec;
|
||||
}
|
||||
$sHeightSpec = addslashes(trim($this->m_oAttDef->GetHeight()));
|
||||
if ($sHeightSpec != '')
|
||||
{
|
||||
$aConfig['height'] = $sHeightSpec;
|
||||
}
|
||||
$sConfigJS = json_encode($aConfig);
|
||||
|
||||
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
|
||||
|
||||
// Please read...
|
||||
// ValidateCKEditField triggers a timer... calling itself indefinitely
|
||||
@@ -80,9 +98,26 @@ class UIHTMLEditorWidget
|
||||
|
||||
// Could also be bound to 'instanceReady.ckeditor'
|
||||
$oPage->add_ready_script("$('#$iId').bind('validate', function(evt, sFormId) { return ValidateCKEditField('$iId', '', {$this->m_sMandatory}, sFormId, '') } );\n");
|
||||
$oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); } );\n");
|
||||
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$iId').bind('update', function(evt){
|
||||
BlockField('cke_$iId', $('#$iId').attr('disabled'));
|
||||
//Delayed execution - ckeditor must be properly initialized before setting readonly
|
||||
var retryCount = 0;
|
||||
var oMe = $('#$iId');
|
||||
var delayedSetReadOnly = function () {
|
||||
if (oMe.data('ckeditorInstance').editable() == undefined && retryCount++ < 10) {
|
||||
setTimeout(delayedSetReadOnly, retryCount * 100); //Wait a while longer each iteration
|
||||
}
|
||||
else
|
||||
{
|
||||
oMe.data('ckeditorInstance').setReadOnly(oMe.prop('disabled'));
|
||||
}
|
||||
};
|
||||
setTimeout(delayedSetReadOnly, 50);
|
||||
});
|
||||
EOF
|
||||
);
|
||||
return $sHtmlValue;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Class UILinksWidgetDirect
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,14 @@ class UILinksWidgetDirect
|
||||
protected $sInputid;
|
||||
protected $sNameSuffix;
|
||||
protected $sLinkedClass;
|
||||
|
||||
|
||||
/**
|
||||
* UILinksWidgetDirect constructor.
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param string $sInputId
|
||||
* @param string $sNameSuffix
|
||||
*/
|
||||
public function __construct($sClass, $sAttCode, $sInputId, $sNameSuffix = '')
|
||||
{
|
||||
$this->sClass = $sClass;
|
||||
@@ -71,8 +78,15 @@ class UILinksWidgetDirect
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet|ormLinkSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
*/
|
||||
public function Display(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
|
||||
{
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
switch($oLinksetDef->GetEditMode())
|
||||
@@ -89,7 +103,7 @@ class UILinksWidgetDirect
|
||||
$sDefault = "default[$sExtKeyToMe]=".$oCurrentObj->GetKey();
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sParams = $oAppContext->GetForLink();
|
||||
$oPage->p("<a target=\"_blank\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sTargetClass&$sParams{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sTargetClass))."</a>\n");
|
||||
$oPage->p("<a target=\"_blank\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sTargetClass&$sParams&{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sTargetClass))."</a>\n");
|
||||
}
|
||||
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/);
|
||||
break;
|
||||
@@ -115,8 +129,16 @@ class UILinksWidgetDirect
|
||||
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, true /* bDisplayMenu*/);
|
||||
}
|
||||
}
|
||||
|
||||
protected function DisplayAsBlock(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param bool $bDisplayMenu
|
||||
*/
|
||||
protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $bDisplayMenu)
|
||||
{
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$sTargetClass = $oLinksetDef->GetLinkedClass();
|
||||
@@ -143,6 +165,7 @@ class UILinksWidgetDirect
|
||||
'target_attr' => $oLinksetDef->GetExtKeyToMe(),
|
||||
'object_id' => $oCurrentObj ? $oCurrentObj->GetKey() : null,
|
||||
'menu' => $bDisplayMenu,
|
||||
'menu_actions_target' => '_blank',
|
||||
'default' => $aDefaults,
|
||||
'table_id' => $this->sClass.'_'.$this->sAttCode,
|
||||
);
|
||||
@@ -151,45 +174,11 @@ class UILinksWidgetDirect
|
||||
$oBlock->Display($oPage, $this->sInputid, $aParams);
|
||||
}
|
||||
}
|
||||
|
||||
protected function DisplayEditInPlace(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
{
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
|
||||
$oValue->Rewind();
|
||||
$oPage->add('<table class="listContainer" id="'.$this->sInputid.'"><tr><td>');
|
||||
|
||||
$aData = array();
|
||||
while($oLinkObj = $oValue->Fetch())
|
||||
{
|
||||
$aRow = array();
|
||||
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.$oLinkObj->GetKey().'"/>';
|
||||
foreach($this->aZlist as $sLinkedAttCode)
|
||||
{
|
||||
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
|
||||
}
|
||||
$aData[] = $aRow;
|
||||
}
|
||||
$oPage->table($aAttribs, $aData);
|
||||
$oPage->add('</td></tr></table>'); //listcontainer
|
||||
$sInputName = $sFormPrefix.'attr_'.$this->sAttCode;
|
||||
$aLabels = array(
|
||||
'delete' => Dict::S('UI:Button:Delete'),
|
||||
// 'modify' => 'Modify...' ,
|
||||
'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)),
|
||||
'remove' => Dict::S('UI:Button:Remove'),
|
||||
'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
);
|
||||
$oContext = new ApplicationContext();
|
||||
$sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
|
||||
$sJSONLabels = json_encode($aLabels);
|
||||
$sJSONButtons = json_encode($aButtons);
|
||||
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
|
||||
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper });");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param string $sProposedRealClass
|
||||
*/
|
||||
public function GetObjectCreationDlg(WebPage $oPage, $sProposedRealClass = '')
|
||||
{
|
||||
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
|
||||
@@ -215,14 +204,14 @@ class UILinksWidgetDirect
|
||||
$aKeys = array_keys($aPossibleClasses);
|
||||
$sRealClass = $aKeys[0];
|
||||
}
|
||||
|
||||
|
||||
if ($sRealClass != '')
|
||||
{
|
||||
$oPage->add("<h1>".MetaModel::GetClassIcon($sRealClass)." ".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($sRealClass))."</h1>\n");
|
||||
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
|
||||
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
|
||||
$aFieldFlags = array( $sExtKeyToMe => OPT_ATT_HIDDEN);
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, null, array(), array('formPrefix' => $this->sInputid, 'noRelations' => true, 'fieldsFlags' => $aFieldFlags));
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, null, array(), array('formPrefix' => $this->sInputid, 'noRelations' => true, 'fieldsFlags' => $aFieldFlags));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -239,7 +228,61 @@ class UILinksWidgetDirect
|
||||
}
|
||||
$oPage->add('</div></div>');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObjectSet $oValue
|
||||
* @param array $aArgs
|
||||
* @param string $sFormPrefix
|
||||
* @param DBObject $oCurrentObj
|
||||
* @param array $aButtons
|
||||
*/
|
||||
protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
|
||||
{
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
|
||||
$oValue->Rewind();
|
||||
$oPage->add('<table class="listContainer" id="'.$this->sInputid.'"><tr><td>');
|
||||
|
||||
$aData = array();
|
||||
while($oLinkObj = $oValue->Fetch())
|
||||
{
|
||||
$aRow = array();
|
||||
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.$oLinkObj->GetKey().'"/>';
|
||||
foreach($this->aZlist as $sLinkedAttCode)
|
||||
{
|
||||
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
|
||||
}
|
||||
$aData[] = $aRow;
|
||||
}
|
||||
$oPage->table($aAttribs, $aData);
|
||||
$oPage->add('</td></tr></table>'); //listcontainer
|
||||
$sInputName = $sFormPrefix.'attr_'.$this->sAttCode;
|
||||
$aLabels = array(
|
||||
'delete' => Dict::S('UI:Button:Delete'),
|
||||
// 'modify' => 'Modify...' ,
|
||||
'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)),
|
||||
'remove' => Dict::S('UI:Button:Remove'),
|
||||
'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)),
|
||||
);
|
||||
$oContext = new ApplicationContext();
|
||||
$sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
|
||||
$sJSONLabels = json_encode($aLabels);
|
||||
$sJSONButtons = json_encode($aButtons);
|
||||
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
|
||||
// Don't automatically launch the search if the table is huge
|
||||
$bDoSearch = !utils::IsHighCardinality($this->sLinkedClass);
|
||||
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
|
||||
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper, do_search: $sJSDoSearch});");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject $oCurrentObj
|
||||
* @throws Exception
|
||||
*/
|
||||
public function GetObjectsSelectionDlg($oPage, $oCurrentObj)
|
||||
{
|
||||
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
|
||||
@@ -262,8 +305,9 @@ class UILinksWidgetDirect
|
||||
{
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
}
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open') || utils::IsHighCardinality($this->sLinkedClass);
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", array('open' => true));
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", array('open' => $bOpen));
|
||||
$sHtml .= "<form id=\"ObjectsAddForm_{$this->sInputid}\">\n";
|
||||
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->sInputid}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
|
||||
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
|
||||
@@ -273,16 +317,15 @@ class UILinksWidgetDirect
|
||||
$sHtml .= "</div>\n";
|
||||
$sHtml .= "</form>\n";
|
||||
$oPage->add($sHtml);
|
||||
//$oPage->add_ready_script("$('#SearchFormToAdd_{$this->sAttCode}{$this->sNameSuffix} form').bind('submit.uilinksWizard', oWidget{$this->sInputId}.SearchObjectsToAdd);");
|
||||
//$oPage->add_ready_script("$('#SearchFormToAdd_{$this->sAttCode}{$this->sNameSuffix}').resize(oWidget{$this->siInputId}.UpdateSizes);");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search for objects to be linked to the current object (i.e "remote" objects)
|
||||
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
|
||||
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of $this->sLinkedClass
|
||||
* @param array $aAlreadyLinked Array of indentifiers of objects which are already linke to the current object (or about to be linked)
|
||||
* @param DBObject $oCurrentObj The object currently being edited... if known...
|
||||
* @throws Exception
|
||||
*/
|
||||
public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinked = array(), $oCurrentObj = null)
|
||||
{
|
||||
@@ -294,7 +337,7 @@ class UILinksWidgetDirect
|
||||
$valuesDef = $oLinksetDef->GetValuesDef();
|
||||
if ($valuesDef === null)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($this->sLinkedClass);
|
||||
$oFilter = new DBObjectSearch($sRemoteClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -327,6 +370,10 @@ class UILinksWidgetDirect
|
||||
$oBlock->Display($oP, "ResultsToAdd_{$this->sInputid}", array('menu' => false, 'cssCount'=> '#count_'.$this->sInputid , 'selection_mode' => true, 'table_id' => 'add_'.$this->sInputid)); // Don't display the 'Actions' menu on the results
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebPage $oP
|
||||
* @param $oFullSetFilter
|
||||
*/
|
||||
public function DoAddObjects(WebPage $oP, $oFullSetFilter)
|
||||
{
|
||||
$aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter);
|
||||
@@ -354,7 +401,14 @@ class UILinksWidgetDirect
|
||||
}
|
||||
return $aAttribs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param string $sRealClass
|
||||
* @param array $aValues
|
||||
* @param int $iTempId
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetRow($oPage, $sRealClass, $aValues, $iTempId)
|
||||
{
|
||||
if ($sRealClass == '')
|
||||
@@ -366,7 +420,13 @@ class UILinksWidgetDirect
|
||||
|
||||
return $this->GetObjectRow($oPage, $oLinkObj, $iTempId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param $oLinkObj
|
||||
* @param int $iTempId
|
||||
* @return mixed
|
||||
*/
|
||||
protected function GetObjectRow($oPage, $oLinkObj, $iTempId)
|
||||
{
|
||||
$aAttribs = $this->GetTableConfig();
|
||||
@@ -382,7 +442,7 @@ class UILinksWidgetDirect
|
||||
/**
|
||||
* Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context
|
||||
* @param DBObject $oSourceObj
|
||||
* @param DBObjectSearch $oSearch
|
||||
* @param DBSearch|DBObjectSearch $oSearch
|
||||
*/
|
||||
protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch)
|
||||
{
|
||||
@@ -401,7 +461,6 @@ class UILinksWidgetDirect
|
||||
|
||||
if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sSrcClass, $sAttCode);
|
||||
$defaultValue = $oSourceObj->Get($sAttCode);
|
||||
|
||||
// Find the attcode for the same 'context' parameter in the destination class
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class UILinksWidget
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -39,7 +39,15 @@ class UILinksWidget
|
||||
protected $m_sLinkedClass;
|
||||
protected $m_sRemoteClass;
|
||||
protected $m_bDuplicatesAllowed;
|
||||
|
||||
|
||||
/**
|
||||
* UILinksWidget constructor.
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param int $iInputId
|
||||
* @param string $sNameSuffix
|
||||
* @param bool $bDuplicatesAllowed
|
||||
*/
|
||||
public function __construct($sClass, $sAttCode, $iInputId, $sNameSuffix = '', $bDuplicatesAllowed = false)
|
||||
{
|
||||
$this->m_sClass = $sClass;
|
||||
@@ -92,16 +100,19 @@ class UILinksWidget
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A one-row form for editing a link record
|
||||
* @param WebPage $oP Web page used for the ouput
|
||||
* @param DBObject $oLinkedObj The object to which all the elements of the linked set refer to
|
||||
* @param DBObject $oLinkedObj Remote object
|
||||
* @param mixed $linkObjOrId Either the object linked or a unique number for new link records to add
|
||||
* @param Hash $aArgs Extra context arguments
|
||||
* @return string The HTML fragment of the one-row form
|
||||
* @param array $aArgs Extra context arguments
|
||||
* @param DBObject $oCurrentObj The object to which all the elements of the linked set refer to
|
||||
* @param int $iUniqueId A unique identifier of new links
|
||||
* @param boolean $bReadOnly Display link as editable or read-only. Default is false (editable)
|
||||
* @return array The HTML fragment of the one-row form
|
||||
*/
|
||||
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId = null, $aArgs = array(), $oCurrentObj )
|
||||
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false)
|
||||
{
|
||||
$sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}";
|
||||
$aRow = array();
|
||||
@@ -115,16 +126,33 @@ class UILinksWidget
|
||||
$aArgs['prefix'] = $sPrefix;
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$key}";
|
||||
$aArgs['this'] = $linkObjOrId;
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$key\">";
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_{$sPrefix}id{$sNameSuffix}\" value=\"$key\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']';
|
||||
$sSafeId = utils::GetSafeId($sFieldId);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $linkObjOrId->Get($sFieldCode), '' /* DisplayValue */, $sSafeId, $sNameSuffix, 0, $aArgs);
|
||||
$aFieldsMap[$sFieldCode] = $sSafeId;
|
||||
}
|
||||
|
||||
if($bReadOnly)
|
||||
{
|
||||
$aRow['form::checkbox'] = "";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
|
||||
$aRow[$sFieldCode] = $sDisplayValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"$key\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$key\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']';
|
||||
$sSafeId = utils::GetSafeId($sFieldId);
|
||||
$sValue = $linkObjOrId->Get($sFieldCode);
|
||||
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'.
|
||||
cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, $sDisplayValue, $sSafeId, $sNameSuffix, 0, $aArgs).
|
||||
'</div></div></div>';
|
||||
$aFieldsMap[$sFieldCode] = $sSafeId;
|
||||
}
|
||||
}
|
||||
|
||||
$sState = $linkObjOrId->GetState();
|
||||
}
|
||||
else
|
||||
@@ -135,66 +163,60 @@ class UILinksWidget
|
||||
// New link existing only in memory
|
||||
$oNewLinkObj = $linkObjOrId;
|
||||
$iRemoteObjKey = $oNewLinkObj->Get($this->m_sExtKeyToRemote);
|
||||
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
|
||||
$linkObjOrId = -$iRemoteObjKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
$iRemoteObjKey = -$linkObjOrId;
|
||||
$iRemoteObjKey = $linkObjOrId;
|
||||
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
|
||||
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, -$linkObjOrId);
|
||||
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
|
||||
}
|
||||
$sPrefix .= "[$linkObjOrId][";
|
||||
$sPrefix .= "[-$iUniqueId][";
|
||||
$sNameSuffix = "]"; // To make a tabular form
|
||||
$aArgs['prefix'] = $sPrefix;
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".(-$linkObjOrId);
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".($iUniqueId < 0 ? -$iUniqueId : $iUniqueId);
|
||||
$aArgs['this'] = $oNewLinkObj;
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$linkObjOrId\">";
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_{$sPrefix}id{$sNameSuffix}\" value=\"\">";
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"-$iUniqueId\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId.']';
|
||||
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.-$iUniqueId.']';
|
||||
$sSafeId = utils::GetSafeId($sFieldId);
|
||||
$sValue = $oNewLinkObj->Get($sFieldCode);
|
||||
$sDisplayValue = $oNewLinkObj->GetEditValue($sFieldCode);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $oNewLinkObj->Get($sFieldCode) /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, $sSafeId /* id */, $sNameSuffix, 0, $aArgs);
|
||||
$aRow[$sFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'.
|
||||
cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, $sDisplayValue, $sSafeId /* id */, $sNameSuffix, 0, $aArgs).
|
||||
'</div></div></div>';
|
||||
$aFieldsMap[$sFieldCode] = $sSafeId;
|
||||
}
|
||||
$sState = '';
|
||||
$oP->add_script(
|
||||
<<<EOF
|
||||
$(".date-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
$(".datetime-pick").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '../images/calendar.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'yy-mm-dd 00:00:00',
|
||||
constrainInput: false,
|
||||
changeMonth: true,
|
||||
changeYear: true
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
$sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe);
|
||||
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
|
||||
|
||||
$sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote);
|
||||
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
|
||||
|
||||
// Rows created with ajax call need OnLinkAdded call.
|
||||
// Rows added before loading the form cannot call OnLinkAdded.
|
||||
if ($iUniqueId > 0)
|
||||
{
|
||||
$oP->add_script(
|
||||
<<<EOF
|
||||
PrepareWidgets();
|
||||
oWidget{$this->m_iInputId}.OnLinkAdded($iUniqueId, $iRemoteObjKey);
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(!$bReadOnly)
|
||||
{
|
||||
$sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe);
|
||||
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
|
||||
|
||||
$sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote);
|
||||
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
|
||||
}
|
||||
|
||||
$iFieldsCount = count($aFieldsMap);
|
||||
$sJsonFieldsMap = json_encode($aFieldsMap);
|
||||
|
||||
@@ -215,7 +237,11 @@ EOF
|
||||
|
||||
/**
|
||||
* Display one row of the whole form
|
||||
* @return none
|
||||
* @param WebPage $oP
|
||||
* @param array $aConfig
|
||||
* @param array $aRow
|
||||
* @param int $iRowId
|
||||
* @return string
|
||||
*/
|
||||
protected function DisplayFormRow(WebPage $oP, $aConfig, $aRow, $iRowId)
|
||||
{
|
||||
@@ -233,8 +259,8 @@ EOF
|
||||
/**
|
||||
* Display the table with the form for editing all the links at once
|
||||
* @param WebPage $oP The web page used for the output
|
||||
* @param Hash $aConfig The table's header configuration
|
||||
* @param Hash $aData The tabular data to be displayed
|
||||
* @param array $aConfig The table's header configuration
|
||||
* @param array $aData The tabular data to be displayed
|
||||
* @return string Html fragment representing the form table
|
||||
*/
|
||||
protected function DisplayFormTable(WebPage $oP, $aConfig, $aData)
|
||||
@@ -271,48 +297,60 @@ EOF
|
||||
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the HTML fragment corresponding to the linkset editing widget
|
||||
* @param WebPage $oP The web page used for all the output
|
||||
* @param DBObjectSet The initial value of the linked set
|
||||
* @param Hash $aArgs Extra context arguments
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject|ormLinkSet $oValue
|
||||
* @param array $aArgs Extra context arguments
|
||||
* @param string $sFormPrefix prefix of the fields in the current form
|
||||
* @param DBObject $oCurrentObj the current object to which the linkset is related
|
||||
* @return string The HTML fragment to be inserted into the page
|
||||
*/
|
||||
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
|
||||
public function Display(WebPage $oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
|
||||
{
|
||||
$sHtmlValue = '';
|
||||
$sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode);
|
||||
$sHtmlValue .= "<div id=\"linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix}\">\n";
|
||||
$sHtmlValue .= "<input type=\"hidden\" id=\"{$sFormPrefix}{$this->m_iInputId}\">\n";
|
||||
$oValue->Rewind();
|
||||
$aForm = array();
|
||||
$iAddedId = 1; // Unique id for new links
|
||||
while($oCurrentLink = $oValue->Fetch())
|
||||
{
|
||||
$aRow = array();
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote));
|
||||
if ($oCurrentLink->IsNew())
|
||||
{
|
||||
$key = -$oLinkedObj->GetKey();
|
||||
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
$key = $oCurrentLink->GetKey();
|
||||
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj);
|
||||
}
|
||||
// We try to retrieve the remote object as usual
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */);
|
||||
// If successful, it means that we can edit its link
|
||||
if($oLinkedObj !== null)
|
||||
{
|
||||
$bReadOnly = false;
|
||||
}
|
||||
// Else we retrieve it without restrictions (silos) and will display its link as readonly
|
||||
else
|
||||
{
|
||||
$bReadOnly = true;
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */, true);
|
||||
}
|
||||
|
||||
if ($oCurrentLink->IsNew())
|
||||
{
|
||||
$key = -($iAddedId++);
|
||||
}
|
||||
else
|
||||
{
|
||||
$key = $oCurrentLink->GetKey();
|
||||
}
|
||||
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly);
|
||||
}
|
||||
$sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm);
|
||||
$sDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false';
|
||||
// Don't automatically launch the search if the table is huge
|
||||
$bDoSearch = !utils::IsHighCardinality($this->m_sRemoteClass);
|
||||
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
|
||||
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
|
||||
$oPage->add_ready_script(<<<EOF
|
||||
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper, '{$this->m_sExtKeyToRemote}');
|
||||
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper, '{$this->m_sExtKeyToRemote}', $sJSDoSearch);
|
||||
oWidget{$this->m_iInputId}.Init();
|
||||
$('#{$this->m_iInputId}').bind('update_value', function() { $(this).val(oWidget{$this->m_iInputId}.GetUpdatedValue()); })
|
||||
EOF
|
||||
);
|
||||
$sHtmlValue .= "<span style=\"float:left;\"> <img src=\"../images/tv-item-last.gif\"> <input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget{$this->m_iInputId}.RemoveSelected();\" >";
|
||||
@@ -320,13 +358,19 @@ EOF
|
||||
$sHtmlValue .= "<span style=\"clear:both;\"><p> </p></span>\n";
|
||||
$sHtmlValue .= "</div>\n";
|
||||
$oPage->add_at_the_end("<div id=\"dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}\"></div>"); // To prevent adding forms inside the main form
|
||||
return $sHtmlValue;
|
||||
return $sHtmlValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @return string
|
||||
*/
|
||||
protected static function GetTargetClass($sClass, $sAttCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$sLinkedClass = $oAttDef->GetLinkedClass();
|
||||
$sTargetClass = '';
|
||||
switch(get_class($oAttDef))
|
||||
{
|
||||
case 'AttributeLinkedSetIndirect':
|
||||
@@ -341,14 +385,19 @@ EOF
|
||||
|
||||
return $sTargetClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param WebPage $oPage
|
||||
* @param DBObject $oCurrentObj
|
||||
*/
|
||||
public function GetObjectPickerDialog($oPage, $oCurrentObj)
|
||||
{
|
||||
$bOpen = MetaModel::GetConfig()->Get('legacy_search_drawer_open') || utils::IsHighCardinality($this->m_sRemoteClass);
|
||||
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
|
||||
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
|
||||
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => true));
|
||||
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => $bOpen));
|
||||
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\">\n";
|
||||
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
|
||||
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
|
||||
@@ -384,51 +433,31 @@ EOF
|
||||
}
|
||||
if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0)
|
||||
{
|
||||
// Positive IDs correspond to existing link records
|
||||
// negative IDs correspond to "remote" objects to be linked
|
||||
$aLinkIds = array();
|
||||
$aRemoteObjIds = array();
|
||||
foreach($aAlreadyLinkedIds as $iId)
|
||||
{
|
||||
if ($iId > 0)
|
||||
{
|
||||
$aLinkIds[] = $iId;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aRemoteObjIds[] = -$iId;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($aLinkIds) >0)
|
||||
{
|
||||
// Search for the links to find to which "remote" object they are linked
|
||||
$oLinkFilter = new DBObjectSearch($this->m_sLinkedClass);
|
||||
$oLinkFilter->AddCondition('id', $aLinkIds, 'IN');
|
||||
$oLinkSet = new CMDBObjectSet($oLinkFilter);
|
||||
while($oLink = $oLinkSet->Fetch())
|
||||
{
|
||||
$aRemoteObjIds[] = $oLink->Get($this->m_sExtKeyToRemote);
|
||||
}
|
||||
}
|
||||
$oFilter->AddCondition('id', $aRemoteObjIds, 'NOTIN');
|
||||
$oFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN');
|
||||
}
|
||||
$oSet = new CMDBObjectSet($oFilter);
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false);
|
||||
$oBlock->Display($oP, "ResultsToAdd_{$this->m_sAttCode}", array('menu' => false, 'cssCount'=> '#count_'.$this->m_sAttCode.$this->m_sNameSuffix , 'selection_mode' => true, 'table_id' => 'add_'.$this->m_sAttCode)); // Don't display the 'Actions' menu on the results
|
||||
}
|
||||
|
||||
public function DoAddObjects(WebPage $oP, $oFullSetFilter, $oCurrentObj)
|
||||
|
||||
/**
|
||||
* @param WebPage $oP
|
||||
* @param int $iMaxAddedId
|
||||
* @param $oFullSetFilter
|
||||
* @param DBObject $oCurrentObj
|
||||
*/
|
||||
public function DoAddObjects(WebPage $oP, $iMaxAddedId, $oFullSetFilter, $oCurrentObj)
|
||||
{
|
||||
$aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter);
|
||||
|
||||
$iAdditionId = $iMaxAddedId + 1;
|
||||
foreach($aLinkedObjectIds as $iObjectId)
|
||||
{
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId);
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false);
|
||||
if (is_object($oLinkedObj))
|
||||
{
|
||||
$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId, array(), $oCurrentObj ); // Not yet created link get negative Ids
|
||||
$oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId));
|
||||
$aRow = $this->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
|
||||
$oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iAdditionId));
|
||||
$iAdditionId++;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -440,7 +469,7 @@ EOF
|
||||
/**
|
||||
* Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context
|
||||
* @param DBObject $oSourceObj
|
||||
* @param DBObjectSearch $oSearch
|
||||
* @param DBSearch|DBObjectSearch $oSearch
|
||||
*/
|
||||
protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch)
|
||||
{
|
||||
@@ -459,7 +488,6 @@ EOF
|
||||
|
||||
if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sSrcClass, $sAttCode);
|
||||
$defaultValue = $oSourceObj->Get($sAttCode);
|
||||
|
||||
// Find the attcode for the same 'context' parameter in the destination class
|
||||
@@ -479,4 +507,3 @@ EOF
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -58,9 +58,15 @@ class UIPasswordWidget
|
||||
$sConfirmPasswordValue = $aPasswordValues ? $aPasswordValues['confirm'] : '*****';
|
||||
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
|
||||
$sHtmlValue = '';
|
||||
$sHtmlValue = '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/> <span class="form_validation" id="v_'.$this->iId.'"></span><br/>';
|
||||
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/> '.Dict::S('UI:PasswordConfirm').' <input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
|
||||
$sHtmlValue .= '<div class="field_input_zone field_input_onewaypassword">';
|
||||
$sHtmlValue .= '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/>';
|
||||
$sHtmlValue .= '<span>'.Dict::S('UI:PasswordConfirm').'</span>';
|
||||
$sHtmlValue .= '<input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
|
||||
$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'[changed]" value="'.$sChangedValue.'"/>';
|
||||
$sHtmlValue .= '</div>';
|
||||
|
||||
$sHtmlValue .= '<span class="form_validation" id="v_'.$this->iId.'"></span><span class="field_status" id="fstatus_'.$this->iId.'"></span>';
|
||||
|
||||
$oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate
|
||||
$oPage->add_ready_script("$('#$this->iId').bind('keyup change validate', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate
|
||||
|
||||
@@ -1,423 +0,0 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 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/>
|
||||
|
||||
|
||||
/**
|
||||
* Class UILinksWizard
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class UILinksWizard
|
||||
{
|
||||
protected $m_sClass;
|
||||
protected $m_sLinkageAttr;
|
||||
protected $m_iObjectId;
|
||||
protected $m_sLinkedClass;
|
||||
protected $m_sLinkingAttCode;
|
||||
protected $m_aEditableFields;
|
||||
protected $m_aTableConfig;
|
||||
|
||||
public function __construct($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass = '')
|
||||
{
|
||||
$this->m_sClass = $sClass;
|
||||
$this->m_sLinkageAttr = $sLinkageAttr;
|
||||
$this->m_iObjectId = $iObjectId;
|
||||
$this->m_sLinkedClass = $sLinkedClass; // Will try to guess below, if it's empty
|
||||
$this->m_sLinkingAttCode = ''; // Will be filled once we've found the attribute corresponding to the linked class
|
||||
|
||||
$this->m_aEditableFields = array();
|
||||
$this->m_aTableConfig = array();
|
||||
$this->m_aTableConfig['form::checkbox'] = array( 'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onChange=\"var value = this.checked; $('.selection').each( function() { this.checked = value; } );OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+'));
|
||||
foreach(MetaModel::GetAttributesList($this->m_sClass) as $sAttCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oAttDef->IsExternalKey() && ($sAttCode != $this->m_sLinkageAttr))
|
||||
{
|
||||
if (empty($this->m_sLinkedClass))
|
||||
{
|
||||
// This is a class of objects we can manage !
|
||||
// Since nothing was specify, any class will do !
|
||||
$this->m_sLinkedClass = $oAttDef->GetTargetClass();
|
||||
$this->m_sLinkingAttCode = $sAttCode;
|
||||
}
|
||||
else if ($this->m_sLinkedClass == $oAttDef->GetTargetClass())
|
||||
{
|
||||
// This is the class of objects we want to manage !
|
||||
$this->m_sLinkingAttCode = $sAttCode;
|
||||
}
|
||||
}
|
||||
else if ( (!$oAttDef->IsExternalKey()) && (!$oAttDef->IsExternalField()))
|
||||
{
|
||||
$this->m_aEditableFields[] = $sAttCode;
|
||||
$this->m_aTableConfig[$sAttCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());
|
||||
}
|
||||
}
|
||||
if (empty($this->m_sLinkedClass))
|
||||
{
|
||||
throw( new Exception(Dict::Format('UI:Error:IncorrectLinkDefinition_LinkedClass_Class', $sLinkedClass, $sClass)));
|
||||
}
|
||||
foreach(MetaModel::GetZListItems($this->m_sLinkedClass, 'list') as $sFieldCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
|
||||
$this->m_aTableConfig['static::'.$sFieldCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
public function Display(WebPage $oP, $aExtraParams = array())
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$oTargetObj = MetaModel::GetObject($sTargetClass, $this->m_iObjectId);
|
||||
|
||||
$oP->set_title("iTop - ".MetaModel::GetName($this->m_sLinkedClass)." objects linked with ".MetaModel::GetName(get_class($oTargetObj)).": ".$oTargetObj->GetRawName());
|
||||
$oP->add("<div class=\"wizContainer\">\n");
|
||||
$oP->add("<form method=\"post\">\n");
|
||||
$oP->add("<div class=\"page_header\">\n");
|
||||
$oP->add("<input type=\"hidden\" id=\"linksToRemove\" name=\"linksToRemove\" value=\"\">\n");
|
||||
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"do_modify_links\">\n");
|
||||
$oP->add("<input type=\"hidden\" name=\"class\" value=\"{$this->m_sClass}\">\n");
|
||||
$oP->add("<input type=\"hidden\" name=\"linkage\" value=\"{$this->m_sLinkageAttr}\">\n");
|
||||
$oP->add("<input type=\"hidden\" name=\"object_id\" value=\"{$this->m_iObjectId}\">\n");
|
||||
$oP->add("<input type=\"hidden\" name=\"linking_attcode\" value=\"{$this->m_sLinkingAttCode}\">\n");
|
||||
$oP->add("<h1>".Dict::Format('UI:ManageObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."</h1>\n");
|
||||
$oP->add("</div>\n");
|
||||
$oP->add_script(
|
||||
<<<EOF
|
||||
function OnSelectChange()
|
||||
{
|
||||
var nbChecked = $('.selection:checked').length;
|
||||
if (nbChecked > 0)
|
||||
{
|
||||
$('#btnRemove').removeAttr('disabled');
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#btnRemove').attr('disabled','disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function RemoveSelected()
|
||||
{
|
||||
$('.selection:checked').each(
|
||||
function()
|
||||
{
|
||||
$('#linksToRemove').val($('#linksToRemove').val() + ' ' + this.value);
|
||||
$('#row_'+this.value).remove();
|
||||
}
|
||||
);
|
||||
// Disable the button since all the selected items have been removed
|
||||
$('#btnRemove').attr('disabled','disabled');
|
||||
// Re-run the zebra plugin to properly highlight the remaining lines
|
||||
$('.listResults').trigger('update');
|
||||
|
||||
}
|
||||
|
||||
function AddObjects()
|
||||
{
|
||||
// TO DO: compute the list of objects already linked with the current Object
|
||||
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { 'operation': 'addObjects',
|
||||
'class': '{$this->m_sClass}',
|
||||
'linkageAttr': '{$this->m_sLinkageAttr}',
|
||||
'linkedClass': '{$this->m_sLinkedClass}',
|
||||
'objectId': '{$this->m_iObjectId}'
|
||||
},
|
||||
function(data)
|
||||
{
|
||||
$('#ModalDlg').html(data);
|
||||
dlgWidth = $(document).width() - 100;
|
||||
$('#ModalDlg').css('width', dlgWidth);
|
||||
$('#ModalDlg').css('left', 50);
|
||||
$('#ModalDlg').css('top', 50);
|
||||
$('#ModalDlg').dialog( 'open' );
|
||||
},
|
||||
'html'
|
||||
);
|
||||
}
|
||||
|
||||
function SearchObjectsToAdd(currentFormId)
|
||||
{
|
||||
var theMap = { 'class': '{$this->m_sClass}',
|
||||
'linkageAttr': '{$this->m_sLinkageAttr}',
|
||||
'linkedClass': '{$this->m_sLinkedClass}',
|
||||
'objectId': '{$this->m_iObjectId}'
|
||||
}
|
||||
if ($('#'+currentFormId+' :input[name=class]').val() != undefined)
|
||||
{
|
||||
theMap.linkedClass = $('#'+currentFormId+' :input[name=class]').val();
|
||||
}
|
||||
// Gather the parameters from the search form
|
||||
$('#'+currentFormId+' :input').each(
|
||||
function(i)
|
||||
{
|
||||
if (this.name != '')
|
||||
{
|
||||
theMap[this.name] = this.value;
|
||||
}
|
||||
}
|
||||
);
|
||||
theMap['operation'] = 'searchObjectsToAdd';
|
||||
|
||||
// Run the query and display the results
|
||||
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
|
||||
function(data)
|
||||
{
|
||||
$('#SearchResultsToAdd').html(data);
|
||||
$('#SearchResultsToAdd .listResults').tablesorter( { headers: {0: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
|
||||
|
||||
},
|
||||
'html'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function DoAddObjects(currentFormId)
|
||||
{
|
||||
var theMap = { 'class': '{$this->m_sClass}',
|
||||
'linkageAttr': '{$this->m_sLinkageAttr}',
|
||||
'linkedClass': '{$this->m_sLinkedClass}',
|
||||
'objectId': '{$this->m_iObjectId}'
|
||||
}
|
||||
|
||||
// Gather the parameters from the search form
|
||||
$('#'+currentFormId+' :input').each(
|
||||
function(i)
|
||||
{
|
||||
if ( (this.name != '') && ((this.type != 'checkbox') || (this.checked)) )
|
||||
{
|
||||
//console.log(this.type);
|
||||
arrayExpr = /\[\]$/;
|
||||
if (arrayExpr.test(this.name))
|
||||
{
|
||||
// Array
|
||||
if (theMap[this.name] == undefined)
|
||||
{
|
||||
theMap[this.name] = new Array();
|
||||
}
|
||||
theMap[this.name].push(this.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
theMap[this.name] = this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
theMap['operation'] = 'doAddObjects';
|
||||
|
||||
// Run the query and display the results
|
||||
$.post( GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', theMap,
|
||||
function(data)
|
||||
{
|
||||
//console.log('Data: ' + data);
|
||||
if (data != '')
|
||||
{
|
||||
$('#empty_row').remove();
|
||||
}
|
||||
$('.listResults tbody').append(data);
|
||||
$('.listResults').trigger('update');
|
||||
$('.listResults').tablesorter( { headers: {0: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
|
||||
},
|
||||
'html'
|
||||
);
|
||||
$('#ModalDlg').dialog('close');
|
||||
return false;
|
||||
}
|
||||
|
||||
function InitForm()
|
||||
{
|
||||
// make sure that the form is clean
|
||||
$('.selection').each( function() { this.checked = false; });
|
||||
$('#btnRemove').attr('disabled','disabled');
|
||||
$('#linksToRemove').val('');
|
||||
}
|
||||
|
||||
function SubmitHook()
|
||||
{
|
||||
var the_form = this;
|
||||
SearchObjectsToAdd(the_form.id);
|
||||
return false;
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$oP->add_ready_script("InitForm();");
|
||||
$oFilter = new DBObjectSearch($this->m_sClass);
|
||||
$oFilter->AddCondition($this->m_sLinkageAttr, $this->m_iObjectId, '=');
|
||||
$oSet = new DBObjectSet($oFilter);
|
||||
$aForm = array();
|
||||
while($oCurrentLink = $oSet->Fetch())
|
||||
{
|
||||
$aRow = array();
|
||||
$key = $oCurrentLink->GetKey();
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sLinkedClass, $oCurrentLink->Get($this->m_sLinkingAttCode));
|
||||
|
||||
$aForm[$key] = $this->GetFormRow($oP, $oLinkedObj, $oCurrentLink);
|
||||
}
|
||||
//var_dump($aTableLabels);
|
||||
//var_dump($aForm);
|
||||
$this->DisplayFormTable($oP, $this->m_aTableConfig, $aForm);
|
||||
$oP->add("<span style=\"float:left;\"> <img src=\"../images/tv-item-last.gif\"> <input id=\"btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"RemoveSelected();\" >");
|
||||
$oP->add(" <input id=\"btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sLinkedClass))."\" onClick=\"AddObjects();\"></span>\n");
|
||||
$oP->add("<span style=\"float:right;\"><input id=\"btnCancel\" type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"BackToDetails('".$sTargetClass."', ".$this->m_iObjectId.");\">");
|
||||
$oP->add(" <input id=\"btnOk\" type=\"submit\" value=\"".Dict::S('UI:Button:Ok')."\"></span>\n");
|
||||
$oP->add("<span style=\"clear:both;\"><p> </p></span>\n");
|
||||
$oP->add("</div>\n");
|
||||
$oP->add("</form>\n");
|
||||
if (isset($aExtraParams['StartWithAdd']) && ($aExtraParams['StartWithAdd']))
|
||||
{
|
||||
$oP->add_ready_script("AddObjects();");
|
||||
}
|
||||
}
|
||||
|
||||
protected function GetFormRow($oP, $oLinkedObj, $currentLink = null )
|
||||
{
|
||||
$aRow = array();
|
||||
if(is_object($currentLink))
|
||||
{
|
||||
$key = $currentLink->GetKey();
|
||||
$sNameSuffix = "[$key]"; // To make a tabular form
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onChange=\"OnSelectChange();\" value=\"$key\">";
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"linkId[$key]\" value=\"$key\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sClass, $sFieldCode, $oAttDef, $currentLink->Get($sFieldCode), '' /* DisplayValue */, $key, $sNameSuffix);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// form for creating a new record
|
||||
$sNameSuffix = "[$currentLink]"; // To make a tabular form
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onChange=\"OnSelectChange();\" value=\"$currentLink\">";
|
||||
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"linkId[$currentLink]\" value=\"$currentLink\">";
|
||||
foreach($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sFieldCode);
|
||||
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, '' /* id */, $sNameSuffix);
|
||||
}
|
||||
}
|
||||
foreach(MetaModel::GetZListItems($this->m_sLinkedClass, 'list') as $sFieldCode)
|
||||
{
|
||||
$aRow['static::'.$sFieldCode] = $oLinkedObj->GetAsHTML($sFieldCode);
|
||||
}
|
||||
return $aRow;
|
||||
}
|
||||
|
||||
protected function DisplayFormTable(WebPage $oP, $aConfig, $aData)
|
||||
{
|
||||
$oP->add("<table class=\"listResults\">\n");
|
||||
// Header
|
||||
$oP->add("<thead>\n");
|
||||
$oP->add("<tr>\n");
|
||||
foreach($aConfig as $sName=>$aDef)
|
||||
{
|
||||
$oP->add("<th title=\"".$aDef['description']."\">".$aDef['label']."</th>\n");
|
||||
}
|
||||
$oP->add("</tr>\n");
|
||||
$oP->add("</thead>\n");
|
||||
|
||||
// Content
|
||||
$oP->add("</tbody>\n");
|
||||
if (count($aData) == 0)
|
||||
{
|
||||
$oP->add("<tr id=\"empty_row\"><td colspan=\"".count($aConfig)."\" style=\"text-align:center;\">".Dict::S('UI:Message:EmptyList:UseAdd')."</td></td>");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($aData as $iRowId => $aRow)
|
||||
{
|
||||
$this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId);
|
||||
}
|
||||
}
|
||||
$oP->add("</tbody>\n");
|
||||
|
||||
// Footer
|
||||
$oP->add("</table>\n");
|
||||
}
|
||||
|
||||
protected function DisplayFormRow(WebPage $oP, $aConfig, $aRow, $iRowId)
|
||||
{
|
||||
$oP->add("<tr id=\"row_$iRowId\">\n");
|
||||
foreach($aConfig as $sName=>$void)
|
||||
{
|
||||
$oP->add("<td>".$aRow[$sName]."</td>\n");
|
||||
}
|
||||
$oP->add("</tr>\n");
|
||||
}
|
||||
|
||||
public function DisplayAddForm(WebPage $oP)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
|
||||
$sTargetClass = $oAttDef->GetTargetClass();
|
||||
$oTargetObj = MetaModel::GetObject($sTargetClass, $this->m_iObjectId);
|
||||
$oP->add("<div class=\"wizContainer\">\n");
|
||||
//$oP->add("<div class=\"page_header\">\n");
|
||||
//$oP->add("<h1>".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."</h1>\n");
|
||||
//$oP->add("</div>\n");
|
||||
|
||||
$oFilter = new DBObjectSearch($this->m_sLinkedClass);
|
||||
$oSet = new CMDBObjectSet($oFilter);
|
||||
$oBlock = new DisplayBlock($oFilter, 'search', false);
|
||||
$oBlock->Display($oP, 'SearchFormToAdd', array('open' => true));
|
||||
$oP->Add("<form id=\"ObjectsAddForm\" OnSubmit=\"return DoAddObjects(this.id);\">\n");
|
||||
$oP->Add("<div id=\"SearchResultsToAdd\">\n");
|
||||
$oP->Add("<div style=\"height: 100px; background: #fff;border-color:#F6F6F1 #E6E6E1 #E6E6E1 #F6F6F1; border-style:solid; border-width:3px; text-align: center; vertical-align: center;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n");
|
||||
$oP->Add("</div>\n");
|
||||
$oP->add("<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ModalDlg').dialog('close');\"> <input type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">");
|
||||
$oP->Add("</div>\n");
|
||||
$oP->Add("</form>\n");
|
||||
$oP->add_ready_script("$('#ModalDlg').dialog('option', {title:'".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "<span class=\"hilite\">".$oTargetObj->GetHyperlink()."</span>")."'});");
|
||||
$oP->add_ready_script("$('div#SearchFormToAdd form').bind('submit.uilinksWizard', SubmitHook);");
|
||||
}
|
||||
|
||||
public function SearchObjectsToAdd(WebPage $oP)
|
||||
{
|
||||
//$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
|
||||
|
||||
$oFilter = new DBObjectSearch($this->m_sLinkedClass);
|
||||
$oSet = new CMDBObjectSet($oFilter);
|
||||
$oBlock = new DisplayBlock($oFilter, 'list', false);
|
||||
$oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true)); // Don't display the 'Actions' menu on the results
|
||||
}
|
||||
|
||||
public function DoAddObjects(WebPage $oP, $aLinkedObjectIds = array())
|
||||
{
|
||||
//$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr);
|
||||
//$sTargetClass = $oAttDef->GetTargetClass();
|
||||
//$oP->Add("<!-- nb of objects to add: ".count($aLinkedObjectIds)." -->\n"); // Just to make sure it's not empty
|
||||
$aTable = array();
|
||||
foreach($aLinkedObjectIds as $iObjectId)
|
||||
{
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sLinkedClass, $iObjectId);
|
||||
if (is_object($oLinkedObj))
|
||||
{
|
||||
$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId ); // Not yet created link get negative Ids
|
||||
$this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
echo Dict::Format('UI:Error:Object_Class_Id_NotFound', $this->m_sLinkedClass, $iObjectId);
|
||||
}
|
||||
}
|
||||
//var_dump($aTable);
|
||||
//$oP->Add("<!-- end of added list -->\n"); // Just to make sure it's not empty
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Store and retrieve user's preferences (i.e persistent per user settings)
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
require_once(APPROOT.'/core/dbobject.class.php');
|
||||
@@ -50,7 +50,7 @@ class appUserPreferences extends DBObject
|
||||
self::Load();
|
||||
}
|
||||
$aPrefs = self::$oUserPrefs->Get('preferences');
|
||||
if (isset($aPrefs[$sCode]))
|
||||
if (array_key_exists($sCode, $aPrefs))
|
||||
{
|
||||
return $aPrefs[$sCode];
|
||||
}
|
||||
@@ -72,9 +72,16 @@ class appUserPreferences extends DBObject
|
||||
self::Load();
|
||||
}
|
||||
$aPrefs = self::$oUserPrefs->Get('preferences');
|
||||
$aPrefs[$sCode] = $sValue;
|
||||
self::$oUserPrefs->Set('preferences', $aPrefs);
|
||||
self::Save();
|
||||
if (array_key_exists($sCode, $aPrefs) && ($aPrefs[$sCode] === $sValue))
|
||||
{
|
||||
// Do not write it again
|
||||
}
|
||||
else
|
||||
{
|
||||
$aPrefs[$sCode] = $sValue;
|
||||
self::$oUserPrefs->Set('preferences', $aPrefs);
|
||||
self::Save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,7 +155,9 @@ class appUserPreferences extends DBObject
|
||||
{
|
||||
if (self::$oUserPrefs->IsModified())
|
||||
{
|
||||
utils::PushArchiveMode(false);
|
||||
self::$oUserPrefs->DBUpdate();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,7 +181,9 @@ class appUserPreferences extends DBObject
|
||||
$oObj->Set('preferences', array()); // Default preferences: an empty array
|
||||
try
|
||||
{
|
||||
utils::PushArchiveMode(false);
|
||||
$oObj->DBInsert();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
@@ -207,7 +218,8 @@ class appUserPreferences extends DBObject
|
||||
*/
|
||||
public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
|
||||
{
|
||||
utils::PushArchiveMode(false);
|
||||
$this->DBDelete($oDeletionPlan);
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2014 Combodo SARL
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class WebPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -71,8 +71,9 @@ class WebPage implements Page
|
||||
protected $bTrashUnexpectedOutput;
|
||||
protected $s_sOutputFormat;
|
||||
protected $a_OutputOptions;
|
||||
protected $bPrintable;
|
||||
|
||||
public function __construct($s_title)
|
||||
public function __construct($s_title, $bPrintable = false)
|
||||
{
|
||||
$this->s_title = $s_title;
|
||||
$this->s_content = "";
|
||||
@@ -92,6 +93,7 @@ class WebPage implements Page
|
||||
$this->bTrashUnexpectedOutput = false;
|
||||
$this->s_OutputFormat = utils::ReadParam('output_format', 'html');
|
||||
$this->a_OutputOptions = array();
|
||||
$this->bPrintable = $bPrintable;
|
||||
ob_start(); // Start capturing the output
|
||||
}
|
||||
|
||||
@@ -269,6 +271,18 @@ class WebPage implements Page
|
||||
$this->a_linked_stylesheets[] = array( 'link' => $s_linked_stylesheet, 'condition' => $s_condition);
|
||||
}
|
||||
|
||||
public function add_saas($sSaasRelPath)
|
||||
{
|
||||
$sCssRelPath = utils::GetCSSFromSASS($sSaasRelPath);
|
||||
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
if ($sRootUrl === '')
|
||||
{
|
||||
// We're running the setup of the first install...
|
||||
$sRootUrl = '../';
|
||||
}
|
||||
$sCSSUrl = $sRootUrl.$sCssRelPath;
|
||||
$this->add_linked_stylesheet($sCSSUrl);
|
||||
}
|
||||
/**
|
||||
* Add some custom header to the page
|
||||
*/
|
||||
@@ -294,6 +308,15 @@ class WebPage implements Page
|
||||
|
||||
$this->add($this->GetDetails($aFields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the page is a PDF page
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_pdf()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the current state of the 'html' part of the page output
|
||||
@@ -322,27 +345,40 @@ class WebPage implements Page
|
||||
*/
|
||||
public function GetDetails($aFields)
|
||||
{
|
||||
$sHtml = "<table class=\"details\">\n";
|
||||
$sHtml .= "<tbody>\n";
|
||||
$sHtml = "<div class=\"details\">\n";
|
||||
foreach($aFields as $aAttrib)
|
||||
{
|
||||
$sHtml .= "<tr>\n";
|
||||
$sDataAttCode = isset($aAttrib['attcode']) ? "data-attcode=\"{$aAttrib['attcode']}\"" : '';
|
||||
$sLayout = isset($aAttrib['layout']) ? $aAttrib['layout'] : 'small';
|
||||
$sHtml .= "<div class=\"field_container field_{$sLayout}\" $sDataAttCode>\n";
|
||||
$sHtml .= "<div class=\"field_label label\">{$aAttrib['label']}</div>\n";
|
||||
|
||||
$sHtml .= "<div class=\"field_data\">\n";
|
||||
// By Rom, for csv import, proposed to show several values for column selection
|
||||
if (is_array($aAttrib['value']))
|
||||
{
|
||||
$sHtml .= "<td class=\"label\">".$aAttrib['label']."</td><td>".implode("</td><td>", $aAttrib['value'])."</td>\n";
|
||||
$sHtml .= "<div class=\"field_value\">".implode("</div><div>", $aAttrib['value'])."</div>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHtml .= "<td class=\"label\">".$aAttrib['label']."</td><td>".$aAttrib['value']."</td>\n";
|
||||
$sHtml .= "<div class=\"field_value\">".$aAttrib['value']."</div>\n";
|
||||
}
|
||||
$sComment = (isset($aAttrib['comments'])) ? $aAttrib['comments'] : ' ';
|
||||
$sInfo = (isset($aAttrib['infos'])) ? $aAttrib['infos'] : ' ';
|
||||
$sHtml .= "<td>$sComment</td><td>$sInfo</td>\n";
|
||||
$sHtml .= "</tr>\n";
|
||||
// Checking if we should add comments & infos
|
||||
$sComment = (isset($aAttrib['comments'])) ? $aAttrib['comments'] : '';
|
||||
$sInfo = (isset($aAttrib['infos'])) ? $aAttrib['infos'] : '';
|
||||
if($sComment !== '')
|
||||
{
|
||||
$sHtml .= "<div class=\"field_comments\">$sComment</div>\n";
|
||||
}
|
||||
if($sInfo !== '')
|
||||
{
|
||||
$sHtml .= "<div class=\"field_infos\">$sInfo</div>\n";
|
||||
}
|
||||
$sHtml .= "</div>\n";
|
||||
|
||||
$sHtml .= "</div>\n";
|
||||
}
|
||||
$sHtml .= "</tbody>\n";
|
||||
$sHtml .= "</table>\n";
|
||||
$sHtml .= "</div>\n";
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -436,7 +472,7 @@ class WebPage implements Page
|
||||
{
|
||||
if (Utils::GetConfig() && Utils::GetConfig()->Get('debug_report_spurious_chars'))
|
||||
{
|
||||
IssueLog::Error("Trashing unexpected output:'$s_captured_output'\n");
|
||||
IssueLog::Error("Trashing unexpected output:'$sOutput'\n");
|
||||
}
|
||||
}
|
||||
$sOutput = '';
|
||||
@@ -460,6 +496,7 @@ class WebPage implements Page
|
||||
echo "<html>\n";
|
||||
echo "<head>\n";
|
||||
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
|
||||
echo "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, shrink-to-fit=no\" />";
|
||||
echo "<title>".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."</title>\n";
|
||||
echo $this->get_base_tag();
|
||||
foreach($this->a_linked_scripts as $s_script)
|
||||
@@ -488,11 +525,19 @@ class WebPage implements Page
|
||||
$this->output_dict_entries();
|
||||
foreach($this->a_linked_stylesheets as $a_stylesheet)
|
||||
{
|
||||
if (strpos($a_stylesheet['link'], '?') === false)
|
||||
{
|
||||
$s_stylesheet = $a_stylesheet['link']."?itopversion=".ITOP_VERSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
$s_stylesheet = $a_stylesheet['link']."&itopversion=".ITOP_VERSION;
|
||||
}
|
||||
if ($a_stylesheet['condition'] != "")
|
||||
{
|
||||
echo "<!--[if {$a_stylesheet['condition']}]>\n";
|
||||
}
|
||||
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$a_stylesheet['link']}\" />\n";
|
||||
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$s_stylesheet}\" />\n";
|
||||
if ($a_stylesheet['condition'] != "")
|
||||
{
|
||||
echo "<![endif]-->\n";
|
||||
@@ -510,7 +555,7 @@ class WebPage implements Page
|
||||
}
|
||||
if (class_exists('MetaModel') && MetaModel::GetConfig())
|
||||
{
|
||||
echo "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico\" />\n";
|
||||
echo "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico?itopversion=".ITOP_VERSION."\" />\n";
|
||||
}
|
||||
echo "</head>\n";
|
||||
echo "<body>\n";
|
||||
@@ -523,9 +568,9 @@ class WebPage implements Page
|
||||
echo "</body>\n";
|
||||
echo "</html>\n";
|
||||
|
||||
if (class_exists('MetaModel'))
|
||||
if (class_exists('DBSearch'))
|
||||
{
|
||||
MetaModel::RecordQueryTrace();
|
||||
DBSearch::RecordQueryTrace();
|
||||
}
|
||||
if (class_exists('ExecutionKPI'))
|
||||
{
|
||||
@@ -652,7 +697,16 @@ class WebPage implements Page
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the output must be printable (using print.css, for sure!)
|
||||
* @return bool ...
|
||||
*/
|
||||
public function IsPrintableVersion()
|
||||
{
|
||||
return $this->bPrintable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a named output option for the given format
|
||||
* @param string $sFormat The format: html or pdf
|
||||
@@ -689,32 +743,34 @@ class WebPage implements Page
|
||||
{
|
||||
$sPrevUrl = '';
|
||||
$sHtml = '';
|
||||
foreach ($aActions as $aAction)
|
||||
if (!$this->IsPrintableVersion())
|
||||
{
|
||||
$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
|
||||
$sOnClick = isset($aAction['onclick']) ? ' onclick="'.htmlspecialchars($aAction['onclick'], ENT_QUOTES, "UTF-8").'"' : '';
|
||||
$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
|
||||
if (empty($aAction['url']))
|
||||
foreach ($aActions as $aAction)
|
||||
{
|
||||
if ($sPrevUrl != '') // Don't output consecutively two separators...
|
||||
$sClass = isset($aAction['css_classes']) ? ' class="'.implode(' ', $aAction['css_classes']).'"' : '';
|
||||
$sOnClick = isset($aAction['onclick']) ? ' onclick="'.htmlspecialchars($aAction['onclick'], ENT_QUOTES, "UTF-8").'"' : '';
|
||||
$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
|
||||
if (empty($aAction['url']))
|
||||
{
|
||||
$sHtml .= "<li>{$aAction['label']}</li>";
|
||||
if ($sPrevUrl != '') // Don't output consecutively two separators...
|
||||
{
|
||||
$sHtml .= "<li>{$aAction['label']}</li>";
|
||||
}
|
||||
$sPrevUrl = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sHtml .= "<li><a $sTarget href=\"{$aAction['url']}\"$sClass $sOnClick>{$aAction['label']}</a></li>";
|
||||
$sPrevUrl = $aAction['url'];
|
||||
}
|
||||
$sPrevUrl = '';
|
||||
}
|
||||
else
|
||||
$sHtml .= "</ul></li></ul></div>";
|
||||
foreach(array_reverse($aFavoriteActions) as $aAction)
|
||||
{
|
||||
$sHtml .= "<li><a $sTarget href=\"{$aAction['url']}\"$sClass $sOnClick>{$aAction['label']}</a></li>";
|
||||
$sPrevUrl = $aAction['url'];
|
||||
$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
|
||||
$sHtml .= "<div class=\"actions_button\"><a $sTarget href='{$aAction['url']}'>{$aAction['label']}</a></div>";
|
||||
}
|
||||
}
|
||||
$sHtml .= "</ul></li></ul></div>";
|
||||
foreach(array_reverse($aFavoriteActions) as $aAction)
|
||||
{
|
||||
$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
|
||||
$sHtml .= "<div class=\"actions_button\"><a $sTarget href='{$aAction['url']}'>{$aAction['label']}</a></div>";
|
||||
}
|
||||
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -938,11 +994,11 @@ class TabManager
|
||||
|
||||
/**
|
||||
* Finds the tab whose title matches a given pattern
|
||||
* @return mixed The name of the tab as a string or false if not found
|
||||
* @return mixed The actual name of the tab (as a string) or false if not found
|
||||
*/
|
||||
public function FindTab($sPattern, $sTabContainer = null)
|
||||
{
|
||||
$return = false;
|
||||
$result = false;
|
||||
if ($sTabContainer == null)
|
||||
{
|
||||
$sTabContainer = $this->m_sCurrentTabContainer;
|
||||
@@ -988,7 +1044,7 @@ class TabManager
|
||||
return "window.setTimeout(\"$('$sSelector').tabs('select', $tab_index);\", 100);"; // Let the time to the tabs widget to initialize
|
||||
}
|
||||
|
||||
public function RenderIntoContent($sContent)
|
||||
public function RenderIntoContent($sContent, WebPage $oPage)
|
||||
{
|
||||
// Render the tabs in the page (if any)
|
||||
foreach($this->m_aTabs as $sTabContainerName => $aTabs)
|
||||
@@ -998,42 +1054,86 @@ class TabManager
|
||||
$container_index = 0;
|
||||
if (count($aTabs['tabs']) > 0)
|
||||
{
|
||||
$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$container_index}\" class=\"light\">\n";
|
||||
$sTabs .= "<ul>\n";
|
||||
// Display the unordered list that will be rendered as the tabs
|
||||
$i = 0;
|
||||
foreach($aTabs['tabs'] as $sTabName => $aTabData)
|
||||
if ($oPage->IsPrintableVersion())
|
||||
{
|
||||
switch($aTabData['type'])
|
||||
$oPage->add_ready_script(
|
||||
<<< EOF
|
||||
oHiddeableChapters = {};
|
||||
EOF
|
||||
);
|
||||
$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$container_index}\" class=\"light\">\n";
|
||||
$i = 0;
|
||||
foreach($aTabs['tabs'] as $sTabName => $aTabData)
|
||||
{
|
||||
case 'ajax':
|
||||
$sTabs .= "<li data-cache=\"".($aTabData['cache'] ? 'true' : 'false')."\"><a href=\"{$aTabData['url']}\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
default:
|
||||
$sTabs .= "<li><a href=\"#tab_{$sPrefix}{$container_index}$i\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
|
||||
$sTabNameEsc = addslashes($sTabName);
|
||||
$sTabId = "tab_{$sPrefix}{$container_index}$i";
|
||||
switch($aTabData['type'])
|
||||
{
|
||||
case 'ajax':
|
||||
$sTabHtml = '';
|
||||
$sUrl = $aTabData['url'];
|
||||
$oPage->add_ready_script(
|
||||
<<< EOF
|
||||
$.post('$sUrl', {printable: '1'}, function(data){
|
||||
$('#$sTabId > .printable-tab-content').append(data);
|
||||
});
|
||||
EOF
|
||||
);
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
default:
|
||||
$sTabHtml = $aTabData['html'];
|
||||
}
|
||||
$sTabs .= "<div class=\"printable-tab\" id=\"$sTabId\"><h2 class=\"printable-tab-title\">".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</h2><div class=\"printable-tab-content\">".$sTabHtml."</div></div>\n";
|
||||
$oPage->add_ready_script(
|
||||
<<< EOF
|
||||
oHiddeableChapters['$sTabId'] = '$sTabNameEsc';
|
||||
EOF
|
||||
);
|
||||
$i++;
|
||||
}
|
||||
$i++;
|
||||
$sTabs .= "</div>\n<!-- end of tabs-->\n";
|
||||
}
|
||||
$sTabs .= "</ul>\n";
|
||||
// Now add the content of the tabs themselves
|
||||
$i = 0;
|
||||
foreach($aTabs['tabs'] as $sTabName => $aTabData)
|
||||
else
|
||||
{
|
||||
switch($aTabData['type'])
|
||||
$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$container_index}\" class=\"light\">\n";
|
||||
$sTabs .= "<ul>\n";
|
||||
// Display the unordered list that will be rendered as the tabs
|
||||
$i = 0;
|
||||
foreach($aTabs['tabs'] as $sTabName => $aTabData)
|
||||
{
|
||||
case 'ajax':
|
||||
// Nothing to add
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
default:
|
||||
$sTabs .= "<div id=\"tab_{$sPrefix}{$container_index}$i\">".$aTabData['html']."</div>\n";
|
||||
switch($aTabData['type'])
|
||||
{
|
||||
case 'ajax':
|
||||
$sTabs .= "<li data-cache=\"".($aTabData['cache'] ? 'true' : 'false')."\"><a href=\"{$aTabData['url']}\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
default:
|
||||
$sTabs .= "<li><a href=\"#tab_{$sPrefix}{$container_index}$i\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$i++;
|
||||
$sTabs .= "</ul>\n";
|
||||
// Now add the content of the tabs themselves
|
||||
$i = 0;
|
||||
foreach($aTabs['tabs'] as $sTabName => $aTabData)
|
||||
{
|
||||
switch($aTabData['type'])
|
||||
{
|
||||
case 'ajax':
|
||||
// Nothing to add
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
default:
|
||||
$sTabs .= "<div id=\"tab_{$sPrefix}{$container_index}$i\">".$aTabData['html']."</div>\n";
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$sTabs .= "</div>\n<!-- end of tabs-->\n";
|
||||
}
|
||||
$sTabs .= "</div>\n<!-- end of tabs-->\n";
|
||||
}
|
||||
$sContent = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $sContent);
|
||||
$container_index++;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class WizardHelper
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -35,7 +35,7 @@ class WizardHelper
|
||||
}
|
||||
/**
|
||||
* Constructs the PHP target object from the parameters sent to the web page by the wizard
|
||||
* @param boolean $bReadUploadedFiles True to also ready any uploaded file (for blob/document fields)
|
||||
* @param boolean $bReadUploadedFiles True to also read any uploaded file (for blob/document fields)
|
||||
* @return object
|
||||
*/
|
||||
public function GetTargetObject($bReadUploadedFiles = false)
|
||||
@@ -52,7 +52,7 @@ class WizardHelper
|
||||
{
|
||||
// Because this is stored in a Javascript array, unused indexes
|
||||
// are filled with null values and unused keys (stored as strings) contain $$NULL$$
|
||||
if ( ($sAttCode !='id') && ($sAttCode !== false) && ($value !== null) && ($value !== '$$NULL$$'))
|
||||
if ( ($sAttCode !='id') && ($value !== '$$NULL$$'))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_aData['m_sClass'], $sAttCode);
|
||||
if (($oAttDef->IsLinkSet()) && ($value != '') )
|
||||
@@ -84,6 +84,18 @@ class WizardHelper
|
||||
$oTargetObj = MetaModel::GetObject($sLinkedAttDef->GetTargetClass(), $aLinkedObject[$sLinkedAttCode]);
|
||||
$oLinkedObj->Set($sLinkedAttCode, $oTargetObj);
|
||||
}
|
||||
elseif($sLinkedAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
$sDate = $aLinkedObject[$sLinkedAttCode];
|
||||
if($sDate !== null && $sDate !== '')
|
||||
{
|
||||
$oDateTimeFormat = AttributeDateTime::GetFormat();
|
||||
$oDate = $oDateTimeFormat->Parse($sDate);
|
||||
$sDate = $oDate->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$oLinkedObj->Set($sLinkedAttCode, $sDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oLinkedObj->Set($sLinkedAttCode, $aLinkedObject[$sLinkedAttCode]);
|
||||
@@ -109,12 +121,50 @@ class WizardHelper
|
||||
$oObj->Set($sAttCode, $oDocument);
|
||||
}
|
||||
}
|
||||
else if ( $oAttDef->GetEditClass() == 'Image' )
|
||||
{
|
||||
if ($bReadUploadedFiles)
|
||||
{
|
||||
$oDocument = utils::ReadPostedDocument('attr_'.$sAttCode, 'fcontents');
|
||||
$oObj->Set($sAttCode, $oDocument);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new empty document, just for displaying the file name
|
||||
$oDocument = new ormDocument(null, '', $value);
|
||||
$oObj->Set($sAttCode, $oDocument);
|
||||
}
|
||||
}
|
||||
else if (($oAttDef->IsExternalKey()) && (!empty($value)) && ($value > 0) )
|
||||
{
|
||||
// For external keys: load the target object so that external fields
|
||||
// get filled too
|
||||
$oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value);
|
||||
$oObj->Set($sAttCode, $oTargetObj);
|
||||
$oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value, false);
|
||||
if ($oTargetObj)
|
||||
{
|
||||
$oObj->Set($sAttCode, $oTargetObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
// May happen for security reasons (portal, see ticket #1074)
|
||||
$oObj->Set($sAttCode, $value);
|
||||
}
|
||||
}
|
||||
else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
if ($value != null)
|
||||
{
|
||||
$oDate = $oAttDef->GetFormat()->Parse($value);
|
||||
if ($oDate instanceof DateTime)
|
||||
{
|
||||
$value = $oDate->format($oAttDef->GetInternalFormat());
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = null;
|
||||
}
|
||||
}
|
||||
$oObj->Set($sAttCode, $value);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -232,11 +282,21 @@ class WizardHelper
|
||||
{
|
||||
return $this->m_aData['m_sClass'];
|
||||
}
|
||||
|
||||
public function GetFormPrefix()
|
||||
{
|
||||
return $this->m_aData['m_sFormPrefix'];
|
||||
}
|
||||
|
||||
public function GetFormPrefix()
|
||||
{
|
||||
return $this->m_aData['m_sFormPrefix'];
|
||||
}
|
||||
|
||||
public function GetInitialState()
|
||||
{
|
||||
return isset($this->m_aData['m_sInitialState']) ? $this->m_aData['m_sInitialState'] : null;
|
||||
}
|
||||
|
||||
public function GetStimulus()
|
||||
{
|
||||
return isset($this->m_aData['m_sStimulus']) ? $this->m_aData['m_sStimulus'] : null;
|
||||
}
|
||||
|
||||
public function GetIdForField($sFieldName)
|
||||
{
|
||||
@@ -276,4 +336,3 @@ class WizardHelper
|
||||
return $oSet;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -13,6 +13,8 @@ Class XLSXWriter
|
||||
protected $shared_strings = array();//unique set
|
||||
protected $shared_string_count = 0;//count of non-unique references to the unique set
|
||||
protected $temp_files = array();
|
||||
protected $date_format = 'YYYY-MM-DD';
|
||||
protected $date_time_format = 'YYYY-MM-DD\ HH:MM:SS';
|
||||
|
||||
public function __construct(){}
|
||||
public function setAuthor($author='') { $this->author=$author; }
|
||||
@@ -26,6 +28,16 @@ Class XLSXWriter
|
||||
}
|
||||
}
|
||||
|
||||
public function setDateFormat($date_format)
|
||||
{
|
||||
$this->date_format = $date_format;
|
||||
}
|
||||
|
||||
public function setDateTimeFormat($date_time_format)
|
||||
{
|
||||
$this->date_time_format = $date_time_format;
|
||||
}
|
||||
|
||||
protected function tempFilename()
|
||||
{
|
||||
$filename = tempnam("/tmp", "xlsx_writer_");
|
||||
@@ -79,7 +91,7 @@ Class XLSXWriter
|
||||
}
|
||||
|
||||
|
||||
public function writeSheet(array $data, $sheet_name='', array $header_types=array() )
|
||||
public function writeSheet(array $data, $sheet_name='', array $header_types=array(), array $header_row=array() )
|
||||
{
|
||||
$data = empty($data) ? array( array('') ) : $data;
|
||||
|
||||
@@ -95,7 +107,10 @@ Class XLSXWriter
|
||||
|
||||
$tabselected = count($this->sheets_meta)==1 ? 'true' : 'false';//only first sheet is selected
|
||||
$cell_formats_arr = empty($header_types) ? array_fill(0, $column_count, 'string') : array_values($header_types);
|
||||
$header_row = empty($header_types) ? array() : array_keys($header_types);
|
||||
if (empty($header_row) && !empty($header_types))
|
||||
{
|
||||
$header_row = empty($header_types) ? array() : array_keys($header_types);
|
||||
}
|
||||
|
||||
$fd = fopen($sheet_filename, "w+");
|
||||
if ($fd===false) { self::log("write failed in ".__CLASS__."::".__FUNCTION__."."); return; }
|
||||
@@ -149,18 +164,14 @@ Class XLSXWriter
|
||||
{
|
||||
static $styles = array('money'=>1,'dollar'=>1,'datetime'=>2,'date'=>3,'string'=>0);
|
||||
$cell = self::xlsCell($row_number, $column_number);
|
||||
$s = isset($styles[$cell_format]) ? $styles[$cell_format] : '0';
|
||||
$s = isset($styles[$cell_format]) && ($value !== '') ? $styles[$cell_format] : '0';
|
||||
|
||||
if (is_numeric($value)) {
|
||||
if (is_int($value) || is_float($value)) {
|
||||
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.($value*1).'</v></c>');//int,float, etc
|
||||
} else if ($cell_format=='date') {
|
||||
} else if (($cell_format=='date') && ($value != '')) {
|
||||
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.intval(self::convert_date_time($value)).'</v></c>');
|
||||
} else if ($cell_format=='datetime') {
|
||||
if ($value === '') {
|
||||
fwrite($fd,'<c r="'.$cell.'" s="0"/>');
|
||||
} else {
|
||||
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.self::convert_date_time($value).'</v></c>');
|
||||
}
|
||||
} else if (($cell_format=='datetime') && ($value != '')) {
|
||||
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'" t="n"><v>'.self::convert_date_time($value).'</v></c>');
|
||||
} else if ($value==''){
|
||||
fwrite($fd,'<c r="'.$cell.'" s="'.$s.'"/>');
|
||||
} else if ($value{0}=='='){
|
||||
@@ -180,8 +191,8 @@ Class XLSXWriter
|
||||
fwrite($fd, '<numFmts count="4">');
|
||||
fwrite($fd, '<numFmt formatCode="GENERAL" numFmtId="164"/>');
|
||||
fwrite($fd, '<numFmt formatCode="[$$-1009]#,##0.00;[RED]\-[$$-1009]#,##0.00" numFmtId="165"/>');
|
||||
fwrite($fd, '<numFmt formatCode="YYYY-MM-DD\ HH:MM:SS" numFmtId="166"/>');
|
||||
fwrite($fd, '<numFmt formatCode="YYYY-MM-DD" numFmtId="167"/>');
|
||||
fwrite($fd, '<numFmt formatCode="'.$this->date_time_format.'" numFmtId="166"/>');
|
||||
fwrite($fd, '<numFmt formatCode="'.$this->date_format.'" numFmtId="167"/>');
|
||||
fwrite($fd, '</numFmts>');
|
||||
fwrite($fd, '<fonts count="4">');
|
||||
fwrite($fd, '<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>');
|
||||
@@ -217,7 +228,7 @@ Class XLSXWriter
|
||||
//fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"/>');
|
||||
fwrite($fd, '</cellStyleXfs>');
|
||||
fwrite($fd, '<cellXfs count="4">');
|
||||
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="164" xfId="0"/>');
|
||||
fwrite($fd, '<xf applyAlignment="1" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="164" xfId="0"><alignment wrapText="1"/></xf>');
|
||||
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="165" xfId="0"/>');
|
||||
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="166" xfId="0"/>');
|
||||
fwrite($fd, '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="167" xfId="0"/>');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2014 Combodo SARL
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class XMLPage
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -62,9 +62,9 @@ class XMLPage extends WebPage
|
||||
}
|
||||
echo $this->s_content;
|
||||
}
|
||||
if (class_exists('MetaModel'))
|
||||
if (class_exists('DBSearch'))
|
||||
{
|
||||
MetaModel::RecordQueryTrace();
|
||||
DBSearch::RecordQueryTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Persistent classes (internal): user defined actions
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -136,7 +136,7 @@ class ActionEmail extends ActionNotification
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,bizmodel",
|
||||
"category" => "core/cmdb,application",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "name",
|
||||
"state_attcode" => "",
|
||||
@@ -157,7 +157,7 @@ class ActionEmail extends ActionNotification
|
||||
MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeOQL("bcc", array("allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeTemplateText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeTemplateHTML("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values"=>new ValueSetEnum('low,normal,high'), "sql"=>"importance", "default_value"=>'normal', "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
@@ -262,8 +262,10 @@ class ActionEmail extends ActionNotification
|
||||
{
|
||||
$sPrefix = '';
|
||||
}
|
||||
$oLog->Set('message', $sPrefix.$sRes);
|
||||
|
||||
if ($oLog)
|
||||
{
|
||||
$oLog->Set('message', $sPrefix . $sRes);
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
@@ -322,6 +324,8 @@ class ActionEmail extends ActionNotification
|
||||
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');
|
||||
|
||||
$oEmail = new EMail();
|
||||
|
||||
@@ -342,7 +346,7 @@ class ActionEmail extends ActionNotification
|
||||
$sTestBody .= "</ul>\n";
|
||||
$sTestBody .= "</p>\n";
|
||||
$sTestBody .= "</div>\n";
|
||||
$oEmail->SetBody($sTestBody);
|
||||
$oEmail->SetBody($sTestBody, 'text/html', $sStyles);
|
||||
$oEmail->SetRecipientTO($this->Get('test_recipient'));
|
||||
$oEmail->SetRecipientFrom($this->Get('test_recipient'));
|
||||
$oEmail->SetReferences($sReference);
|
||||
@@ -351,7 +355,7 @@ class ActionEmail extends ActionNotification
|
||||
else
|
||||
{
|
||||
$oEmail->SetSubject($sSubject);
|
||||
$oEmail->SetBody($sBody);
|
||||
$oEmail->SetBody($sBody, 'text/html', $sStyles);
|
||||
$oEmail->SetRecipientTO($sTo);
|
||||
$oEmail->SetRecipientCC($sCC);
|
||||
$oEmail->SetRecipientBCC($sBCC);
|
||||
|
||||
98
core/apc-compat.php
Normal file
98
core/apc-compat.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
// Copyright (C) 2016-2017 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/>
|
||||
|
||||
// Emulate the API of APC, over APCU
|
||||
// Note: for PHP < 7, this compatibility used to be provided by APCU itself (if compiled with some options)
|
||||
// for PHP 7+, it can be provided by the mean of apcu_bc, which is not so simple to install
|
||||
// The current emulation aims at skipping this complexity
|
||||
if (!function_exists('apc_store') && function_exists('apcu_store'))
|
||||
{
|
||||
function apc_add($key, $var, $ttl = 0)
|
||||
{
|
||||
return apcu_add($key, $var, $ttl);
|
||||
}
|
||||
function apc_cache_info($cache_type = '', $limited = false)
|
||||
{
|
||||
return apcu_cache_info($limited);
|
||||
}
|
||||
function apc_cas($key, $old, $new)
|
||||
{
|
||||
return apcu_cas($key, $old, $new);
|
||||
}
|
||||
function apc_clear_cache($cache_type = '')
|
||||
{
|
||||
return apcu_clear_cache();
|
||||
}
|
||||
function apc_dec($key, $step = 1, &$success = null)
|
||||
{
|
||||
apcu_dec($key, $step, $success);
|
||||
}
|
||||
function apc_delete($key)
|
||||
{
|
||||
return apcu_delete($key);
|
||||
}
|
||||
function apc_exists($keys)
|
||||
{
|
||||
return apcu_exists($keys);
|
||||
}
|
||||
function apc_fetch($key)
|
||||
{
|
||||
return apcu_fetch($key);
|
||||
}
|
||||
function apc_inc($key, $step = 1, &$success = null)
|
||||
{
|
||||
apcu_inc($key, $step, $success);
|
||||
}
|
||||
function apc_sma_info($limited = false)
|
||||
{
|
||||
return apcu_sma_info($limited);
|
||||
}
|
||||
function apc_store($key, $var, $ttl = 0)
|
||||
{
|
||||
return apcu_store($key, $var, $ttl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user cache info... beware of the format of the returned structure that may vary (See usages)
|
||||
* @return array
|
||||
*/
|
||||
function apc_cache_info_compat()
|
||||
{
|
||||
if (!function_exists('apc_cache_info')) return array();
|
||||
|
||||
$oFunction = new ReflectionFunction('apc_cache_info');
|
||||
if ($oFunction->getNumberOfParameters() != 2)
|
||||
{
|
||||
// Beware: APCu behaves slightly differently from APC !!
|
||||
// Worse: the compatibility layer integrated into APC differs from apcu-bc (testing the number of parameters is a must)
|
||||
// In CLI mode (PHP > 7) apc_cache_info returns null and outputs an error message.
|
||||
$aCacheUserData = @apc_cache_info();
|
||||
}
|
||||
else
|
||||
{
|
||||
$aCacheUserData = @apc_cache_info('user');
|
||||
}
|
||||
return $aCacheUserData;
|
||||
}
|
||||
|
||||
// Cache emulation
|
||||
if (!function_exists('apc_store'))
|
||||
{
|
||||
require_once(APPROOT.'core/apc-emulation.php');
|
||||
}
|
||||
301
core/apc-emulation.php
Normal file
301
core/apc-emulation.php
Normal file
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
// Copyright (c) 2010-2017 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/>
|
||||
//
|
||||
|
||||
/**
|
||||
* Date: 27/09/2017
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param string $cache_type
|
||||
* @param bool $limited
|
||||
* @return array|bool
|
||||
*/
|
||||
function apc_cache_info($cache_type = '', $limited = false)
|
||||
{
|
||||
$aInfo = array();
|
||||
$sRootCacheDir = apc_emul_get_cache_filename('');
|
||||
$aInfo['cache_list'] = apc_emul_get_cache_entries($sRootCacheDir);
|
||||
return $aInfo;
|
||||
}
|
||||
|
||||
function apc_emul_get_cache_entries($sEntry)
|
||||
{
|
||||
$aResult = array();
|
||||
if (is_dir($sEntry))
|
||||
{
|
||||
$aFiles = array_diff(scandir($sEntry), array('.', '..'));
|
||||
foreach($aFiles as $sFile)
|
||||
{
|
||||
$sSubFile = $sEntry.'/'.$sFile;
|
||||
$aResult = array_merge($aResult, apc_emul_get_cache_entries($sSubFile));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sKey = basename($sEntry);
|
||||
if (strpos($sKey, '-') === 0)
|
||||
{
|
||||
$sKey = substr($sKey, 1);
|
||||
}
|
||||
$aResult[] = array('info' => $sKey);
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $key
|
||||
* @param $var
|
||||
* @param int $ttl
|
||||
* @return array|bool
|
||||
*/
|
||||
function apc_store($key, $var = NULL, $ttl = 0)
|
||||
{
|
||||
if (is_array($key))
|
||||
{
|
||||
$aResult = array();
|
||||
foreach($key as $sKey => $value)
|
||||
{
|
||||
$aResult[] = apc_emul_store_unit($sKey, $value, $ttl);
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
return apc_emul_store_unit($key, $var, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sKey
|
||||
* @param $value
|
||||
* @param int $iTTL time to live
|
||||
* @return bool
|
||||
*/
|
||||
function apc_emul_store_unit($sKey, $value, $iTTL)
|
||||
{
|
||||
if ($iTTL > 0)
|
||||
{
|
||||
// hint for ttl management
|
||||
$sKey = '-'.$sKey;
|
||||
}
|
||||
|
||||
$sFilename = apc_emul_get_cache_filename($sKey);
|
||||
// try to create the folder
|
||||
$sDirname = dirname($sFilename);
|
||||
if (!file_exists($sDirname))
|
||||
{
|
||||
if (!@mkdir($sDirname, 0755, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$bRes = !(@file_put_contents($sFilename, serialize($value), LOCK_EX) === false);
|
||||
apc_emul_manage_new_entry($sFilename);
|
||||
return $bRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key string|array
|
||||
* @return mixed
|
||||
*/
|
||||
function apc_fetch($key)
|
||||
{
|
||||
if (is_array($key))
|
||||
{
|
||||
$aResult = array();
|
||||
foreach($key as $sKey)
|
||||
{
|
||||
$aResult[$sKey] = apc_emul_fetch_unit($sKey);
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
return apc_emul_fetch_unit($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sKey
|
||||
* @return bool|mixed
|
||||
*/
|
||||
function apc_emul_fetch_unit($sKey)
|
||||
{
|
||||
// Try the 'TTLed' version
|
||||
$sValue = apc_emul_readcache_locked(apc_emul_get_cache_filename('-'.$sKey));
|
||||
if ($sValue === false)
|
||||
{
|
||||
$sValue = apc_emul_readcache_locked(apc_emul_get_cache_filename($sKey));
|
||||
if ($sValue === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$oRes = @unserialize($sValue);
|
||||
return $oRes;
|
||||
}
|
||||
|
||||
function apc_emul_readcache_locked($sFilename)
|
||||
{
|
||||
$file = @fopen($sFilename, 'r');
|
||||
if ($file === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
flock($file, LOCK_SH);
|
||||
$sContent = @fread($file, @filesize($sFilename));
|
||||
flock($file, LOCK_UN);
|
||||
fclose($file);
|
||||
return $sContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cache_type
|
||||
* @return bool
|
||||
*/
|
||||
function apc_clear_cache($cache_type = '')
|
||||
{
|
||||
$sRootCacheDir = apc_emul_get_cache_filename('');
|
||||
apc_emul_delete_entry($sRootCacheDir);
|
||||
return true;
|
||||
}
|
||||
|
||||
function apc_emul_delete_entry($sCache)
|
||||
{
|
||||
if (is_dir($sCache))
|
||||
{
|
||||
$aFiles = array_diff(scandir($sCache), array('.', '..'));
|
||||
foreach($aFiles as $sFile)
|
||||
{
|
||||
$sSubFile = $sCache.'/'.$sFile;
|
||||
if (!apc_emul_delete_entry($sSubFile))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!@rmdir($sCache))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!@unlink($sCache))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @return bool|string[]
|
||||
*/
|
||||
function apc_delete($key)
|
||||
{
|
||||
return apc_emul_delete_entry(apc_emul_get_cache_filename($key));
|
||||
}
|
||||
|
||||
|
||||
function apc_emul_get_cache_filename($sKey)
|
||||
{
|
||||
$sPath = str_replace(array(' ', '/', '\\', '.'), '-', $sKey);
|
||||
return utils::GetCachePath().'apc-emul/'.$sPath;
|
||||
}
|
||||
|
||||
|
||||
/** Manage the cache files when a new cache entry is added
|
||||
* @param string $sNewFilename new cache file added
|
||||
*/
|
||||
function apc_emul_manage_new_entry($sNewFilename)
|
||||
{
|
||||
// Check only once per request
|
||||
static $aFilesByTime = null;
|
||||
static $iFileCount = 0;
|
||||
$iMaxFiles = MetaModel::GetConfig()->Get('apc_cache_emulation.max_entries');
|
||||
if ($iMaxFiles == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!$aFilesByTime)
|
||||
{
|
||||
$sRootCacheDir = apc_emul_get_cache_filename('');
|
||||
$aFilesByTime = apc_emul_list_files_time($sRootCacheDir);
|
||||
$iFileCount = count($aFilesByTime);
|
||||
if ($iMaxFiles !== 0)
|
||||
{
|
||||
asort($aFilesByTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aFilesByTime[$sNewFilename] = time();
|
||||
$iFileCount++;
|
||||
}
|
||||
if ($iFileCount > $iMaxFiles)
|
||||
{
|
||||
$iFileNbToRemove = $iFileCount - $iMaxFiles;
|
||||
foreach($aFilesByTime as $sFileToRemove => $iTime)
|
||||
{
|
||||
@unlink($sFileToRemove);
|
||||
if ($iFileNbToRemove-- === 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
$aFilesByTime = array_slice($aFilesByTime, $iFileCount - $iMaxFiles, null, true);
|
||||
$iFileCount = $iMaxFiles;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the list of files with their associated access time
|
||||
* @param string $sCheck Directory to scan
|
||||
* @param array $aFilesByTime used by recursion
|
||||
* @return array
|
||||
*/
|
||||
function apc_emul_list_files_time($sCheck, &$aFilesByTime = array())
|
||||
{
|
||||
// Garbage collection
|
||||
$aFiles = array_diff(@scandir($sCheck), array('.', '..'));
|
||||
foreach($aFiles as $sFile)
|
||||
{
|
||||
$sSubFile = $sCheck.'/'.$sFile;
|
||||
if (is_dir($sSubFile))
|
||||
{
|
||||
apc_emul_list_files_time($sSubFile, $aFilesByTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
$iTime = apc_emul_get_file_time($sSubFile);
|
||||
if ($iTime !== false)
|
||||
{
|
||||
$aFilesByTime[$sSubFile] = $iTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $aFilesByTime;
|
||||
}
|
||||
|
||||
/** Get the file access time if TTL is managed
|
||||
* @param string $sFilename
|
||||
* @return bool|int returns the file atime or false if not relevant
|
||||
*/
|
||||
function apc_emul_get_file_time($sFilename)
|
||||
{
|
||||
if (strpos(basename($sFilename), '-') === 0)
|
||||
{
|
||||
return @fileatime($sFilename);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ class ExecAsyncTask implements iBackgroundProcess
|
||||
|
||||
public function Process($iTimeLimit)
|
||||
{
|
||||
$sNow = date('Y-m-d H:i:s');
|
||||
$sNow = date(AttributeDateTime::GetSQLFormat());
|
||||
// Criteria: planned, and expected to occur... ASAP or in the past
|
||||
$sOQL = "SELECT AsyncTask WHERE (status = 'planned') AND (ISNULL(planned) OR (planned < '$sNow'))";
|
||||
$iProcessed = 0;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
38
core/autoload.php
Normal file
38
core/autoload.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 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/>
|
||||
|
||||
MetaModel::IncludeModule('application/transaction.class.inc.php');
|
||||
MetaModel::IncludeModule('application/menunode.class.inc.php');
|
||||
MetaModel::IncludeModule('application/user.preferences.class.inc.php');
|
||||
MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
|
||||
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
|
||||
MetaModel::IncludeModule('application/query.class.inc.php');
|
||||
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');
|
||||
|
||||
MetaModel::IncludeModule('core/event.class.inc.php');
|
||||
MetaModel::IncludeModule('core/action.class.inc.php');
|
||||
MetaModel::IncludeModule('core/trigger.class.inc.php');
|
||||
MetaModel::IncludeModule('core/bulkexport.class.inc.php');
|
||||
MetaModel::IncludeModule('core/ownershiplock.class.inc.php');
|
||||
MetaModel::IncludeModule('synchro/synchrodatasource.class.inc.php');
|
||||
MetaModel::IncludeModule('core/backgroundtask.class.inc.php');
|
||||
MetaModel::IncludeModule('core/inlineimage.class.inc.php');
|
||||
|
||||
MetaModel::IncludeModule('webservices/webservices.basic.php');
|
||||
|
||||
//MetaModel::IncludeModule('addons', 'user rights', 'addons/userrights/userrightsprofile.class.inc.php');
|
||||
55
core/background.inc.php
Normal file
55
core/background.inc.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// Copyright (C) 2017 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/>
|
||||
|
||||
|
||||
/**
|
||||
* Tasks performed in the background
|
||||
*
|
||||
* @copyright Copyright (C) 2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
class ObsolescenceDateUpdater implements iBackgroundProcess
|
||||
{
|
||||
public function GetPeriodicity()
|
||||
{
|
||||
return MetaModel::GetConfig()->Get('obsolescence.date_update_interval'); // 10 mn
|
||||
}
|
||||
|
||||
public function Process($iUnixTimeLimit)
|
||||
{
|
||||
$iCountSet = 0;
|
||||
$iCountReset = 0;
|
||||
$iClasses = 0;
|
||||
foreach (MetaModel::EnumObsoletableClasses() as $sClass)
|
||||
{
|
||||
$oObsoletedToday = new DBObjectSearch($sClass);
|
||||
$oObsoletedToday->AddCondition('obsolescence_flag', 1, '=');
|
||||
$oObsoletedToday->AddCondition('obsolescence_date', null, '=');
|
||||
$sToday = date(AttributeDate::GetSQLFormat());
|
||||
$iCountSet += MetaModel::BulkUpdate($oObsoletedToday, array('obsolescence_date' => $sToday));
|
||||
|
||||
$oObsoletedToday = new DBObjectSearch($sClass);
|
||||
$oObsoletedToday->AddCondition('obsolescence_flag', 1, '!=');
|
||||
$oObsoletedToday->AddCondition('obsolescence_date', null, '!=');
|
||||
$iCountReset += MetaModel::BulkUpdate($oObsoletedToday, array('obsolescence_date' => null));
|
||||
}
|
||||
echo "Obsolescence date updated (classes: $iClasses ; set: $iCountSet ; reset: $iCountReset)\n";
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Bulk change facility (common to interactive and batch usages)
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -258,7 +258,7 @@ class BulkChange
|
||||
protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey)
|
||||
protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported
|
||||
protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined)
|
||||
protected $m_sDateFormat; // Date format specification, see utils::StringToTime()
|
||||
protected $m_sDateFormat; // Date format specification, see DateTime::createFromFormat
|
||||
protected $m_bLocalizedValues; // Values in the data set are localized (see AttributeEnum)
|
||||
protected $m_aExtKeysMappingCache; // Cache for resolving external keys based on the given search criterias
|
||||
|
||||
@@ -295,7 +295,7 @@ class BulkChange
|
||||
protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults)
|
||||
{
|
||||
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
$oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass());
|
||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||
foreach ($this->m_aExtKeys[$sAttCode] as $sForeignAttCode => $iCol)
|
||||
{
|
||||
if ($sForeignAttCode == 'id')
|
||||
@@ -366,7 +366,7 @@ class BulkChange
|
||||
}
|
||||
else
|
||||
{
|
||||
$oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass());
|
||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||
$aCacheKeys = array();
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
{
|
||||
@@ -471,6 +471,11 @@ class BulkChange
|
||||
// skip the private key, if any
|
||||
if ($sAttCode == 'id') continue;
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
|
||||
// skip reconciliation keys
|
||||
if (!$oAttDef->IsWritable() && in_array($sAttCode, $this->m_aReconcilKeys)){ continue; }
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
$aReasons = array();
|
||||
$iFlags = $oTargetObj->GetAttributeFlags($sAttCode, $aReasons);
|
||||
@@ -554,7 +559,15 @@ class BulkChange
|
||||
else
|
||||
{
|
||||
// By default... nothing happens
|
||||
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
$aResults[$iCol]= new CellStatus_Void($oAttDef->GetFormat()->Format($aRowData[$iCol]));
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -795,26 +808,55 @@ class BulkChange
|
||||
|
||||
if (!is_null($this->m_sDateFormat) && (strlen($this->m_sDateFormat) > 0))
|
||||
{
|
||||
$sDateTimeFormat = $this->m_sDateFormat; // the specified format is actually the date AND time format
|
||||
$oDateTimeFormat = new DateTimeFormat($sDateTimeFormat);
|
||||
$sDateFormat = $oDateTimeFormat->ToDateFormat();
|
||||
AttributeDateTime::SetFormat($oDateTimeFormat);
|
||||
AttributeDate::SetFormat(new DateTimeFormat($sDateFormat));
|
||||
// Translate dates from the source data
|
||||
//
|
||||
foreach ($this->m_aAttList as $sAttCode => $iCol)
|
||||
{
|
||||
if ($sAttCode == 'id') continue;
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime)
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
foreach($this->m_aData as $iRow => $aRowData)
|
||||
{
|
||||
$sNewDate = utils::StringToTime($this->m_aData[$iRow][$iCol], $this->m_sDateFormat);
|
||||
if ($sNewDate !== false)
|
||||
$sFormat = $sDateTimeFormat;
|
||||
$sValue = $this->m_aData[$iRow][$iCol];
|
||||
if (!empty($sValue))
|
||||
{
|
||||
// Todo - improve the reporting
|
||||
$this->m_aData[$iRow][$iCol] = $sNewDate;
|
||||
if ($oAttDef instanceof AttributeDate)
|
||||
{
|
||||
$sFormat = $sDateFormat;
|
||||
}
|
||||
$oFormat = new DateTimeFormat($sFormat);
|
||||
$sRegExp = $oFormat->ToRegExpr('/');
|
||||
if (!preg_match($sRegExp, $this->m_aData[$iRow][$iCol]))
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$oDate = DateTime::createFromFormat($sFormat, $this->m_aData[$iRow][$iCol]);
|
||||
if ($oDate !== false)
|
||||
{
|
||||
$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
|
||||
$this->m_aData[$iRow][$iCol] = $sNewDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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'));
|
||||
$this->m_aData[$iRow][$iCol] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -839,7 +881,7 @@ class BulkChange
|
||||
}
|
||||
try
|
||||
{
|
||||
$oReconciliationFilter = new CMDBSearchFilter($this->m_sClass);
|
||||
$oReconciliationFilter = new DBObjectSearch($this->m_sClass);
|
||||
$bSkipQuery = false;
|
||||
foreach($this->m_aReconcilKeys as $sAttCode)
|
||||
{
|
||||
@@ -1173,7 +1215,7 @@ EOF
|
||||
{
|
||||
$sAttCode = $oOperation->Get('attcode');
|
||||
|
||||
if (get_class($oOperation) == 'CMDBChangeOpSetAttributeScalar')
|
||||
if ((get_class($oOperation) == 'CMDBChangeOpSetAttributeScalar') || (get_class($oOperation) == 'CMDBChangeOpSetAttributeURL'))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ($oAttDef->IsExternalKey())
|
||||
@@ -1269,5 +1311,3 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
|
||||
431
core/bulkexport.class.inc.php
Normal file
431
core/bulkexport.class.inc.php
Normal file
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 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/>
|
||||
|
||||
define('EXPORTER_DEFAULT_CHUNK_SIZE', 1000);
|
||||
|
||||
class BulkExportException extends Exception
|
||||
{
|
||||
protected $sLocalizedMessage;
|
||||
public function __construct($message, $sLocalizedMessage, $code = null, $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->sLocalizedMessage = $sLocalizedMessage;
|
||||
}
|
||||
|
||||
public function GetLocalizedMessage()
|
||||
{
|
||||
return $this->sLocalizedMessage;
|
||||
}
|
||||
}
|
||||
class BulkExportMissingParameterException extends BulkExportException
|
||||
{
|
||||
public function __construct($sFieldCode)
|
||||
{
|
||||
parent::__construct('Missing parameter: '.$sFieldCode, Dict::Format('Core:BulkExport:MissingParameter_Param', $sFieldCode));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class BulkExport
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class BulkExportResult extends DBObject
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => 'core/cmdb',
|
||||
"key_type" => 'autoincrement',
|
||||
"name_attcode" => array('created'),
|
||||
"state_attcode" => '',
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => 'priv_bulk_export_result',
|
||||
"db_key_field" => 'id',
|
||||
"db_finalclass_field" => '',
|
||||
"display_template" => '',
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("created", array("allowed_values"=>null, "sql"=>"created", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("user_id", array("allowed_values"=>null, "sql"=>"user_id", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("chunk_size", array("allowed_values"=>null, "sql"=>"chunk_size", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("format", array("allowed_values"=>null, "sql"=>"format", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("temp_file_path", array("allowed_values"=>null, "sql"=>"temp_file_path", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeLongText("search", array("allowed_values"=>null, "sql"=>"search", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeLongText("status_info", array("allowed_values"=>null, "sql"=>"status_info", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
}
|
||||
|
||||
public function ComputeValues()
|
||||
{
|
||||
$this->Set('user_id', UserRights::GetUserId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collector for cleaning "old" export results from the database and the disk.
|
||||
* This background process runs once per day and deletes the results of all exports which
|
||||
* are older than one day.
|
||||
*/
|
||||
class BulkExportResultGC implements iBackgroundProcess
|
||||
{
|
||||
public function GetPeriodicity()
|
||||
{
|
||||
return 24*3600; // seconds
|
||||
}
|
||||
|
||||
public function Process($iTimeLimit)
|
||||
{
|
||||
$sDateLimit = date(AttributeDateTime::GetSQLFormat(), time() - 24*3600); // Every BulkExportResult older than one day will be deleted
|
||||
|
||||
$sOQL = "SELECT BulkExportResult WHERE created < '$sDateLimit'";
|
||||
$iProcessed = 0;
|
||||
while (time() < $iTimeLimit)
|
||||
{
|
||||
// Next one ?
|
||||
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('created' => true) /* order by*/, array(), null, 1 /* limit count */);
|
||||
$oSet->OptimizeColumnLoad(array('temp_file_path'));
|
||||
$oResult = $oSet->Fetch();
|
||||
if (is_null($oResult))
|
||||
{
|
||||
// Nothing to be done
|
||||
break;
|
||||
}
|
||||
$iProcessed++;
|
||||
@unlink($oResult->Get('temp_file_path'));
|
||||
utils::PushArchiveMode(false);
|
||||
$oResult->DBDelete();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
return "Cleaned $iProcessed old export results(s).";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class BulkExport
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
abstract class BulkExport
|
||||
{
|
||||
protected $oSearch;
|
||||
protected $iChunkSize;
|
||||
protected $sFormatCode;
|
||||
protected $aStatusInfo;
|
||||
protected $oBulkExportResult;
|
||||
protected $sTmpFile;
|
||||
protected $bLocalizeOutput;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->oSearch = null;
|
||||
$this->iChunkSize = 0;
|
||||
$this->sFormatCode = null;
|
||||
$this->aStatusInfo = array();
|
||||
$this->oBulkExportResult = null;
|
||||
$this->sTmpFile = '';
|
||||
$this->bLocalizeOutput = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first class capable of exporting the data in the given format
|
||||
* @param string $sFormat The lowercase format (e.g. html, csv, spreadsheet, xlsx, xml, json, pdf...)
|
||||
* @param DBSearch $oSearch The search/filter defining the set of objects to export or null when listing the supported formats
|
||||
* @return iBulkExport|NULL
|
||||
*/
|
||||
static public function FindExporter($sFormatCode, $oSearch = null)
|
||||
{
|
||||
foreach(get_declared_classes() as $sPHPClass)
|
||||
{
|
||||
$oRefClass = new ReflectionClass($sPHPClass);
|
||||
if ($oRefClass->isSubclassOf('BulkExport') && !$oRefClass->isAbstract())
|
||||
{
|
||||
$oBulkExporter = new $sPHPClass();
|
||||
if ($oBulkExporter->IsFormatSupported($sFormatCode, $oSearch))
|
||||
{
|
||||
if ($oSearch)
|
||||
{
|
||||
$oBulkExporter->SetObjectList($oSearch);
|
||||
}
|
||||
return $oBulkExporter;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the exporter corresponding to the given persistent token
|
||||
* @param int $iPersistentToken The identifier of the BulkExportResult object storing the information
|
||||
* @return iBulkExport|NULL
|
||||
*/
|
||||
static public function FindExporterFromToken($iPersistentToken = null)
|
||||
{
|
||||
$oBulkExporter = null;
|
||||
$oInfo = MetaModel::GetObject('BulkExportResult', $iPersistentToken, false);
|
||||
if ($oInfo && ($oInfo->Get('user_id') == UserRights::GetUserId()))
|
||||
{
|
||||
$sFormatCode = $oInfo->Get('format');
|
||||
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
|
||||
|
||||
$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->sTmpFile = $oInfo->Get('temp_file_path');
|
||||
$oBulkExporter->oBulkExportResult = $oInfo;
|
||||
}
|
||||
}
|
||||
return $oBulkExporter;
|
||||
}
|
||||
|
||||
public function AppendToTmpFile($data)
|
||||
{
|
||||
if ($this->sTmpFile == '')
|
||||
{
|
||||
$this->sTmpFile = $this->MakeTmpFile($this->GetFileExtension());
|
||||
}
|
||||
$hFile = fopen($this->sTmpFile, 'ab');
|
||||
if ($hFile !== false)
|
||||
{
|
||||
fwrite($hFile, $data);
|
||||
fclose($hFile);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetTmpFilePath()
|
||||
{
|
||||
return $this->sTmpFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all possible export formats. The output is a hash array in the form: 'format_code' => 'localized format label'
|
||||
* @return multitype:string
|
||||
*/
|
||||
static public function FindSupportedFormats()
|
||||
{
|
||||
$aSupportedFormats = array();
|
||||
foreach(get_declared_classes() as $sPHPClass)
|
||||
{
|
||||
$oRefClass = new ReflectionClass($sPHPClass);
|
||||
if ($oRefClass->isSubClassOf('BulkExport') && !$oRefClass->isAbstract())
|
||||
{
|
||||
$oBulkExporter = new $sPHPClass;
|
||||
$aFormats = $oBulkExporter->GetSupportedFormats();
|
||||
$aSupportedFormats = array_merge($aSupportedFormats, $aFormats);
|
||||
}
|
||||
}
|
||||
return $aSupportedFormats;
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
* @see iBulkExport::SetChunkSize()
|
||||
*/
|
||||
public function SetChunkSize($iChunkSize)
|
||||
{
|
||||
$this->iChunkSize = $iChunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
* @see iBulkExport::SetObjectList()
|
||||
*/
|
||||
public function SetObjectList(DBSearch $oSearch)
|
||||
{
|
||||
$this->oSearch = $oSearch;
|
||||
}
|
||||
|
||||
public function SetFormat($sFormatCode)
|
||||
{
|
||||
$this->sFormatCode = $sFormatCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
* @see iBulkExport::IsFormatSupported()
|
||||
*/
|
||||
public function IsFormatSupported($sFormatCode, $oSearch = null)
|
||||
{
|
||||
return array_key_exists($sFormatCode, $this->GetSupportedFormats());
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
* @see iBulkExport::GetSupportedFormats()
|
||||
*/
|
||||
public function GetSupportedFormats()
|
||||
{
|
||||
return array(); // return array('csv' => Dict::S('UI:ExportFormatCSV'));
|
||||
}
|
||||
|
||||
|
||||
public function SetHttpHeaders(WebPage $oPage)
|
||||
{
|
||||
}
|
||||
|
||||
public function GetHeader()
|
||||
{
|
||||
}
|
||||
abstract public function GetNextChunk(&$aStatus);
|
||||
public function GetFooter()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function SaveState()
|
||||
{
|
||||
if ($this->oBulkExportResult === null)
|
||||
{
|
||||
$this->oBulkExportResult = new BulkExportResult();
|
||||
$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('status_info', json_encode($this->GetStatusInfo()));
|
||||
utils::PushArchiveMode(false);
|
||||
$ret = $this->oBulkExportResult->DBWrite();
|
||||
utils::PopArchiveMode();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function Cleanup()
|
||||
{
|
||||
if (($this->oBulkExportResult && (!$this->oBulkExportResult->IsNew())))
|
||||
{
|
||||
$sFilename = $this->oBulkExportResult->Get('temp_file_path');
|
||||
if ($sFilename != '')
|
||||
{
|
||||
@unlink($sFilename);
|
||||
}
|
||||
utils::PushArchiveMode(false);
|
||||
$this->oBulkExportResult->DBDelete();
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
|
||||
public function EnumFormParts()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function DisplayFormPart(WebPage $oP, $sPartId)
|
||||
{
|
||||
}
|
||||
|
||||
public function DisplayUsage(Page $oP)
|
||||
{
|
||||
|
||||
}
|
||||
public function ReadParameters()
|
||||
{
|
||||
$this->bLocalizeOutput = !((bool)utils::ReadParam('no_localize', 0, true, 'integer'));
|
||||
}
|
||||
|
||||
public function GetResultAsHtml()
|
||||
{
|
||||
|
||||
}
|
||||
public function GetRawResult()
|
||||
{
|
||||
|
||||
}
|
||||
public function GetMimeType()
|
||||
{
|
||||
|
||||
}
|
||||
public function GetFileExtension()
|
||||
{
|
||||
|
||||
}
|
||||
public function GetCharacterSet()
|
||||
{
|
||||
return 'UTF-8';
|
||||
}
|
||||
|
||||
public function GetStatistics()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function GetDownloadFileName()
|
||||
{
|
||||
return Dict::Format('Core:BulkExportOf_Class', MetaModel::GetName($this->oSearch->GetClass())).'.'.$this->GetFileExtension();
|
||||
}
|
||||
|
||||
public function SetStatusInfo($aStatusInfo)
|
||||
{
|
||||
$this->aStatusInfo = $aStatusInfo;
|
||||
}
|
||||
|
||||
public function GetStatusInfo()
|
||||
{
|
||||
return $this->aStatusInfo;
|
||||
}
|
||||
|
||||
protected function MakeTmpFile($sExtension)
|
||||
{
|
||||
if(!is_dir(APPROOT."data/bulk_export"))
|
||||
{
|
||||
@mkdir(APPROOT."data/bulk_export", 0777, true /* recursive */);
|
||||
clearstatcache();
|
||||
}
|
||||
if (!is_writable(APPROOT."data/bulk_export"))
|
||||
{
|
||||
throw new Exception('Data directory "'.APPROOT.'data/bulk_export" could not be written.');
|
||||
}
|
||||
|
||||
$iNum = rand();
|
||||
$sFileName = '';
|
||||
do
|
||||
{
|
||||
$iNum++;
|
||||
$sToken = sprintf("%08x", $iNum);
|
||||
$sFileName = APPROOT."data/bulk_export/$sToken.".$sExtension;
|
||||
$hFile = @fopen($sFileName, 'x');
|
||||
}
|
||||
while($hFile === false);
|
||||
|
||||
fclose($hFile);
|
||||
return $sFileName;
|
||||
}
|
||||
}
|
||||
|
||||
// The built-in exports
|
||||
require_once(APPROOT.'core/tabularbulkexport.class.inc.php');
|
||||
require_once(APPROOT.'core/htmlbulkexport.class.inc.php');
|
||||
if (extension_loaded('gd'))
|
||||
{
|
||||
// PDF export - via TCPDF - requires GD
|
||||
require_once(APPROOT.'core/pdfbulkexport.class.inc.php');
|
||||
}
|
||||
require_once(APPROOT.'core/csvbulkexport.class.inc.php');
|
||||
require_once(APPROOT.'core/excelbulkexport.class.inc.php');
|
||||
require_once(APPROOT.'core/spreadsheetbulkexport.class.inc.php');
|
||||
require_once(APPROOT.'core/xmlbulkexport.class.inc.php');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Persistent classes (internal) : cmdbChangeOp and derived
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -55,7 +55,7 @@ class CMDBChangeOp extends DBObject
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date")));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("userinfo", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"userinfo")));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("objclass", array("allowed_values"=>null, "sql"=>"objclass", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("objkey", array("allowed_values"=>null, "sql"=>"objkey", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeObjectKey("objkey", array("allowed_values"=>null, "class_attcode"=>"objclass", "sql"=>"objkey", "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
MetaModel::Init_SetZListItems('details', array('change', 'date', 'userinfo')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('change', 'date', 'userinfo')); // Attributes to be displayed for the complete details
|
||||
@@ -242,6 +242,61 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Record the modification of an URL
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class CMDBChangeOpSetAttributeURL extends CMDBChangeOpSetAttribute
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"key_type" => "",
|
||||
"name_attcode" => "change",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_changeop_setatt_url",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeURL("oldvalue", array("allowed_values"=>null, "sql"=>"oldvalue", "target" => '_blank', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeURL("newvalue", array("allowed_values"=>null, "sql"=>"newvalue", "target" => '_blank', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe (as a text string) the modifications corresponding to this change
|
||||
*/
|
||||
public function GetDescription()
|
||||
{
|
||||
$sResult = '';
|
||||
$oTargetObjectClass = $this->Get('objclass');
|
||||
$oTargetObjectKey = $this->Get('objkey');
|
||||
$oTargetSearch = new DBObjectSearch($oTargetObjectClass);
|
||||
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
|
||||
|
||||
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
|
||||
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
|
||||
{
|
||||
if (!MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) return ''; // Protects against renamed attributes...
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
|
||||
$sAttName = $oAttDef->GetLabel();
|
||||
$sNewValue = $this->Get('newvalue');
|
||||
$sOldValue = $this->Get('oldvalue');
|
||||
$sResult = $oAttDef->DescribeChangeAsHTML($sOldValue, $sNewValue);
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the modification of a blob
|
||||
@@ -300,11 +355,19 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
|
||||
$sAttName = $this->Get('attcode');
|
||||
}
|
||||
$oPrevDoc = $this->Get('prevdata');
|
||||
$sDocView = $oPrevDoc->GetAsHtml();
|
||||
$sDocView .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_',$oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n";
|
||||
$sDocView .= Dict::Format('UI:DownloadDocument_', $oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata'))."\n";
|
||||
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sDocView);
|
||||
if ($oPrevDoc->IsEmpty())
|
||||
{
|
||||
$sPrevious = '';
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sPrevious);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sDocView = $oPrevDoc->GetAsHtml();
|
||||
$sDocView .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_', $oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n";
|
||||
$sDocView .= Dict::Format('UI:DownloadDocument_', $oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata'))."\n";
|
||||
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sDocView);
|
||||
}
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
@@ -529,9 +592,6 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
|
||||
*/
|
||||
public function GetDescription()
|
||||
{
|
||||
// Temporary, until we change the options of GetDescription() -needs a more global revision
|
||||
$bIsHtml = true;
|
||||
|
||||
$sResult = '';
|
||||
$oTargetObjectClass = $this->Get('objclass');
|
||||
$oTargetObjectKey = $this->Get('objkey');
|
||||
@@ -560,6 +620,66 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the modification of a multiline string (text) containing some HTML markup
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class CMDBChangeOpSetAttributeHTML extends CMDBChangeOpSetAttributeLongText
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"key_type" => "",
|
||||
"name_attcode" => "change",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_changeop_setatt_html",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
|
||||
}
|
||||
/**
|
||||
* Describe (as a text string) the modifications corresponding to this change
|
||||
*/
|
||||
public function GetDescription()
|
||||
{
|
||||
$sResult = '';
|
||||
$oTargetObjectClass = $this->Get('objclass');
|
||||
$oTargetObjectKey = $this->Get('objkey');
|
||||
$oTargetSearch = new DBObjectSearch($oTargetObjectClass);
|
||||
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
|
||||
|
||||
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
|
||||
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
|
||||
{
|
||||
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode')))
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
|
||||
$sAttName = $oAttDef->GetLabel();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The attribute was renamed or removed from the object ?
|
||||
$sAttName = $this->Get('attcode');
|
||||
}
|
||||
$sTextView = '<div class="history_entry history_entry_truncated"><div class="history_html_content">'.$this->Get('prevdata').'</div></div>';
|
||||
|
||||
//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView);
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the modification of a caselog (text)
|
||||
* since the caselog itself stores the history
|
||||
@@ -619,10 +739,20 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
|
||||
// The attribute was renamed or removed from the object ?
|
||||
$sAttName = $this->Get('attcode');
|
||||
}
|
||||
$sResult = Dict::Format('Change:AttName_EntryAdded', $sAttName);
|
||||
$oObj = $oMonoObjectSet->Fetch();
|
||||
$oCaseLog = $oObj->Get($this->Get('attcode'));
|
||||
$iMaxVisibleLength = MetaModel::getConfig()->Get('max_history_case_log_entry_length', 0);
|
||||
$sTextEntry = '<div class="history_entry history_entry_truncated"><div class="history_html_content">'.$oCaseLog->GetEntryAt($this->Get('lastentry')).'</div></div>';
|
||||
|
||||
$sResult = Dict::Format('Change:AttName_EntryAdded', $sAttName, $sTextEntry);
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
protected function ToHtml($sRawText)
|
||||
{
|
||||
return str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sRawText, ENT_QUOTES, 'UTF-8'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -843,4 +973,70 @@ class CMDBChangeOpSetAttributeLinksTune extends CMDBChangeOpSetAttributeLinks
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
/**
|
||||
* Record the modification of custom fields
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class CMDBChangeOpSetAttributeCustomFields extends CMDBChangeOpSetAttribute
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"key_type" => "",
|
||||
"name_attcode" => "change",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_changeop_setatt_custfields",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeLongText("prevdata", array("allowed_values"=>null, "sql"=>"prevdata", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe (as a text string) the modifications corresponding to this change
|
||||
*/
|
||||
public function GetDescription()
|
||||
{
|
||||
$sResult = '';
|
||||
if (MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode')))
|
||||
{
|
||||
$oTargetObjectClass = $this->Get('objclass');
|
||||
$oTargetObjectKey = $this->Get('objkey');
|
||||
$oTargetSearch = new DBObjectSearch($oTargetObjectClass);
|
||||
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
|
||||
|
||||
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
|
||||
if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
|
||||
{
|
||||
$aValues = json_decode($this->Get('prevdata'), true);
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
|
||||
$sAttName = $oAttDef->GetLabel();
|
||||
|
||||
try
|
||||
{
|
||||
$oHandler = $oAttDef->GetHandler($aValues);
|
||||
$sValueDesc = $oHandler->GetAsHTML($aValues);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$sValueDesc = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
$sTextView = '<div>'.$sValueDesc.'</div>';
|
||||
|
||||
$sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView);
|
||||
}
|
||||
}
|
||||
return $sResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Class cmdbObject
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -46,18 +46,20 @@ require_once('stimulus.class.inc.php');
|
||||
require_once('valuesetdef.class.inc.php');
|
||||
require_once('MyHelpers.class.inc.php');
|
||||
|
||||
require_once('expression.class.inc.php');
|
||||
|
||||
require_once('cmdbsource.class.inc.php');
|
||||
require_once('sqlquery.class.inc.php');
|
||||
require_once('oql/expression.class.inc.php');
|
||||
require_once('oql/oqlquery.class.inc.php');
|
||||
require_once('oql/oqlexception.class.inc.php');
|
||||
require_once('oql/oql-parser.php');
|
||||
require_once('oql/oql-lexer.php');
|
||||
require_once('oql/oqlinterpreter.class.inc.php');
|
||||
|
||||
require_once('cmdbsource.class.inc.php');
|
||||
require_once('sqlquery.class.inc.php');
|
||||
require_once('sqlobjectquery.class.inc.php');
|
||||
require_once('sqlunionquery.class.inc.php');
|
||||
|
||||
require_once('dbobject.class.php');
|
||||
require_once('dbobjectsearch.class.php');
|
||||
require_once('dbsearch.class.php');
|
||||
require_once('dbobjectset.class.php');
|
||||
|
||||
require_once('backgroundprocess.inc.php');
|
||||
@@ -229,6 +231,202 @@ abstract class CMDBObject extends DBObject
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sAttCode
|
||||
* @param $original Original value
|
||||
* @param $value Current value
|
||||
*/
|
||||
protected function RecordAttChange($sAttCode, $original, $value)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
if ($oAttDef->IsExternalField()) return;
|
||||
if ($oAttDef->IsLinkSet()) return;
|
||||
if ($oAttDef->GetTrackingLevel() == ATTRIBUTE_TRACKING_NONE) return;
|
||||
|
||||
if ($oAttDef instanceOf AttributeOneWayPassword)
|
||||
{
|
||||
// One Way encrypted passwords' history is stored -one way- encrypted
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeOneWayPassword");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = '';
|
||||
}
|
||||
$oMyChangeOp->Set("prev_pwd", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeEncryptedString)
|
||||
{
|
||||
// Encrypted string history is stored encrypted
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeEncrypted");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = '';
|
||||
}
|
||||
$oMyChangeOp->Set("prevstring", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeBlob)
|
||||
{
|
||||
// Data blobs
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeBlob");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = new ormDocument();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeStopWatch)
|
||||
{
|
||||
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
|
||||
//
|
||||
foreach ($oAttDef->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef)
|
||||
{
|
||||
$item_value = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $value, $this);
|
||||
$item_original = $oAttDef->GetSubItemValue($oSubItemAttDef->Get('item_code'), $original, $this);
|
||||
|
||||
if ($item_value != $item_original)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sSubItemAttCode);
|
||||
|
||||
$oMyChangeOp->Set("oldvalue", $item_original);
|
||||
$oMyChangeOp->Set("newvalue", $item_value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeCaseLog)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCaseLog");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
$oMyChangeOp->Set("lastentry", $value->GetLatestEntryIndex());
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeLongText)
|
||||
{
|
||||
// Data blobs
|
||||
if ($oAttDef->GetFormat() == 'html')
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
|
||||
}
|
||||
else
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
|
||||
}
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (!is_null($original) && ($original instanceof ormCaseLog))
|
||||
{
|
||||
$original = $original->GetText();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeText)
|
||||
{
|
||||
// Data blobs
|
||||
if ($oAttDef->GetFormat() == 'html')
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeHTML");
|
||||
}
|
||||
else
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
|
||||
}
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (!is_null($original) && ($original instanceof ormCaseLog))
|
||||
{
|
||||
$original = $original->GetText();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeBoolean)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original ? 1 : 0);
|
||||
$oMyChangeOp->Set("newvalue", $value ? 1 : 0);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeHierarchicalKey)
|
||||
{
|
||||
// Hierarchical keys
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value[$sAttCode]);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeCustomFields)
|
||||
{
|
||||
// Custom fields
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCustomFields");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("prevdata", json_encode($original->GetValues()));
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeURL)
|
||||
{
|
||||
// URLs
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeURL");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scalars
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aValues
|
||||
* @param array $aOrigValues
|
||||
*/
|
||||
protected function RecordAttChanges(array $aValues, array $aOrigValues)
|
||||
{
|
||||
parent::RecordAttChanges($aValues, $aOrigValues);
|
||||
@@ -237,11 +435,6 @@ abstract class CMDBObject extends DBObject
|
||||
//
|
||||
foreach ($aValues as $sAttCode=> $value)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
if ($oAttDef->IsExternalField()) continue;
|
||||
if ($oAttDef->IsLinkSet()) continue;
|
||||
if ($oAttDef->GetTrackingLevel() == ATTRIBUTE_TRACKING_NONE) continue;
|
||||
|
||||
if (array_key_exists($sAttCode, $aOrigValues))
|
||||
{
|
||||
$original = $aOrigValues[$sAttCode];
|
||||
@@ -250,152 +443,7 @@ abstract class CMDBObject extends DBObject
|
||||
{
|
||||
$original = null;
|
||||
}
|
||||
|
||||
if ($oAttDef instanceOf AttributeOneWayPassword)
|
||||
{
|
||||
// One Way encrypted passwords' history is stored -one way- encrypted
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeOneWayPassword");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = '';
|
||||
}
|
||||
$oMyChangeOp->Set("prev_pwd", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeEncryptedString)
|
||||
{
|
||||
// Encrypted string history is stored encrypted
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeEncrypted");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = '';
|
||||
}
|
||||
$oMyChangeOp->Set("prevstring", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeBlob)
|
||||
{
|
||||
// Data blobs
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeBlob");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = new ormDocument();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeStopWatch)
|
||||
{
|
||||
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
|
||||
//
|
||||
if (is_null($original))
|
||||
{
|
||||
$original = new OrmStopWatch();
|
||||
}
|
||||
foreach ($oAttDef->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef)
|
||||
{
|
||||
$item_value = $oSubItemAttDef->GetValue($value);
|
||||
$item_original = $oSubItemAttDef->GetValue($original);
|
||||
|
||||
if ($item_value != $item_original)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sSubItemAttCode);
|
||||
|
||||
$oMyChangeOp->Set("oldvalue", $item_original);
|
||||
$oMyChangeOp->Set("newvalue", $item_value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeCaseLog)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCaseLog");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
$oMyChangeOp->Set("lastentry", $value->GetLatestEntryIndex());
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeLongText)
|
||||
{
|
||||
// Data blobs
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeLongText");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (!is_null($original) && ($original instanceof ormCaseLog))
|
||||
{
|
||||
$original = $original->GetText();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeText)
|
||||
{
|
||||
// Data blobs
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
|
||||
if (!is_null($original) && ($original instanceof ormCaseLog))
|
||||
{
|
||||
$original = $original->GetText();
|
||||
}
|
||||
$oMyChangeOp->Set("prevdata", $original);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeBoolean)
|
||||
{
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original ? 1 : 0);
|
||||
$oMyChangeOp->Set("newvalue", $value ? 1 : 0);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeHierarchicalKey)
|
||||
{
|
||||
// Hierarchical keys
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value[$sAttCode]);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scalars
|
||||
//
|
||||
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
|
||||
$oMyChangeOp->Set("objclass", get_class($this));
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
$this->RecordAttChange($sAttCode, $original, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,18 +570,18 @@ abstract class CMDBObject extends DBObject
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues)
|
||||
public static function BulkUpdate(DBSearch $oFilter, array $aValues)
|
||||
{
|
||||
return $this->BulkUpdateTracked_Internal($oFilter, $aValues);
|
||||
return static::BulkUpdateTracked_Internal($oFilter, $aValues);
|
||||
}
|
||||
|
||||
public static function BulkUpdateTracked(CMDBChange $oChange, DBObjectSearch $oFilter, array $aValues)
|
||||
public static function BulkUpdateTracked(CMDBChange $oChange, DBSearch $oFilter, array $aValues)
|
||||
{
|
||||
self::SetCurrentChange($oChange);
|
||||
$this->BulkUpdateTracked_Internal($oFilter, $aValues);
|
||||
static::BulkUpdateTracked_Internal($oFilter, $aValues);
|
||||
}
|
||||
|
||||
protected static function BulkUpdateTracked_Internal(DBObjectSearch $oFilter, array $aValues)
|
||||
protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues)
|
||||
{
|
||||
// $aValues is an array of $sAttCode => $value
|
||||
|
||||
@@ -561,6 +609,34 @@ abstract class CMDBObject extends DBObject
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function DBArchive()
|
||||
{
|
||||
// Note: do the job anyway, so as to repair any DB discrepancy
|
||||
$bOriginal = $this->Get('archive_flag');
|
||||
parent::DBArchive();
|
||||
|
||||
if (!$bOriginal)
|
||||
{
|
||||
utils::PushArchiveMode(false);
|
||||
$this->RecordAttChange('archive_flag', false, true);
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
|
||||
public function DBUnarchive()
|
||||
{
|
||||
// Note: do the job anyway, so as to repair any DB discrepancy
|
||||
$bOriginal = $this->Get('archive_flag');
|
||||
parent::DBUnarchive();
|
||||
|
||||
if ($bOriginal)
|
||||
{
|
||||
utils::PushArchiveMode(false);
|
||||
$this->RecordAttChange('archive_flag', true, false);
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -580,7 +656,7 @@ class CMDBObjectSet extends DBObjectSet
|
||||
|
||||
static public function FromScratch($sClass)
|
||||
{
|
||||
$oFilter = new CMDBSearchFilter($sClass);
|
||||
$oFilter = new DBObjectSearch($sClass);
|
||||
$oFilter->AddConditionExpression(new FalseExpression());
|
||||
$oRetSet = new self($oFilter);
|
||||
// NOTE: THIS DOES NOT WORK IF m_bLoaded is private in the base class (and you will not get any error message)
|
||||
@@ -604,7 +680,7 @@ class CMDBObjectSet extends DBObjectSet
|
||||
// let's create one search definition
|
||||
$sClass = reset($aClasses);
|
||||
$sAlias = key($aClasses);
|
||||
$oFilter = new CMDBSearchFilter($sClass, $sAlias);
|
||||
$oFilter = new DBObjectSearch($sClass, $sAlias);
|
||||
|
||||
$oRetSet = new CMDBObjectSet($oFilter);
|
||||
$oRetSet->m_bLoaded = true; // no DB load
|
||||
@@ -614,18 +690,5 @@ class CMDBObjectSet extends DBObjectSet
|
||||
$oRetSet->AddObjectExtended($aObjectsByClassAlias);
|
||||
}
|
||||
return $oRetSet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: investigate how to get rid of this class that was made to workaround some language limitation... or a poor design!
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class CMDBSearchFilter extends DBObjectSearch
|
||||
{
|
||||
// this is the public interface (?)
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2014 Combodo SARL
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* DB Server abstraction
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -33,13 +33,15 @@ class MySQLException extends CoreException
|
||||
{
|
||||
if ($oException != null)
|
||||
{
|
||||
$aContext['mysql_error'] = $oException->getCode();
|
||||
$aContext['mysql_errno'] = $oException->getMessage();
|
||||
$aContext['mysql_errno'] = $oException->getCode();
|
||||
$this->code = $oException->getCode();
|
||||
$aContext['mysql_error'] = $oException->getMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
$aContext['mysql_error'] = CMDBSource::GetError();
|
||||
$aContext['mysql_errno'] = CMDBSource::GetErrNo();
|
||||
$this->code = CMDBSource::GetErrNo();
|
||||
$aContext['mysql_error'] = CMDBSource::GetError();
|
||||
}
|
||||
parent::__construct($sIssue, $aContext);
|
||||
}
|
||||
@@ -336,7 +338,7 @@ class CMDBSource
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
}
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
if ($oResult === false)
|
||||
@@ -368,7 +370,7 @@ class CMDBSource
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
}
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
|
||||
if ($oResult === false)
|
||||
@@ -404,7 +406,7 @@ class CMDBSource
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
}
|
||||
if ($oResult === false)
|
||||
{
|
||||
@@ -430,7 +432,7 @@ class CMDBSource
|
||||
}
|
||||
catch(mysqli_sql_exception $e)
|
||||
{
|
||||
MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
|
||||
}
|
||||
if ($oResult === false)
|
||||
{
|
||||
@@ -541,6 +543,37 @@ class CMDBSource
|
||||
return ($aFieldData["Type"]);
|
||||
}
|
||||
|
||||
public static function GetFieldSpec($sTable, $sField)
|
||||
{
|
||||
$aTableInfo = self::GetTableInfo($sTable);
|
||||
if (empty($aTableInfo)) return false;
|
||||
if (!array_key_exists($sField, $aTableInfo["Fields"])) return false;
|
||||
$aFieldData = $aTableInfo["Fields"][$sField];
|
||||
$sRet = $aFieldData["Type"];
|
||||
if ($aFieldData["Null"] == 'NO')
|
||||
{
|
||||
$sRet .= ' NOT NULL';
|
||||
}
|
||||
if (is_numeric($aFieldData["Default"]))
|
||||
{
|
||||
if (strtolower(substr($aFieldData["Type"], 0, 5)) == 'enum(')
|
||||
{
|
||||
// Force quotes to match the column declaration statement
|
||||
$sRet .= ' DEFAULT '.self::Quote($aFieldData["Default"], true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$default = $aFieldData["Default"] + 0; // Coerce to a numeric variable
|
||||
$sRet .= ' DEFAULT '.self::Quote($default);
|
||||
}
|
||||
}
|
||||
elseif (is_string($aFieldData["Default"]) == 'string')
|
||||
{
|
||||
$sRet .= ' DEFAULT '.self::Quote($aFieldData["Default"]);
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public static function HasIndex($sTable, $sIndexId, $aFields = null)
|
||||
{
|
||||
$aTableInfo = self::GetTableInfo($sTable);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -18,8 +18,9 @@
|
||||
|
||||
|
||||
define('ITOP_APPLICATION', 'iTop');
|
||||
define('ITOP_VERSION', '$ITOP_VERSION$');
|
||||
define('ITOP_REVISION', '$WCREV$');
|
||||
define('ITOP_APPLICATION_SHORT', 'iTop');
|
||||
define('ITOP_VERSION', '2.4.0');
|
||||
define('ITOP_REVISION', 'svn');
|
||||
define('ITOP_BUILD_DATE', '$WCNOW$');
|
||||
|
||||
define('ACCESS_USER_WRITE', 1);
|
||||
@@ -30,7 +31,7 @@ define('ACCESS_READONLY', 0);
|
||||
/**
|
||||
* Configuration read/write
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -48,7 +49,6 @@ define ('DEFAULT_LOG_GLOBAL', true);
|
||||
define ('DEFAULT_LOG_NOTIFICATION', true);
|
||||
define ('DEFAULT_LOG_ISSUE', true);
|
||||
define ('DEFAULT_LOG_WEB_SERVICE', true);
|
||||
define ('DEFAULT_LOG_QUERIES', false);
|
||||
|
||||
define ('DEFAULT_QUERY_CACHE_ENABLED', true);
|
||||
|
||||
@@ -77,7 +77,6 @@ class Config
|
||||
protected $m_aDataModels;
|
||||
protected $m_aWebServiceCategories;
|
||||
protected $m_aAddons;
|
||||
protected $m_aDictionaries;
|
||||
|
||||
protected $m_aModuleSettings;
|
||||
|
||||
@@ -162,7 +161,7 @@ class Config
|
||||
'default' => '/usr/bin/dot',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'php_path' => array(
|
||||
'type' => 'string',
|
||||
@@ -196,14 +195,22 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'allow_target_creation' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Displays the + button on external keys to create target objects',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'allow_menu_on_linkset' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Display Action menus in view mode on any LinkedSet with edit_mode != none',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'allow_target_creation' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Displays the + button on external keys to create target objects',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
// Levels that trigger a confirmation in the CSV import/synchro wizard
|
||||
'csv_import_min_object_confirmation' => array(
|
||||
'type' => 'integer',
|
||||
@@ -247,7 +254,7 @@ class Config
|
||||
),
|
||||
'access_mode' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Combination of flags (ACCESS_USER_WRITE | ACCESS_ADMIN_WRITE, or ACCESS_FULL)',
|
||||
'description' => 'Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3',
|
||||
'default' => ACCESS_FULL,
|
||||
'value' => ACCESS_FULL,
|
||||
'source_of_value' => '',
|
||||
@@ -277,6 +284,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'log_rest_service' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Log the usage of the REST/JSON service',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'synchro_trace' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'Synchronization details: none, display, save (includes \'display\')',
|
||||
@@ -397,6 +412,14 @@ class Config
|
||||
'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',
|
||||
'default' => "",
|
||||
'value' => "",
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'apc_cache.enabled' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'If set, the APC cache is allowed (the PHP extension must also be active)',
|
||||
@@ -413,6 +436,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'apc_cache_emulation.max_entries' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Maximum number of cache entries (0 means no limit)',
|
||||
'default' => 1000,
|
||||
'value' => 1000,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'timezone' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitely configured in PHP',
|
||||
@@ -710,6 +741,15 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'portal_dispatch_urls' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'Associative array of sPortalId => Home page URL (relatively to the application root)',
|
||||
// examples... not used
|
||||
'default' => array(),
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'max_execution_time_per_loop' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Maximum execution time requested, per loop, during bulk operations. Zero means no limit.',
|
||||
@@ -728,6 +768,15 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'max_history_case_log_entry_length' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'The length (in number of characters) at which to truncate the (expandable) display (in the history) of a case log entry. If zero, the display in the history is not truncated.',
|
||||
// examples... not used
|
||||
'default' => 60,
|
||||
'value' => 60,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'full_text_chunk_duration' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Delay after which the results are displayed.',
|
||||
@@ -777,14 +826,6 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'xlsx_exporter_cleanup_old_files_delay' => array(
|
||||
'type' => 'int',
|
||||
'description' => 'Delay (in seconds) for which to let the exported XLSX files on the server so that the user who initiated the export can download the result',
|
||||
'default' => 86400,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'xlsx_exporter_memory_limit' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'Memory limit to use when (interactively) exporting data to Excel',
|
||||
@@ -809,6 +850,142 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'transaction_storage' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'The type of mechanism to use for storing the unique identifiers for transactions (Session|File).',
|
||||
'default' => 'Session',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'transactions_enabled' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not the whole mechanism to prevent multiple submissions of a page is enabled.',
|
||||
'default' => true,
|
||||
'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.',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'concurrent_lock_enabled' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to activate the locking mechanism in order to prevent concurrent edition of the same object.',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'concurrent_lock_expiration_delay' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Delay (in seconds) for a concurrent lock to expire',
|
||||
'default' => 120,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'concurrent_lock_override_profiles' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'The list of profiles allowed to "kill" a lock',
|
||||
'default' => array('Administrator'),
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'html_sanitizer' => array(
|
||||
'type' => 'string',
|
||||
'description' => 'The class to use for HTML sanitization: HTMLDOMSanitizer, HTMLPurifierSanitizer or HTMLNullSanitizer',
|
||||
'default' => 'HTMLDOMSanitizer',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'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.',
|
||||
'default' => '250',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'inline_image_max_storage_width' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.',
|
||||
'default' => '1600',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'date_and_time_format' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'Format for date and time display (per language)',
|
||||
'default' => array('default' => array('date' => 'Y-m-d', 'time' => 'H:i:s', 'date_time' => '$date $time')),
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'breadcrumb.max_count' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Maximum number of items kept in the history breadcrumb. Set it to 0 to entirely disable the breadcrumb.',
|
||||
'default' => 8,
|
||||
'value' => 8,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'legacy_search_drawer_open' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to display the "search drawer" open by default as in previous versions of iTop.',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'obsolescence.show_obsolete_data' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Default value for the user preference "show obsolete data"',
|
||||
'default' => false,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'obsolescence.date_update_interval' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Delay in seconds between two refreshes of the obsolescence dates.',
|
||||
'default' => 600,
|
||||
'value' => 600,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'disable_attachments_download_legacy_portal' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Disable attachments download from legacy portal',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'optimize_requests_for_join_count' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Optimize request joins to minimize the count (default is true, try to set it to false in case of performance issues)',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'high_cardinality_classes' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'List of classes with high cardinality (force auto-complete mode)',
|
||||
'default' => array(),
|
||||
'value' => array(),
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
);
|
||||
|
||||
public function IsProperty($sPropCode)
|
||||
@@ -869,7 +1046,6 @@ class Config
|
||||
protected $m_bLogNotification;
|
||||
protected $m_bLogIssue;
|
||||
protected $m_bLogWebService;
|
||||
protected $m_bLogQueries; // private setting
|
||||
protected $m_bQueryCacheEnabled; // private setting
|
||||
|
||||
/**
|
||||
@@ -933,34 +1109,10 @@ class Config
|
||||
$bLoadConfig = false;
|
||||
}
|
||||
|
||||
$this->m_aAppModules = array(
|
||||
// Some default modules, always present can be move to an official iTop Module later if needed
|
||||
'application/transaction.class.inc.php',
|
||||
'application/menunode.class.inc.php',
|
||||
'application/user.preferences.class.inc.php',
|
||||
'application/user.dashboard.class.inc.php',
|
||||
'application/audit.rule.class.inc.php',
|
||||
'application/query.class.inc.php',
|
||||
// Romain - That's dirty, because those classes are in fact part of the core
|
||||
// but I needed those classes to be derived from cmdbAbstractObject
|
||||
// (to be managed via the GUI) and this class in not really known from
|
||||
// the core, PLUS I needed the includes to be there also for the setup
|
||||
// to create the tables.
|
||||
'core/event.class.inc.php',
|
||||
'core/action.class.inc.php',
|
||||
'core/trigger.class.inc.php',
|
||||
'synchro/synchrodatasource.class.inc.php',
|
||||
'core/backgroundtask.class.inc.php',
|
||||
);
|
||||
$this->m_aDataModels = array();
|
||||
$this->m_aWebServiceCategories = array(
|
||||
'webservices/webservices.basic.php',
|
||||
);
|
||||
$this->m_aAddons = array(
|
||||
// Default AddOn, always present can be moved to an official iTop Module later if needed
|
||||
'user rights' => 'addons/userrights/userrightsprofile.class.inc.php',
|
||||
);
|
||||
$this->m_aDictionaries = self::ScanDictionariesDir();
|
||||
|
||||
foreach($this->m_aSettings as $sPropCode => $aSettingInfo)
|
||||
{
|
||||
@@ -988,6 +1140,7 @@ class Config
|
||||
$this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE;
|
||||
$this->m_sEncryptionKey = DEFAULT_ENCRYPTION_KEY;
|
||||
$this->m_aCharsets = array();
|
||||
$this->m_bQueryCacheEnabled = DEFAULT_QUERY_CACHE_ENABLED;
|
||||
|
||||
$this->m_aModuleSettings = array();
|
||||
|
||||
@@ -1057,18 +1210,7 @@ class Config
|
||||
{
|
||||
throw new ConfigException('Missing array in configuration file', array('file' => $sConfigFile, 'expected' => '$MySettings'));
|
||||
}
|
||||
if (!isset($MyModules) || !is_array($MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules'));
|
||||
}
|
||||
if (!array_key_exists('application', $MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'application\']'));
|
||||
}
|
||||
if (!array_key_exists('business', $MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'business\']'));
|
||||
}
|
||||
|
||||
if (!array_key_exists('addons', $MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'addons\']'));
|
||||
@@ -1078,18 +1220,8 @@ class Config
|
||||
// Add one, by default
|
||||
$MyModules['addons']['user rights'] = '/addons/userrights/userrightsnull.class.inc.php';
|
||||
}
|
||||
if (!array_key_exists('dictionaries', $MyModules))
|
||||
{
|
||||
throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'dictionaries\']'));
|
||||
}
|
||||
$this->m_aAppModules = $MyModules['application'];
|
||||
$this->m_aDataModels = $MyModules['business'];
|
||||
if (isset($MyModules['webservices']))
|
||||
{
|
||||
$this->m_aWebServiceCategories = $MyModules['webservices'];
|
||||
}
|
||||
|
||||
$this->m_aAddons = $MyModules['addons'];
|
||||
$this->m_aDictionaries = $MyModules['dictionaries'];
|
||||
|
||||
foreach($MySettings as $sPropCode => $rawvalue)
|
||||
{
|
||||
@@ -1120,7 +1252,6 @@ class Config
|
||||
$this->m_bLogNotification = isset($MySettings['log_notification']) ? (bool) trim($MySettings['log_notification']) : DEFAULT_LOG_NOTIFICATION;
|
||||
$this->m_bLogIssue = isset($MySettings['log_issue']) ? (bool) trim($MySettings['log_issue']) : DEFAULT_LOG_ISSUE;
|
||||
$this->m_bLogWebService = isset($MySettings['log_web_service']) ? (bool) trim($MySettings['log_web_service']) : DEFAULT_LOG_WEB_SERVICE;
|
||||
$this->m_bLogQueries = isset($MySettings['log_queries']) ? (bool) trim($MySettings['log_queries']) : DEFAULT_LOG_QUERIES;
|
||||
$this->m_bQueryCacheEnabled = isset($MySettings['query_cache_enabled']) ? (bool) trim($MySettings['query_cache_enabled']) : DEFAULT_QUERY_CACHE_ENABLED;
|
||||
|
||||
$this->m_iMinDisplayLimit = isset($MySettings['min_display_limit']) ? trim($MySettings['min_display_limit']) : DEFAULT_MIN_DISPLAY_LIMIT;
|
||||
@@ -1150,41 +1281,29 @@ class Config
|
||||
{
|
||||
return $this->m_aModuleSettings[$sModule][$sProperty];
|
||||
}
|
||||
return $defaultvalue;
|
||||
// Fall back to the predefined XML parameter, if any
|
||||
return $this->GetModuleParameter($sModule, $sProperty, $defaultvalue);
|
||||
}
|
||||
|
||||
public function GetModuleParameter($sModule, $sProperty, $defaultvalue = null)
|
||||
{
|
||||
$ret = $defaultvalue;
|
||||
if (class_exists('ModulesXMLParameters'))
|
||||
{
|
||||
$aAllParams = ModulesXMLParameters::GetData($sModule);
|
||||
if(array_key_exists($sProperty, $aAllParams))
|
||||
{
|
||||
$ret = $aAllParams[$sProperty];
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function SetModuleSetting($sModule, $sProperty, $value)
|
||||
{
|
||||
$this->m_aModuleSettings[$sModule][$sProperty] = $value;
|
||||
}
|
||||
|
||||
public function GetAppModules()
|
||||
{
|
||||
return $this->m_aAppModules;
|
||||
}
|
||||
public function SetAppModules($aAppModules)
|
||||
{
|
||||
$this->m_aAppModules = $aAppModules;
|
||||
}
|
||||
|
||||
public function GetDataModels()
|
||||
{
|
||||
return $this->m_aDataModels;
|
||||
}
|
||||
public function SetDataModels($aDataModels)
|
||||
{
|
||||
$this->m_aDataModels = $aDataModels;
|
||||
}
|
||||
|
||||
public function GetWebServiceCategories()
|
||||
{
|
||||
return $this->m_aWebServiceCategories;
|
||||
}
|
||||
public function SetWebServiceCategories($aWebServiceCategories)
|
||||
{
|
||||
$this->m_aWebServiceCategories = $aWebServiceCategories;
|
||||
}
|
||||
|
||||
public function GetAddons()
|
||||
{
|
||||
return $this->m_aAddons;
|
||||
@@ -1194,15 +1313,6 @@ class Config
|
||||
$this->m_aAddons = $aAddons;
|
||||
}
|
||||
|
||||
public function GetDictionaries()
|
||||
{
|
||||
return $this->m_aDictionaries;
|
||||
}
|
||||
public function SetDictionaries($aDictionaries)
|
||||
{
|
||||
$this->m_aDictionaries = $aDictionaries;
|
||||
}
|
||||
|
||||
public function GetDBHost()
|
||||
{
|
||||
return $this->m_sDBHost;
|
||||
@@ -1260,7 +1370,7 @@ class Config
|
||||
|
||||
public function GetLogQueries()
|
||||
{
|
||||
return $this->m_bLogQueries;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function GetQueryCacheEnabled()
|
||||
@@ -1462,6 +1572,7 @@ class Config
|
||||
$aSettings['log_notification'] = $this->m_bLogNotification;
|
||||
$aSettings['log_issue'] = $this->m_bLogIssue;
|
||||
$aSettings['log_web_service'] = $this->m_bLogWebService;
|
||||
$aSettings['query_cache_enabled'] = $this->m_bQueryCacheEnabled;
|
||||
$aSettings['min_display_limit'] = $this->m_iMinDisplayLimit;
|
||||
$aSettings['max_display_limit'] = $this->m_iMaxDisplayLimit;
|
||||
$aSettings['standard_reload_interval'] = $this->m_iStandardReloadInterval;
|
||||
@@ -1469,6 +1580,7 @@ class Config
|
||||
$aSettings['secure_connection_required'] = $this->m_bSecureConnectionRequired;
|
||||
$aSettings['default_language'] = $this->m_sDefaultLanguage;
|
||||
$aSettings['allowed_login_types'] = $this->m_sAllowedLoginTypes;
|
||||
$aSettings['ext_auth_variable'] = $this->m_sExtAuthVariable;
|
||||
$aSettings['encryption_key'] = $this->m_sEncryptionKey;
|
||||
$aSettings['csv_import_charsets'] = $this->m_aCharsets;
|
||||
|
||||
@@ -1479,26 +1591,10 @@ class Config
|
||||
$aSettings['module_settings'][$sModule][$sProperty] = $value;
|
||||
}
|
||||
}
|
||||
foreach($this->m_aAppModules as $sFile)
|
||||
{
|
||||
$aSettings['application_list'][] = $sFile;
|
||||
}
|
||||
foreach($this->m_aDataModels as $sFile)
|
||||
{
|
||||
$aSettings['datamodel_list'][] = $sFile;
|
||||
}
|
||||
foreach($this->m_aWebServiceCategories as $sFile)
|
||||
{
|
||||
$aSettings['webservice_list'][] = $sFile;
|
||||
}
|
||||
foreach($this->m_aAddons as $sKey => $sFile)
|
||||
{
|
||||
$aSettings['addon_list'][] = $sFile;
|
||||
}
|
||||
foreach($this->m_aDictionaries as $sFile)
|
||||
{
|
||||
$aSettings['dictionary_list'][] = $sFile;
|
||||
}
|
||||
return $aSettings;
|
||||
}
|
||||
|
||||
@@ -1534,6 +1630,7 @@ class Config
|
||||
'log_notification' => $this->m_bLogNotification,
|
||||
'log_issue' => $this->m_bLogIssue,
|
||||
'log_web_service' => $this->m_bLogWebService,
|
||||
'query_cache_enabled' => $this->m_bQueryCacheEnabled,
|
||||
'secure_connection_required' => $this->m_bSecureConnectionRequired,
|
||||
);
|
||||
foreach($aBoolValues as $sKey => $bValue)
|
||||
@@ -1572,6 +1669,7 @@ class Config
|
||||
'db_collation' => $this->m_sDBCollation,
|
||||
'default_language' => $this->m_sDefaultLanguage,
|
||||
'allowed_login_types' => $this->m_sAllowedLoginTypes,
|
||||
'ext_auth_variable' => $this->m_sExtAuthVariable,
|
||||
'encryption_key' => $this->m_sEncryptionKey,
|
||||
'csv_import_charsets' => $this->m_aCharsets,
|
||||
);
|
||||
@@ -1640,36 +1738,12 @@ class Config
|
||||
fwrite($hFile, " *\n");
|
||||
fwrite($hFile, " */\n");
|
||||
fwrite($hFile, "\$MyModules = array(\n");
|
||||
fwrite($hFile, "\t'application' => array (\n");
|
||||
foreach($this->m_aAppModules as $sFile)
|
||||
{
|
||||
fwrite($hFile, "\t\t'$sFile',\n");
|
||||
}
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, "\t'business' => array (\n");
|
||||
foreach($this->m_aDataModels as $sFile)
|
||||
{
|
||||
fwrite($hFile, "\t\t'$sFile',\n");
|
||||
}
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, "\t'webservices' => array (\n");
|
||||
foreach($this->m_aWebServiceCategories as $sFile)
|
||||
{
|
||||
fwrite($hFile, "\t\t'$sFile',\n");
|
||||
}
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, "\t'addons' => array (\n");
|
||||
foreach($this->m_aAddons as $sKey => $sFile)
|
||||
{
|
||||
fwrite($hFile, "\t\t'$sKey' => '$sFile',\n");
|
||||
}
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, "\t'dictionaries' => array (\n");
|
||||
foreach($this->m_aDictionaries as $sFile)
|
||||
{
|
||||
fwrite($hFile, "\t\t'$sFile',\n");
|
||||
}
|
||||
fwrite($hFile, "\t),\n");
|
||||
fwrite($hFile, ");\n");
|
||||
fwrite($hFile, '?'.'>'); // Avoid perturbing the syntax highlighting !
|
||||
return fclose($hFile);
|
||||
@@ -1679,26 +1753,6 @@ class Config
|
||||
throw new ConfigException("Could not write to configuration file", array('file' => $sFileName));
|
||||
}
|
||||
}
|
||||
|
||||
protected static function ScanDictionariesDir()
|
||||
{
|
||||
$aResult = array();
|
||||
// Populate automatically the list of dictionary files
|
||||
$sDir = APPROOT.'/dictionaries';
|
||||
if ($hDir = @opendir($sDir))
|
||||
{
|
||||
while (($sFile = readdir($hDir)) !== false)
|
||||
{
|
||||
$aMatches = array();
|
||||
if (preg_match("/^([^\.]+\.)?dictionary\.itop\.(ui|core)\.php$/i", $sFile, $aMatches)) // Dictionary files named like [<Lang>.]dictionary.[core|ui].php are loaded automatically
|
||||
{
|
||||
$aResult[] = 'dictionaries/'.$sFile;
|
||||
}
|
||||
}
|
||||
closedir($hDir);
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to initialize a configuration from the page arguments
|
||||
@@ -1709,6 +1763,10 @@ class Config
|
||||
{
|
||||
$this->Set('app_root_url', $aParamValues['application_path']);
|
||||
}
|
||||
if (isset($aParamValues['graphviz_path']))
|
||||
{
|
||||
$this->Set('graphviz_path', $aParamValues['graphviz_path']);
|
||||
}
|
||||
if (isset($aParamValues['mode']) && isset($aParamValues['language']))
|
||||
{
|
||||
if (($aParamValues['mode'] == 'install') || $this->GetDefaultLanguage() == '')
|
||||
@@ -1730,48 +1788,44 @@ class Config
|
||||
$this->SetDBName($sDBName);
|
||||
$this->SetDBSubname($aParamValues['db_prefix']);
|
||||
}
|
||||
|
||||
if (!is_null($sModulesDir))
|
||||
|
||||
if (isset($aParamValues['selected_modules']))
|
||||
{
|
||||
if (isset($aParamValues['selected_modules']))
|
||||
{
|
||||
$aSelectedModules = explode(',', $aParamValues['selected_modules']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aSelectedModules = null;
|
||||
}
|
||||
|
||||
$aSelectedModules = explode(',', $aParamValues['selected_modules']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aSelectedModules = null;
|
||||
}
|
||||
$this->UpdateIncludes($sModulesDir, $aSelectedModules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to rebuild the default configuration and the list of includes from a directory and a list of selected modules
|
||||
* @param string $sModulesDir The relative path to the directory to scan for modules (typically the 'env-xxx' directory resulting from the compilation)
|
||||
* @param array $aSelectedModules An array of selected modules' identifiers. If null all modules found will be considered as installed
|
||||
* @throws Exception
|
||||
*/
|
||||
public function UpdateIncludes($sModulesDir, $aSelectedModules = null)
|
||||
{
|
||||
if (!is_null($sModulesDir))
|
||||
{
|
||||
// Initialize the arrays below with default values for the application...
|
||||
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
|
||||
$aAddOns = $oEmptyConfig->GetAddOns();
|
||||
$aAppModules = $oEmptyConfig->GetAppModules();
|
||||
$aDataModels = $oEmptyConfig->GetDataModels();
|
||||
$aWebServiceCategories = $oEmptyConfig->GetWebServiceCategories();
|
||||
$aDictionaries = $oEmptyConfig->GetDictionaries();
|
||||
// Merge the values with the ones provided by the modules
|
||||
// Make sure when don't load the same file twice...
|
||||
|
||||
|
||||
$aModules = ModuleDiscovery::GetAvailableModules(array(APPROOT.$sModulesDir));
|
||||
foreach($aModules as $sModuleId => $aModuleInfo)
|
||||
foreach ($aModules as $sModuleId => $aModuleInfo)
|
||||
{
|
||||
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
list ($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules))
|
||||
{
|
||||
if (isset($aModuleInfo['datamodel']))
|
||||
{
|
||||
$aDataModels = array_unique(array_merge($aDataModels, $aModuleInfo['datamodel']));
|
||||
}
|
||||
if (isset($aModuleInfo['webservice']))
|
||||
{
|
||||
$aWebServiceCategories = array_unique(array_merge($aWebServiceCategories, $aModuleInfo['webservice']));
|
||||
}
|
||||
if (isset($aModuleInfo['settings']))
|
||||
{
|
||||
list($sName, $sVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
foreach($aModuleInfo['settings'] as $sProperty => $value)
|
||||
list ($sName, $sVersion) = ModuleDiscovery::GetModuleName($sModuleId);
|
||||
foreach ($aModuleInfo['settings'] as $sProperty => $value)
|
||||
{
|
||||
if ($bPreserveModuleSettings && isset($this->m_aModuleSettings[$sName][$sProperty]))
|
||||
if (isset($this->m_aModuleSettings[$sName][$sProperty]))
|
||||
{
|
||||
// Do nothing keep the original value
|
||||
}
|
||||
@@ -1792,27 +1846,12 @@ class Config
|
||||
{
|
||||
throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aModuleInfo['label']);
|
||||
}
|
||||
$aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig');
|
||||
$aCallSpec = array($sModuleInstallerClass,'BeforeWritingConfig');
|
||||
call_user_func_array($aCallSpec, array($this));
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->SetAddOns($aAddOns);
|
||||
$this->SetAppModules($aAppModules);
|
||||
$this->SetDataModels($aDataModels);
|
||||
$this->SetWebServiceCategories($aWebServiceCategories);
|
||||
|
||||
// Scan dictionaries
|
||||
//
|
||||
if (!is_null($sModulesDir))
|
||||
{
|
||||
foreach(glob(APPROOT.$sModulesDir.'/dictionaries/*.dict.php') as $sFilePath)
|
||||
{
|
||||
$sFile = basename($sFilePath);
|
||||
$aDictionaries[] = $sModulesDir.'/dictionaries/'.$sFile;
|
||||
}
|
||||
}
|
||||
$this->SetDictionaries($aDictionaries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1831,15 +1870,12 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick an dirty way to clone a config file into another environment
|
||||
* Obsolete: kept only for backward compatibility of the Toolkit
|
||||
* Quick and dirty way to clone a config file into another environment
|
||||
*/
|
||||
public function ChangeModulesPath($sSourceEnv, $sTargetEnv)
|
||||
{
|
||||
$sSearchPrefix = 'env-'.$sSourceEnv.'/';
|
||||
$sNewPrefix = 'env-'.$sTargetEnv.'/';
|
||||
self::ChangePrefix($this->m_aDataModels, $sSearchPrefix, $sNewPrefix);
|
||||
self::ChangePrefix($this->m_aWebServiceCategories, $sSearchPrefix, $sNewPrefix);
|
||||
self::ChangePrefix($this->m_aDictionaries, $sSearchPrefix, $sNewPrefix);
|
||||
// Now does nothing since the includes are built into the environment itself
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
75
core/contexttag.class.inc.php
Normal file
75
core/contexttag.class.inc.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// Copyright (C) 2016-2017 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/>
|
||||
|
||||
|
||||
/**
|
||||
* Simple helper class for keeping track of the context inside the call stack
|
||||
*
|
||||
* To check (anywhere in the code) if a particular context tag is present
|
||||
* in the call stack simply do:
|
||||
*
|
||||
* if (ContextTag::Check(<the_tag>)) ...
|
||||
*
|
||||
* For example to know if the code is being executed in the context of a portal do:
|
||||
*
|
||||
* if (ContextTag::Check('GUI:Portal'))
|
||||
*
|
||||
* @copyright Copyright (C) 2016-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class ContextTag
|
||||
{
|
||||
protected static $aStack = array();
|
||||
|
||||
/**
|
||||
* Store a context tag on the stack
|
||||
* @param string $sTag
|
||||
*/
|
||||
public function __construct($sTag)
|
||||
{
|
||||
static::$aStack[] = $sTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the context stack
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
array_pop(static::$aStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given tag is present in the stack
|
||||
* @param string $sTag
|
||||
* @return bool
|
||||
*/
|
||||
public static function Check($sTag)
|
||||
{
|
||||
return in_array($sTag, static::$aStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the whole stack as an array
|
||||
* @return hash
|
||||
*/
|
||||
public static function GetStack()
|
||||
{
|
||||
return static::$aStack;
|
||||
}
|
||||
}
|
||||
@@ -112,4 +112,13 @@ class SecurityException extends CoreException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Throwned when querying on an object that exists in the database but is archived
|
||||
*
|
||||
* @see N.1108
|
||||
*/
|
||||
class ArchivedObjectException extends CoreException
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
381
core/csvbulkexport.class.inc.php
Normal file
381
core/csvbulkexport.class.inc.php
Normal file
@@ -0,0 +1,381 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2016 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/>
|
||||
|
||||
/**
|
||||
* Bulk export: CSV export
|
||||
*
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class CSVBulkExport extends TabularBulkExport
|
||||
{
|
||||
public function DisplayUsage(Page $oP)
|
||||
{
|
||||
$oP->p(" * csv format options:");
|
||||
$oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
|
||||
$oP->p(" *\tseparator: (optional) character to be used as the separator (default is ',').");
|
||||
$oP->p(" *\tcharset: (optional) character set for encoding the result (default is 'UTF-8').");
|
||||
$oP->p(" *\ttext-qualifier: (optional) character to be used around text strings (default is '\"').");
|
||||
$oP->p(" *\tno_localize: set to 1 to retrieve non-localized values (for instance for ENUM values). Default is 0 (= localized values)");
|
||||
$oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)");
|
||||
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format used in the user interface). e.g. 'Y-m-d H:i:s'");
|
||||
}
|
||||
|
||||
public function ReadParameters()
|
||||
{
|
||||
parent::ReadParameters();
|
||||
$this->aStatusInfo['separator'] = utils::ReadParam('separator', ',', true, 'raw_data');
|
||||
if (strtolower($this->aStatusInfo['separator']) == 'tab')
|
||||
{
|
||||
$this->aStatusInfo['separator'] = "\t";
|
||||
}
|
||||
else if (strtolower($this->aStatusInfo['separator']) == 'other')
|
||||
{
|
||||
$this->aStatusInfo['separator'] = utils::ReadParam('other-separator', ',', true, 'raw_data');
|
||||
}
|
||||
|
||||
$this->aStatusInfo['text_qualifier'] = utils::ReadParam('text-qualifier', '"', true, 'raw_data');
|
||||
if (strtolower($this->aStatusInfo['text_qualifier']) == 'other')
|
||||
{
|
||||
$this->aStatusInfo['text_qualifier'] = utils::ReadParam('other-text-qualifier', '"', true, 'raw_data');
|
||||
}
|
||||
|
||||
$this->aStatusInfo['charset'] = strtoupper(utils::ReadParam('charset', 'UTF-8', true, 'raw_data'));
|
||||
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
|
||||
|
||||
$sDateFormatRadio = utils::ReadParam('csv_date_format_radio', '');
|
||||
switch($sDateFormatRadio)
|
||||
{
|
||||
case 'default':
|
||||
// Export from the UI => format = same as is the UI
|
||||
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
// Custom format specified from the UI
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function SuggestField($sClass, $sAttCode)
|
||||
{
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id': // replace 'id' by 'friendlyname'
|
||||
$sAttCode = 'friendlyname';
|
||||
break;
|
||||
|
||||
default:
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeExternalKey)
|
||||
{
|
||||
$sAttCode .= '_friendlyname';
|
||||
}
|
||||
}
|
||||
|
||||
return parent::SuggestField($sClass, $sAttCode);
|
||||
}
|
||||
|
||||
public function EnumFormParts()
|
||||
{
|
||||
return array_merge(parent::EnumFormParts(), array('csv_options' => array('separator', 'charset', 'text-qualifier', 'no_localize', 'formatted_text') ,'interactive_fields_csv' => array('interactive_fields_csv')));
|
||||
}
|
||||
|
||||
public function DisplayFormPart(WebPage $oP, $sPartId)
|
||||
{
|
||||
switch($sPartId)
|
||||
{
|
||||
case 'interactive_fields_csv':
|
||||
$this->GetInteractiveFieldsWidget($oP, 'interactive_fields_csv');
|
||||
break;
|
||||
|
||||
case 'csv_options':
|
||||
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:CSVOptions').'</legend>');
|
||||
$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');
|
||||
$sCustomDateTimeFormat = utils::ReadParam('', ',', true, 'raw_data');
|
||||
$aSep = array(
|
||||
';' => Dict::S('UI:CSVImport:SeparatorSemicolon+'),
|
||||
',' => Dict::S('UI:CSVImport:SeparatorComma+'),
|
||||
'tab' => Dict::S('UI:CSVImport:SeparatorTab+'),
|
||||
);
|
||||
$sOtherSeparator = '';
|
||||
if (!array_key_exists($sRawSeparator, $aSep))
|
||||
{
|
||||
$sOtherSeparator = $sRawSeparator;
|
||||
$sRawSeparator = 'other';
|
||||
}
|
||||
$aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' <input type="text" size="3" name="other-separator" value="'.htmlentities($sOtherSeparator, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
|
||||
foreach($aSep as $sVal => $sLabel)
|
||||
{
|
||||
$sChecked = ($sVal == $sRawSeparator) ? 'checked' : '';
|
||||
$oP->add('<input type="radio" name="separator" value="'.htmlentities($sVal, ENT_QUOTES, 'UTF-8').'" '.$sChecked.'/> '.$sLabel.'<br/>');
|
||||
}
|
||||
|
||||
$oP->add('</td><td style="vertical-align:top">');
|
||||
|
||||
$oP->add('<h3>'.Dict::S('UI:CSVImport:TextQualifierCharacter').'</h3>');
|
||||
|
||||
$sRawQualifier = utils::ReadParam('text-qualifier', '"', true, 'raw_data');
|
||||
$aQualifiers = array(
|
||||
'"' => Dict::S('UI:CSVImport:QualifierDoubleQuote+'),
|
||||
'\'' => Dict::S('UI:CSVImport:QualifierSimpleQuote+'),
|
||||
);
|
||||
$sOtherQualifier = '';
|
||||
if (!array_key_exists($sRawQualifier, $aQualifiers))
|
||||
{
|
||||
$sOtherQualifier = $sRawQualifier;
|
||||
$sRawQualifier = 'other';
|
||||
}
|
||||
$aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' <input type="text" size="3" name="other-text-qualifier" value="'.htmlentities($sOtherQualifier, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
|
||||
foreach($aQualifiers as $sVal => $sLabel)
|
||||
{
|
||||
$sChecked = ($sVal == $sRawQualifier) ? 'checked' : '';
|
||||
$oP->add('<input type="radio" name="text-qualifier" value="'.htmlentities($sVal, ENT_QUOTES, 'UTF-8').'" '.$sChecked.'/> '.$sLabel.'<br/>');
|
||||
}
|
||||
|
||||
$sChecked = (utils::ReadParam('no_localize', 0) == 1) ? ' checked ' : '';
|
||||
$oP->add('</td><td style="vertical-align:top">');
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:CSVLocalization').'</h3>');
|
||||
$oP->add('<input type="checkbox" id="csv_no_localize" name="no_localize" value="1"'.$sChecked.'><label for="csv_no_localize"> '.Dict::S('Core:BulkExport:OptionNoLocalize').'</label>');
|
||||
$oP->add('<br/>');
|
||||
$oP->add('<br/>');
|
||||
$oP->add(Dict::S('UI:CSVImport:Encoding').': <select name="charset" style="font-family:Arial,Helvetica,Sans-serif">'); // IE 8 has some troubles if the font is different
|
||||
$aPossibleEncodings = utils::GetPossibleEncodings(MetaModel::GetConfig()->GetCSVImportCharsets());
|
||||
$sDefaultEncoding = MetaModel::GetConfig()->Get('csv_file_default_charset');
|
||||
foreach($aPossibleEncodings as $sIconvCode => $sDisplayName )
|
||||
{
|
||||
$sSelected = '';
|
||||
if ($sIconvCode == $sDefaultEncoding)
|
||||
{
|
||||
$sSelected = ' selected';
|
||||
}
|
||||
$oP->add('<option value="'.$sIconvCode.'"'.$sSelected.'>'.$sDisplayName.'</option>');
|
||||
}
|
||||
$oP->add('</select>');
|
||||
|
||||
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
|
||||
$oP->add('<input type="checkbox" id="csv_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="csv_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
|
||||
$oP->add('</td><td style="vertical-align:top">');
|
||||
|
||||
$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
|
||||
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
|
||||
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
|
||||
$oP->add('<input type="radio" id="csv_date_time_format_default" name="csv_date_format_radio" value="default"'.$sDefaultChecked.'><label for="csv_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="csv_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oP->add('<input type="radio" id="csv_date_time_format_custom" name="csv_date_format_radio" value="custom"'.$sCustomChecked.'><label for="csv_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
|
||||
$oP->add('</td></tr></table>');
|
||||
|
||||
$oP->add('</fieldset>');
|
||||
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#csv_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_csv_options').on('preview_updated', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
$('#csv_date_time_format_default').on('click', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
$('#csv_date_time_format_custom').on('click', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
$('#csv_custom_date_time_format').on('click', function() { $('#csv_date_time_format_custom').prop('checked', true); FormatDatesInPreview('csv', 'csv'); }).on('keyup', function() { FormatDatesInPreview('csv', 'csv'); });
|
||||
EOF
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
return parent:: DisplayFormPart($oP, $sPartId);
|
||||
}
|
||||
}
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
}
|
||||
return '<div class="text-preview">'.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
|
||||
protected function GetValue($oObj, $sAttCode)
|
||||
{
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
$sRet = $oObj->GetKey();
|
||||
break;
|
||||
|
||||
default:
|
||||
$sRet = trim($oObj->GetAsCSV($sAttCode), '"');
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetHeader()
|
||||
{
|
||||
$oSet = new DBObjectSet($this->oSearch);
|
||||
$this->aStatusInfo['status'] = 'running';
|
||||
$this->aStatusInfo['position'] = 0;
|
||||
$this->aStatusInfo['total'] = $oSet->Count();
|
||||
|
||||
$aData = array();
|
||||
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
|
||||
{
|
||||
$aData[] = $aFieldSpec['sColLabel'];
|
||||
}
|
||||
$sFrom = array("\r\n", $this->aStatusInfo['text_qualifier']);
|
||||
$sTo = array("\n", $this->aStatusInfo['text_qualifier'].$this->aStatusInfo['text_qualifier']);
|
||||
foreach($aData as $idx => $sData)
|
||||
{
|
||||
// Escape and encode (if needed) the headers
|
||||
$sEscaped = str_replace($sFrom, $sTo, (string)$sData);
|
||||
$aData[$idx] = $this->aStatusInfo['text_qualifier'].$sEscaped.$this->aStatusInfo['text_qualifier'];
|
||||
if ($this->aStatusInfo['charset'] != 'UTF-8')
|
||||
{
|
||||
// Note: due to bugs in the glibc library it's safer to call iconv on the smallest possible string
|
||||
// and thus to convert field by field and not the whole row or file at once (see ticket #991)
|
||||
$aData[$idx] = @iconv('UTF-8', $this->aStatusInfo['charset'].'//IGNORE//TRANSLIT', $aData[$idx]);
|
||||
}
|
||||
}
|
||||
$sData = implode($this->aStatusInfo['separator'], $aData)."\n";
|
||||
|
||||
return $sData;
|
||||
}
|
||||
|
||||
public function GetNextChunk(&$aStatus)
|
||||
{
|
||||
$sRetCode = 'run';
|
||||
$iPercentage = 0;
|
||||
|
||||
$oSet = new DBObjectSet($this->oSearch);
|
||||
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
|
||||
$this->OptimizeColumnLoad($oSet);
|
||||
|
||||
$iCount = 0;
|
||||
$sData = '';
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
|
||||
$sExportDateTimeFormat = $this->aStatusInfo['date_format'];
|
||||
$oPrevDateTimeFormat = AttributeDateTime::GetFormat();
|
||||
$oPrevDateFormat = AttributeDate::GetFormat();
|
||||
if ($sExportDateTimeFormat !== (string)$oPrevDateTimeFormat)
|
||||
{
|
||||
// Change date & time formats
|
||||
$oDateTimeFormat = new DateTimeFormat($sExportDateTimeFormat);
|
||||
$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
|
||||
AttributeDateTime::SetFormat($oDateTimeFormat);
|
||||
AttributeDate::SetFormat($oDateFormat);
|
||||
}
|
||||
while($aRow = $oSet->FetchAssoc())
|
||||
{
|
||||
set_time_limit($iLoopTimeLimit);
|
||||
$aData = array();
|
||||
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
|
||||
{
|
||||
$sAlias = $aFieldSpec['sAlias'];
|
||||
$sAttCode = $aFieldSpec['sAttCode'];
|
||||
|
||||
$sField = '';
|
||||
$oObj = $aRow[$sAlias];
|
||||
if ($oObj != null)
|
||||
{
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
$sField = $oObj->GetKey();
|
||||
break;
|
||||
|
||||
default:
|
||||
$sField = $oObj->GetAsCSV($sAttCode, $this->aStatusInfo['separator'], $this->aStatusInfo['text_qualifier'], $this->bLocalizeOutput, !$this->aStatusInfo['formatted_text']);
|
||||
}
|
||||
}
|
||||
if ($this->aStatusInfo['charset'] != 'UTF-8')
|
||||
{
|
||||
// Note: due to bugs in the glibc library it's safer to call iconv on the smallest possible string
|
||||
// and thus to convert field by field and not the whole row or file at once (see ticket #991)
|
||||
$aData[] = @iconv('UTF-8', $this->aStatusInfo['charset'].'//IGNORE//TRANSLIT', $sField);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aData[] = $sField;
|
||||
}
|
||||
}
|
||||
$sData .= implode($this->aStatusInfo['separator'], $aData)."\n";
|
||||
$iCount++;
|
||||
}
|
||||
// Restore original date & time formats
|
||||
AttributeDateTime::SetFormat($oPrevDateTimeFormat);
|
||||
AttributeDate::SetFormat($oPrevDateFormat);
|
||||
set_time_limit($iPreviousTimeLimit);
|
||||
$this->aStatusInfo['position'] += $this->iChunkSize;
|
||||
if ($this->aStatusInfo['total'] == 0)
|
||||
{
|
||||
$iPercentage = 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
$iPercentage = floor(min(100.0, 100.0*$this->aStatusInfo['position']/$this->aStatusInfo['total']));
|
||||
}
|
||||
|
||||
if ($iCount < $this->iChunkSize)
|
||||
{
|
||||
$sRetCode = 'done';
|
||||
}
|
||||
|
||||
$aStatus = array('code' => $sRetCode, 'message' => Dict::S('Core:BulkExport:RetrievingData'), 'percentage' => $iPercentage);
|
||||
return $sData;
|
||||
}
|
||||
|
||||
public function GetSupportedFormats()
|
||||
{
|
||||
return array('csv' => Dict::S('Core:BulkExport:CSVFormat'));
|
||||
}
|
||||
|
||||
public function GetMimeType()
|
||||
{
|
||||
return 'text/csv';
|
||||
}
|
||||
|
||||
public function GetFileExtension()
|
||||
{
|
||||
return 'csv';
|
||||
}
|
||||
public function GetCharacterSet()
|
||||
{
|
||||
return $this->aStatusInfo['charset'];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -54,12 +54,14 @@ class CSVParser
|
||||
private $m_sCSVData;
|
||||
private $m_sSep;
|
||||
private $m_sTextQualifier;
|
||||
private $m_iTimeLimitPerRow;
|
||||
|
||||
public function __construct($sTxt, $sSep = ',', $sTextQualifier = '"')
|
||||
public function __construct($sTxt, $sSep = ',', $sTextQualifier = '"', $iTimeLimitPerRow = null)
|
||||
{
|
||||
$this->m_sCSVData = str_replace("\r\n", "\n", $sTxt);
|
||||
$this->m_sSep = $sSep;
|
||||
$this->m_sTextQualifier = $sTextQualifier;
|
||||
$this->m_iTimeLimitPerRow = $iTimeLimitPerRow;
|
||||
}
|
||||
|
||||
protected $m_sCurrCell = '';
|
||||
@@ -129,6 +131,12 @@ class CSVParser
|
||||
// blank line, skip silently
|
||||
}
|
||||
$this->m_aCurrRow = array();
|
||||
|
||||
// More time for the next row
|
||||
if ($this->m_iTimeLimitPerRow !== null)
|
||||
{
|
||||
set_time_limit($this->m_iTimeLimitPerRow);
|
||||
}
|
||||
}
|
||||
protected function __AddCellTrimmed($c = null, $aFieldMap = null)
|
||||
{
|
||||
@@ -181,6 +189,13 @@ class CSVParser
|
||||
$iDataLength = strlen($this->m_sCSVData);
|
||||
|
||||
$iState = stSTARTING;
|
||||
$iTimeLimit = null;
|
||||
if ($this->m_iTimeLimitPerRow !== null)
|
||||
{
|
||||
// Give some time for the first row
|
||||
$iTimeLimit = ini_get('max_execution_time');
|
||||
set_time_limit($this->m_iTimeLimitPerRow);
|
||||
}
|
||||
for($i = 0; $i <= $iDataLength ; $i++)
|
||||
{
|
||||
if ($i == $iDataLength)
|
||||
@@ -237,6 +252,11 @@ class CSVParser
|
||||
$iLineCount = count($this->m_aDataSet);
|
||||
if (($iMax > 0) && ($iLineCount >= $iMax)) break;
|
||||
}
|
||||
if ($iTimeLimit !== null)
|
||||
{
|
||||
// Restore the previous time limit
|
||||
set_time_limit($iTimeLimit);
|
||||
}
|
||||
return $this->m_aDataSet;
|
||||
}
|
||||
|
||||
|
||||
140
core/customfieldshandler.class.inc.php
Normal file
140
core/customfieldshandler.class.inc.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 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\Form\Form;
|
||||
use Combodo\iTop\Form\FormManager;
|
||||
|
||||
/**
|
||||
* Base class to implement a handler for AttributeCustomFields
|
||||
*
|
||||
* @copyright Copyright (C) 2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
abstract class CustomFieldsHandler
|
||||
{
|
||||
protected $sAttCode;
|
||||
protected $aValues;
|
||||
protected $oForm;
|
||||
|
||||
/**
|
||||
* This constructor's prototype must be frozen.
|
||||
* Any specific behavior must be implemented in BuildForm()
|
||||
*
|
||||
* @param $sAttCode
|
||||
*/
|
||||
final public function __construct($sAttCode)
|
||||
{
|
||||
$this->sAttCode = $sAttCode;
|
||||
$this->aValues = null;
|
||||
}
|
||||
|
||||
abstract public function BuildForm(DBObject $oHostObject, $sFormId);
|
||||
|
||||
/**
|
||||
*
|
||||
* @return \Combodo\iTop\Form\Form
|
||||
*/
|
||||
public function GetForm()
|
||||
{
|
||||
return $this->oForm;
|
||||
}
|
||||
|
||||
public function SetCurrentValues($aValues)
|
||||
{
|
||||
$this->aValues = $aValues;
|
||||
}
|
||||
|
||||
static public function GetPrerequisiteAttributes($sClass = null)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* List the available verbs for 'GetForTemplate'
|
||||
*/
|
||||
static public function EnumTemplateVerbs()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
|
||||
* @param $aValues array The current values
|
||||
* @param $sVerb string The verb specifying the representation of the value
|
||||
* @param $bLocalize bool Whether or not to localize the value
|
||||
* @return string
|
||||
*/
|
||||
abstract public function GetForTemplate($aValues, $sVerb, $bLocalize = true);
|
||||
|
||||
/**
|
||||
* @param $aValues
|
||||
* @param bool|true $bLocalize
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function GetAsHTML($aValues, $bLocalize = true);
|
||||
|
||||
/**
|
||||
* @param $aValues
|
||||
* @param bool|true $bLocalize
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function GetAsXML($aValues, $bLocalize = true);
|
||||
|
||||
/**
|
||||
* @param $aValues
|
||||
* @param string $sSeparator
|
||||
* @param string $sTextQualifier
|
||||
* @param bool|true $bLocalize
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function GetAsCSV($aValues, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true);
|
||||
|
||||
/**
|
||||
* @param DBObject $oHostObject
|
||||
* @return array Associative array id => value
|
||||
*/
|
||||
abstract public function ReadValues(DBObject $oHostObject);
|
||||
|
||||
/**
|
||||
* Record the data (currently in the processing of recording the host object)
|
||||
* It is assumed that the data has been checked prior to calling Write()
|
||||
* @param DBObject $oHostObject
|
||||
* @param array Associative array id => value
|
||||
*/
|
||||
abstract public function WriteValues(DBObject $oHostObject, $aValues);
|
||||
|
||||
/**
|
||||
* Cleanup data upon object deletion (object id still available here)
|
||||
* @param DBObject $oHostObject
|
||||
*/
|
||||
abstract public function DeleteValues(DBObject $oHostObject);
|
||||
|
||||
/**
|
||||
* @param $aValuesA
|
||||
* @param $aValuesB
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function CompareValues($aValuesA, $aValuesB);
|
||||
|
||||
/**
|
||||
* String representation of the value, must depend solely on the semantics
|
||||
* @return string
|
||||
*/
|
||||
abstract public function GetValueFingerprint();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2015 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -21,7 +21,7 @@
|
||||
* data generator
|
||||
* helps the consultants in creating dummy data sets, for various test purposes (validation, usability, scalability)
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -267,7 +267,7 @@ class cmdbDataGenerator
|
||||
function GenerateKey($sClass, $aFilterCriteria)
|
||||
{
|
||||
$retKey = null;
|
||||
$oFilter = new CMDBSearchFilter($sClass);
|
||||
$oFilter = new DBObjectSearch($sClass);
|
||||
foreach($aFilterCriteria as $sFilterCode => $filterValue)
|
||||
{
|
||||
$oFilter->AddCondition($sFilterCode, $filterValue, '=');
|
||||
@@ -346,7 +346,7 @@ class cmdbDataGenerator
|
||||
*/
|
||||
protected function OrganizationExists($sCode)
|
||||
{
|
||||
$oFilter = new CMDBSearchFilter('bizOrganization');
|
||||
$oFilter = new DBObjectSearch('Organization');
|
||||
$oFilter->AddCondition('code', $sCode, '=');
|
||||
$oSet = new CMDBObjectSet($oFilter);
|
||||
return ($oSet->Count() > 0);
|
||||
@@ -361,7 +361,7 @@ class cmdbDataGenerator
|
||||
protected function GetOrganization($sId)
|
||||
{
|
||||
$oOrg = null;
|
||||
$oFilter = new CMDBSearchFilter('bizOrganization');
|
||||
$oFilter = new DBObjectSearch('Organization');
|
||||
$oFilter->AddCondition('id', $sId, '=');
|
||||
$oSet = new CMDBObjectSet($oFilter);
|
||||
if ($oSet->Count() > 0)
|
||||
|
||||
3
core/datamodel.core.xml
Normal file
3
core/datamodel.core.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design>
|
||||
</itop_design>
|
||||
428
core/datetimeformat.class.inc.php
Normal file
428
core/datetimeformat.class.inc.php
Normal file
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 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/>
|
||||
|
||||
|
||||
/**
|
||||
* Helper class to generate Date & Time formatting strings in the various conventions
|
||||
* from the PHP DateTime::createFromFormat convention.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $oFormat = new DateTimeFormat('m/d/Y H:i');
|
||||
* $oFormat->ToExcel();
|
||||
* >> 'MM/dd/YYYY HH:mm'
|
||||
*
|
||||
* @author Denis Flaven <denis.flaven@combodo.com>
|
||||
*
|
||||
*/
|
||||
class DateTimeFormat
|
||||
{
|
||||
protected $sPHPFormat;
|
||||
|
||||
/**
|
||||
* Constructs the DateTimeFormat object
|
||||
* @param string $sPHPFormat A format string using the PHP 'DateTime::createFromFormat' convention
|
||||
*/
|
||||
public function __construct($sPHPFormat)
|
||||
{
|
||||
$this->sPHPFormat = (string)$sPHPFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->sPHPFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping table for converting between various conventions for date/time formats
|
||||
*/
|
||||
protected static function GetFormatMapping()
|
||||
{
|
||||
return array(
|
||||
// Days
|
||||
'd' => array('regexpr' => '(0[1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'dd', 'excel' => 'dd', 'moment' => 'DD'), // Day of the month: 2 digits (with leading zero)
|
||||
'j' => array('regexpr' => '([1-9]|[1-2][0-9]||3[0-1])', 'datepicker' => 'd', 'excel' => 'd', 'moment' => 'D'), // Day of the month: 1 or 2 digits (without leading zero)
|
||||
// Months
|
||||
'm' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'mm', 'excel' => 'MM', 'moment' => 'MM' ), // Month on 2 digits i.e. 01-12
|
||||
'n' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'm', 'excel' => 'm', 'moment' => 'M'), // Month on 1 or 2 digits 1-12
|
||||
// Years
|
||||
'Y' => array('regexpr' => '([0-9]{4})', 'datepicker' => 'yy', 'excel' => 'YYYY', 'moment' => 'YYYY'), // Year on 4 digits
|
||||
'y' => array('regexpr' => '([0-9]{2})', 'datepicker' => 'y', 'excel' => 'YY', 'moment' => 'YY'), // Year on 2 digits
|
||||
// Hours
|
||||
'H' => array('regexpr' => '([0-1][0-9]|2[0-3])', 'datepicker' => 'HH', 'excel' => 'HH', 'moment' => 'HH'), // Hour 00..23
|
||||
'h' => array('regexpr' => '(0[1-9]|1[0-2])', 'datepicker' => 'hh', 'excel' => 'hh', 'moment' => 'hh'), // Hour 01..12
|
||||
'G' => array('regexpr' => '([1-9]|[1[0-9]|2[0-3])', 'datepicker' => 'H', 'excel' => 'H', 'moment' => 'H'), // Hour 0..23
|
||||
'g' => array('regexpr' => '([1-9]|1[0-2])', 'datepicker' => 'h', 'excel' => 'h', 'moment' => 'h'), // Hour 1..12
|
||||
'a' => array('regexpr' => '(am|pm)', 'datepicker' => 'tt', 'excel' => 'am/pm', 'moment' => 'a'),
|
||||
'A' => array('regexpr' => '(AM|PM)', 'datepicker' => 'TT', 'excel' => 'AM/PM', 'moment' => 'A'),
|
||||
// Minutes
|
||||
'i' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'mm', 'excel' => 'mm', 'moment' => 'mm'),
|
||||
// Seconds
|
||||
's' => array('regexpr' => '([0-5][0-9])', 'datepicker' => 'ss', 'excel' => 'ss', 'moment' => 'ss'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the PHP format into the specified format, taking care of escaping the litteral characters
|
||||
* using the supplied escaping expression
|
||||
* @param string $sOutputFormatCode THe target format code: regexpr|datepicker|excel|moment
|
||||
* @param string $sEscapePattern The replacement string for escaping characters in the output string. %s is the source char.
|
||||
* @param string $bEscapeAll True to systematically escape all litteral characters
|
||||
* @param array $sSpecialChars A string containing the only characters to escape in the output
|
||||
* @return string The string in the requested format
|
||||
*/
|
||||
protected function Transform($sOutputFormatCode, $sEscapePattern, $bEscapeAll = false, $sSpecialChars = '')
|
||||
{
|
||||
$aMappings = static::GetFormatMapping();
|
||||
$sResult = '';
|
||||
|
||||
$bEscaping = false;
|
||||
for($i=0; $i < strlen($this->sPHPFormat); $i++)
|
||||
{
|
||||
if (($this->sPHPFormat[$i] == '\\'))
|
||||
{
|
||||
$bEscaping = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($bEscaping)
|
||||
{
|
||||
if (($sSpecialChars === '') || (strpos($sSpecialChars, $this->sPHPFormat[$i]) !== false))
|
||||
{
|
||||
$sResult .= sprintf($sEscapePattern, $this->sPHPFormat[$i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sResult .= $this->sPHPFormat[$i];
|
||||
}
|
||||
|
||||
$bEscaping = false;
|
||||
}
|
||||
else if(array_key_exists($this->sPHPFormat[$i], $aMappings))
|
||||
{
|
||||
// Not a litteral value, must be replaced by its regular expression pattern
|
||||
$sResult .= $aMappings[$this->sPHPFormat[$i]][$sOutputFormatCode];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($bEscapeAll || (strpos($sSpecialChars, $this->sPHPFormat[$i]) !== false))
|
||||
{
|
||||
$sResult .= sprintf($sEscapePattern, $this->sPHPFormat[$i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal char with no special meaning, no need to escape it
|
||||
$sResult .= $this->sPHPFormat[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date into the supplied format string
|
||||
* @param mixed $date An int, string, DateTime object or null !!
|
||||
* @throws Exception
|
||||
* @return string The formatted date
|
||||
*/
|
||||
public function Format($date)
|
||||
{
|
||||
if ($date == null)
|
||||
{
|
||||
$sDate = '';
|
||||
}
|
||||
else if (($date === '0000-00-00') || ($date === '0000-00-00 00:00:00'))
|
||||
{
|
||||
$sDate = '';
|
||||
}
|
||||
else if ($date instanceof DateTime)
|
||||
{
|
||||
// Parameter is a DateTime
|
||||
$sDate = $date->format($this->sPHPFormat);
|
||||
}
|
||||
else if (is_int($date))
|
||||
{
|
||||
// Parameter is a Unix timestamp
|
||||
$oDate = new DateTime();
|
||||
$oDate->setTimestamp($date);
|
||||
$sDate = $oDate->format($this->sPHPFormat);
|
||||
}
|
||||
else if (is_string($date))
|
||||
{
|
||||
$oDate = new DateTime($date);
|
||||
$sDate = $oDate->format($this->sPHPFormat);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(__CLASS__."::Format: Unexpected date value: ".print_r($date, true));
|
||||
}
|
||||
return $sDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a date in the supplied format and return the date as a string in the internal format
|
||||
* @param string $sDate The string to parse
|
||||
* @param string $sFormat The format, in PHP createFromFormat convention
|
||||
* @throws Exception
|
||||
* @return DateTime|null
|
||||
*/
|
||||
public function Parse($sDate)
|
||||
{
|
||||
if (($sDate == null) || ($sDate == '0000-00-00 00:00:00') || ($sDate == '0000-00-00'))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sFormat = preg_replace('/\\?/', '', $this->sPHPFormat); // replace escaped characters by a wildcard for parsing
|
||||
$oDate = DateTime::createFromFormat($this->sPHPFormat, $sDate);
|
||||
if ($oDate === false)
|
||||
{
|
||||
throw new Exception(__CLASS__."::Parse: Unable to parse the date: '$sDate' using the format: '{$this->sPHPFormat}'");
|
||||
}
|
||||
return $oDate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date or datetime format string in the jQuery UI date picker format
|
||||
* @return string The format string using the date picker convention
|
||||
*/
|
||||
public function ToDatePicker()
|
||||
{
|
||||
return $this->Transform('datepicker', "'%s'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a date or datetime format string in the Excel format
|
||||
* @return string The format string using the Excel convention
|
||||
*/
|
||||
public function ToExcel()
|
||||
{
|
||||
return $this->Transform('excel', "%s");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a date or datetime format string in the moment.js format
|
||||
* @return string The format string using the moment.js convention
|
||||
*/
|
||||
public function ToMomentJS()
|
||||
{
|
||||
return $this->Transform('moment', "[%s]", true /* escape all */);
|
||||
}
|
||||
|
||||
public static function GetJSSQLToCustomFormat()
|
||||
{
|
||||
$aPHPToMoment = array();
|
||||
foreach(self::GetFormatMapping() as $sPHPCode => $aMapping)
|
||||
{
|
||||
$aPHPToMoment[$sPHPCode] = $aMapping['moment'];
|
||||
}
|
||||
$sJSMapping = json_encode($aPHPToMoment);
|
||||
|
||||
$sFunction =
|
||||
<<<EOF
|
||||
function PHPDateTimeFormatToSubFormat(sPHPFormat, sPlaceholders)
|
||||
{
|
||||
var iMax = 0;
|
||||
var iMin = 999;
|
||||
var bEscaping = false;
|
||||
for(var i=0; i<sPHPFormat.length; i++)
|
||||
{
|
||||
var c = sPHPFormat[i];
|
||||
if (c == '\\\\')
|
||||
{
|
||||
bEscaping = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bEscaping)
|
||||
{
|
||||
bEscaping = false;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sPlaceholders.search(c) != -1)
|
||||
{
|
||||
iMax = Math.max(iMax, i);
|
||||
iMin = Math.min(iMin, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sPHPFormat.substr(iMin, iMax - iMin + 1);
|
||||
}
|
||||
|
||||
function PHPDateTimeFormatToMomentFormat(sPHPFormat)
|
||||
{
|
||||
var aFormatMapping = $sJSMapping;
|
||||
var sMomentFormat = '';
|
||||
|
||||
var bEscaping = false;
|
||||
for(var i=0; i<sPHPFormat.length; i++)
|
||||
{
|
||||
var c = sPHPFormat[i];
|
||||
if (c == '\\\\')
|
||||
{
|
||||
bEscaping = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bEscaping)
|
||||
{
|
||||
sMomentFormat += '['+c+']';
|
||||
bEscaping = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (aFormatMapping[c] !== undefined)
|
||||
{
|
||||
sMomentFormat += aFormatMapping[c];
|
||||
}
|
||||
else
|
||||
{
|
||||
sMomentFormat += '['+c+']';
|
||||
}
|
||||
}
|
||||
}
|
||||
return sMomentFormat;
|
||||
}
|
||||
|
||||
function DateFormatFromPHP(sSQLDate, sPHPFormat)
|
||||
{
|
||||
if (sSQLDate === '') return '';
|
||||
var sPHPDateFormat = PHPDateTimeFormatToSubFormat(sPHPFormat, 'Yydjmn');
|
||||
var sMomentFormat = PHPDateTimeFormatToMomentFormat(sPHPDateFormat);
|
||||
return moment(sSQLDate).format(sMomentFormat);
|
||||
}
|
||||
|
||||
function DateTimeFormatFromPHP(sSQLDate, sPHPFormat)
|
||||
{
|
||||
if (sSQLDate === '') return '';
|
||||
var sMomentFormat = PHPDateTimeFormatToMomentFormat(sPHPFormat);
|
||||
return moment(sSQLDate).format(sMomentFormat);
|
||||
}
|
||||
EOF
|
||||
;
|
||||
return $sFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a placeholder text for a date or datetime format string
|
||||
* @return string The placeholder text (localized)
|
||||
*/
|
||||
public function ToPlaceholder()
|
||||
{
|
||||
$aMappings = static::GetFormatMapping();
|
||||
$sResult = '';
|
||||
|
||||
$bEscaping = false;
|
||||
for($i=0; $i < strlen($this->sPHPFormat); $i++)
|
||||
{
|
||||
if (($this->sPHPFormat[$i] == '\\'))
|
||||
{
|
||||
$bEscaping = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($bEscaping)
|
||||
{
|
||||
$sResult .= $this->sPHPFormat[$i]; // No need to escape characters in the placeholder
|
||||
$bEscaping = false;
|
||||
}
|
||||
else if(array_key_exists($this->sPHPFormat[$i], $aMappings))
|
||||
{
|
||||
// Not a litteral value, must be replaced by Dict equivalent
|
||||
$sResult .= Dict::S('Core:DateTime:Placeholder_'.$this->sPHPFormat[$i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Normal char with no special meaning
|
||||
$sResult .= $this->sPHPFormat[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a subformat (Date or Time) by extracting the part of the whole DateTime format containing only the given placeholders
|
||||
* @return string
|
||||
*/
|
||||
protected function ToSubFormat($aPlaceholders)
|
||||
{
|
||||
$iStart = 999;
|
||||
$iEnd = 0;
|
||||
|
||||
foreach($aPlaceholders as $sChar)
|
||||
{
|
||||
$iPos = strpos($this->sPHPFormat, $sChar);
|
||||
if ($iPos !== false)
|
||||
{
|
||||
if (($iPos > 0) && ($this->sPHPFormat[$iPos-1] == '\\'))
|
||||
{
|
||||
// The placeholder is actually escaped, it's a litteral character, ignore it
|
||||
continue;
|
||||
}
|
||||
$iStart = min($iStart, $iPos);
|
||||
$iEnd = max($iEnd, $iPos);
|
||||
}
|
||||
}
|
||||
$sFormat = substr($this->sPHPFormat, $iStart, $iEnd - $iStart + 1);
|
||||
return $sFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces the Date format string by extracting only the date part of the date and time format string
|
||||
* @return string
|
||||
*/
|
||||
public function ToDateFormat()
|
||||
{
|
||||
return $this->ToSubFormat(array('Y', 'y', 'd', 'j', 'm', 'n'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces the Time format string by extracting only the time part of the date and time format string
|
||||
* @return string
|
||||
*/
|
||||
public function ToTimeFormat()
|
||||
{
|
||||
return $this->ToSubFormat(array('H', 'h', 'G', 'g', 'i', 's', 'a', 'A'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the regular expression to (approximately) validate a date/time for the current format
|
||||
* The validation does not take into account the number of days in a month (i.e. June 31st will pass, as well as Feb 30th!)
|
||||
* @param string $sDelimiter Surround the regexp (and escape) if needed
|
||||
* @return string The regular expression in PCRE syntax
|
||||
*/
|
||||
public function ToRegExpr($sDelimiter = null)
|
||||
{
|
||||
$sRet = '^'.$this->Transform('regexpr', "\\%s", false /* escape all */, '.?*$^()[]:').'$';
|
||||
if ($sDelimiter !== null)
|
||||
{
|
||||
$sRet = $sDelimiter.str_replace($sDelimiter, '\\'.$sDelimiter, $sRet).$sDelimiter;
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
63
core/dbobjectiterator.php
Normal file
63
core/dbobjectiterator.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2017 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/>
|
||||
|
||||
|
||||
/**
|
||||
* A set of persistent objects, could be heterogeneous as long as the objects in the set have a common ancestor class
|
||||
*
|
||||
* @package iTopORM
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
interface iDBObjectSetIterator extends Countable
|
||||
{
|
||||
/**
|
||||
* The class of the objects of the collection (at least a common ancestor)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetClass();
|
||||
|
||||
/**
|
||||
* The total number of objects in the collection
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function Count();
|
||||
|
||||
/**
|
||||
* Reset the cursor to the first item in the collection. Equivalent to Seek(0)
|
||||
*
|
||||
* @return DBObject The fetched object or null when at the end
|
||||
*/
|
||||
public function Rewind();
|
||||
|
||||
/**
|
||||
* Position the cursor to the given 0-based position
|
||||
*
|
||||
* @param int $iRow
|
||||
*/
|
||||
public function Seek($iPosition);
|
||||
|
||||
/**
|
||||
* Fetch the object at the current position in the collection and move the cursor to the next position.
|
||||
*
|
||||
* @return DBObject The fetched object or null when at the end
|
||||
*/
|
||||
public function Fetch();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2014 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -16,11 +16,12 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
require_once('dbobjectiterator.php');
|
||||
|
||||
/**
|
||||
* Object set management
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -30,31 +31,66 @@
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
class DBObjectSet
|
||||
class DBObjectSet implements iDBObjectSetIterator
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $m_aAddedIds; // Ids of objects added (discrete lists)
|
||||
/**
|
||||
* @var hash array of (row => array of (classalias) => object/null) storing the objects added "in memory"
|
||||
*/
|
||||
protected $m_aAddedObjects;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $m_aArgs;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $m_aAttToLoad;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $m_aOrderBy;
|
||||
/**
|
||||
* @var bool True when the filter has been used OR the set is built step by step (AddObject...)
|
||||
*/
|
||||
public $m_bLoaded;
|
||||
/**
|
||||
* @var int Total number of rows for the query without LIMIT. null if unknown yet
|
||||
*/
|
||||
protected $m_iNumTotalDBRows;
|
||||
/**
|
||||
* @var int Total number of rows LOADED in $this->m_oSQLResult via a SQL query. 0 by default
|
||||
*/
|
||||
protected $m_iNumLoadedDBRows;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $m_iCurrRow;
|
||||
/**
|
||||
* @var DBSearch
|
||||
*/
|
||||
protected $m_oFilter;
|
||||
/**
|
||||
* @var mysqli_result
|
||||
*/
|
||||
protected $m_oSQLResult;
|
||||
protected $m_bSort;
|
||||
|
||||
/**
|
||||
* Create a new set based on a Search definition.
|
||||
*
|
||||
* @param DBObjectSearch $oFilter The search filter defining the objects which are part of the set (multiple columns/objects per row are supported)
|
||||
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
|
||||
* @param hash $aArgs Values to substitute for the search/query parameters (if any). Format: param_name => value
|
||||
*
|
||||
* @param DBSearch $oFilter The search filter defining the objects which are part of the set (multiple columns/objects per row are supported)
|
||||
* @param array $aOrderBy Array of '[<classalias>.]attcode' => bAscending
|
||||
* @param array $aArgs Values to substitute for the search/query parameters (if any). Format: param_name => value
|
||||
* @param hash $aExtendedDataSpec
|
||||
* @param int $iLimitCount Maximum number of rows to load (i.e. equivalent to MySQL's LIMIT start, count)
|
||||
* @param int $iLimitStart Index of the first row to load (i.e. equivalent to MySQL's LIMIT start, count)
|
||||
* @param bool $bSort if false no order by is done
|
||||
*/
|
||||
public function __construct(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0)
|
||||
public function __construct(DBSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bSort = true)
|
||||
{
|
||||
$this->m_oFilter = $oFilter->DeepClone();
|
||||
$this->m_aAddedIds = array();
|
||||
@@ -64,11 +100,12 @@ class DBObjectSet
|
||||
$this->m_aExtendedDataSpec = $aExtendedDataSpec;
|
||||
$this->m_iLimitCount = $iLimitCount;
|
||||
$this->m_iLimitStart = $iLimitStart;
|
||||
$this->m_bSort = $bSort;
|
||||
|
||||
$this->m_iNumTotalDBRows = null; // Total number of rows for the query without LIMIT. null if unknown yet
|
||||
$this->m_iNumLoadedDBRows = 0; // Total number of rows LOADED in $this->m_oSQLResult via a SQL query. 0 by default
|
||||
$this->m_bLoaded = false; // true when the filter has been used OR the set is built step by step (AddObject...)
|
||||
$this->m_aAddedObjects = array(); // array of (row => array of (classalias) => object/null) storing the objects added "in memory"
|
||||
$this->m_iNumTotalDBRows = null;
|
||||
$this->m_iNumLoadedDBRows = 0;
|
||||
$this->m_bLoaded = false;
|
||||
$this->m_aAddedObjects = array();
|
||||
$this->m_iCurrRow = 0;
|
||||
$this->m_oSQLResult = null;
|
||||
}
|
||||
@@ -86,7 +123,7 @@ class DBObjectSet
|
||||
$sRet = '';
|
||||
$this->Rewind();
|
||||
$sRet .= "Set (".$this->m_oFilter->ToOQL().")<br/>\n";
|
||||
$sRet .= "Query: <pre style=\"font-size: smaller; display:inline;\">".MetaModel::MakeSelectQuery($this->m_oFilter, array()).")</pre>\n";
|
||||
$sRet .= "Query: <pre style=\"font-size: smaller; display:inline;\">".$this->m_oFilter->MakeSelectQuery().")</pre>\n";
|
||||
|
||||
$sRet .= $this->Count()." records<br/>\n";
|
||||
if ($this->Count() > 0)
|
||||
@@ -123,6 +160,17 @@ class DBObjectSet
|
||||
$this->m_iCurrRow = 0;
|
||||
$this->m_oSQLResult = null;
|
||||
}
|
||||
|
||||
public function SetShowObsoleteData($bShow)
|
||||
{
|
||||
$this->m_oFilter->SetShowObsoleteData($bShow);
|
||||
}
|
||||
|
||||
public function GetShowObsoleteData()
|
||||
{
|
||||
return $this->m_oFilter->GetShowObsoleteData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the subset of attributes to load (for each class of objects) before performing the SQL query for retrieving the rows from the DB
|
||||
*
|
||||
@@ -150,11 +198,25 @@ class DBObjectSet
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad);
|
||||
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad] = $oAttDef;
|
||||
if ($oAttDef->IsExternalKey())
|
||||
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
|
||||
{
|
||||
// Add the external key friendly name anytime
|
||||
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_friendlyname');
|
||||
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_friendlyname'] = $oFriendlyNameAttDef;
|
||||
|
||||
if (MetaModel::IsArchivable($oAttDef->GetTargetClass(EXTKEY_ABSOLUTE)))
|
||||
{
|
||||
// Add the archive flag if necessary
|
||||
$oArchiveFlagAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_archive_flag');
|
||||
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_archive_flag'] = $oArchiveFlagAttDef;
|
||||
}
|
||||
|
||||
if (MetaModel::IsObsoletable($oAttDef->GetTargetClass(EXTKEY_ABSOLUTE)))
|
||||
{
|
||||
// Add the obsolescence flag if necessary
|
||||
$oObsoleteFlagAttDef = MetaModel::GetAttributeDef($sClass, $sAttToLoad.'_obsolescence_flag');
|
||||
$aAttToLoadWithAttDef[$sClassAlias][$sAttToLoad.'_obsolescence_flag'] = $oObsoleteFlagAttDef;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +224,20 @@ class DBObjectSet
|
||||
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, 'friendlyname');
|
||||
$aAttToLoadWithAttDef[$sClassAlias]['friendlyname'] = $oFriendlyNameAttDef;
|
||||
|
||||
if (MetaModel::IsArchivable($sClass))
|
||||
{
|
||||
// Add the archive flag if necessary
|
||||
$oArchiveFlagAttDef = MetaModel::GetAttributeDef($sClass, 'archive_flag');
|
||||
$aAttToLoadWithAttDef[$sClassAlias]['archive_flag'] = $oArchiveFlagAttDef;
|
||||
}
|
||||
|
||||
if (MetaModel::IsObsoletable($sClass))
|
||||
{
|
||||
// Add the obsolescence flag if necessary
|
||||
$oObsoleteFlagAttDef = MetaModel::GetAttributeDef($sClass, 'obsolescence_flag');
|
||||
$aAttToLoadWithAttDef[$sClassAlias]['obsolescence_flag'] = $oObsoleteFlagAttDef;
|
||||
}
|
||||
|
||||
// Make sure that the final class is requested anytime, whatever the specification (needed for object construction!)
|
||||
if (!MetaModel::IsStandaloneClass($sClass) && !array_key_exists('finalclass', $aAttToLoadWithAttDef[$sClassAlias]))
|
||||
{
|
||||
@@ -192,7 +268,7 @@ class DBObjectSet
|
||||
*
|
||||
* @param string $sClass The class (or an ancestor) for the objects to be added in this set
|
||||
*
|
||||
* @return DBObject The empty set
|
||||
* @return DBObjectSet The empty set
|
||||
*/
|
||||
static public function FromScratch($sClass)
|
||||
{
|
||||
@@ -237,7 +313,7 @@ class DBObjectSet
|
||||
// let's create one search definition corresponding only to the first column
|
||||
$sClass = reset($aClasses);
|
||||
$sAlias = key($aClasses);
|
||||
$oFilter = new CMDBSearchFilter($sClass, $sAlias);
|
||||
$oFilter = new DBObjectSearch($sClass, $sAlias);
|
||||
|
||||
$oRetSet = new self($oFilter);
|
||||
$oRetSet->m_bLoaded = true; // no DB load
|
||||
@@ -264,8 +340,14 @@ class DBObjectSet
|
||||
}
|
||||
|
||||
return self::FromArray($sTargetClass, $aTargets);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: After calling this method, the set cursor will be at the end of the set. You might want to rewind it.
|
||||
*
|
||||
* @param bool $bWithId
|
||||
* @return array
|
||||
*/
|
||||
public function ToArray($bWithId = true)
|
||||
{
|
||||
$aRet = array();
|
||||
@@ -332,8 +414,15 @@ class DBObjectSet
|
||||
$iRow++;
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: After calling this method, the set cursor will be at the end of the set. You might want to rewind it.
|
||||
*
|
||||
* @param string $sAttCode
|
||||
* @param bool $bWithId
|
||||
* @return array
|
||||
*/
|
||||
public function GetColumnAsArray($sAttCode, $bWithId = true)
|
||||
{
|
||||
$aRet = array();
|
||||
@@ -353,7 +442,7 @@ class DBObjectSet
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the DBObjectSearch corresponding to the objects present in this set
|
||||
* Retrieve the DBSearch corresponding to the objects present in this set
|
||||
*
|
||||
* Limitation:
|
||||
* This method will NOT work for sets with several columns (i.e. several objects per row)
|
||||
@@ -364,9 +453,10 @@ class DBObjectSet
|
||||
{
|
||||
// Make sure that we carry on the parameters of the set with the filter
|
||||
$oFilter = $this->m_oFilter->DeepClone();
|
||||
$oFilter->SetShowObsoleteData(true);
|
||||
// Note: the arguments found within a set can be object (but not in a filter)
|
||||
// That's why PrepareQueryArguments must be invoked there
|
||||
$oFilter->SetInternalParams(array_merge($oFilter->GetInternalParams(), MetaModel::PrepareQueryArguments($this->m_aArgs)));
|
||||
$oFilter->SetInternalParams(array_merge($oFilter->GetInternalParams(), $this->m_aArgs));
|
||||
|
||||
if (count($this->m_aAddedIds) == 0)
|
||||
{
|
||||
@@ -445,8 +535,8 @@ class DBObjectSet
|
||||
|
||||
/**
|
||||
* Sets the sort order for loading the rows from the DB. Changing the order by causes a Reload.
|
||||
*
|
||||
* @param hash $aOrderBy Format: field_code => boolean (true = ascending, false = descending)
|
||||
*
|
||||
* @param hash $aOrderBy Format: [alias.]attcode => boolean (true = ascending, false = descending)
|
||||
*/
|
||||
public function SetOrderBy($aOrderBy)
|
||||
{
|
||||
@@ -461,6 +551,34 @@ class DBObjectSet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sort order for loading the rows from the DB. Changing the order by causes a Reload.
|
||||
*
|
||||
* @param hash $aAliases Format: alias => boolean (true = ascending, false = descending). If omitted, then it defaults to all the selected classes
|
||||
*/
|
||||
public function SetOrderByClasses($aAliases = null)
|
||||
{
|
||||
if ($aAliases === null)
|
||||
{
|
||||
$aAliases = array();
|
||||
foreach ($this->GetSelectedClasses() as $sAlias => $sClass)
|
||||
{
|
||||
$aAliases[$sAlias] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$aAttributes = array();
|
||||
foreach ($aAliases as $sAlias => $bClassDirection)
|
||||
{
|
||||
foreach (MetaModel::GetOrderByDefault($this->m_oFilter->GetClass($sAlias)) as $sAttCode => $bAttributeDirection)
|
||||
{
|
||||
$bDirection = $bClassDirection ? $bAttributeDirection : !$bAttributeDirection;
|
||||
$aAttributes[$sAlias.'.'.$sAttCode] = $bDirection;
|
||||
}
|
||||
}
|
||||
$this->SetOrderBy($aAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 'count' limit for loading the rows from the DB
|
||||
*
|
||||
@@ -486,10 +604,15 @@ class DBObjectSet
|
||||
*
|
||||
* Limitation: the sort order has no effect on objects added in-memory
|
||||
*
|
||||
* @return hash Format: field_code => boolean (true = ascending, false = descending)
|
||||
* @return array Format: field_code => boolean (true = ascending, false = descending)
|
||||
*/
|
||||
public function GetRealSortOrder()
|
||||
{
|
||||
if (!$this->m_bSort)
|
||||
{
|
||||
// No order by
|
||||
return array();
|
||||
}
|
||||
// Get the class default sort order if not specified with the API
|
||||
//
|
||||
if (empty($this->m_aOrderBy))
|
||||
@@ -511,14 +634,7 @@ class DBObjectSet
|
||||
// Note: it is mandatory to set this value now, to protect against reentrance
|
||||
$this->m_bLoaded = true;
|
||||
|
||||
if ($this->m_iLimitCount > 0)
|
||||
{
|
||||
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
|
||||
}
|
||||
$sSQL = $this->_makeSelectQuery($this->m_aAttToLoad);
|
||||
|
||||
if (is_object($this->m_oSQLResult))
|
||||
{
|
||||
@@ -527,8 +643,36 @@ class DBObjectSet
|
||||
$this->m_oSQLResult = null;
|
||||
}
|
||||
$this->m_iNumTotalDBRows = null;
|
||||
|
||||
$this->m_oSQLResult = CMDBSource::Query($sSQL);
|
||||
|
||||
try
|
||||
{
|
||||
$this->m_oSQLResult = CMDBSource::Query($sSQL);
|
||||
} catch (MySQLException $e)
|
||||
{
|
||||
// 1116 = ER_TOO_MANY_TABLES
|
||||
// https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_too_many_tables
|
||||
if ($e->getCode() != 1116)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// N.689 Workaround for the 61 max joins in MySQL : full lazy load !
|
||||
$aAttToLoad = array();
|
||||
foreach($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass)
|
||||
{
|
||||
$aAttToLoad[$sClassAlias] = array();
|
||||
$bIsAbstractClass = MetaModel::IsAbstract($sClass);
|
||||
$bIsClassWithChildren = MetaModel::HasChildrenClasses($sClass);
|
||||
if ($bIsAbstractClass || $bIsClassWithChildren)
|
||||
{
|
||||
// we need finalClass field at least to be able to instantiate the real corresponding object !
|
||||
$aAttToLoad[$sClassAlias]['finalclass'] = MetaModel::GetAttributeDef($sClass, 'finalclass');
|
||||
}
|
||||
}
|
||||
$sSQL = $this->_makeSelectQuery($aAttToLoad);
|
||||
$this->m_oSQLResult = CMDBSource::Query($sSQL); // may fail again
|
||||
}
|
||||
|
||||
if ($this->m_oSQLResult === false) return;
|
||||
|
||||
if (($this->m_iLimitCount == 0) && ($this->m_iLimitStart == 0))
|
||||
@@ -539,24 +683,54 @@ class DBObjectSet
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of rows in this set. Independently of the SetLimit used for loading the set and taking into account the rows added in-memory.
|
||||
*
|
||||
* May actually perform the SQL query SELECT COUNT... if the set was not previously loaded, or loaded with a SetLimit
|
||||
*
|
||||
* @return int The total number of rows for this set.
|
||||
* @param string[] $aAttToLoad
|
||||
*
|
||||
* @return string SQL query
|
||||
*/
|
||||
public function Count()
|
||||
private function _makeSelectQuery($aAttToLoad)
|
||||
{
|
||||
if ($this->m_iLimitCount > 0)
|
||||
{
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery($this->GetRealSortOrder(), $this->m_aArgs, $aAttToLoad,
|
||||
$this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery($this->GetRealSortOrder(), $this->m_aArgs, $aAttToLoad,
|
||||
$this->m_aExtendedDataSpec);
|
||||
}
|
||||
|
||||
return $sSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of rows in this set. Independently of the SetLimit used for loading the set and taking into
|
||||
* account the rows added in-memory.
|
||||
*
|
||||
* May actually perform the SQL query SELECT COUNT... if the set was not previously loaded, or loaded with a
|
||||
* SetLimit
|
||||
*
|
||||
* @param int $iLimit used for autocomplete: the count is only used to know if the number of entries exceed
|
||||
* a certain amount or not
|
||||
*
|
||||
* @return int The total number of rows for this set.
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function Count($iLimit = 0)
|
||||
{
|
||||
if (is_null($this->m_iNumTotalDBRows))
|
||||
{
|
||||
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, array(), $this->m_aArgs, null, null, 0, 0, true);
|
||||
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit, 0, true);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
if (!$resQuery) return 0;
|
||||
|
||||
|
||||
$aRow = CMDBSource::FetchArray($resQuery);
|
||||
CMDBSource::FreeResult($resQuery);
|
||||
$this->m_iNumTotalDBRows = $aRow['COUNT'];
|
||||
}
|
||||
|
||||
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
|
||||
}
|
||||
|
||||
@@ -837,92 +1011,18 @@ class DBObjectSet
|
||||
* Compare two sets of objects to determine if their content is identical or not.
|
||||
*
|
||||
* Limitation:
|
||||
* Works only on objects written to the DB, since we rely on their identifiers
|
||||
* Works only for sets of 1 column (i.e. one class of object selected)
|
||||
*
|
||||
* @param DBObjectSet $oObjectSet
|
||||
* @param array $aExcludeColumns The list of columns to exclude frop the comparison
|
||||
* @return boolean True if the sets are identical, false otherwise
|
||||
*/
|
||||
public function HasSameContents(DBObjectSet $oObjectSet)
|
||||
{
|
||||
if ($this->GetRootClass() != $oObjectSet->GetRootClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ($this->Count() != $oObjectSet->Count())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$aId2Row = array();
|
||||
$bRet = true;
|
||||
$iCurrPos = $this->m_iCurrRow; // Save the cursor
|
||||
$idx = 0;
|
||||
|
||||
// Optimization: we retain the first $iMaxObjects objects in memory
|
||||
// to speed up the comparison of small sets (see below: $oObject->Equals($oSibling))
|
||||
$iMaxObjects = 20;
|
||||
$aCachedObjects = array();
|
||||
while($oObj = $this->Fetch())
|
||||
{
|
||||
$aId2Row[$oObj->GetKey()] = $idx;
|
||||
if ($idx <= $iMaxObjects)
|
||||
{
|
||||
$aCachedObjects[$idx] = $oObj;
|
||||
}
|
||||
$idx++;
|
||||
}
|
||||
|
||||
$oObjectSet->Rewind();
|
||||
while ($oObject = $oObjectSet->Fetch())
|
||||
{
|
||||
$iObjectKey = $oObject->GetKey();
|
||||
if ($iObjectKey < 0)
|
||||
{
|
||||
$bRet = false;
|
||||
break;
|
||||
}
|
||||
if (!array_key_exists($iObjectKey, $aId2Row))
|
||||
{
|
||||
$bRet = false;
|
||||
break;
|
||||
}
|
||||
$iRow = $aId2Row[$iObjectKey];
|
||||
if (array_key_exists($iRow, $aCachedObjects))
|
||||
{
|
||||
// Cache hit
|
||||
$oSibling = $aCachedObjects[$iRow];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Go fetch it from the DB, unless it's an object added in-memory
|
||||
$oSibling = $this->GetObjectAt($iRow);
|
||||
}
|
||||
if (!$oObject->Equals($oSibling))
|
||||
{
|
||||
$bRet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->Seek($iCurrPos); // Restore the cursor
|
||||
return $bRet;
|
||||
public function HasSameContents(DBObjectSet $oObjectSet, $aExcludeColumns = array())
|
||||
{
|
||||
$oComparator = new DBObjectSetComparator($this, $oObjectSet, $aExcludeColumns);
|
||||
return $oComparator->SetsAreEquivalent();
|
||||
}
|
||||
|
||||
protected function GetObjectAt($iIndex)
|
||||
{
|
||||
if (!$this->m_bLoaded) $this->Load();
|
||||
|
||||
// Save the current position for iteration
|
||||
$iCurrPos = $this->m_iCurrRow;
|
||||
|
||||
$this->Seek($iIndex);
|
||||
$oObject = $this->Fetch();
|
||||
|
||||
// Restore the current position for iteration
|
||||
$this->Seek($this->m_iCurrRow);
|
||||
|
||||
return $oObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new set (in memory) made of objects of the given set which are NOT present in the current set
|
||||
*
|
||||
@@ -967,12 +1067,7 @@ class DBObjectSet
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the "RelatedObjects" (for the given relation, as defined by MetaModel::GetRelatedObjects) for a whole set of DBObjects
|
||||
*
|
||||
* @param string $sRelCode The code of the relation to use for the computation
|
||||
* @param int $iMaxDepth Teh maximum recursion depth
|
||||
*
|
||||
* @return Array An array containg all the "related" objects
|
||||
* Will be deprecated soon - use MetaModel::GetRelatedObjectsDown/Up instead to take redundancy into account
|
||||
*/
|
||||
public function GetRelatedObjects($sRelCode, $iMaxDepth = 99)
|
||||
{
|
||||
@@ -996,6 +1091,50 @@ class DBObjectSet
|
||||
}
|
||||
return $aRelatedObjs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the "RelatedObjects" (forward or "down" direction) for the set
|
||||
* for the specified relation
|
||||
*
|
||||
* @param string $sRelCode The code of the relation to use for the computation
|
||||
* @param int $iMaxDepth Maximum recursion depth
|
||||
* @param boolean $bEnableReduncancy Whether or not to take into account the redundancy
|
||||
*
|
||||
* @return RelationGraph The graph of all the related objects
|
||||
*/
|
||||
public function GetRelatedObjectsDown($sRelCode, $iMaxDepth = 99, $bEnableRedundancy = true)
|
||||
{
|
||||
$oGraph = new RelationGraph();
|
||||
$this->Rewind();
|
||||
while($oObj = $this->Fetch())
|
||||
{
|
||||
$oGraph->AddSourceObject($oObj);
|
||||
}
|
||||
$oGraph->ComputeRelatedObjectsDown($sRelCode, $iMaxDepth, $bEnableRedundancy);
|
||||
return $oGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the "RelatedObjects" (reverse or "up" direction) for the set
|
||||
* for the specified relation
|
||||
*
|
||||
* @param string $sRelCode The code of the relation to use for the computation
|
||||
* @param int $iMaxDepth Maximum recursion depth
|
||||
* @param boolean $bEnableReduncancy Whether or not to take into account the redundancy
|
||||
*
|
||||
* @return RelationGraph The graph of all the related objects
|
||||
*/
|
||||
public function GetRelatedObjectsUp($sRelCode, $iMaxDepth = 99, $bEnableRedundancy = true)
|
||||
{
|
||||
$oGraph = new RelationGraph();
|
||||
$this->Rewind();
|
||||
while($oObj = $this->Fetch())
|
||||
{
|
||||
$oGraph->AddSinkObject($oObj);
|
||||
}
|
||||
$oGraph->ComputeRelatedObjectsUp($sRelCode, $iMaxDepth, $bEnableRedundancy);
|
||||
return $oGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an object that contains the values that are common to all the objects
|
||||
@@ -1091,7 +1230,8 @@ class DBObjectSet
|
||||
*/
|
||||
public function ListConstantFields()
|
||||
{
|
||||
$aScalarArgs = $this->ExpandArgs();
|
||||
// The complete list of arguments will include magic arguments (e.g. current_user->attcode)
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($this->m_oFilter->GetInternalParams(), $this->m_aArgs);
|
||||
$aConst = $this->m_oFilter->ListConstantFields();
|
||||
|
||||
foreach($aConst as $sClassAlias => $aVals)
|
||||
@@ -1108,40 +1248,10 @@ class DBObjectSet
|
||||
return $aConst;
|
||||
}
|
||||
|
||||
protected function ExpandArgs()
|
||||
{
|
||||
$aScalarArgs = $this->m_oFilter->GetInternalParams();
|
||||
foreach($this->m_aArgs as $sArgName => $value)
|
||||
{
|
||||
if (MetaModel::IsValidObject($value))
|
||||
{
|
||||
if (strpos($sArgName, '->object()') === false)
|
||||
{
|
||||
// Lazy syntax - develop the object contextual parameters
|
||||
$aScalarArgs = array_merge($aScalarArgs, $value->ToArgsForQuery($sArgName));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Leave as is
|
||||
$aScalarArgs[$sArgName] = $value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_array($value)) // Sometimes ExtraParams contains a mix (like defaults[]) so non scalar parameters are ignored
|
||||
{
|
||||
$aScalarArgs[$sArgName] = (string) $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
$aScalarArgs['current_contact_id'] = UserRights::GetContactId();
|
||||
return $aScalarArgs;
|
||||
}
|
||||
|
||||
public function ApplyParameters()
|
||||
{
|
||||
$aScalarArgs = $this->ExpandArgs();
|
||||
$this->m_oFilter->ApplyParameters($aScalarArgs);
|
||||
$aAllArgs = MetaModel::PrepareQueryArguments($this->m_oFilter->GetInternalParams(), $this->m_aArgs);
|
||||
$this->m_oFilter->ApplyParameters($aAllArgs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1156,3 +1266,219 @@ function HashCountComparison($a, $b) // Sort descending on 'count'
|
||||
}
|
||||
return ($a['count'] > $b['count']) ? -1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to compare the content of two DBObjectSets based on the fingerprints of the contained objects
|
||||
* The FIRST SET MUST BE LOADED FROM THE DATABASE, the second one can be a set of objects in memory
|
||||
* When computing the actual differences, the algorithm tries to preserve as much as possible the EXISTING
|
||||
* objects (i.e. prefers 'modified' to 'removed' + 'added')
|
||||
*
|
||||
* LIMITATIONS:
|
||||
* - only DBObjectSets with one column (i.e. one class of object selected) are supported
|
||||
* - the first set must be the one loaded from the database
|
||||
*/
|
||||
class DBObjectSetComparator
|
||||
{
|
||||
protected $aFingerprints1;
|
||||
protected $aFingerprints2;
|
||||
protected $aIDs1;
|
||||
protected $aIDs2;
|
||||
protected $aExcludedColumns;
|
||||
|
||||
/**
|
||||
* @var iDBObjectSetIterator
|
||||
*/
|
||||
protected $oSet1;
|
||||
/**
|
||||
* @var iDBObjectSetIterator
|
||||
*/
|
||||
protected $oSet2;
|
||||
|
||||
protected $sAdditionalKeyColumn;
|
||||
protected $aAdditionalKeys;
|
||||
|
||||
/**
|
||||
* Initializes the comparator
|
||||
* @param iDBObjectSetIterator $oSet1 The first set of objects to compare, or null
|
||||
* @param iDBObjectSetIterator $oSet2 The second set of objects to compare, or null
|
||||
* @param array $aExcludedColumns The list of columns (= attribute codes) to exclude from the comparison
|
||||
* @param string $sAdditionalKeyColumn The attribute code of an additional column to be considered as a key indentifying the object (useful for n:n links)
|
||||
*/
|
||||
public function __construct(iDBObjectSetIterator $oSet1, iDBObjectSetIterator $oSet2, $aExcludedColumns = array(), $sAdditionalKeyColumn = null)
|
||||
{
|
||||
$this->aFingerprints1 = null;
|
||||
$this->aFingerprints2 = null;
|
||||
$this->aIDs1 = array();
|
||||
$this->aIDs2 = array();
|
||||
$this->aExcludedColumns = $aExcludedColumns;
|
||||
$this->sAdditionalKeyColumn = $sAdditionalKeyColumn;
|
||||
$this->aAdditionalKeys = null;
|
||||
$this->oSet1 = $oSet1;
|
||||
$this->oSet2 = $oSet2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the lists of fingerprints and initializes internal structures, if it was not already done
|
||||
*/
|
||||
protected function ComputeFingerprints()
|
||||
{
|
||||
if ($this->aFingerprints1 === null)
|
||||
{
|
||||
$this->aFingerprints1 = array();
|
||||
$this->aFingerprints2 = array();
|
||||
$this->aAdditionalKeys = array();
|
||||
|
||||
if ($this->oSet1 !== null)
|
||||
{
|
||||
$this->oSet1->Rewind();
|
||||
while($oObj = $this->oSet1->Fetch())
|
||||
{
|
||||
$sFingerprint = $oObj->Fingerprint($this->aExcludedColumns);
|
||||
$this->aFingerprints1[$sFingerprint] = $oObj;
|
||||
if (!$oObj->IsNew())
|
||||
{
|
||||
$this->aIDs1[$oObj->GetKey()] = $oObj;
|
||||
}
|
||||
}
|
||||
$this->oSet1->Rewind();
|
||||
}
|
||||
|
||||
if ($this->oSet2 !== null)
|
||||
{
|
||||
$this->oSet2->Rewind();
|
||||
while($oObj = $this->oSet2->Fetch())
|
||||
{
|
||||
$sFingerprint = $oObj->Fingerprint($this->aExcludedColumns);
|
||||
$this->aFingerprints2[$sFingerprint] = $oObj;
|
||||
if (!$oObj->IsNew())
|
||||
{
|
||||
$this->aIDs2[$oObj->GetKey()] = $oObj;
|
||||
}
|
||||
|
||||
if ($this->sAdditionalKeyColumn !== null)
|
||||
{
|
||||
$this->aAdditionalKeys[$oObj->Get($this->sAdditionalKeyColumn)] = $oObj;
|
||||
}
|
||||
}
|
||||
$this->oSet2->Rewind();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the sets are equivalent or not. Returns as soon as the first difference is found.
|
||||
* @return boolean true if the set have an equivalent content, false otherwise
|
||||
*/
|
||||
public function SetsAreEquivalent()
|
||||
{
|
||||
if (($this->oSet1 === null) && ($this->oSet2 === null))
|
||||
{
|
||||
// Both sets are empty, they are equal
|
||||
return true;
|
||||
}
|
||||
else if (($this->oSet1 === null) || ($this->oSet2 === null))
|
||||
{
|
||||
// one of them is empty, they are different
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($this->oSet1->GetRootClass() != $this->oSet2->GetRootClass()) || ($this->oSet1->Count() != $this->oSet2->Count())) return false;
|
||||
|
||||
$this->ComputeFingerprints();
|
||||
|
||||
// Check that all objects in Set1 are also in Set2
|
||||
foreach($this->aFingerprints1 as $sFingerprint => $oObj)
|
||||
{
|
||||
if (!array_key_exists($sFingerprint, $this->aFingerprints2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Vice versa
|
||||
// Check that all objects in Set2 are also in Set1
|
||||
foreach($this->aFingerprints2 as $sFingerprint => $oObj)
|
||||
{
|
||||
if (!array_key_exists($sFingerprint, $this->aFingerprints1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of differences between the two sets. In ordeer to write back into the database only the minimum changes
|
||||
* THE FIRST SET MUST BE THE ONE LOADED FROM THE DATABASE
|
||||
* Returns a hash: 'added' => DBObject(s), 'removed' => DBObject(s), 'modified' => DBObjects(s)
|
||||
* @return Ambigous <int:DBObject: , unknown>
|
||||
*/
|
||||
public function GetDifferences()
|
||||
{
|
||||
$aResult = array('added' => array(), 'removed' => array(), 'modified' => array());
|
||||
$this->ComputeFingerprints();
|
||||
|
||||
// Check that all objects in Set1 are also in Set2
|
||||
foreach($this->aFingerprints1 as $sFingerprint => $oObj)
|
||||
{
|
||||
// Beware: the elements from the first set MUST come from the database, otherwise the result will be irrelevant
|
||||
if ($oObj->IsNew()) throw new Exception('Cannot compute differences when elements from the first set are NOT in the database');
|
||||
if (array_key_exists($oObj->GetKey(), $this->aIDs2) && ($this->aIDs2[$oObj->GetKey()]->IsModified()))
|
||||
{
|
||||
// The very same object exists in both set, but was modified since its load
|
||||
$aResult['modified'][$oObj->GetKey()] = $this->aIDs2[$oObj->GetKey()];
|
||||
}
|
||||
else if (($this->sAdditionalKeyColumn !== null) && array_key_exists($oObj->Get($this->sAdditionalKeyColumn), $this->aAdditionalKeys))
|
||||
{
|
||||
// Special case for n:n links where the link is recreated between the very same 2 objects, but some of its attributes are modified
|
||||
// Let's consider this as a "modification" instead of "deletion" + "creation" in order to have a "clean" history for the objects
|
||||
$oDestObj = $this->aAdditionalKeys[$oObj->Get($this->sAdditionalKeyColumn)];
|
||||
$oCloneObj = $this->CopyFrom($oObj, $oDestObj);
|
||||
$aResult['modified'][$oObj->GetKey()] = $oCloneObj;
|
||||
// Mark this as processed, so that the pass on aFingerprints2 below ignores this object
|
||||
$sNewFingerprint = $oDestObj->Fingerprint($this->aExcludedColumns);
|
||||
$this->aFingerprints2[$sNewFingerprint] = $oCloneObj;
|
||||
}
|
||||
else if (!array_key_exists($sFingerprint, $this->aFingerprints2))
|
||||
{
|
||||
$aResult['removed'][] = $oObj;
|
||||
}
|
||||
}
|
||||
|
||||
// Vice versa
|
||||
// Check that all objects in Set2 are also in Set1
|
||||
foreach($this->aFingerprints2 as $sFingerprint => $oObj)
|
||||
{
|
||||
if (array_key_exists($oObj->GetKey(), $this->aIDs1) && ($oObj->IsModified()))
|
||||
{
|
||||
// Already marked as modified above
|
||||
//$aResult['modified'][$oObj->GetKey()] = $oObj;
|
||||
}
|
||||
else if (!array_key_exists($sFingerprint, $this->aFingerprints1))
|
||||
{
|
||||
$aResult['added'][] = $oObj;
|
||||
}
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helpr to clone (in memory) an object and to apply to it the values taken from a second object
|
||||
* @param DBObject $oObjToClone
|
||||
* @param DBObject $oObjWithValues
|
||||
* @return DBObject The modified clone
|
||||
*/
|
||||
protected function CopyFrom($oObjToClone, $oObjWithValues)
|
||||
{
|
||||
$oObj = MetaModel::GetObject(get_class($oObjToClone), $oObjToClone->GetKey());
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if (!in_array($sAttCode, $this->aExcludedColumns) && $oAttDef->IsWritable())
|
||||
{
|
||||
$oObj->Set($sAttCode, $oObjWithValues->Get($sAttCode));
|
||||
}
|
||||
}
|
||||
return $oObj;
|
||||
}
|
||||
}
|
||||
875
core/dbsearch.class.php
Normal file
875
core/dbsearch.class.php
Normal file
@@ -0,0 +1,875 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2017 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/>
|
||||
|
||||
|
||||
require_once('dbobjectsearch.class.php');
|
||||
require_once('dbunionsearch.class.php');
|
||||
|
||||
/**
|
||||
* An object search
|
||||
*
|
||||
* Note: in the ancient times of iTop, a search was named after DBObjectSearch.
|
||||
* When the UNION has been introduced, it has been decided to:
|
||||
* - declare a hierarchy of search classes, with two leafs :
|
||||
* - one class to cope with a single query (A JOIN B... WHERE...)
|
||||
* - and the other to cope with several queries (query1 UNION query2)
|
||||
* - in order to preserve forward/backward compatibility of the existing modules
|
||||
* - keep the name of DBObjectSearch even if it a little bit confusing
|
||||
* - do not provide a type-hint for function parameters defined in the modules
|
||||
* - leave the statements DBObjectSearch::FromOQL in the modules, though DBSearch is more relevant
|
||||
*
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
abstract class DBSearch
|
||||
{
|
||||
const JOIN_POINTING_TO = 0;
|
||||
const JOIN_REFERENCED_BY = 1;
|
||||
|
||||
protected $m_bNoContextParameters = false;
|
||||
protected $m_aModifierProperties = array();
|
||||
protected $m_bArchiveMode = false;
|
||||
protected $m_bShowObsoleteData = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->Init();
|
||||
}
|
||||
|
||||
protected function Init()
|
||||
{
|
||||
// Set the obsolete and archive modes to the default ones
|
||||
$this->m_bArchiveMode = utils::IsArchiveMode();
|
||||
$this->m_bShowObsoleteData = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a deep clone (as opposed to "clone" which does copy a reference to the underlying objects)
|
||||
**/
|
||||
public function DeepClone()
|
||||
{
|
||||
return unserialize(serialize($this)); // Beware this serializes/unserializes the search and its parameters as well
|
||||
}
|
||||
|
||||
abstract public function AllowAllData();
|
||||
abstract public function IsAllDataAllowed();
|
||||
|
||||
public function SetArchiveMode($bEnable)
|
||||
{
|
||||
$this->m_bArchiveMode = $bEnable;
|
||||
}
|
||||
public function GetArchiveMode()
|
||||
{
|
||||
return $this->m_bArchiveMode;
|
||||
}
|
||||
|
||||
public function SetShowObsoleteData($bShow)
|
||||
{
|
||||
$this->m_bShowObsoleteData = $bShow;
|
||||
}
|
||||
public function GetShowObsoleteData()
|
||||
{
|
||||
if ($this->m_bArchiveMode || $this->IsAllDataAllowed())
|
||||
{
|
||||
// Enable obsolete data too!
|
||||
$bRet = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$bRet = $this->m_bShowObsoleteData;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
public function NoContextParameters() {$this->m_bNoContextParameters = true;}
|
||||
public function HasContextParameters() {return $this->m_bNoContextParameters;}
|
||||
|
||||
public function SetModifierProperty($sPluginClass, $sProperty, $value)
|
||||
{
|
||||
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
|
||||
}
|
||||
|
||||
public function GetModifierProperties($sPluginClass)
|
||||
{
|
||||
if (array_key_exists($sPluginClass, $this->m_aModifierProperties))
|
||||
{
|
||||
return $this->m_aModifierProperties[$sPluginClass];
|
||||
}
|
||||
else
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function GetClassName($sAlias);
|
||||
abstract public function GetClass();
|
||||
abstract public function GetClassAlias();
|
||||
|
||||
/**
|
||||
* Change the class (only subclasses are supported as of now, because the conditions must fit the new class)
|
||||
* Defaults to the first selected class (most of the time it is also the first joined class
|
||||
*/
|
||||
abstract public function ChangeClass($sNewClass, $sAlias = null);
|
||||
abstract public function GetSelectedClasses();
|
||||
|
||||
/**
|
||||
* @param array $aSelectedClasses array of aliases
|
||||
* @throws CoreException
|
||||
*/
|
||||
abstract public function SetSelectedClasses($aSelectedClasses);
|
||||
|
||||
/**
|
||||
* Change any alias of the query tree
|
||||
*
|
||||
* @param $sOldName
|
||||
* @param $sNewName
|
||||
* @return bool True if the alias has been found and changed
|
||||
*/
|
||||
abstract public function RenameAlias($sOldName, $sNewName);
|
||||
|
||||
abstract public function IsAny();
|
||||
|
||||
public function Describe(){return 'deprecated - use ToOQL() instead';}
|
||||
public function DescribeConditionPointTo($sExtKeyAttCode, $aPointingTo){return 'deprecated - use ToOQL() instead';}
|
||||
public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode){return 'deprecated - use ToOQL() instead';}
|
||||
public function DescribeConditionRelTo($aRelInfo){return 'deprecated - use ToOQL() instead';}
|
||||
public function DescribeConditions(){return 'deprecated - use ToOQL() instead';}
|
||||
public function __DescribeHTML(){return 'deprecated - use ToOQL() instead';}
|
||||
|
||||
abstract public function ResetCondition();
|
||||
abstract public function MergeConditionExpression($oExpression);
|
||||
abstract public function AddConditionExpression($oExpression);
|
||||
abstract public function AddNameCondition($sName);
|
||||
abstract public function AddCondition($sFilterCode, $value, $sOpCode = null);
|
||||
/**
|
||||
* Specify a condition on external keys or link sets
|
||||
* @param sAttSpec Can be either an attribute code or extkey->[sAttSpec] or linkset->[sAttSpec] and so on, recursively
|
||||
* Example: infra_list->ci_id->location_id->country
|
||||
* @param value The value to match (can be an array => IN(val1, val2...)
|
||||
* @return void
|
||||
*/
|
||||
abstract public function AddConditionAdvanced($sAttSpec, $value);
|
||||
abstract public function AddCondition_FullText($sFullText);
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
abstract public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null);
|
||||
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sForeignExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
*/
|
||||
abstract public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null);
|
||||
|
||||
abstract public function Intersect(DBSearch $oFilter);
|
||||
|
||||
/**
|
||||
* @param DBSearch $oFilter
|
||||
* @param integer $iDirection
|
||||
* @param string $sExtKeyAttCode
|
||||
* @param integer $iOperatorCode
|
||||
* @param array &$RealisasingMap Map of aliases from the attached query, that could have been renamed by the optimization process
|
||||
* @return DBSearch
|
||||
*/
|
||||
public function Join(DBSearch $oFilter, $iDirection, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
|
||||
{
|
||||
$oSourceFilter = $this->DeepClone();
|
||||
$oRet = null;
|
||||
|
||||
if ($oFilter instanceof DBUnionSearch)
|
||||
{
|
||||
$aSearches = array();
|
||||
foreach ($oFilter->GetSearches() as $oSearch)
|
||||
{
|
||||
$aSearches[] = $oSourceFilter->Join($oSearch, $iDirection, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
$oRet = new DBUnionSearch($aSearches);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iDirection === static::JOIN_POINTING_TO)
|
||||
{
|
||||
$oSourceFilter->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($iOperatorCode !== TREE_OPERATOR_EQUALS)
|
||||
{
|
||||
throw new Exception('Only TREE_OPERATOR_EQUALS operator code is supported yet for AddCondition_ReferencedBy.');
|
||||
}
|
||||
$oSourceFilter->AddCondition_ReferencedBy($oFilter, $sExtKeyAttCode, TREE_OPERATOR_EQUALS, $aRealiasingMap);
|
||||
}
|
||||
$oRet = $oSourceFilter;
|
||||
}
|
||||
|
||||
return $oRet;
|
||||
}
|
||||
|
||||
abstract public function SetInternalParams($aParams);
|
||||
abstract public function GetInternalParams();
|
||||
abstract public function GetQueryParams($bExcludeMagicParams = true);
|
||||
abstract public function ListConstantFields();
|
||||
|
||||
/**
|
||||
* Turn the parameters (:xxx) into scalar values in order to easily
|
||||
* serialize a search
|
||||
*/
|
||||
abstract public function ApplyParameters($aArgs);
|
||||
|
||||
public function serialize($bDevelopParams = false, $aContextParams = null)
|
||||
{
|
||||
$sOql = $this->ToOql($bDevelopParams, $aContextParams);
|
||||
return base64_encode(serialize(array($sOql, $this->GetInternalParams(), $this->m_aModifierProperties)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sValue Serialized OQL query
|
||||
*
|
||||
* @return \DBSearch
|
||||
*/
|
||||
static public function unserialize($sValue)
|
||||
{
|
||||
$aData = unserialize(base64_decode($sValue));
|
||||
$sOql = $aData[0];
|
||||
$aParams = $aData[1];
|
||||
// We've tried to use gzcompress/gzuncompress, but for some specific queries
|
||||
// it was not working at all (See Trac #193)
|
||||
// gzuncompress was issuing a warning "data error" and the return object was null
|
||||
$oRetFilter = self::FromOQL($sOql, $aParams);
|
||||
$oRetFilter->m_aModifierProperties = $aData[2];
|
||||
return $oRetFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DBObjectSearch from $oSearch with a new alias $sAlias
|
||||
*
|
||||
* Note : This has not be tested with UNION queries.
|
||||
*
|
||||
* @param DBSearch $oSearch
|
||||
* @param string $sAlias
|
||||
* @return DBObjectSearch
|
||||
*/
|
||||
static public function CloneWithAlias(DBSearch $oSearch, $sAlias)
|
||||
{
|
||||
$oSearchWithAlias = new DBObjectSearch($oSearch->GetClass(), $sAlias);
|
||||
$oSearchWithAlias = $oSearchWithAlias->Intersect($oSearch);
|
||||
return $oSearchWithAlias;
|
||||
}
|
||||
|
||||
abstract public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false);
|
||||
|
||||
static protected $m_aOQLQueries = array();
|
||||
|
||||
// Do not filter out depending on user rights
|
||||
// In particular when we are currently in the process of evaluating the user rights...
|
||||
static public function FromOQL_AllData($sQuery, $aParams = null)
|
||||
{
|
||||
$oRes = self::FromOQL($sQuery, $aParams);
|
||||
$oRes->AllowAllData();
|
||||
return $oRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sQuery
|
||||
* @param array $aParams
|
||||
* @return DBSearch
|
||||
* @throws OQLException
|
||||
*/
|
||||
static public function FromOQL($sQuery, $aParams = null)
|
||||
{
|
||||
if (empty($sQuery))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Query caching
|
||||
$sQueryId = md5($sQuery);
|
||||
$bOQLCacheEnabled = true;
|
||||
if ($bOQLCacheEnabled)
|
||||
{
|
||||
if (array_key_exists($sQueryId, self::$m_aOQLQueries))
|
||||
{
|
||||
// hit!
|
||||
$oResultFilter = self::$m_aOQLQueries[$sQueryId]->DeepClone();
|
||||
}
|
||||
elseif (self::$m_bUseAPCCache)
|
||||
{
|
||||
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
|
||||
//
|
||||
$sAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-dbsearch-cache-'.$sQueryId;
|
||||
$oKPI = new ExecutionKPI();
|
||||
$result = apc_fetch($sAPCCacheId);
|
||||
$oKPI->ComputeStats('Search APC (fetch)', $sQuery);
|
||||
|
||||
if (is_object($result))
|
||||
{
|
||||
$oResultFilter = $result;
|
||||
self::$m_aOQLQueries[$sQueryId] = $oResultFilter->DeepClone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($oResultFilter))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
|
||||
$oOql = new OqlInterpreter($sQuery);
|
||||
$oOqlQuery = $oOql->ParseQuery();
|
||||
|
||||
$oMetaModel = new ModelReflectionRuntime();
|
||||
$oOqlQuery->Check($oMetaModel, $sQuery); // Exceptions thrown in case of issue
|
||||
|
||||
$oResultFilter = $oOqlQuery->ToDBSearch($sQuery);
|
||||
|
||||
$oKPI->ComputeStats('Parse OQL', $sQuery);
|
||||
|
||||
if ($bOQLCacheEnabled)
|
||||
{
|
||||
self::$m_aOQLQueries[$sQueryId] = $oResultFilter->DeepClone();
|
||||
|
||||
if (self::$m_bUseAPCCache)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
apc_store($sAPCCacheId, $oResultFilter, self::$m_iQueryCacheTTL);
|
||||
$oKPI->ComputeStats('Search APC (store)', $sQueryId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($aParams))
|
||||
{
|
||||
$oResultFilter->SetInternalParams($aParams);
|
||||
}
|
||||
|
||||
// Set the default fields
|
||||
$oResultFilter->Init();
|
||||
|
||||
return $oResultFilter;
|
||||
}
|
||||
|
||||
// Alternative to object mapping: the data are transfered directly into an array
|
||||
// This is 10 times faster than creating a set of objects, and makes sense when optimization is required
|
||||
/**
|
||||
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
|
||||
*/
|
||||
public function ToDataArray($aColumns = array(), $aOrderBy = array(), $aArgs = array())
|
||||
{
|
||||
$sSQL = $this->MakeSelectQuery($aOrderBy, $aArgs);
|
||||
$resQuery = CMDBSource::Query($sSQL);
|
||||
if (!$resQuery)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
$sClassAlias = $this->GetClassAlias();
|
||||
$aColMap = array();
|
||||
foreach ($aColumns as $sAttCode)
|
||||
{
|
||||
$sColName = $sClassAlias.$sAttCode;
|
||||
if (in_array($sColName, $aQueryCols))
|
||||
{
|
||||
$aColMap[$sAttCode] = $sColName;
|
||||
}
|
||||
}
|
||||
|
||||
$aRes = array();
|
||||
while ($aRow = CMDBSource::FetchArray($resQuery))
|
||||
{
|
||||
$aMappedRow = array();
|
||||
foreach ($aColMap as $sAttCode => $sColName)
|
||||
{
|
||||
$aMappedRow[$sAttCode] = $aRow[$sColName];
|
||||
}
|
||||
$aRes[] = $aMappedRow;
|
||||
}
|
||||
CMDBSource::FreeResult($resQuery);
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Construction of the SQL queries
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
protected static $m_aQueryStructCache = array();
|
||||
|
||||
|
||||
public function MakeGroupByQuery($aArgs, $aGroupByExpr, $bExcludeNullValues = false)
|
||||
{
|
||||
if ($bExcludeNullValues)
|
||||
{
|
||||
// Null values are not handled (though external keys set to 0 are allowed)
|
||||
$oQueryFilter = $this->DeepClone();
|
||||
foreach ($aGroupByExpr as $oGroupByExp)
|
||||
{
|
||||
$oNull = new FunctionExpression('ISNULL', array($oGroupByExp));
|
||||
$oNotNull = new BinaryExpression($oNull, '!=', new TrueExpression());
|
||||
$oQueryFilter->AddConditionExpression($oNotNull);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oQueryFilter = $this;
|
||||
}
|
||||
|
||||
$aAttToLoad = array();
|
||||
$oSQLQuery = $oQueryFilter->GetSQLQuery(array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr);
|
||||
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
|
||||
try
|
||||
{
|
||||
$bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
|
||||
$sRes = $oSQLQuery->RenderGroupBy($aScalarArgs, $bBeautifulSQL);
|
||||
}
|
||||
catch (MissingQueryArgument $e)
|
||||
{
|
||||
// Add some information...
|
||||
$e->addInfo('OQL', $this->ToOQL());
|
||||
throw $e;
|
||||
}
|
||||
$this->AddQueryTraceGroupBy($aArgs, $aGroupByExpr, $sRes);
|
||||
return $sRes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array|hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
|
||||
* @param array $aArgs
|
||||
* @param null $aAttToLoad
|
||||
* @param null $aExtendedDataSpec
|
||||
* @param int $iLimitCount
|
||||
* @param int $iLimitStart
|
||||
* @param bool $bGetCount
|
||||
* @return string
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
* @throws MissingQueryArgument
|
||||
*/
|
||||
public function MakeSelectQuery($aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false)
|
||||
{
|
||||
// Check the order by specification, and prefix with the class alias
|
||||
// and make sure that the ordering columns are going to be selected
|
||||
//
|
||||
$sClass = $this->GetClass();
|
||||
$sClassAlias = $this->GetClassAlias();
|
||||
$aOrderSpec = array();
|
||||
foreach ($aOrderBy as $sFieldAlias => $bAscending)
|
||||
{
|
||||
if (!is_bool($bAscending))
|
||||
{
|
||||
throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value");
|
||||
}
|
||||
|
||||
$iDotPos = strpos($sFieldAlias, '.');
|
||||
if ($iDotPos === false)
|
||||
{
|
||||
$sAttClass = $sClass;
|
||||
$sAttClassAlias = $sClassAlias;
|
||||
$sAttCode = $sFieldAlias;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sAttClassAlias = substr($sFieldAlias, 0, $iDotPos);
|
||||
$sAttClass = $this->GetClassName($sAttClassAlias);
|
||||
$sAttCode = substr($sFieldAlias, $iDotPos + 1);
|
||||
}
|
||||
|
||||
if ($sAttCode != 'id')
|
||||
{
|
||||
MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sAttCode, MetaModel::GetAttributesList($sAttClass));
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($sAttClass, $sAttCode);
|
||||
foreach($oAttDef->GetOrderBySQLExpressions($sAttClassAlias) as $sSQLExpression)
|
||||
{
|
||||
$aOrderSpec[$sSQLExpression] = $bAscending;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aOrderSpec['`'.$sAttClassAlias.$sAttCode.'`'] = $bAscending;
|
||||
}
|
||||
|
||||
// Make sure that the columns used for sorting are present in the loaded columns
|
||||
if (!is_null($aAttToLoad) && !isset($aAttToLoad[$sAttClassAlias][$sAttCode]))
|
||||
{
|
||||
$aAttToLoad[$sAttClassAlias][$sAttCode] = MetaModel::GetAttributeDef($sAttClass, $sAttCode);
|
||||
}
|
||||
}
|
||||
|
||||
$oSQLQuery = $this->GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount);
|
||||
|
||||
if ($this->m_bNoContextParameters)
|
||||
{
|
||||
// Only internal parameters
|
||||
$aScalarArgs = $this->GetInternalParams();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The complete list of arguments will include magic arguments (e.g. current_user->attcode)
|
||||
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
|
||||
}
|
||||
try
|
||||
{
|
||||
$bBeautifulSQL = self::$m_bTraceQueries || self::$m_bDebugQuery || self::$m_bIndentQueries;
|
||||
$sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, $bBeautifulSQL);
|
||||
if ($sClassAlias == '_itop_')
|
||||
{
|
||||
IssueLog::Info('SQL Query (_itop_): '.$sRes);
|
||||
}
|
||||
}
|
||||
catch (MissingQueryArgument $e)
|
||||
{
|
||||
// Add some information...
|
||||
$e->addInfo('OQL', $this->ToOQL());
|
||||
throw $e;
|
||||
}
|
||||
$this->AddQueryTraceSelect($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $sRes);
|
||||
return $sRes;
|
||||
}
|
||||
|
||||
|
||||
protected function GetSQLQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null)
|
||||
{
|
||||
$oSQLQuery = $this->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
|
||||
$oSQLQuery->SetSourceOQL($this->ToOQL());
|
||||
|
||||
// Join to an additional table, if required...
|
||||
//
|
||||
if ($aExtendedDataSpec != null)
|
||||
{
|
||||
$sTableAlias = '_extended_data_';
|
||||
$aExtendedFields = array();
|
||||
foreach($aExtendedDataSpec['fields'] as $sColumn)
|
||||
{
|
||||
$sColRef = $this->GetClassAlias().'_extdata_'.$sColumn;
|
||||
$aExtendedFields[$sColRef] = new FieldExpressionResolved($sColumn, $sTableAlias);
|
||||
}
|
||||
$oSQLQueryExt = new SQLObjectQuery($aExtendedDataSpec['table'], $sTableAlias, $aExtendedFields);
|
||||
$oSQLQuery->AddInnerJoin($oSQLQueryExt, 'id', $aExtendedDataSpec['join_key'] /*, $sTableAlias*/);
|
||||
}
|
||||
|
||||
return $oSQLQuery;
|
||||
}
|
||||
|
||||
public abstract function GetSQLQueryStructure(
|
||||
$aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null
|
||||
);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Cache/Trace/Log queries
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
protected static $m_bDebugQuery = false;
|
||||
protected static $m_aQueriesLog = array();
|
||||
protected static $m_bQueryCacheEnabled = false;
|
||||
protected static $m_bUseAPCCache = false;
|
||||
protected static $m_iQueryCacheTTL = 3600;
|
||||
protected static $m_bTraceQueries = false;
|
||||
protected static $m_bIndentQueries = false;
|
||||
protected static $m_bOptimizeQueries = false;
|
||||
|
||||
public static function StartDebugQuery()
|
||||
{
|
||||
$aBacktrace = debug_backtrace();
|
||||
self::$m_bDebugQuery = true;
|
||||
}
|
||||
public static function StopDebugQuery()
|
||||
{
|
||||
self::$m_bDebugQuery = false;
|
||||
}
|
||||
|
||||
public static function EnableQueryCache($bEnabled, $bUseAPC, $iTimeToLive = 3600)
|
||||
{
|
||||
self::$m_bQueryCacheEnabled = $bEnabled;
|
||||
self::$m_bUseAPCCache = $bUseAPC;
|
||||
self::$m_iQueryCacheTTL = $iTimeToLive;
|
||||
}
|
||||
public static function EnableQueryTrace($bEnabled)
|
||||
{
|
||||
self::$m_bTraceQueries = $bEnabled;
|
||||
}
|
||||
public static function EnableQueryIndentation($bEnabled)
|
||||
{
|
||||
self::$m_bIndentQueries = $bEnabled;
|
||||
}
|
||||
public static function EnableOptimizeQuery($bEnabled)
|
||||
{
|
||||
self::$m_bOptimizeQueries = $bEnabled;
|
||||
}
|
||||
|
||||
|
||||
protected function AddQueryTraceSelect($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $sSql)
|
||||
{
|
||||
if (self::$m_bTraceQueries)
|
||||
{
|
||||
$aQueryData = array(
|
||||
'type' => 'select',
|
||||
'filter' => $this,
|
||||
'order_by' => $aOrderBy,
|
||||
'args' => $aArgs,
|
||||
'att_to_load' => $aAttToLoad,
|
||||
'extended_data_spec' => $aExtendedDataSpec,
|
||||
'limit_count' => $iLimitCount,
|
||||
'limit_start' => $iLimitStart,
|
||||
'is_count' => $bGetCount
|
||||
);
|
||||
$sOql = $this->ToOQL(true, $aArgs);
|
||||
self::AddQueryTrace($aQueryData, $sOql, $sSql);
|
||||
}
|
||||
}
|
||||
|
||||
protected function AddQueryTraceGroupBy($aArgs, $aGroupByExpr, $sSql)
|
||||
{
|
||||
if (self::$m_bTraceQueries)
|
||||
{
|
||||
$aQueryData = array(
|
||||
'type' => 'group_by',
|
||||
'filter' => $this,
|
||||
'args' => $aArgs,
|
||||
'group_by_expr' => $aGroupByExpr
|
||||
);
|
||||
$sOql = $this->ToOQL(true, $aArgs);
|
||||
self::AddQueryTrace($aQueryData, $sOql, $sSql);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function AddQueryTrace($aQueryData, $sOql, $sSql)
|
||||
{
|
||||
if (self::$m_bTraceQueries)
|
||||
{
|
||||
$sQueryId = md5(serialize($aQueryData));
|
||||
$sMySQLQueryId = md5($sSql);
|
||||
if(!isset(self::$m_aQueriesLog[$sQueryId]))
|
||||
{
|
||||
self::$m_aQueriesLog[$sQueryId]['data'] = serialize($aQueryData);
|
||||
self::$m_aQueriesLog[$sQueryId]['oql'] = $sOql;
|
||||
self::$m_aQueriesLog[$sQueryId]['hits'] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_aQueriesLog[$sQueryId]['hits']++;
|
||||
}
|
||||
if(!isset(self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]))
|
||||
{
|
||||
self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['sql'] = $sSql;
|
||||
self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['count'] = 1;
|
||||
$iTableCount = count(CMDBSource::ExplainQuery($sSql));
|
||||
self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['table_count'] = $iTableCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_aQueriesLog[$sQueryId]['queries'][$sMySQLQueryId]['count']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function RecordQueryTrace()
|
||||
{
|
||||
if (!self::$m_bTraceQueries)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$iOqlCount = count(self::$m_aQueriesLog);
|
||||
$iSqlCount = 0;
|
||||
foreach (self::$m_aQueriesLog as $sQueryId => $aOqlData)
|
||||
{
|
||||
$iSqlCount += $aOqlData['hits'];
|
||||
}
|
||||
$sHtml = "<h2>Stats on SELECT queries: OQL=$iOqlCount, SQL=$iSqlCount</h2>\n";
|
||||
foreach (self::$m_aQueriesLog as $sQueryId => $aOqlData)
|
||||
{
|
||||
$sOql = $aOqlData['oql'];
|
||||
$sHits = $aOqlData['hits'];
|
||||
|
||||
$sHtml .= "<p><b>$sHits</b> hits for OQL query: $sOql</p>\n";
|
||||
$sHtml .= "<ul id=\"ClassesRelationships\" class=\"treeview\">\n";
|
||||
foreach($aOqlData['queries'] as $aSqlData)
|
||||
{
|
||||
$sQuery = $aSqlData['sql'];
|
||||
$sSqlHits = $aSqlData['count'];
|
||||
$iTableCount = $aSqlData['table_count'];
|
||||
$sHtml .= "<li><b>$sSqlHits</b> hits for SQL ($iTableCount tables): <pre style=\"font-size:60%\">$sQuery</pre></li>\n";
|
||||
}
|
||||
$sHtml .= "</ul>\n";
|
||||
}
|
||||
|
||||
$sLogFile = 'queries.latest';
|
||||
file_put_contents(APPROOT.'data/'.$sLogFile.'.html', $sHtml);
|
||||
|
||||
$sLog = "<?php\n\$aQueriesLog = ".var_export(self::$m_aQueriesLog, true).";";
|
||||
file_put_contents(APPROOT.'data/'.$sLogFile.'.log', $sLog);
|
||||
|
||||
// Cumulate the queries
|
||||
$sAllQueries = APPROOT.'data/queries.log';
|
||||
if (file_exists($sAllQueries))
|
||||
{
|
||||
// Merge the new queries into the existing log
|
||||
include($sAllQueries);
|
||||
$aQueriesLog = array();
|
||||
foreach (self::$m_aQueriesLog as $sQueryId => $aOqlData)
|
||||
{
|
||||
if (!array_key_exists($sQueryId, $aQueriesLog))
|
||||
{
|
||||
$aQueriesLog[$sQueryId] = $aOqlData;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aQueriesLog = self::$m_aQueriesLog;
|
||||
}
|
||||
$sLog = "<?php\n\$aQueriesLog = ".var_export($aQueriesLog, true).";";
|
||||
file_put_contents($sAllQueries, $sLog);
|
||||
}
|
||||
|
||||
protected static function DbgTrace($value)
|
||||
{
|
||||
if (!self::$m_bDebugQuery)
|
||||
{
|
||||
return;
|
||||
}
|
||||
$aBacktrace = debug_backtrace();
|
||||
$iCallStackPos = count($aBacktrace) - self::$m_bDebugQuery;
|
||||
$sIndent = "";
|
||||
for ($i = 0 ; $i < $iCallStackPos ; $i++)
|
||||
{
|
||||
$sIndent .= " .-=^=-. ";
|
||||
}
|
||||
$aCallers = array();
|
||||
foreach($aBacktrace as $aStackInfo)
|
||||
{
|
||||
$aCallers[] = $aStackInfo["function"];
|
||||
}
|
||||
$sCallers = "Callstack: ".implode(', ', $aCallers);
|
||||
$sFunction = "<b title=\"$sCallers\">".$aBacktrace[1]["function"]."</b>";
|
||||
|
||||
if (is_object($value))
|
||||
{
|
||||
echo "$sIndent$sFunction:\n<pre>\n";
|
||||
print_r($value);
|
||||
echo "</pre>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "$sIndent$sFunction: $value<br/>\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Experimental!
|
||||
* todo: implement the change tracking
|
||||
*
|
||||
* @param $bArchive
|
||||
* @throws Exception
|
||||
*/
|
||||
function DBBulkWriteArchiveFlag($bArchive)
|
||||
{
|
||||
$sClass = $this->GetClass();
|
||||
if (!MetaModel::IsArchivable($sClass))
|
||||
{
|
||||
throw new Exception($sClass.' is not an archivable class');
|
||||
}
|
||||
|
||||
$iFlag = $bArchive ? 1 : 0;
|
||||
|
||||
$oSet = new DBObjectSet($this);
|
||||
if (MetaModel::IsStandaloneClass($sClass))
|
||||
{
|
||||
$oSet->OptimizeColumnLoad(array($this->GetClassAlias() => array('')));
|
||||
$aIds = array($sClass => $oSet->GetColumnAsArray('id'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$oSet->OptimizeColumnLoad(array($this->GetClassAlias() => array('finalclass')));
|
||||
$aTemp = $oSet->GetColumnAsArray('finalclass');
|
||||
$aIds = array();
|
||||
foreach ($aTemp as $iObjectId => $sObjectClass)
|
||||
{
|
||||
$aIds[$sObjectClass][$iObjectId] = $iObjectId;
|
||||
}
|
||||
}
|
||||
foreach ($aIds as $sFinalClass => $aObjectIds)
|
||||
{
|
||||
$sIds = implode(', ', $aObjectIds);
|
||||
|
||||
$sArchiveRoot = MetaModel::GetAttributeOrigin($sFinalClass, 'archive_flag');
|
||||
$sRootTable = MetaModel::DBGetTable($sArchiveRoot);
|
||||
$sRootKey = MetaModel::DBGetKey($sArchiveRoot);
|
||||
$aJoins = array("`$sRootTable`");
|
||||
$aUpdates = array();
|
||||
foreach (MetaModel::EnumParentClasses($sFinalClass, ENUM_PARENT_CLASSES_ALL) as $sParentClass)
|
||||
{
|
||||
if (!MetaModel::IsValidAttCode($sParentClass, 'archive_flag'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$sTable = MetaModel::DBGetTable($sParentClass);
|
||||
$aUpdates[] = "`$sTable`.`archive_flag` = $iFlag";
|
||||
if ($sParentClass == $sArchiveRoot)
|
||||
{
|
||||
if ($bArchive)
|
||||
{
|
||||
// Set the date (do not change it)
|
||||
$sDate = '"'.date(AttributeDate::GetSQLFormat()).'"';
|
||||
$aUpdates[] = "`$sTable`.`archive_date` = coalesce(`$sTable`.`archive_date`, $sDate)";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset the date
|
||||
$aUpdates[] = "`$sTable`.`archive_date` = null";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$sKey = MetaModel::DBGetKey($sParentClass);
|
||||
$aJoins[] = "`$sTable` ON `$sTable`.`$sKey` = `$sRootTable`.`$sRootKey`";
|
||||
}
|
||||
}
|
||||
$sJoins = implode(' INNER JOIN ', $aJoins);
|
||||
$sValues = implode(', ', $aUpdates);
|
||||
$sUpdateQuery = "UPDATE $sJoins SET $sValues WHERE `$sRootTable`.`$sRootKey` IN ($sIds)";
|
||||
CMDBSource::Query($sUpdateQuery);
|
||||
}
|
||||
}
|
||||
|
||||
public function UpdateContextFromUser()
|
||||
{
|
||||
$this->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||
}
|
||||
}
|
||||
563
core/dbunionsearch.class.php
Normal file
563
core/dbunionsearch.class.php
Normal file
@@ -0,0 +1,563 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2017 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/>
|
||||
|
||||
|
||||
/**
|
||||
* A union of DBObjectSearches
|
||||
*
|
||||
* @copyright Copyright (C) 2015-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class DBUnionSearch extends DBSearch
|
||||
{
|
||||
protected $aSearches; // source queries
|
||||
protected $aSelectedClasses; // alias => classes (lowest common ancestors) computed at construction
|
||||
|
||||
public function __construct($aSearches)
|
||||
{
|
||||
if (count ($aSearches) == 0)
|
||||
{
|
||||
throw new CoreException('A DBUnionSearch must be made of at least one search');
|
||||
}
|
||||
|
||||
$this->aSearches = array();
|
||||
foreach ($aSearches as $oSearch)
|
||||
{
|
||||
if ($oSearch instanceof DBUnionSearch)
|
||||
{
|
||||
foreach ($oSearch->aSearches as $oSubSearch)
|
||||
{
|
||||
$this->aSearches[] = $oSubSearch->DeepClone();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->aSearches[] = $oSearch->DeepClone();
|
||||
}
|
||||
}
|
||||
|
||||
$this->ComputeSelectedClasses();
|
||||
}
|
||||
|
||||
public function AllowAllData()
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
}
|
||||
public function IsAllDataAllowed()
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
if ($oSearch->IsAllDataAllowed() === false) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function SetArchiveMode($bEnable)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->SetArchiveMode($bEnable);
|
||||
}
|
||||
parent::SetArchiveMode($bEnable);
|
||||
}
|
||||
|
||||
public function SetShowObsoleteData($bShow)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->SetShowObsoleteData($bShow);
|
||||
}
|
||||
parent::SetShowObsoleteData($bShow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the lowest common ancestor for each of the selected class
|
||||
*/
|
||||
protected function ComputeSelectedClasses()
|
||||
{
|
||||
// 1 - Collect all the column/classes
|
||||
$aColumnToClasses = array();
|
||||
foreach ($this->aSearches as $iPos => $oSearch)
|
||||
{
|
||||
$aSelected = array_values($oSearch->GetSelectedClasses());
|
||||
|
||||
if ($iPos != 0)
|
||||
{
|
||||
if (count($aSelected) < count($aColumnToClasses))
|
||||
{
|
||||
throw new Exception('Too few selected classes in the subquery #'.($iPos+1));
|
||||
}
|
||||
if (count($aSelected) > count($aColumnToClasses))
|
||||
{
|
||||
throw new Exception('Too many selected classes in the subquery #'.($iPos+1));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($aSelected as $iColumn => $sClass)
|
||||
{
|
||||
$aColumnToClasses[$iColumn][] = $sClass;
|
||||
}
|
||||
}
|
||||
|
||||
// 2 - Build the index column => alias
|
||||
$oFirstSearch = $this->aSearches[0];
|
||||
$aColumnToAlias = array_keys($oFirstSearch->GetSelectedClasses());
|
||||
|
||||
// 3 - Compute alias => lowest common ancestor
|
||||
$this->aSelectedClasses = array();
|
||||
foreach ($aColumnToClasses as $iColumn => $aClasses)
|
||||
{
|
||||
$sAlias = $aColumnToAlias[$iColumn];
|
||||
$sAncestor = MetaModel::GetLowestCommonAncestor($aClasses);
|
||||
if (is_null($sAncestor))
|
||||
{
|
||||
throw new Exception('Could not find a common ancestor for the column '.($iColumn+1).' (Classes: '.implode(', ', $aClasses).')');
|
||||
}
|
||||
$this->aSelectedClasses[$sAlias] = $sAncestor;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetSearches()
|
||||
{
|
||||
return $this->aSearches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limited to the selected classes
|
||||
*/
|
||||
public function GetClassName($sAlias)
|
||||
{
|
||||
if (array_key_exists($sAlias, $this->aSelectedClasses))
|
||||
{
|
||||
return $this->aSelectedClasses[$sAlias];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new CoreException("Invalid class alias '$sAlias'");
|
||||
}
|
||||
}
|
||||
|
||||
public function GetClass()
|
||||
{
|
||||
return reset($this->aSelectedClasses);
|
||||
}
|
||||
|
||||
public function GetClassAlias()
|
||||
{
|
||||
reset($this->aSelectedClasses);
|
||||
return key($this->aSelectedClasses);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change the class (only subclasses are supported as of now, because the conditions must fit the new class)
|
||||
* Defaults to the first selected class
|
||||
* Only the selected classes can be changed
|
||||
*/
|
||||
public function ChangeClass($sNewClass, $sAlias = null)
|
||||
{
|
||||
if (is_null($sAlias))
|
||||
{
|
||||
$sAlias = $this->GetClassAlias();
|
||||
}
|
||||
elseif (!array_key_exists($sAlias, $this->aSelectedClasses))
|
||||
{
|
||||
// discard silently - necessary when recursing (??? copied from DBObjectSearch)
|
||||
return;
|
||||
}
|
||||
|
||||
// 1 - identify the impacted column
|
||||
$iColumn = array_search($sAlias, array_keys($this->aSelectedClasses));
|
||||
|
||||
// 2 - change for each search
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aSearchAliases = array_keys($oSearch->GetSelectedClasses());
|
||||
$sSearchAlias = $aSearchAliases[$iColumn];
|
||||
$oSearch->ChangeClass($sNewClass, $sSearchAlias);
|
||||
}
|
||||
|
||||
// 3 - record the change
|
||||
$this->aSelectedClasses[$sAlias] = $sNewClass;
|
||||
}
|
||||
|
||||
public function GetSelectedClasses()
|
||||
{
|
||||
return $this->aSelectedClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aSelectedClasses array of aliases
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function SetSelectedClasses($aSelectedClasses)
|
||||
{
|
||||
// 1 - change for each search
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
// Throws an exception if not valid
|
||||
$oSearch->SetSelectedClasses($aSelectedClasses);
|
||||
}
|
||||
// 2 - update the lowest common ancestors
|
||||
$this->ComputeSelectedClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change any alias of the query tree
|
||||
*
|
||||
* @param $sOldName
|
||||
* @param $sNewName
|
||||
* @return bool True if the alias has been found and changed
|
||||
*/
|
||||
public function RenameAlias($sOldName, $sNewName)
|
||||
{
|
||||
$bRet = false;
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$bRet = $oSearch->RenameAlias($sOldName, $sNewName) || $bRet;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
public function IsAny()
|
||||
{
|
||||
$bIsAny = true;
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
if (!$oSearch->IsAny())
|
||||
{
|
||||
$bIsAny = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $bIsAny;
|
||||
}
|
||||
|
||||
public function ResetCondition()
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->ResetCondition();
|
||||
}
|
||||
}
|
||||
|
||||
public function MergeConditionExpression($oExpression)
|
||||
{
|
||||
$aAliases = array_keys($this->aSelectedClasses);
|
||||
foreach ($this->aSearches as $iSearchIndex => $oSearch)
|
||||
{
|
||||
$oClonedExpression = $oExpression->DeepClone();
|
||||
if ($iSearchIndex != 0)
|
||||
{
|
||||
foreach (array_keys($oSearch->GetSelectedClasses()) as $iColumn => $sSearchAlias)
|
||||
{
|
||||
$oClonedExpression->RenameAlias($aAliases[$iColumn], $sSearchAlias);
|
||||
}
|
||||
}
|
||||
$oSearch->MergeConditionExpression($oClonedExpression);
|
||||
}
|
||||
}
|
||||
|
||||
public function AddConditionExpression($oExpression)
|
||||
{
|
||||
$aAliases = array_keys($this->aSelectedClasses);
|
||||
foreach ($this->aSearches as $iSearchIndex => $oSearch)
|
||||
{
|
||||
$oClonedExpression = $oExpression->DeepClone();
|
||||
if ($iSearchIndex != 0)
|
||||
{
|
||||
foreach (array_keys($oSearch->GetSelectedClasses()) as $iColumn => $sSearchAlias)
|
||||
{
|
||||
$oClonedExpression->RenameAlias($aAliases[$iColumn], $sSearchAlias);
|
||||
}
|
||||
}
|
||||
$oSearch->AddConditionExpression($oClonedExpression);
|
||||
}
|
||||
}
|
||||
|
||||
public function AddNameCondition($sName)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddNameCondition($sName);
|
||||
}
|
||||
}
|
||||
|
||||
public function AddCondition($sFilterCode, $value, $sOpCode = null)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddCondition($sFilterCode, $value, $sOpCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a condition on external keys or link sets
|
||||
* @param sAttSpec Can be either an attribute code or extkey->[sAttSpec] or linkset->[sAttSpec] and so on, recursively
|
||||
* Example: infra_list->ci_id->location_id->country
|
||||
* @param value The value to match (can be an array => IN(val1, val2...)
|
||||
* @return void
|
||||
*/
|
||||
public function AddConditionAdvanced($sAttSpec, $value)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddConditionAdvanced($sAttSpec, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function AddCondition_FullText($sFullText)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->AddCondition_FullText($sFullText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObjectSearch $oFilter
|
||||
* @param $sForeignExtKeyAttCode
|
||||
* @param int $iOperatorCode
|
||||
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public function Intersect(DBSearch $oFilter)
|
||||
{
|
||||
$aSearches = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aSearches[] = $oSearch->Intersect($oFilter);
|
||||
}
|
||||
return new DBUnionSearch($aSearches);
|
||||
}
|
||||
|
||||
public function SetInternalParams($aParams)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->SetInternalParams($aParams);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetInternalParams()
|
||||
{
|
||||
$aParams = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aParams = array_merge($oSearch->GetInternalParams(), $aParams);
|
||||
}
|
||||
return $aParams;
|
||||
}
|
||||
|
||||
public function GetQueryParams($bExcludeMagicParams = true)
|
||||
{
|
||||
$aParams = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aParams = array_merge($oSearch->GetQueryParams($bExcludeMagicParams), $aParams);
|
||||
}
|
||||
return $aParams;
|
||||
}
|
||||
|
||||
public function ListConstantFields()
|
||||
{
|
||||
// Somewhat complex to implement for unions, for a poor benefit
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the parameters (:xxx) into scalar values in order to easily
|
||||
* serialize a search
|
||||
*/
|
||||
public function ApplyParameters($aArgs)
|
||||
{
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$oSearch->ApplyParameters($aArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloads for query building
|
||||
*/
|
||||
public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false)
|
||||
{
|
||||
$aSubQueries = array();
|
||||
foreach ($this->aSearches as $oSearch)
|
||||
{
|
||||
$aSubQueries[] = $oSearch->ToOQL($bDevelopParams, $aContextParams, $bWithAllowAllFlag);
|
||||
}
|
||||
$sRet = implode(' UNION ', $aSubQueries);
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new DBUnionSearch object where duplicates queries have been removed based on their OQLs
|
||||
*
|
||||
* @return \DBUnionSearch
|
||||
*/
|
||||
public function RemoveDuplicateQueries()
|
||||
{
|
||||
$aQueries = array();
|
||||
$aSearches = array();
|
||||
|
||||
foreach ($this->GetSearches() as $oTmpSearch)
|
||||
{
|
||||
$sQuery = $oTmpSearch->ToOQL(true);
|
||||
if (!in_array($sQuery, $aQueries))
|
||||
{
|
||||
$aQueries[] = $sQuery;
|
||||
$aSearches[] = $oTmpSearch;
|
||||
}
|
||||
}
|
||||
|
||||
$oNewSearch = new DBUnionSearch($aSearches);
|
||||
|
||||
return $oNewSearch;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Construction of the SQL queries
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public function MakeDeleteQuery($aArgs = array())
|
||||
{
|
||||
throw new Exception('MakeDeleteQuery is not implemented for the unions!');
|
||||
}
|
||||
|
||||
public function MakeUpdateQuery($aValues, $aArgs = array())
|
||||
{
|
||||
throw new Exception('MakeUpdateQuery is not implemented for the unions!');
|
||||
}
|
||||
|
||||
public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null)
|
||||
{
|
||||
if (count($this->aSearches) == 1)
|
||||
{
|
||||
return $this->aSearches[0]->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr);
|
||||
}
|
||||
|
||||
$aSQLQueries = array();
|
||||
$aAliases = array_keys($this->aSelectedClasses);
|
||||
foreach ($this->aSearches as $iSearch => $oSearch)
|
||||
{
|
||||
$aSearchAliases = array_keys($oSearch->GetSelectedClasses());
|
||||
|
||||
// The selected classes from the query build perspective are the lowest common ancestors amongst the various queries
|
||||
// (used when it comes to determine which attributes must be selected)
|
||||
$aSearchSelectedClasses = array();
|
||||
foreach ($aSearchAliases as $iColumn => $sSearchAlias)
|
||||
{
|
||||
$sAlias = $aAliases[$iColumn];
|
||||
$aSearchSelectedClasses[$sSearchAlias] = $this->aSelectedClasses[$sAlias];
|
||||
}
|
||||
|
||||
if ($bGetCount)
|
||||
{
|
||||
// Select only ids for the count to allow optimization of joins
|
||||
foreach($aSearchAliases as $sSearchAlias)
|
||||
{
|
||||
$aQueryAttToLoad[$sSearchAlias] = array();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (is_null($aAttToLoad))
|
||||
{
|
||||
$aQueryAttToLoad = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// (Eventually) Transform the aliases
|
||||
$aQueryAttToLoad = array();
|
||||
foreach($aAttToLoad as $sAlias => $aAttributes)
|
||||
{
|
||||
$iColumn = array_search($sAlias, $aAliases);
|
||||
$sQueryAlias = ($iColumn === false) ? $sAlias : $aSearchAliases[$iColumn];
|
||||
$aQueryAttToLoad[$sQueryAlias] = $aAttributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($aGroupByExpr))
|
||||
{
|
||||
$aQueryGroupByExpr = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clone (and eventually transform) the group by expressions
|
||||
$aQueryGroupByExpr = array();
|
||||
$aTranslationData = array();
|
||||
$aQueryColumns = array_keys($oSearch->GetSelectedClasses());
|
||||
foreach ($aAliases as $iColumn => $sAlias)
|
||||
{
|
||||
$sQueryAlias = $aQueryColumns[$iColumn];
|
||||
$aTranslationData[$sAlias]['*'] = $sQueryAlias;
|
||||
$aQueryGroupByExpr[$sAlias.'id'] = new FieldExpression('id', $sQueryAlias);
|
||||
}
|
||||
foreach ($aGroupByExpr as $sExpressionAlias => $oExpression)
|
||||
{
|
||||
$aQueryGroupByExpr[$sExpressionAlias] = $oExpression->Translate($aTranslationData, false, false);
|
||||
}
|
||||
}
|
||||
$oSubQuery = $oSearch->GetSQLQueryStructure($aQueryAttToLoad, false, $aQueryGroupByExpr, $aSearchSelectedClasses);
|
||||
if (count($aSearchAliases) > 1)
|
||||
{
|
||||
// Necessary to make sure that selected columns will match throughout all the queries
|
||||
// (default order of selected fields depending on the order of JOINS)
|
||||
$oSubQuery->SortSelectedFields();
|
||||
}
|
||||
$aSQLQueries[] = $oSubQuery;
|
||||
}
|
||||
|
||||
$oSQLQuery = new SQLUnionQuery($aSQLQueries, $aGroupByExpr);
|
||||
//MyHelpers::var_dump_html($oSQLQuery, true);
|
||||
//MyHelpers::var_dump_html($oSQLQuery->RenderSelect(), true);
|
||||
if (self::$m_bDebugQuery) $oSQLQuery->DisplayHtml();
|
||||
return $oSQLQuery;
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,7 @@ class DeletionPlan
|
||||
}
|
||||
if ($aData['mode'] == DEL_MANUAL)
|
||||
{
|
||||
$this->m_aToDelete[$sClass][$iId]['issue'] = $sClass.'::'.$iId.' '.Dict::S('UI:Delete:MustBeDeletedManually');
|
||||
$this->m_bFoundStopper = true;
|
||||
$this->m_bFoundManualDelete = true;
|
||||
}
|
||||
|
||||
268
core/designdocument.class.inc.php
Normal file
268
core/designdocument.class.inc.php
Normal file
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 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/>
|
||||
|
||||
/**
|
||||
* Design document and associated nodes
|
||||
* @package Core
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop;
|
||||
|
||||
use \DOMDocument;
|
||||
use \DOMFormatException;
|
||||
|
||||
/**
|
||||
* Class \Combodo\iTop\DesignDocument
|
||||
*
|
||||
* A design document is the DOM tree that modelize behaviors. One of its
|
||||
* characteristics is that it can be altered by the mean of the same kind of document.
|
||||
*
|
||||
*/
|
||||
class DesignDocument extends DOMDocument
|
||||
{
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('1.0', 'UTF-8');
|
||||
$this->Init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloadable. Called prior to data loading.
|
||||
*/
|
||||
protected function Init()
|
||||
{
|
||||
$this->registerNodeClass('DOMElement', '\Combodo\iTop\DesignElement');
|
||||
|
||||
$this->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS)
|
||||
$this->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload of the standard API
|
||||
*/
|
||||
public function load($filename, $options = 0)
|
||||
{
|
||||
parent::load($filename, LIBXML_NOBLANKS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload of the standard API
|
||||
*/
|
||||
public function save($filename, $options = 0)
|
||||
{
|
||||
$this->documentElement->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
|
||||
return parent::save($filename, LIBXML_NOBLANKS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTML representation of the DOM, for debugging purposes
|
||||
* @param bool|false $bReturnRes Echoes or returns the HTML representation
|
||||
* @return mixed void or the HTML representation of the DOM
|
||||
*/
|
||||
public function Dump($bReturnRes = false)
|
||||
{
|
||||
$sXml = $this->saveXML();
|
||||
if ($bReturnRes)
|
||||
{
|
||||
return $sXml;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "<pre>\n";
|
||||
echo htmlentities($sXml);
|
||||
echo "</pre>\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote and escape strings for use within an XPath expression
|
||||
* Usage: DesignDocument::GetNodes('class[@id='.DesignDocument::XPathQuote($sId).']');
|
||||
* @param $sValue The value to be quoted
|
||||
* @return string to be used within an XPath
|
||||
*/
|
||||
public static function XPathQuote($sValue)
|
||||
{
|
||||
if (strpos($sValue, '"') !== false)
|
||||
{
|
||||
$aParts = explode('"', $sValue);
|
||||
$sRet = 'concat("'.implode('", \'"\', "', $aParts).'")';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = '"'.$sValue.'"';
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts some nodes from the DOM
|
||||
* @param string $sXPath A XPath expression
|
||||
* @param DesignNode|null $oContextNode The node to start the search from
|
||||
* @return \DOMNodeList
|
||||
*/
|
||||
public function GetNodes($sXPath, $oContextNode = null)
|
||||
{
|
||||
$oXPath = new \DOMXPath($this);
|
||||
if (is_null($oContextNode))
|
||||
{
|
||||
$oResult = $oXPath->query($sXPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oResult = $oXPath->query($sXPath, $oContextNode);
|
||||
}
|
||||
return $oResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative to getNodePath, that gives the id of nodes instead of the position within the children
|
||||
* @param $oNode The node to describe
|
||||
* @return string
|
||||
*/
|
||||
public static function GetItopNodePath($oNode)
|
||||
{
|
||||
if ($oNode instanceof \DOMDocument) return '';
|
||||
if (is_null($oNode)) return '';
|
||||
|
||||
$sId = $oNode->getAttribute('id');
|
||||
$sNodeDesc = ($sId != '') ? $oNode->nodeName.'['.$sId.']' : $oNode->nodeName;
|
||||
return self::GetItopNodePath($oNode->parentNode).'/'.$sNodeDesc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DesignElement: helper to read/change the DOM
|
||||
* @package ModelFactory
|
||||
*/
|
||||
class DesignElement extends \DOMElement
|
||||
{
|
||||
/**
|
||||
* Extracts some nodes from the DOM
|
||||
* @param string $sXPath A XPath expression
|
||||
* @return \DOMNodeList
|
||||
*/
|
||||
public function GetNodes($sXPath)
|
||||
{
|
||||
return $this->ownerDocument->GetNodes($sXPath, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTML representation of the DOM, for debugging purposes
|
||||
* @param bool|false $bReturnRes Echoes or returns the HTML representation
|
||||
* @return mixed void or the HTML representation of the DOM
|
||||
*/
|
||||
public function Dump($bReturnRes = false)
|
||||
{
|
||||
$oDoc = new DesignDocument();
|
||||
$oClone = $oDoc->importNode($this->cloneNode(true), true);
|
||||
$oDoc->appendChild($oClone);
|
||||
|
||||
$sXml = $oDoc->saveXML($oClone);
|
||||
if ($bReturnRes)
|
||||
{
|
||||
return $sXml;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "<pre>\n";
|
||||
echo htmlentities($sXml);
|
||||
echo "</pre>\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node directly under the given node
|
||||
* @param $sTagName
|
||||
* @param bool|true $bMustExist
|
||||
* @return MFElement
|
||||
* @throws DOMFormatException
|
||||
*/
|
||||
public function GetUniqueElement($sTagName, $bMustExist = true)
|
||||
{
|
||||
$oNode = null;
|
||||
foreach($this->childNodes as $oChildNode)
|
||||
{
|
||||
if ($oChildNode->nodeName == $sTagName)
|
||||
{
|
||||
$oNode = $oChildNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($bMustExist && is_null($oNode))
|
||||
{
|
||||
throw new DOMFormatException('Missing unique tag: '.$sTagName);
|
||||
}
|
||||
return $oNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node directly under the current node, or null if missing
|
||||
* @param $sTagName
|
||||
* @return MFElement
|
||||
* @throws DOMFormatException
|
||||
*/
|
||||
public function GetOptionalElement($sTagName)
|
||||
{
|
||||
return $this->GetUniqueElement($sTagName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TEXT of the current node (possibly from several child nodes)
|
||||
* @param null $sDefault
|
||||
* @return null|string
|
||||
*/
|
||||
public function GetText($sDefault = null)
|
||||
{
|
||||
$sText = null;
|
||||
foreach($this->childNodes as $oChildNode)
|
||||
{
|
||||
if ($oChildNode instanceof \DOMText)
|
||||
{
|
||||
if (is_null($sText)) $sText = '';
|
||||
$sText .= $oChildNode->wholeText;
|
||||
}
|
||||
}
|
||||
if (is_null($sText))
|
||||
{
|
||||
return $sDefault;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TEXT value from a child node
|
||||
* @param string $sTagName
|
||||
* @param string|null $sDefault
|
||||
* @return string
|
||||
*/
|
||||
public function GetChildText($sTagName, $sDefault = null)
|
||||
{
|
||||
$sRet = $sDefault;
|
||||
if ($oChild = $this->GetOptionalElement($sTagName))
|
||||
{
|
||||
$sRet = $oChild->GetText($sDefault);
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -57,26 +57,13 @@ define('DICT_ERR_EXCEPTION', 2); // when a string is missing, throw an exception
|
||||
|
||||
class Dict
|
||||
{
|
||||
protected static $m_bTraceFiles = false;
|
||||
protected static $m_aEntryFiles = array();
|
||||
|
||||
protected static $m_iErrorMode = DICT_ERR_STRING;
|
||||
protected static $m_sDefaultLanguage = 'EN US';
|
||||
protected static $m_sCurrentLanguage = null; // No language selected by default
|
||||
|
||||
protected static $m_aLanguages = array(); // array( code => array( 'description' => '...', 'localized_description' => '...') ...)
|
||||
protected static $m_aData = array();
|
||||
|
||||
|
||||
public static function EnableTraceFiles()
|
||||
{
|
||||
self::$m_bTraceFiles = true;
|
||||
}
|
||||
|
||||
public static function GetEntryFiles()
|
||||
{
|
||||
return self::$m_aEntryFiles;
|
||||
}
|
||||
protected static $m_sApplicationPrefix = null;
|
||||
|
||||
public static function SetDefaultLanguage($sLanguageCode)
|
||||
{
|
||||
@@ -119,11 +106,20 @@ class Dict
|
||||
self::$m_iErrorMode = $iErrorMode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a localised string from the dictonary
|
||||
* @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
|
||||
* @throws DictExceptionMissingString
|
||||
* @return unknown|Ambigous <>|string
|
||||
*/
|
||||
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||
{
|
||||
// Attempt to find the string in the user language
|
||||
//
|
||||
self::InitLangIfNeeded(self::GetUserLanguage());
|
||||
|
||||
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
|
||||
{
|
||||
// It may happen, when something happens before the dictionnaries get loaded
|
||||
@@ -138,6 +134,8 @@ class Dict
|
||||
{
|
||||
// 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))
|
||||
{
|
||||
@@ -145,6 +143,8 @@ class Dict
|
||||
}
|
||||
// Attempt to find the string in english
|
||||
//
|
||||
self::InitLangIfNeeded('EN US');
|
||||
|
||||
$aDefaultDictionary = self::$m_aData['EN US'];
|
||||
if (array_key_exists($sStringCode, $aDefaultDictionary))
|
||||
{
|
||||
@@ -175,6 +175,12 @@ class Dict
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats a localized string with numbered placeholders (%1$s...) for the additional arguments
|
||||
* See vsprintf for more information about the syntax of the placeholders
|
||||
* @param string $sFormatCode
|
||||
* @return string
|
||||
*/
|
||||
public static function Format($sFormatCode /*, ... arguments ....*/)
|
||||
{
|
||||
$sLocalizedFormat = self::S($sFormatCode);
|
||||
@@ -189,43 +195,95 @@ class Dict
|
||||
|
||||
return vsprintf($sLocalizedFormat, $aArguments);
|
||||
}
|
||||
|
||||
|
||||
// sLanguageCode: Code identifying the language i.e. FR-FR
|
||||
// sEnglishLanguageDesc: Description of the language code, in English. i.e. French (France)
|
||||
// sLocalizedLanguageDesc: Description of the language code, in its own language. i.e. Français (France)
|
||||
// aEntries: Hash array of dictionnary entries
|
||||
// ~~ or ~* can be used to indicate entries still to be translated.
|
||||
public static function Add($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries)
|
||||
|
||||
/**
|
||||
* 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'
|
||||
* @param hash $aEntries Hash array of dictionnary entries
|
||||
*/
|
||||
public static function SetEntries($sLanguageCode, $aEntries)
|
||||
{
|
||||
if (self::$m_bTraceFiles)
|
||||
self::$m_aData[$sLanguageCode] = $aEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of available languages
|
||||
* @param hash $aLanguagesList
|
||||
*/
|
||||
public static function SetLanguagesList($aLanguagesList)
|
||||
{
|
||||
self::$m_aLanguages = $aLanguagesList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a language from the language dictionary, if not already loaded
|
||||
* @param string $sLangCode Language code
|
||||
* @return boolean
|
||||
*/
|
||||
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))
|
||||
{
|
||||
$aBacktrace = debug_backtrace();
|
||||
$sFile = $aBacktrace[0]["file"];
|
||||
|
||||
foreach($aEntries as $sKey => $sValue)
|
||||
// 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_aEntryFiles[$sLanguageCode][$sKey] = array(
|
||||
'file' => $sFile,
|
||||
'value' => $sValue
|
||||
);
|
||||
unset(self::$m_aData[$sLangCode]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$bResult = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||
if (!$bResult)
|
||||
{
|
||||
self::$m_aLanguages[$sLanguageCode] = array('description' => $sEnglishLanguageDesc, 'localized_description' => $sLocalizedLanguageDesc);
|
||||
self::$m_aData[$sLanguageCode] = array();
|
||||
}
|
||||
foreach($aEntries as $sCode => $sValue)
|
||||
{
|
||||
self::$m_aData[$sLanguageCode][$sCode] = self::FilterString($sValue);
|
||||
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
|
||||
require_once($sDictFile);
|
||||
|
||||
if (function_exists('apc_store') && (self::$m_sApplicationPrefix !== null))
|
||||
{
|
||||
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
|
||||
*/
|
||||
public static function EnableCache($sApplicationPrefix)
|
||||
{
|
||||
self::$m_sApplicationPrefix = $sApplicationPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cached entries (cached using APC)
|
||||
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
|
||||
*/
|
||||
public static function ResetCache($sApplicationPrefix)
|
||||
{
|
||||
if (function_exists('apc_delete'))
|
||||
{
|
||||
foreach(self::$m_aLanguages as $sLang => $void)
|
||||
{
|
||||
apc_delete($sApplicationPrefix.'-dict-'.$sLang);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* Clone a string in every language (if it exists in that language)
|
||||
*/
|
||||
*/
|
||||
public static function CloneString($sSourceCode, $sDestCode)
|
||||
{
|
||||
foreach(self::$m_aLanguages as $sLanguageCode => $foo)
|
||||
@@ -236,14 +294,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]))
|
||||
@@ -251,7 +309,7 @@ class Dict
|
||||
$aMissing[$sStringCode] = $sValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
|
||||
{
|
||||
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
|
||||
@@ -279,57 +337,23 @@ class Dict
|
||||
{
|
||||
MyHelpers::var_dump_html(self::$m_aData);
|
||||
}
|
||||
|
||||
public static function InCache($sApplicationPrefix)
|
||||
{
|
||||
if (function_exists('apc_fetch'))
|
||||
{
|
||||
$bResult = false;
|
||||
// Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter
|
||||
//
|
||||
self::$m_aData = apc_fetch($sApplicationPrefix.'-dict');
|
||||
if (is_bool(self::$m_aData) && (self::$m_aData === false))
|
||||
{
|
||||
self::$m_aData = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$m_aLanguages = apc_fetch($sApplicationPrefix.'-languages');
|
||||
if (is_bool(self::$m_aLanguages) && (self::$m_aLanguages === false))
|
||||
{
|
||||
self::$m_aLanguages = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
$bResult = true;
|
||||
}
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function InitCache($sApplicationPrefix)
|
||||
{
|
||||
if (function_exists('apc_store'))
|
||||
{
|
||||
apc_store($sApplicationPrefix.'-languages', self::$m_aLanguages);
|
||||
apc_store($sApplicationPrefix.'-dict', self::$m_aData);
|
||||
}
|
||||
}
|
||||
|
||||
public static function ResetCache($sApplicationPrefix)
|
||||
// Only used by the setup to determine the list of languages to display in the initial setup screen
|
||||
// otherwise replaced by LoadModule by its own handler
|
||||
// sLanguageCode: Code identifying the language i.e. FR-FR
|
||||
// sEnglishLanguageDesc: Description of the language code, in English. i.e. French (France)
|
||||
// sLocalizedLanguageDesc: Description of the language code, in its own language. i.e. Français (France)
|
||||
// aEntries: Hash array of dictionnary entries
|
||||
// ~~ or ~* can be used to indicate entries still to be translated.
|
||||
public static function Add($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries)
|
||||
{
|
||||
if (function_exists('apc_delete'))
|
||||
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
|
||||
{
|
||||
apc_delete($sApplicationPrefix.'-languages');
|
||||
apc_delete($sApplicationPrefix.'-dict');
|
||||
self::$m_aLanguages[$sLanguageCode] = array('description' => $sEnglishLanguageDesc, 'localized_description' => $sLocalizedLanguageDesc);
|
||||
self::$m_aData[$sLanguageCode] = array();
|
||||
}
|
||||
}
|
||||
|
||||
protected static function FilterString($s)
|
||||
{
|
||||
return str_replace(array('~~', '~*'), '', $s);
|
||||
// No need to actually load the strings since it's only used to know the list of languages
|
||||
// at setup time !!
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
1614
core/displayablegraph.class.inc.php
Normal file
1614
core/displayablegraph.class.inc.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -20,7 +20,7 @@
|
||||
/**
|
||||
* Send an email (abstraction for synchronous/asynchronous modes)
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -57,9 +57,6 @@ class EMail
|
||||
{
|
||||
$this->m_aData = array();
|
||||
$this->m_oMessage = Swift_Message::newInstance();
|
||||
|
||||
$oEncoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit');
|
||||
$this->m_oMessage->setEncoder($oEncoder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,6 +154,9 @@ class EMail
|
||||
|
||||
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');
|
||||
@@ -194,6 +194,7 @@ class EMail
|
||||
$oMailer = Swift_Mailer::newInstance($oTransport);
|
||||
|
||||
$aFailedRecipients = array();
|
||||
$this->m_oMessage->setMaxLineLength(0);
|
||||
$iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients);
|
||||
if ($iSent === 0)
|
||||
{
|
||||
@@ -208,9 +209,46 @@ class EMail
|
||||
return EMAIL_SEND_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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[@data-img-id]";
|
||||
$oImagesList = $oXPath->query($sXPath);
|
||||
|
||||
if ($oImagesList->length != 0)
|
||||
{
|
||||
foreach($oImagesList as $oImg)
|
||||
{
|
||||
$iAttId = $oImg->getAttribute('data-img-id');
|
||||
$oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */);
|
||||
if ($oAttachment)
|
||||
{
|
||||
$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');
|
||||
}
|
||||
}
|
||||
|
||||
public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null)
|
||||
{
|
||||
{
|
||||
if ($bForceSynchronous)
|
||||
{
|
||||
return $this->SendSynchronous($aIssues, $oLog);
|
||||
@@ -265,8 +303,14 @@ class EMail
|
||||
$this->AddToHeader('References', $sReferences);
|
||||
}
|
||||
|
||||
public function SetBody($sBody, $sMimeType = 'text/html')
|
||||
public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null)
|
||||
{
|
||||
if (($sMimeType === 'text/html') && ($sCustomStyles !== null))
|
||||
{
|
||||
require_once(APPROOT.'lib/emogrifier/Classes/Emogrifier.php');
|
||||
$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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2013 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -264,8 +264,8 @@ class EventIssue extends Event
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a string
|
||||
$aPost[$sKey] = (string) $sValue;
|
||||
// Not a string (avoid warnings in case the value cannot be easily casted into a string)
|
||||
$aPost[$sKey] = @(string) $sValue;
|
||||
}
|
||||
}
|
||||
$this->Set('arguments_post', $aPost);
|
||||
@@ -332,6 +332,42 @@ class EventWebService extends Event
|
||||
}
|
||||
}
|
||||
|
||||
class EventRestService extends Event
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_event_restservice",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
"display_template" => "",
|
||||
"order_by_default" => array('date' => false)
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeString("operation", array("allowed_values"=>null, "sql"=>"operation", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeText("json_input", array("allowed_values"=>null, "sql"=>"json_input", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("code", array("allowed_values"=>null, "sql"=>"code", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeText("json_output", array("allowed_values"=>null, "sql"=>"json_output", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("provider", array("allowed_values"=>null, "sql"=>"provider", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'operation', 'version', 'json_input', 'message', 'code', 'json_output', 'provider')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'operation', 'message')); // Attributes to be displayed for a list
|
||||
// Search criteria
|
||||
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
|
||||
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
|
||||
}
|
||||
}
|
||||
|
||||
class EventLoginUsage extends Event
|
||||
{
|
||||
public static function Init()
|
||||
@@ -372,4 +408,30 @@ class EventLoginUsage extends Event
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
class EventOnObject extends Event
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb,view_in_gui",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_event_onobject",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
"display_template" => "",
|
||||
"order_by_default" => array('date' => false)
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeString("obj_class", array("allowed_values"=>null, "sql"=>"obj_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("obj_key", array("allowed_values"=>null, "sql"=>"obj_key", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
|
||||
// Display lists
|
||||
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'obj_class', 'obj_key', 'message')); // Attributes to be displayed for the complete details
|
||||
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'obj_class', 'obj_key', 'message')); // Attributes to be displayed for a list
|
||||
}
|
||||
}
|
||||
|
||||
384
core/excelbulkexport.class.inc.php
Normal file
384
core/excelbulkexport.class.inc.php
Normal file
@@ -0,0 +1,384 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2016 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/>
|
||||
|
||||
/**
|
||||
* Bulk export: Excel (xlsx) export
|
||||
*
|
||||
* @copyright Copyright (C) 2015-2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'application/xlsxwriter.class.php');
|
||||
|
||||
class ExcelBulkExport extends TabularBulkExport
|
||||
{
|
||||
protected $sData;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->aStatusInfo['status'] = 'not_started';
|
||||
$this->aStatusInfo['position'] = 0;
|
||||
}
|
||||
|
||||
public function Cleanup()
|
||||
{
|
||||
@unlink($this->aStatusInfo['tmp_file']);
|
||||
parent::Cleanup();
|
||||
}
|
||||
|
||||
public function DisplayUsage(Page $oP)
|
||||
{
|
||||
$oP->p(" * xlsx format options:");
|
||||
$oP->p(" *\tfields: the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
|
||||
$oP->p(" *\tformatted_text: set to 1 to export case logs and formatted text fields with their HTML markup. Default is 0 (= plain text)");
|
||||
$oP->p(" *\tdate_format: the format to use when exporting date and time fields (default = the SQL format). e.g. 'Y-m-d H:i:s'");
|
||||
}
|
||||
|
||||
public function ReadParameters()
|
||||
{
|
||||
parent::ReadParameters();
|
||||
$this->aStatusInfo['formatted_text'] = (bool)utils::ReadParam('formatted_text', 0, true);
|
||||
|
||||
$sDateFormatRadio = utils::ReadParam('excel_date_format_radio', '');
|
||||
switch($sDateFormatRadio)
|
||||
{
|
||||
case 'default':
|
||||
// Export from the UI => format = same as is the UI
|
||||
$this->aStatusInfo['date_format'] = (string)AttributeDateTime::GetFormat();
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
// Custom format specified from the UI
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Export from the command line (or scripted) => default format is SQL, as in previous versions of iTop, unless specified otherwise
|
||||
$this->aStatusInfo['date_format'] = utils::ReadParam('date_format', (string)AttributeDateTime::GetSQLFormat(), true, 'raw_data');
|
||||
}
|
||||
}
|
||||
|
||||
public function EnumFormParts()
|
||||
{
|
||||
return array_merge(parent::EnumFormParts(), array('xlsx_options' => array('formatted_text') ,'interactive_fields_xlsx' => array('interactive_fields_xlsx')));
|
||||
}
|
||||
|
||||
public function DisplayFormPart(WebPage $oP, $sPartId)
|
||||
{
|
||||
switch($sPartId)
|
||||
{
|
||||
case 'interactive_fields_xlsx':
|
||||
$this->GetInteractiveFieldsWidget($oP, 'interactive_fields_xlsx');
|
||||
break;
|
||||
|
||||
case 'xlsx_options':
|
||||
$oP->add('<fieldset><legend>'.Dict::S('Core:BulkExport:XLSXOptions').'</legend>');
|
||||
$oP->add('<table class="export_parameters"><tr><td style="vertical-align:top">');
|
||||
|
||||
$sChecked = (utils::ReadParam('formatted_text', 0) == 1) ? ' checked ' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:TextFormat').'</h3>');
|
||||
$oP->add('<input type="checkbox" id="xlsx_formatted_text" name="formatted_text" value="1"'.$sChecked.'><label for="xlsx_formatted_text"> '.Dict::S('Core:BulkExport:OptionFormattedText').'</label>');
|
||||
|
||||
$oP->add('</td><td style="vertical-align:top">');
|
||||
|
||||
$sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data');
|
||||
$sDefaultChecked = ($sDateTimeFormat == (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$sCustomChecked = ($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()) ? ' checked' : '';
|
||||
$oP->add('<h3>'.Dict::S('Core:BulkExport:DateTimeFormat').'</h3>');
|
||||
$sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8');
|
||||
$sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8');
|
||||
$oP->add('<input type="radio" id="excel_date_time_format_default" name="excel_date_format_radio" value="default"'.$sDefaultChecked.'><label for="excel_date_time_format_default"> '.Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample).'</label><br/>');
|
||||
$sFormatInput = '<input type="text" size="15" name="date_format" id="excel_custom_date_time_format" title="" value="'.htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8').'"/>';
|
||||
$oP->add('<input type="radio" id="excel_date_time_format_custom" name="excel_date_format_radio" value="custom"'.$sCustomChecked.'><label for="excel_date_time_format_custom"> '.Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput).'</label>');
|
||||
|
||||
$oP->add('</td></tr></table>');
|
||||
|
||||
$oP->add('</fieldset>');
|
||||
$sJSTooltip = json_encode('<div class="date_format_tooltip">'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').'</div>');
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#excel_custom_date_time_format').tooltip({content: function() { return $sJSTooltip; } });
|
||||
$('#form_part_xlsx_options').on('preview_updated', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
$('#excel_date_time_format_default').on('click', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
$('#excel_date_time_format_custom').on('click', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
$('#excel_custom_date_time_format').on('click', function() { $('#excel_date_time_format_custom').prop('checked', true); FormatDatesInPreview('excel', 'xlsx'); }).on('keyup', function() { FormatDatesInPreview('excel', 'xlsx'); });
|
||||
EOF
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
return parent:: DisplayFormPart($oP, $sPartId);
|
||||
}
|
||||
}
|
||||
|
||||
protected function SuggestField($sClass, $sAttCode)
|
||||
{
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id': // replace 'id' by 'friendlyname'
|
||||
$sAttCode = 'friendlyname';
|
||||
break;
|
||||
|
||||
default:
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeExternalKey)
|
||||
{
|
||||
$sAttCode .= '_friendlyname';
|
||||
}
|
||||
}
|
||||
|
||||
return parent::SuggestField($sClass, $sAttCode);
|
||||
}
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
}
|
||||
return '<div class="text-preview">'.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
|
||||
protected function GetValue($oObj, $sAttCode)
|
||||
{
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
$sRet = $oObj->GetKey();
|
||||
break;
|
||||
|
||||
default:
|
||||
$value = $oObj->Get($sAttCode);
|
||||
if ($value instanceOf ormCaseLog)
|
||||
{
|
||||
if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
|
||||
{
|
||||
$sText = $value->GetText();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sText = $value->GetAsPlainText();
|
||||
}
|
||||
// Extract the case log as text and remove the "===" which make Excel think that the cell contains a formula the next time you edit it!
|
||||
$sRet = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $sText));
|
||||
}
|
||||
else if ($value instanceOf DBObjectSet)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
|
||||
}
|
||||
else if ($value instanceOf ormDocument)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
// Date and times are formatted using the ISO encoding, not the localized format
|
||||
if ($oAttDef->IsNull($value))
|
||||
{
|
||||
// NOt a valid date
|
||||
$sRet = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = $value;
|
||||
}
|
||||
}
|
||||
else if (array_key_exists('formatted_text', $this->aStatusInfo) && $this->aStatusInfo['formatted_text'])
|
||||
{
|
||||
$sRet = $oAttDef->GetEditValue($value, $oObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = $oAttDef->GetAsPlainText($value, $oObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetHeader()
|
||||
{
|
||||
$oSet = new DBObjectSet($this->oSearch);
|
||||
$this->aStatusInfo['status'] = 'retrieving';
|
||||
$this->aStatusInfo['tmp_file'] = $this->MakeTmpFile('data');
|
||||
$this->aStatusInfo['position'] = 0;
|
||||
$this->aStatusInfo['total'] = $oSet->Count();
|
||||
|
||||
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
|
||||
{
|
||||
$sExtendedAttCode = $aFieldSpec['sFieldSpec'];
|
||||
$sAttCode = $aFieldSpec['sAttCode'];
|
||||
$sColLabel = $aFieldSpec['sColLabel'];
|
||||
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
$sType = '0';
|
||||
break;
|
||||
|
||||
default:
|
||||
$oAttDef = MetaModel::GetAttributeDef($aFieldSpec['sClass'], $aFieldSpec['sAttCode']);
|
||||
$sType = 'string';
|
||||
if($oAttDef instanceof AttributeDate)
|
||||
{
|
||||
$sType = 'date';
|
||||
}
|
||||
else if($oAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
$sType = 'datetime';
|
||||
}
|
||||
}
|
||||
$aTableHeaders[] = array('label' => $sColLabel, 'type' => $sType);
|
||||
}
|
||||
|
||||
$sRow = json_encode($aTableHeaders);
|
||||
$hFile = @fopen($this->aStatusInfo['tmp_file'], 'ab');
|
||||
if ($hFile === false)
|
||||
{
|
||||
throw new Exception('ExcelBulkExport: Failed to open temporary data file: "'.$this->aStatusInfo['tmp_file'].'" for writing.');
|
||||
}
|
||||
fwrite($hFile, $sRow."\n");
|
||||
fclose($hFile);
|
||||
return '';
|
||||
}
|
||||
|
||||
public function GetNextChunk(&$aStatus)
|
||||
{
|
||||
$sRetCode = 'run';
|
||||
$iPercentage = 0;
|
||||
|
||||
$hFile = fopen($this->aStatusInfo['tmp_file'], 'ab');
|
||||
|
||||
$oSet = new DBObjectSet($this->oSearch);
|
||||
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
|
||||
$this->OptimizeColumnLoad($oSet);
|
||||
|
||||
$iCount = 0;
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
|
||||
while($aRow = $oSet->FetchAssoc())
|
||||
{
|
||||
set_time_limit($iLoopTimeLimit);
|
||||
$aData = array();
|
||||
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
|
||||
{
|
||||
$sAlias = $aFieldSpec['sAlias'];
|
||||
$sAttCode = $aFieldSpec['sAttCode'];
|
||||
|
||||
$oObj = $aRow[$sAlias];
|
||||
$sField = '';
|
||||
if ($oObj)
|
||||
{
|
||||
$sField = $this->GetValue($oObj, $sAttCode);
|
||||
}
|
||||
$aData[] = $sField;
|
||||
}
|
||||
fwrite($hFile, json_encode($aData)."\n");
|
||||
$iCount++;
|
||||
}
|
||||
set_time_limit($iPreviousTimeLimit);
|
||||
$this->aStatusInfo['position'] += $this->iChunkSize;
|
||||
if ($this->aStatusInfo['total'] == 0)
|
||||
{
|
||||
$iPercentage = 100;
|
||||
$sRetCode = 'done'; // Next phase (GetFooter) will be to build the xlsx file
|
||||
}
|
||||
else
|
||||
{
|
||||
$iPercentage = floor(min(100.0, 100.0*$this->aStatusInfo['position']/$this->aStatusInfo['total']));
|
||||
}
|
||||
if ($iCount < $this->iChunkSize)
|
||||
{
|
||||
$sRetCode = 'done';
|
||||
}
|
||||
$aStatus = array('code' => $sRetCode, 'message' => Dict::S('Core:BulkExport:RetrievingData'), 'percentage' => $iPercentage);
|
||||
return ''; // The actual XLSX file is built in GetFooter();
|
||||
}
|
||||
|
||||
public function GetFooter()
|
||||
{
|
||||
$hFile = @fopen($this->aStatusInfo['tmp_file'], 'rb');
|
||||
if ($hFile === false)
|
||||
{
|
||||
throw new Exception('ExcelBulkExport: Failed to open temporary data file: "'.$this->aStatusInfo['tmp_file'].'" for reading.');
|
||||
}
|
||||
$sHeaders = fgets($hFile);
|
||||
$aHeaders = json_decode($sHeaders, true);
|
||||
|
||||
$aData = array();
|
||||
while($sLine = fgets($hFile))
|
||||
{
|
||||
$aRow = json_decode($sLine);
|
||||
$aData[] = $aRow;
|
||||
}
|
||||
fclose($hFile);
|
||||
|
||||
$fStartExcel = microtime(true);
|
||||
$writer = new XLSXWriter();
|
||||
$oDateTimeFormat = new DateTimeFormat($this->aStatusInfo['date_format']);
|
||||
$writer->setDateTimeFormat($oDateTimeFormat->ToExcel());
|
||||
$oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat());
|
||||
$writer->setDateFormat($oDateFormat->ToExcel());
|
||||
$writer->setAuthor(UserRights::GetUserFriendlyName());
|
||||
$aHeaderTypes = array();
|
||||
$aHeaderNames = array();
|
||||
foreach($aHeaders as $Header)
|
||||
{
|
||||
$aHeaderNames[] = $Header['label'];
|
||||
$aHeaderTypes[] = $Header['type'];
|
||||
}
|
||||
$writer->writeSheet($aData,'Sheet1', $aHeaderTypes, $aHeaderNames);
|
||||
$fExcelTime = microtime(true) - $fStartExcel;
|
||||
//$this->aStatistics['excel_build_duration'] = $fExcelTime;
|
||||
|
||||
$fTime = microtime(true);
|
||||
$data = $writer->writeToString();
|
||||
$fExcelSaveTime = microtime(true) - $fTime;
|
||||
//$this->aStatistics['excel_write_duration'] = $fExcelSaveTime;
|
||||
|
||||
@unlink($this->aStatusInfo['tmp_file']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function GetMimeType()
|
||||
{
|
||||
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
}
|
||||
|
||||
public function GetFileExtension()
|
||||
{
|
||||
return 'xlsx';
|
||||
}
|
||||
|
||||
public function GetSupportedFormats()
|
||||
{
|
||||
return array('xlsx' => Dict::S('Core:BulkExport:XLSXFormat'));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
110
core/expressioncache.class.inc.php
Normal file
110
core/expressioncache.class.inc.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
// Copyright (c) 2010-2017 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/>
|
||||
//
|
||||
|
||||
class ExpressionCache
|
||||
{
|
||||
static private $aCache = array();
|
||||
|
||||
static public function GetCachedExpression($sClass, $sAttCode)
|
||||
{
|
||||
// read current cache
|
||||
@include_once (static::GetCacheFileName());
|
||||
|
||||
$oExpr = null;
|
||||
$sKey = static::GetKey($sClass, $sAttCode);
|
||||
if (array_key_exists($sKey, static::$aCache))
|
||||
{
|
||||
$oExpr = static::$aCache[$sKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (class_exists('ExpressionCacheData'))
|
||||
{
|
||||
if (array_key_exists($sKey, ExpressionCacheData::$aCache))
|
||||
{
|
||||
$sVal = ExpressionCacheData::$aCache[$sKey];
|
||||
$oExpr = unserialize($sVal);
|
||||
static::$aCache[$sKey] = $oExpr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $oExpr;
|
||||
}
|
||||
|
||||
|
||||
static public function Warmup()
|
||||
{
|
||||
$sFilePath = static::GetCacheFileName();
|
||||
|
||||
if (!is_file($sFilePath))
|
||||
{
|
||||
$content = <<<EOF
|
||||
<?php
|
||||
// Copyright (c) 2010-2017 Combodo SARL
|
||||
// Generated Expression Cache file
|
||||
|
||||
class ExpressionCacheData
|
||||
{
|
||||
static \$aCache = array(
|
||||
EOF;
|
||||
|
||||
foreach(MetaModel::GetClasses() as $sClass)
|
||||
{
|
||||
$content .= static::GetSerializedExpression($sClass, 'friendlyname');
|
||||
if (MetaModel::IsObsoletable($sClass))
|
||||
{
|
||||
$content .= static::GetSerializedExpression($sClass, 'obsolescence_flag');
|
||||
}
|
||||
}
|
||||
|
||||
$content .= <<<EOF
|
||||
);
|
||||
}
|
||||
EOF;
|
||||
|
||||
file_put_contents($sFilePath, $content);
|
||||
}
|
||||
}
|
||||
|
||||
static private function GetSerializedExpression($sClass, $sAttCode)
|
||||
{
|
||||
$sKey = static::GetKey($sClass, $sAttCode);
|
||||
$oExpr = DBObjectSearch::GetPolymorphicExpression($sClass, $sAttCode);
|
||||
return "'".$sKey."' => '".serialize($oExpr)."',\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sClass
|
||||
* @param $sAttCode
|
||||
* @return string
|
||||
*/
|
||||
static private function GetKey($sClass, $sAttCode)
|
||||
{
|
||||
return $sClass.'::'.$sAttCode;
|
||||
}
|
||||
|
||||
public static function GetCacheFileName()
|
||||
{
|
||||
return utils::GetCachePath().'expressioncache.php';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
204
core/htmlbulkexport.class.inc.php
Normal file
204
core/htmlbulkexport.class.inc.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
// Copyright (C) 2015 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/>
|
||||
|
||||
/**
|
||||
* Bulk export: HTML export
|
||||
*
|
||||
* @copyright Copyright (C) 2015 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class HTMLBulkExport extends TabularBulkExport
|
||||
{
|
||||
public function DisplayUsage(Page $oP)
|
||||
{
|
||||
$oP->p(" * html format options:");
|
||||
$oP->p(" *\tfields: (mandatory) the comma separated list of field codes to export (e.g: name,org_id,service_name...).");
|
||||
}
|
||||
|
||||
public function EnumFormParts()
|
||||
{
|
||||
return array_merge(parent::EnumFormParts(), array('interactive_fields_html' => array('interactive_fields_html')));
|
||||
}
|
||||
|
||||
public function DisplayFormPart(WebPage $oP, $sPartId)
|
||||
{
|
||||
switch($sPartId)
|
||||
{
|
||||
case 'interactive_fields_html':
|
||||
$this->GetInteractiveFieldsWidget($oP, 'interactive_fields_html');
|
||||
break;
|
||||
|
||||
default:
|
||||
return parent:: DisplayFormPart($oP, $sPartId);
|
||||
}
|
||||
}
|
||||
|
||||
protected function GetSampleData($oObj, $sAttCode)
|
||||
{
|
||||
if ($sAttCode != 'id')
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
$sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date';
|
||||
return '<div class="'.$sClass.'" data-date="'.$oObj->Get($sAttCode).'">'.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'</div>';
|
||||
}
|
||||
}
|
||||
return $this->GetValue($oObj, $sAttCode);
|
||||
}
|
||||
|
||||
protected function GetValue($oObj, $sAttCode)
|
||||
{
|
||||
switch($sAttCode)
|
||||
{
|
||||
case 'id':
|
||||
$sRet = $oObj->GetHyperlink();
|
||||
break;
|
||||
|
||||
default:
|
||||
$value = $oObj->Get($sAttCode);
|
||||
if ($value instanceof ormCaseLog)
|
||||
{
|
||||
$sRet = $value->GetAsSimpleHtml();
|
||||
}
|
||||
elseif ($value instanceof ormStopWatch)
|
||||
{
|
||||
$sRet = $value->GetTimeSpent();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = $oObj->GetAsHtml($sAttCode);
|
||||
}
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
public function GetHeader()
|
||||
{
|
||||
$sData = '';
|
||||
|
||||
$oSet = new DBObjectSet($this->oSearch);
|
||||
$this->aStatusInfo['status'] = 'running';
|
||||
$this->aStatusInfo['position'] = 0;
|
||||
$this->aStatusInfo['total'] = $oSet->Count();
|
||||
|
||||
$sData .= "<table class=\"listResults\">\n";
|
||||
$sData .= "<thead>\n";
|
||||
$sData .= "<tr>\n";
|
||||
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
|
||||
{
|
||||
$sData .= "<th>".$aFieldSpec['sColLabel']."</th>\n";
|
||||
}
|
||||
$sData .= "</tr>\n";
|
||||
$sData .= "</thead>\n";
|
||||
$sData .= "<tbody>\n";
|
||||
return $sData;
|
||||
}
|
||||
|
||||
public function GetNextChunk(&$aStatus)
|
||||
{
|
||||
$sRetCode = 'run';
|
||||
$iPercentage = 0;
|
||||
|
||||
$oSet = new DBObjectSet($this->oSearch);
|
||||
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
|
||||
$this->OptimizeColumnLoad($oSet);
|
||||
|
||||
$sFirstAlias = $this->oSearch->GetClassAlias();
|
||||
|
||||
$iCount = 0;
|
||||
$sData = '';
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
|
||||
while($aRow = $oSet->FetchAssoc())
|
||||
{
|
||||
set_time_limit($iLoopTimeLimit);
|
||||
$oMainObj = $aRow[$sFirstAlias];
|
||||
$sHilightClass = '';
|
||||
if ($oMainObj)
|
||||
{
|
||||
$sHilightClass = $aRow[$sFirstAlias]->GetHilightClass();
|
||||
}
|
||||
if ($sHilightClass != '')
|
||||
{
|
||||
$sData .= "<tr class=\"$sHilightClass\">";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sData .= "<tr>";
|
||||
}
|
||||
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
|
||||
{
|
||||
$sAlias = $aFieldSpec['sAlias'];
|
||||
$sAttCode = $aFieldSpec['sAttCode'];
|
||||
|
||||
$oObj = $aRow[$sAlias];
|
||||
$sField = '';
|
||||
if ($oObj)
|
||||
{
|
||||
$sField = $this->GetValue($oObj, $sAttCode);
|
||||
}
|
||||
$sValue = ($sField === '') ? ' ' : $sField;
|
||||
$sData .= "<td>$sValue</td>";
|
||||
}
|
||||
$sData .= "</tr>";
|
||||
$iCount++;
|
||||
}
|
||||
set_time_limit($iPreviousTimeLimit);
|
||||
$this->aStatusInfo['position'] += $this->iChunkSize;
|
||||
if ($this->aStatusInfo['total'] == 0)
|
||||
{
|
||||
$iPercentage = 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
$iPercentage = floor(min(100.0, 100.0*$this->aStatusInfo['position']/$this->aStatusInfo['total']));
|
||||
}
|
||||
|
||||
if ($iCount < $this->iChunkSize)
|
||||
{
|
||||
$sRetCode = 'done';
|
||||
}
|
||||
|
||||
$aStatus = array('code' => $sRetCode, 'message' => Dict::S('Core:BulkExport:RetrievingData'), 'percentage' => $iPercentage);
|
||||
return $sData;
|
||||
}
|
||||
|
||||
public function GetFooter()
|
||||
{
|
||||
$sData = "</tbody>\n";
|
||||
$sData .= "</table>\n";
|
||||
return $sData;
|
||||
}
|
||||
|
||||
public function GetSupportedFormats()
|
||||
{
|
||||
return array('html' => Dict::S('Core:BulkExport:HTMLFormat'));
|
||||
}
|
||||
|
||||
public function GetMimeType()
|
||||
{
|
||||
return 'text/html';
|
||||
}
|
||||
|
||||
public function GetFileExtension()
|
||||
{
|
||||
return 'html';
|
||||
}
|
||||
}
|
||||
381
core/htmlsanitizer.class.inc.php
Normal file
381
core/htmlsanitizer.class.inc.php
Normal file
@@ -0,0 +1,381 @@
|
||||
<?php
|
||||
// Copyright (C) 2016-2017 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/>
|
||||
/**
|
||||
* Base class for all possible implementations of HTML Sanitization
|
||||
*/
|
||||
abstract class HTMLSanitizer
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// Do nothing..
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the given HTML document
|
||||
* @param string $sHTML
|
||||
* @return string
|
||||
*/
|
||||
abstract public function DoSanitize($sHTML);
|
||||
|
||||
/**
|
||||
* Sanitize an HTML string with the configured sanitizer, falling back to HTMLDOMSanitizer in case of Exception or invalid configuration
|
||||
* @param string $sHTML
|
||||
* @return string
|
||||
*/
|
||||
public static function Sanitize($sHTML)
|
||||
{
|
||||
$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 = 'HTMLDOMSanitizer';
|
||||
}
|
||||
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
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
return $sCleanHTML;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy HTMLSanitizer which does nothing at all!
|
||||
* Can be used if HTML Sanitization is not important
|
||||
* (for example when importing "safe" data during an on-boarding)
|
||||
* and performance is at stake
|
||||
*
|
||||
*/
|
||||
class HTMLNullSanitizer extends HTMLSanitizer
|
||||
{
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
* @see HTMLSanitizer::Sanitize()
|
||||
*/
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
return $sHTML;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A standard-compliant HTMLSanitizer based on the HTMLPurifier library by Edward Z. Yang
|
||||
* Complete but quite slow
|
||||
* http://htmlpurifier.org
|
||||
*/
|
||||
/*
|
||||
class HTMLPurifierSanitizer extends HTMLSanitizer
|
||||
{
|
||||
protected static $oPurifier = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (self::$oPurifier == null)
|
||||
{
|
||||
$sLibPath = APPROOT.'lib/htmlpurifier/HTMLPurifier.auto.php';
|
||||
if (!file_exists($sLibPath))
|
||||
{
|
||||
throw new Exception("Missing library '$sLibPath', cannot use HTMLPurifierSanitizer.");
|
||||
}
|
||||
require_once($sLibPath);
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
$sCleanHtml = self::$oPurifier->purify($sHTML);
|
||||
return $sCleanHtml;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
class HTMLDOMSanitizer extends HTMLSanitizer
|
||||
{
|
||||
protected $oDoc;
|
||||
protected static $aTagsWhiteList = array(
|
||||
'html' => array(),
|
||||
'body' => array(),
|
||||
'a' => array('href', 'name', 'style', 'target', 'title'),
|
||||
'p' => array('style'),
|
||||
'blockquote' => array('style'),
|
||||
'br' => array(),
|
||||
'span' => array('style'),
|
||||
'div' => array('style'),
|
||||
'b' => array(),
|
||||
'i' => array(),
|
||||
'u' => array(),
|
||||
'em' => array(),
|
||||
'strong' => array(),
|
||||
'img' => array('src', 'style', 'alt', 'title'),
|
||||
'ul' => array('style'),
|
||||
'ol' => array('style'),
|
||||
'li' => array('style'),
|
||||
'h1' => array('style'),
|
||||
'h2' => array('style'),
|
||||
'h3' => array('style'),
|
||||
'h4' => array('style'),
|
||||
'nav' => array('style'),
|
||||
'section' => array('style'),
|
||||
'code' => array('style'),
|
||||
'table' => array('style', 'width', 'summary', 'align', 'border', 'cellpadding', 'cellspacing'),
|
||||
'thead' => array('style'),
|
||||
'tbody' => array('style'),
|
||||
'tr' => array('style'),
|
||||
'td' => array('style', 'colspan'),
|
||||
'th' => array('style'),
|
||||
'fieldset' => array('style'),
|
||||
'legend' => array('style'),
|
||||
'font' => array('face', 'color', 'style', 'size'),
|
||||
'big' => array(),
|
||||
'small' => array(),
|
||||
'tt' => array(),
|
||||
'code' => array(),
|
||||
'kbd' => array(),
|
||||
'samp' => array(),
|
||||
'var' => array(),
|
||||
'del' => array(),
|
||||
's' => array(), // strikethrough
|
||||
'ins' => array(),
|
||||
'cite' => array(),
|
||||
'q' => array(),
|
||||
'hr' => array('style'),
|
||||
'pre' => array(),
|
||||
'center' => array(),
|
||||
'caption' => array(),
|
||||
);
|
||||
|
||||
protected static $aAttrsWhiteList = array(
|
||||
'src' => '/^(http:|https:|data:)/i',
|
||||
);
|
||||
|
||||
protected static $aStylesWhiteList = array(
|
||||
'background-color', 'color', 'float', 'font', 'font-style', 'font-size', 'font-family', 'padding', 'margin', 'border', 'cellpadding', 'cellspacing', 'bordercolor', 'border-collapse', 'width', 'height', 'text-align',
|
||||
);
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Building href validation pattern from url and email validation patterns as the patterns are not used the same way in HTML content than in standard attributes value.
|
||||
// eg. "foo@bar.com" vs "mailto:foo@bar.com?subject=Title&body=Hello%20world"
|
||||
if (!array_key_exists('href', self::$aAttrsWhiteList))
|
||||
{
|
||||
// Regular urls
|
||||
$sUrlPattern = utils::GetConfig()->Get('url_validation_pattern');
|
||||
// Mailto urls
|
||||
$sMailtoPattern = '(mailto:(' . utils::GetConfig()->Get('email_validation_pattern') . ')(?:\?(?:subject|body)=([a-zA-Z0-9+\$_.-]*)(?:&(?:subject|body)=([a-zA-Z0-9+\$_.-]*))?)?)';
|
||||
|
||||
$sPattern = $sUrlPattern . '|' . $sMailtoPattern;
|
||||
$sPattern = '/'.str_replace('/', '\/', $sPattern).'/i';
|
||||
self::$aAttrsWhiteList['href'] = $sPattern;
|
||||
}
|
||||
}
|
||||
|
||||
public function DoSanitize($sHTML)
|
||||
{
|
||||
$this->oDoc = new DOMDocument();
|
||||
$this->oDoc->preserveWhitespace = true;
|
||||
|
||||
// MS outlook implements empty lines by the mean of <p><o:p></o:p></p>
|
||||
// We have to transform that into <p><br></p> (which is how Thunderbird implements empty lines)
|
||||
// Unfortunately, DOMDocument::loadHTML does not take the tag namespaces into account (once loaded there is no way to know if the tag did have a namespace)
|
||||
// therefore we have to do the transformation upfront
|
||||
$sHTML = preg_replace('@<o:p>\s*</o:p>@', '<br>', $sHTML);
|
||||
|
||||
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
|
||||
|
||||
$this->CleanNode($this->oDoc);
|
||||
|
||||
$oXPath = new DOMXPath($this->oDoc);
|
||||
$sXPath = "//body";
|
||||
$oNodesList = $oXPath->query($sXPath);
|
||||
|
||||
if ($oNodesList->length == 0)
|
||||
{
|
||||
// No body, save the whole document
|
||||
$sCleanHtml = $this->oDoc->saveHTML();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
return $sCleanHtml;
|
||||
}
|
||||
|
||||
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 (!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'))
|
||||
{
|
||||
$this->ProcessImage($oNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now remove them
|
||||
foreach($aChildElementsToRemove as $oDomElement)
|
||||
{
|
||||
$oElement->removeChild($oDomElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an extra attribute data-img-id for images which are based on an actual InlineImage
|
||||
* so that we can later reconstruct the full "src" URL when needed
|
||||
* @param DOMNode $oElement
|
||||
*/
|
||||
protected function ProcessImage(DOMNode $oElement)
|
||||
{
|
||||
$sSrc = $oElement->getAttribute('src');
|
||||
$sDownloadUrl = str_replace(array('.', '?'), array('\.', '\?'), INLINEIMAGE_DOWNLOAD_URL); // Escape . and ?
|
||||
$sUrlPattern = '|'.$sDownloadUrl.'([0-9]+)&s=([0-9a-f]+)|';
|
||||
if (preg_match($sUrlPattern, $sSrc, $aMatches))
|
||||
{
|
||||
$oElement->setAttribute('data-img-id', $aMatches[1]);
|
||||
$oElement->setAttribute('data-img-secret', $aMatches[2]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function CleanStyle($sStyle)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
protected function IsValidAttributeContent($sAttributeName, $sValue)
|
||||
{
|
||||
if (array_key_exists($sAttributeName, self::$aAttrsWhiteList))
|
||||
{
|
||||
return preg_match(self::$aAttrsWhiteList[$sAttributeName], $sValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
547
core/inlineimage.class.inc.php
Normal file
547
core/inlineimage.class.inc.php
Normal file
@@ -0,0 +1,547 @@
|
||||
<?php
|
||||
// Copyright (C) 2016 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/>
|
||||
|
||||
define('INLINEIMAGE_DOWNLOAD_URL', 'pages/ajax.document.php?operation=download_inlineimage&id=');
|
||||
|
||||
/**
|
||||
* Persistent classes (internal): store images referenced inside HTML formatted text fields
|
||||
*
|
||||
* @copyright Copyright (C) 2016 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class InlineImage extends DBObject
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
'category' => 'addon',
|
||||
'key_type' => 'autoincrement',
|
||||
'name_attcode' => array('item_class', 'temp_id'),
|
||||
'state_attcode' => '',
|
||||
'reconc_keys' => array(''),
|
||||
'db_table' => 'inline_image',
|
||||
'db_key_field' => 'id',
|
||||
'db_finalclass_field' => '',
|
||||
'indexes' => array(
|
||||
array('temp_id'),
|
||||
array('item_class', 'item_id'),
|
||||
array('item_org_id'),
|
||||
),
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("expire", array("allowed_values"=>null, "sql"=>'expire', "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("temp_id", array("allowed_values"=>null, "sql"=>'temp_id', "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("item_class", array("allowed_values"=>null, "sql"=>'item_class', "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeObjectKey("item_id", array("class_attcode"=>'item_class', "allowed_values"=>null, "sql"=>'item_id', "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("item_org_id", array("allowed_values"=>null, "sql"=>'item_org_id', "default_value"=>'0', "is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("secret", array("allowed_values"=>null, "sql" => "secret", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array(), "always_load_in_tables"=>false)));
|
||||
|
||||
|
||||
MetaModel::Init_SetZListItems('details', array('temp_id', 'item_class', 'item_id', 'item_org_id'));
|
||||
MetaModel::Init_SetZListItems('standard_search', array('temp_id', 'item_class', 'item_id'));
|
||||
MetaModel::Init_SetZListItems('list', array('temp_id', 'item_class', 'item_id' ));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps the given context parameter name to the appropriate filter/search code for this class
|
||||
* @param string $sContextParam Name of the context parameter, e.g. 'org_id'
|
||||
* @return string Filter code, e.g. 'customer_id'
|
||||
*/
|
||||
public static function MapContextParam($sContextParam)
|
||||
{
|
||||
if ($sContextParam == 'org_id')
|
||||
{
|
||||
return 'item_org_id';
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/Update all of the '_item' fields
|
||||
* @param DBObject $oItem Container item
|
||||
* @return void
|
||||
*/
|
||||
public function SetItem(DBObject $oItem, $bUpdateOnChange = false)
|
||||
{
|
||||
$sClass = get_class($oItem);
|
||||
$iItemId = $oItem->GetKey();
|
||||
|
||||
$this->Set('item_class', $sClass);
|
||||
$this->Set('item_id', $iItemId);
|
||||
|
||||
$aCallSpec = array($sClass, 'MapContextParam');
|
||||
if (is_callable($aCallSpec))
|
||||
{
|
||||
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
|
||||
{
|
||||
$iOrgId = $oItem->Get($sAttCode);
|
||||
if ($iOrgId > 0)
|
||||
{
|
||||
if ($iOrgId != $this->Get('item_org_id'))
|
||||
{
|
||||
$this->Set('item_org_id', $iOrgId);
|
||||
if ($bUpdateOnChange)
|
||||
{
|
||||
$this->DBUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a default value for item_org_id (if relevant...)
|
||||
* @return void
|
||||
*/
|
||||
public function SetDefaultOrgId()
|
||||
{
|
||||
// First check that the organization CAN be fetched from the target class
|
||||
//
|
||||
$sClass = $this->Get('item_class');
|
||||
$aCallSpec = array($sClass, 'MapContextParam');
|
||||
if (is_callable($aCallSpec))
|
||||
{
|
||||
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
|
||||
{
|
||||
// Second: check that the organization CAN be fetched from the current user
|
||||
//
|
||||
if (MetaModel::IsValidClass('Person'))
|
||||
{
|
||||
$aCallSpec = array($sClass, 'MapContextParam');
|
||||
if (is_callable($aCallSpec))
|
||||
{
|
||||
$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
|
||||
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
|
||||
{
|
||||
// OK - try it
|
||||
//
|
||||
$oCurrentPerson = MetaModel::GetObject('Person', UserRights::GetContactId(), false);
|
||||
if ($oCurrentPerson)
|
||||
{
|
||||
$this->Set('item_org_id', $oCurrentPerson->Get($sAttCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When posting a form, finalize the creation of the inline images
|
||||
* related to the specified object
|
||||
*
|
||||
* @param DBObject $oObject
|
||||
*/
|
||||
public static function FinalizeInlineImages(DBObject $oObject)
|
||||
{
|
||||
$iTransactionId = utils::ReadParam('transaction_id', null);
|
||||
if (!is_null($iTransactionId))
|
||||
{
|
||||
// Attach new (temporary) inline images
|
||||
|
||||
$sTempId = utils::GetUploadTempId($iTransactionId);
|
||||
// The object is being created from a form, check if there are pending inline images for this object
|
||||
$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())
|
||||
{
|
||||
$oInlineImage->SetItem($oObject);
|
||||
$oInlineImage->Set('temp_id', '');
|
||||
$oInlineImage->DBUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the pending images if the form is not submitted
|
||||
* @param string $sTempId
|
||||
*/
|
||||
public static function OnFormCancel($sTempId)
|
||||
{
|
||||
// Delete all "pending" InlineImages for this form
|
||||
$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())
|
||||
{
|
||||
$oInlineImage->DBDelete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the supplied HTML fragment to rebuild the attribute src="" for images
|
||||
* that refer to an InlineImage (detected via the attribute data-img-id="") so that
|
||||
* the URL is consistent with the current URL of the application.
|
||||
* @param string $sHtml The HTML fragment to process
|
||||
* @return string The modified HTML
|
||||
*/
|
||||
public static function FixUrls($sHtml)
|
||||
{
|
||||
$aNeedles = array();
|
||||
$aReplacements = array();
|
||||
// Find img tags with an attribute data-img-id
|
||||
if (preg_match_all('/<img ([^>]*)data-img-id="([0-9]+)"([^>]*)>/i', $sHtml, $aMatches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE))
|
||||
{
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL;
|
||||
foreach($aMatches as $aImgInfo)
|
||||
{
|
||||
$sImgTag = $aImgInfo[0][0];
|
||||
$sSecret = '';
|
||||
if (preg_match('/data-img-secret="([0-9a-f]+)"/', $sImgTag, $aSecretMatches))
|
||||
{
|
||||
$sSecret = '&s='.$aSecretMatches[1];
|
||||
}
|
||||
$sAttId = $aImgInfo[2][0];
|
||||
|
||||
$sNewImgTag = preg_replace('/src="[^"]+"/', 'src="'.htmlentities($sUrl.$sAttId.$sSecret, ENT_QUOTES, 'UTF-8').'"', $sImgTag); // preserve other attributes, must convert & to & to be idempotent with CKEditor
|
||||
$aNeedles[] = $sImgTag;
|
||||
$aReplacements[] = $sNewImgTag;
|
||||
}
|
||||
$sHtml = str_replace($aNeedles, $aReplacements, $sHtml);
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the javascript fragment - to be added to "on document ready" - to adjust (on the fly) the width on Inline Images
|
||||
*/
|
||||
public static function FixImagesWidth()
|
||||
{
|
||||
$iMaxWidth = (int)MetaModel::GetConfig()->Get('inline_image_max_display_width', 0);
|
||||
$sJS = '';
|
||||
if ($iMaxWidth != 0)
|
||||
{
|
||||
$sJS =
|
||||
<<<EOF
|
||||
$('img[data-img-id]').each(function() {
|
||||
if ($(this).width() > $iMaxWidth)
|
||||
{
|
||||
$(this).css({'max-width': '{$iMaxWidth}px', width: '', height: '', 'max-height': ''});
|
||||
}
|
||||
$(this).addClass('inline-image').attr('href', $(this).attr('src'));
|
||||
}).magnificPopup({type: 'image', closeOnContentClick: true });
|
||||
EOF
|
||||
;
|
||||
}
|
||||
|
||||
return $sJS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an the given mimeType is an image that can be processed by the system
|
||||
* @param string $sMimeType
|
||||
* @return boolean
|
||||
*/
|
||||
public static function IsImage($sMimeType)
|
||||
{
|
||||
if (!function_exists('gd_info')) return false; // no image processing capability on this system
|
||||
|
||||
$bRet = false;
|
||||
$aInfo = gd_info(); // What are the capabilities
|
||||
switch($sMimeType)
|
||||
{
|
||||
case 'image/gif':
|
||||
return $aInfo['GIF Read Support'];
|
||||
break;
|
||||
|
||||
case 'image/jpeg':
|
||||
return $aInfo['JPEG Support'];
|
||||
break;
|
||||
|
||||
case 'image/png':
|
||||
return $aInfo['PNG Support'];
|
||||
break;
|
||||
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize an image so that it fits the maximum width/height defined in the config file
|
||||
* @param ormDocument $oImage The original image stored as an array (content / mimetype / filename)
|
||||
* @return ormDocument The resampled image (or the original one if it already fit)
|
||||
*/
|
||||
public static function ResizeImageToFit(ormDocument $oImage, &$aDimensions = null)
|
||||
{
|
||||
$img = false;
|
||||
switch($oImage->GetMimeType())
|
||||
{
|
||||
case 'image/gif':
|
||||
case 'image/jpeg':
|
||||
case 'image/png':
|
||||
$img = @imagecreatefromstring($oImage->GetData());
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unsupported image type, return the image as-is
|
||||
$aDimensions = null;
|
||||
return $oImage;
|
||||
}
|
||||
if ($img === false)
|
||||
{
|
||||
$aDimensions = null;
|
||||
return $oImage;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Let's scale the image, preserving the transparency for GIFs and PNGs
|
||||
$iWidth = imagesx($img);
|
||||
$iHeight = imagesy($img);
|
||||
$aDimensions = array('width' => $iWidth, 'height' => $iHeight);
|
||||
$iMaxImageSize = (int)MetaModel::GetConfig()->Get('inline_image_max_storage_width', 0);
|
||||
|
||||
if (($iMaxImageSize > 0) && ($iWidth <= $iMaxImageSize) && ($iHeight <= $iMaxImageSize))
|
||||
{
|
||||
// No need to resize
|
||||
return $oImage;
|
||||
}
|
||||
|
||||
$fScale = min($iMaxImageSize / $iWidth, $iMaxImageSize / $iHeight);
|
||||
|
||||
$iNewWidth = $iWidth * $fScale;
|
||||
$iNewHeight = $iHeight * $fScale;
|
||||
|
||||
$aDimensions['width'] = $iNewWidth;
|
||||
$aDimensions['height'] = $iNewHeight;
|
||||
|
||||
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
|
||||
|
||||
// Preserve transparency
|
||||
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
|
||||
{
|
||||
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
|
||||
imagealphablending($new, false);
|
||||
imagesavealpha($new, true);
|
||||
}
|
||||
|
||||
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
|
||||
|
||||
ob_start();
|
||||
switch ($oImage->GetMimeType())
|
||||
{
|
||||
case 'image/gif':
|
||||
imagegif($new); // send image to output buffer
|
||||
break;
|
||||
|
||||
case 'image/jpeg':
|
||||
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
|
||||
break;
|
||||
|
||||
case 'image/png':
|
||||
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
|
||||
break;
|
||||
}
|
||||
$oNewImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
|
||||
@ob_end_clean();
|
||||
|
||||
imagedestroy($img);
|
||||
imagedestroy($new);
|
||||
|
||||
return $oNewImage;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the (localized) textual representation of the max upload size
|
||||
* @return string
|
||||
*/
|
||||
public static function GetMaxUpload()
|
||||
{
|
||||
$iMaxUpload = ini_get('upload_max_filesize');
|
||||
if (!$iMaxUpload)
|
||||
{
|
||||
$sRet = Dict::S('Attachments:UploadNotAllowedOnThisSystem');
|
||||
}
|
||||
else
|
||||
{
|
||||
$iMaxUpload = utils::ConvertToBytes($iMaxUpload);
|
||||
if ($iMaxUpload > 1024*1024*1024)
|
||||
{
|
||||
$sRet = Dict::Format('Attachment:Max_Go', sprintf('%0.2f', $iMaxUpload/(1024*1024*1024)));
|
||||
}
|
||||
else if ($iMaxUpload > 1024*1024)
|
||||
{
|
||||
$sRet = Dict::Format('Attachment:Max_Mo', sprintf('%0.2f', $iMaxUpload/(1024*1024)));
|
||||
}
|
||||
else
|
||||
{
|
||||
$sRet = Dict::Format('Attachment:Max_Ko', sprintf('%0.2f', $iMaxUpload/(1024)));
|
||||
}
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fragment of javascript needed to complete the initialization of
|
||||
* CKEditor when creating/modifying an object
|
||||
*
|
||||
* @param DBObject $oObject The object being edited
|
||||
* @param string $sTempId The concatenation of session_id().'_'.$iTransactionId.
|
||||
* @return string The JS fragment to insert in "on document ready"
|
||||
*/
|
||||
public static function EnableCKEditorImageUpload(DBObject $oObject, $sTempId)
|
||||
{
|
||||
$sObjClass = get_class($oObject);
|
||||
$iObjKey = $oObject->GetKey();
|
||||
|
||||
$sAbsoluteUrlAppRoot = utils::GetAbsoluteUrlAppRoot();
|
||||
$sToggleFullScreen = htmlentities(Dict::S('UI:ToggleFullScreen'), ENT_QUOTES, 'UTF-8');
|
||||
$sAppRootUrl = utils::GetAbsoluteUrlAppRoot();
|
||||
|
||||
return
|
||||
<<<EOF
|
||||
// Hook the file upload of all CKEditor instances
|
||||
$('.htmlEditor').each(function() {
|
||||
var oEditor = $(this).ckeditorGet();
|
||||
oEditor.config.extraPlugins = 'font,uploadimage';
|
||||
oEditor.config.uploadUrl = '$sAbsoluteUrlAppRoot'+'pages/ajax.render.php';
|
||||
oEditor.config.filebrowserBrowseUrl = '$sAbsoluteUrlAppRoot'+'pages/ajax.render.php?operation=cke_browse&temp_id=$sTempId&obj_class=$sObjClass&obj_key=$iObjKey';
|
||||
oEditor.on( 'fileUploadResponse', function( evt ) {
|
||||
var fileLoader = evt.data.fileLoader;
|
||||
var xhr = fileLoader.xhr;
|
||||
var data = evt.data;
|
||||
try {
|
||||
var response = JSON.parse( xhr.responseText );
|
||||
|
||||
// Error message does not need to mean that upload finished unsuccessfully.
|
||||
// It could mean that ex. file name was changes during upload due to naming collision.
|
||||
if ( response.error && response.error.message ) {
|
||||
data.message = response.error.message;
|
||||
}
|
||||
|
||||
// But !uploaded means error.
|
||||
if ( !response.uploaded ) {
|
||||
evt.cancel();
|
||||
} else {
|
||||
data.fileName = response.fileName;
|
||||
data.url = response.url;
|
||||
|
||||
// Do not call the default listener.
|
||||
evt.stop();
|
||||
}
|
||||
} catch ( err ) {
|
||||
// Response parsing error.
|
||||
data.message = fileLoader.lang.filetools.responseError;
|
||||
window.console && window.console.log( xhr.responseText );
|
||||
|
||||
evt.cancel();
|
||||
}
|
||||
} );
|
||||
|
||||
oEditor.on( 'fileUploadRequest', function( evt ) {
|
||||
evt.data.fileLoader.uploadUrl += '?operation=cke_img_upload&temp_id=$sTempId&obj_class=$sObjClass';
|
||||
}, null, null, 4 ); // Listener with priority 4 will be executed before priority 5.
|
||||
|
||||
oEditor.on( 'instanceReady', function() {
|
||||
if(!CKEDITOR.env.iOS && $('#'+oEditor.id+'_toolbox .editor_magnifier').length == 0)
|
||||
{
|
||||
$('#'+oEditor.id+'_toolbox').append('<span class="editor_magnifier" title="$sToggleFullScreen" style="display:block;width:12px;height:11px;border:1px #A6A6A6 solid;cursor:pointer; background-image:url(\\'$sAppRootUrl/images/full-screen.png\\')"> </span>');
|
||||
$('#'+oEditor.id+'_toolbox .editor_magnifier').on('click', function() {
|
||||
oEditor.execCommand('maximize');
|
||||
if ($(this).closest('.cke_maximized').length != 0)
|
||||
{
|
||||
$('#'+oEditor.id+'_toolbar_collapser').trigger('click');
|
||||
}
|
||||
});
|
||||
}
|
||||
if (oEditor.widgets.registered.uploadimage)
|
||||
{
|
||||
oEditor.widgets.registered.uploadimage.onUploaded = function( upload ) {
|
||||
var oData = JSON.parse(upload.xhr.responseText);
|
||||
this.replaceWith( '<img src="' + upload.url + '" ' +
|
||||
'width="' + oData.width + '" ' +
|
||||
'height="' + oData.height + '">' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
EOF
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 3600; // Runs every 3600 seconds
|
||||
}
|
||||
|
||||
public function Process($iTimeLimit)
|
||||
{
|
||||
$sDateLimit = date(AttributeDateTime::GetSQLFormat(), time()); // Every temporary InlineImage/Attachment expired will be deleted
|
||||
|
||||
$iProcessed = 0;
|
||||
$sOQL = "SELECT InlineImage WHERE (item_id = 0) AND (expire < '$sDateLimit')";
|
||||
while (time() < $iTimeLimit)
|
||||
{
|
||||
// Next one ?
|
||||
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('expire' => true) /* order by*/, array(), null, 1 /* limit count */);
|
||||
$oSet->OptimizeColumnLoad(array());
|
||||
$oResult = $oSet->Fetch();
|
||||
if (is_null($oResult))
|
||||
{
|
||||
// Nothing to be done
|
||||
break;
|
||||
}
|
||||
$iProcessed++;
|
||||
$oResult->DBDelete();
|
||||
}
|
||||
|
||||
$iProcessed2 = 0;
|
||||
if (class_exists('Attachment'))
|
||||
{
|
||||
$sOQL = "SELECT Attachment WHERE (item_id = 0) AND (expire < '$sDateLimit')";
|
||||
while (time() < $iTimeLimit)
|
||||
{
|
||||
// Next one ?
|
||||
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('expire' => true) /* order by*/, array(), null, 1 /* limit count */);
|
||||
$oSet->OptimizeColumnLoad(array());
|
||||
$oResult = $oSet->Fetch();
|
||||
if (is_null($oResult))
|
||||
{
|
||||
// Nothing to be done
|
||||
break;
|
||||
}
|
||||
$iProcessed2++;
|
||||
$oResult->DBDelete();
|
||||
}
|
||||
}
|
||||
return "Cleaned $iProcessed old temporary InlineImage(s) and $iProcessed2 old temporary Attachment(s).";
|
||||
}
|
||||
}
|
||||
93
core/introspection.class.inc.php
Normal file
93
core/introspection.class.inc.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
// Copyright (C) 2017 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/>
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* require_once(...'introspection.class.inc.php');
|
||||
*/
|
||||
|
||||
require_once('attributedef.class.inc.php');
|
||||
|
||||
class Introspection
|
||||
{
|
||||
protected $aAttributeHierarchy = array(); // class => child classes
|
||||
protected $aAttributes = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->InitAttributes();
|
||||
}
|
||||
|
||||
protected function InitAttributes()
|
||||
{
|
||||
foreach(get_declared_classes() as $sPHPClass)
|
||||
{
|
||||
$oRefClass = new ReflectionClass($sPHPClass);
|
||||
if ($sPHPClass == 'AttributeDefinition' || $oRefClass->isSubclassOf('AttributeDefinition'))
|
||||
{
|
||||
if ($oParentClass = $oRefClass->getParentClass())
|
||||
{
|
||||
$sParentClass = $oParentClass->getName();
|
||||
if (!array_key_exists($sParentClass, $this->aAttributeHierarchy))
|
||||
{
|
||||
$this->aAttributeHierarchy[$sParentClass] = array();
|
||||
}
|
||||
$this->aAttributeHierarchy[$sParentClass][] = $sPHPClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sParentClass = null;
|
||||
}
|
||||
$this->aAttributes[$sPHPClass] = array(
|
||||
'parent' => $sParentClass,
|
||||
'LoadInObject' => $sPHPClass::LoadInObject(),
|
||||
'LoadFromDB' => $sPHPClass::LoadFromDB(),
|
||||
'IsBasedOnDBColumns' => $sPHPClass::IsBasedOnDBColumns(),
|
||||
'IsBasedOnOQLExpression' => $sPHPClass::IsBasedOnOQLExpression(),
|
||||
'IsExternalField' => $sPHPClass::IsExternalField(),
|
||||
'IsScalar' => $sPHPClass::IsScalar(),
|
||||
'IsLinkset' => $sPHPClass::IsLinkset(),
|
||||
'IsHierarchicalKey' => $sPHPClass::IsHierarchicalKey(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
public function GetAttributes()
|
||||
{
|
||||
return $this->aAttributes;
|
||||
}
|
||||
public function GetAttributeHierarchy()
|
||||
{
|
||||
return $this->aAttributeHierarchy;
|
||||
}
|
||||
public function EnumAttributeCharacteristics()
|
||||
{
|
||||
return array(
|
||||
'LoadInObject' => 'Is the value stored in the object itself?',
|
||||
'LoadFromDB' => 'Is the value read from the DB?',
|
||||
'IsBasedOnDBColumns' => 'Is this a value stored within one or several columns?',
|
||||
'IsBasedOnOQLExpression' => 'Is this a value computed after other attributes, by the mean of an OQL expression?',
|
||||
'IsExternalField' => 'Is this a value stored on a related object (external key)?',
|
||||
'IsScalar' => 'Is this a value that makes sense in a SQL/OQL expression?',
|
||||
'IsLinkset' => 'Is this a collection (1-N or N-N)?',
|
||||
'IsHierarchicalKey' => 'Is this attribute an external key pointing to the host class?',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2010-2012 Combodo SARL
|
||||
// Copyright (C) 2010-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* File logging
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -59,88 +59,64 @@ class FileLog
|
||||
$hLogFile = @fopen($this->m_sFile, 'a');
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SetupLog
|
||||
abstract class LogAPI
|
||||
{
|
||||
protected static $m_oFileLog;
|
||||
|
||||
public static function Enable($sTargetFile)
|
||||
{
|
||||
self::$m_oFileLog = new FileLog($sTargetFile);
|
||||
static::$m_oFileLog = new FileLog($sTargetFile);
|
||||
}
|
||||
|
||||
public static function Error($sText)
|
||||
{
|
||||
self::$m_oFileLog->Error($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Error($sText);
|
||||
}
|
||||
}
|
||||
public static function Warning($sText)
|
||||
{
|
||||
self::$m_oFileLog->Warning($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Warning($sText);
|
||||
}
|
||||
}
|
||||
public static function Info($sText)
|
||||
{
|
||||
self::$m_oFileLog->Info($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Info($sText);
|
||||
}
|
||||
}
|
||||
public static function Ok($sText)
|
||||
{
|
||||
self::$m_oFileLog->Ok($sText);
|
||||
if (static::$m_oFileLog)
|
||||
{
|
||||
static::$m_oFileLog->Ok($sText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IssueLog
|
||||
class SetupLog extends LogAPI
|
||||
{
|
||||
protected static $m_oFileLog;
|
||||
|
||||
public static function Enable($sTargetFile)
|
||||
{
|
||||
self::$m_oFileLog = new FileLog($sTargetFile);
|
||||
}
|
||||
public static function Error($sText)
|
||||
{
|
||||
self::$m_oFileLog->Error($sText);
|
||||
}
|
||||
public static function Warning($sText)
|
||||
{
|
||||
self::$m_oFileLog->Warning($sText);
|
||||
}
|
||||
public static function Info($sText)
|
||||
{
|
||||
self::$m_oFileLog->Info($sText);
|
||||
}
|
||||
public static function Ok($sText)
|
||||
{
|
||||
self::$m_oFileLog->Ok($sText);
|
||||
}
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
|
||||
class ToolsLog
|
||||
class IssueLog extends LogAPI
|
||||
{
|
||||
protected static $m_oFileLog;
|
||||
|
||||
public static function Enable($sTargetFile)
|
||||
{
|
||||
self::$m_oFileLog = new FileLog($sTargetFile);
|
||||
}
|
||||
public static function Error($sText)
|
||||
{
|
||||
self::$m_oFileLog->Error($sText);
|
||||
}
|
||||
public static function Warning($sText)
|
||||
{
|
||||
self::$m_oFileLog->Warning($sText);
|
||||
}
|
||||
public static function Info($sText)
|
||||
{
|
||||
self::$m_oFileLog->Info($sText);
|
||||
}
|
||||
public static function Ok($sText)
|
||||
{
|
||||
self::$m_oFileLog->Ok($sText);
|
||||
}
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
|
||||
class ToolsLog extends LogAPI
|
||||
{
|
||||
protected static $m_oFileLog = null;
|
||||
}
|
||||
?>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,21 @@
|
||||
* @copyright Copyright (C) 2013 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Exclude the parent class from the list
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
define('ENUM_CHILD_CLASSES_EXCLUDETOP', 1);
|
||||
/**
|
||||
* Include the parent class in the list
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
define('ENUM_CHILD_CLASSES_ALL', 2);
|
||||
|
||||
|
||||
abstract class ModelReflection
|
||||
{
|
||||
abstract public function GetClassIcon($sClass, $bImgTag = true);
|
||||
@@ -62,6 +76,9 @@ abstract class ModelReflection
|
||||
}
|
||||
|
||||
abstract public function GetIconSelectionField($sCode, $sLabel = '', $defaultValue = '');
|
||||
|
||||
abstract public function GetRootClass($sClass);
|
||||
abstract public function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP);
|
||||
}
|
||||
|
||||
abstract class QueryReflection
|
||||
@@ -69,7 +86,7 @@ abstract class QueryReflection
|
||||
/**
|
||||
* Throws an exception in case of an invalid syntax
|
||||
*/
|
||||
abstract public function __construct($sOQL);
|
||||
abstract public function __construct($sOQL, ModelReflection $oModelReflection);
|
||||
|
||||
abstract public function GetClass();
|
||||
abstract public function GetClassAlias();
|
||||
@@ -222,7 +239,7 @@ class ModelReflectionRuntime extends ModelReflection
|
||||
|
||||
public function GetQuery($sOQL)
|
||||
{
|
||||
return new QueryReflectionRuntime($sOQL);
|
||||
return new QueryReflectionRuntime($sOQL, $this);
|
||||
}
|
||||
|
||||
public function DictString($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||
@@ -234,6 +251,16 @@ class ModelReflectionRuntime extends ModelReflection
|
||||
{
|
||||
return new RunTimeIconSelectionField($sCode, $sLabel, $defaultValue);
|
||||
}
|
||||
|
||||
public function GetRootClass($sClass)
|
||||
{
|
||||
return MetaModel::GetRootClass($sClass);
|
||||
}
|
||||
|
||||
public function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP)
|
||||
{
|
||||
return MetaModel::EnumChildClasses($sClass, $iOption);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +271,7 @@ class QueryReflectionRuntime extends QueryReflection
|
||||
/**
|
||||
* throws an exception in case of a wrong syntax
|
||||
*/
|
||||
public function __construct($sOQL)
|
||||
public function __construct($sOQL, ModelReflection $oModelReflection)
|
||||
{
|
||||
$this->oFilter = DBObjectSearch::FromOQL($sOQL);
|
||||
}
|
||||
|
||||
119
core/moduledesign.class.inc.php
Normal file
119
core/moduledesign.class.inc.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
// Copyright (C) 2015-2016 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/>
|
||||
|
||||
/**
|
||||
* Module specific customizations:
|
||||
* The customizations are done in XML, within a module_design section (itop_design/module_designs/module_design)
|
||||
* The module reads the cusomtizations by the mean of the ModuleDesign API
|
||||
* @package Core
|
||||
*/
|
||||
|
||||
require_once(APPROOT.'application/utils.inc.php');
|
||||
require_once(APPROOT.'core/designdocument.class.inc.php');
|
||||
|
||||
|
||||
/**
|
||||
* Class ModuleDesign
|
||||
*
|
||||
* Usage from within a module:
|
||||
*
|
||||
* // Fetch the design
|
||||
* $oDesign = new ModuleDesign('tagada');
|
||||
*
|
||||
* // Read data from the root node
|
||||
* $oRoot = $oDesign->documentElement;
|
||||
* $oProperties = $oRoot->GetUniqueElement('properties');
|
||||
* $prop1 = $oProperties->GetChildText('property1');
|
||||
* $prop2 = $oProperties->GetChildText('property2');
|
||||
*
|
||||
* // Read data by searching the entire DOM
|
||||
* foreach ($oDesign->GetNodes('/module_design/bricks/brick') as $oBrickNode)
|
||||
* {
|
||||
* $sId = $oBrickNode->getAttribute('id');
|
||||
* $sType = $oBrickNode->getAttribute('xsi:type');
|
||||
* }
|
||||
*
|
||||
* // Search starting a given node
|
||||
* $oBricks = $oDesign->documentElement->GetUniqueElement('bricks');
|
||||
* foreach ($oBricks->GetNodes('brick') as $oBrickNode)
|
||||
* {
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
class ModuleDesign extends \Combodo\iTop\DesignDocument
|
||||
{
|
||||
/**
|
||||
* @param string|null $sDesignSourceId Identifier of the section module_design (generally a module name), null to build an empty design
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($sDesignSourceId = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if (!is_null($sDesignSourceId))
|
||||
{
|
||||
$this->LoadFromCompiledDesigns($sDesignSourceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data where the compiler has left them...
|
||||
* @param $sDesignSourceId String Identifier of the section module_design (generally a module name)
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function LoadFromCompiledDesigns($sDesignSourceId)
|
||||
{
|
||||
$sDesignDir = APPROOT.'env-'.utils::GetCurrentEnvironment().'/core/module_designs/';
|
||||
$sFile = $sDesignDir.$sDesignSourceId.'.xml';
|
||||
if (!file_exists($sFile))
|
||||
{
|
||||
$aFiles = glob($sDesignDir.'/*.xml');
|
||||
if (count($aFiles) == 0)
|
||||
{
|
||||
$sAvailable = 'none!';
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAvailable = array();
|
||||
foreach ($aFiles as $sFile)
|
||||
{
|
||||
$aAvailable[] = "'".basename($sFile, '.xml')."'";
|
||||
}
|
||||
$sAvailable = implode(', ', $aAvailable);
|
||||
}
|
||||
throw new Exception("Could not load module design '$sDesignSourceId'. Available designs: $sAvailable");
|
||||
}
|
||||
|
||||
// Silently keep track of errors
|
||||
libxml_use_internal_errors(true);
|
||||
libxml_clear_errors();
|
||||
$this->load($sFile);
|
||||
//$bValidated = $oDocument->schemaValidate(APPROOT.'setup/itop_design.xsd');
|
||||
$aErrors = libxml_get_errors();
|
||||
if (count($aErrors) > 0)
|
||||
{
|
||||
$aDisplayErrors = array();
|
||||
foreach($aErrors as $oXmlError)
|
||||
{
|
||||
$aDisplayErrors[] = 'Line '.$oXmlError->line.': '.$oXmlError->message;
|
||||
}
|
||||
|
||||
throw new Exception("Invalid XML in '$sFile'. Errors: ".implode(', ', $aDisplayErrors));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2013 Combodo SARL
|
||||
// Copyright (C) 2013-2017 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -24,7 +24,7 @@
|
||||
* Relies on MySQL locks because the API sem_get is not always present in the
|
||||
* installed PHP.
|
||||
*
|
||||
* @copyright Copyright (C) 2013 Combodo SARL
|
||||
* @copyright Copyright (C) 2013-2017 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
class iTopMutex
|
||||
@@ -37,8 +37,26 @@ class iTopMutex
|
||||
public function __construct($sName, $sDBHost = null, $sDBUser = null, $sDBPwd = null)
|
||||
{
|
||||
// Compute the name of a lock for mysql
|
||||
// Note: the name is server-wide!!!
|
||||
$this->sName = 'itop.'.$sName;
|
||||
// Note: names are server-wide!!! So let's make the name specific to this iTop instance
|
||||
$oConfig = MetaModel::GetConfig();
|
||||
if ($oConfig === null)
|
||||
{
|
||||
$oConfig = utils::GetConfig(); // Will return an empty config when called during the setup
|
||||
}
|
||||
$sDBName = $oConfig->GetDBName();
|
||||
$sDBSubname = $oConfig->GetDBSubname();
|
||||
$this->sName = $sName;
|
||||
if (substr($sName, -strlen($sDBName.$sDBSubname)) != $sDBName.$sDBSubname)
|
||||
{
|
||||
// If the name supplied already ends with the expected suffix
|
||||
// don't add it twice, since the setup may try to detect an already
|
||||
// running cron job by its mutex, without knowing if the config already exists or not
|
||||
$this->sName .= $sDBName.$sDBSubname;
|
||||
}
|
||||
|
||||
// Limit the length of the name for MySQL > 5.7.5
|
||||
$this->sName = 'itop.'.md5($this->sName);
|
||||
|
||||
$this->bLocked = false; // Not yet locked
|
||||
|
||||
if (!array_key_exists($this->sName, self::$aAcquiredLocks))
|
||||
@@ -48,7 +66,6 @@ class iTopMutex
|
||||
|
||||
// It is a MUST to create a dedicated session each time a lock is required, because
|
||||
// using GET_LOCK anytime on the same session will RELEASE the current and unique session lock (known issue)
|
||||
$oConfig = utils::GetConfig();
|
||||
$sDBHost = is_null($sDBHost) ? $oConfig->GetDBHost() : $sDBHost;
|
||||
$sDBUser = is_null($sDBUser) ? $oConfig->GetDBUser() : $sDBUser;
|
||||
$sDBPwd = is_null($sDBPwd) ? $oConfig->GetDBPwd() : $sDBPwd;
|
||||
@@ -121,7 +138,43 @@ class iTopMutex
|
||||
$this->bLocked = true;
|
||||
self::$aAcquiredLocks[$this->sName]++;
|
||||
}
|
||||
return ($res === '1');
|
||||
if (($res !== '1') && ($res !== '0'))
|
||||
{
|
||||
$sMsg = 'GET_LOCK('.$this->sName.', 0) returned: '.var_export($res, true).'. Expected values are: 0, 1 or null';
|
||||
IssueLog::Error($sMsg);
|
||||
throw new Exception($sMsg);
|
||||
}
|
||||
return ($res !== '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the mutex is locked WITHOUT TRYING TO ACQUIRE IT
|
||||
* @returns bool True if the mutex is in use, false otherwise
|
||||
*/
|
||||
public function IsLocked()
|
||||
{
|
||||
if ($this->bLocked)
|
||||
{
|
||||
return true; // Already acquired
|
||||
}
|
||||
if (self::$aAcquiredLocks[$this->sName] > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
$res = $this->QueryToScalar("SELECT IS_FREE_LOCK('".$this->sName."')"); // IS_FREE_LOCK detects some error cases that IS_USED_LOCK do not detect
|
||||
if (is_null($res))
|
||||
{
|
||||
$sMsg = "MySQL Error, IS_FREE_LOCK('".$this->sName."') returned null. Error (".mysqli_errno($this->hDBLink).") = '".mysqli_error($this->hDBLink)."'";
|
||||
IssueLog::Error($sMsg);
|
||||
throw new Exception($sMsg);
|
||||
}
|
||||
else if ($res == '1')
|
||||
{
|
||||
// Lock is free
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
php /usr/share/php/PHP/LexerGenerator/cli.php oql-lexer.plex
|
||||
php /usr/share/php/PHP/ParserGenerator/cli.php oql-parser.y
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
c:\itop\php-5.2.3\php.exe -q "C:\itop\PHP-5.2.3\PEAR\PHP\LexerGenerator\cli.php" oql-lexer.plex
|
||||
c:\itop\php-5.2.3\php.exe -q "C:\itop\PHP-5.2.3\PEAR\PHP\ParserGenerator\cli.php" oql-parser.y
|
||||
pause
|
||||
937
core/oql/build/PHP/Lempar.php
Normal file
937
core/oql/build/PHP/Lempar.php
Normal file
@@ -0,0 +1,937 @@
|
||||
<?php
|
||||
/* Driver template for the PHP_ParserGenerator parser generator. (PHP port of LEMON)
|
||||
*/
|
||||
|
||||
/**
|
||||
* This can be used to store both the string representation of
|
||||
* a token, and any useful meta-data associated with the token.
|
||||
*
|
||||
* meta-data should be stored as an array
|
||||
*/
|
||||
class ParseyyToken implements ArrayAccess
|
||||
{
|
||||
public $string = '';
|
||||
public $metadata = array();
|
||||
|
||||
function __construct($s, $m = array())
|
||||
{
|
||||
if ($s instanceof ParseyyToken) {
|
||||
$this->string = $s->string;
|
||||
$this->metadata = $s->metadata;
|
||||
} else {
|
||||
$this->string = (string) $s;
|
||||
if ($m instanceof ParseyyToken) {
|
||||
$this->metadata = $m->metadata;
|
||||
} elseif (is_array($m)) {
|
||||
$this->metadata = $m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __toString()
|
||||
{
|
||||
return $this->string;
|
||||
}
|
||||
|
||||
function offsetExists($offset)
|
||||
{
|
||||
return isset($this->metadata[$offset]);
|
||||
}
|
||||
|
||||
function offsetGet($offset)
|
||||
{
|
||||
return $this->metadata[$offset];
|
||||
}
|
||||
|
||||
function offsetSet($offset, $value)
|
||||
{
|
||||
if ($offset === null) {
|
||||
if (isset($value[0])) {
|
||||
$x = ($value instanceof ParseyyToken) ?
|
||||
$value->metadata : $value;
|
||||
$this->metadata = array_merge($this->metadata, $x);
|
||||
return;
|
||||
}
|
||||
$offset = count($this->metadata);
|
||||
}
|
||||
if ($value === null) {
|
||||
return;
|
||||
}
|
||||
if ($value instanceof ParseyyToken) {
|
||||
if ($value->metadata) {
|
||||
$this->metadata[$offset] = $value->metadata;
|
||||
}
|
||||
} elseif ($value) {
|
||||
$this->metadata[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
function offsetUnset($offset)
|
||||
{
|
||||
unset($this->metadata[$offset]);
|
||||
}
|
||||
}
|
||||
|
||||
/** The following structure represents a single element of the
|
||||
* parser's stack. Information stored includes:
|
||||
*
|
||||
* + The state number for the parser at this level of the stack.
|
||||
*
|
||||
* + The value of the token stored at this level of the stack.
|
||||
* (In other words, the "major" token.)
|
||||
*
|
||||
* + The semantic value stored at this level of the stack. This is
|
||||
* the information used by the action routines in the grammar.
|
||||
* It is sometimes called the "minor" token.
|
||||
*/
|
||||
class ParseyyStackEntry
|
||||
{
|
||||
public $stateno; /* The state-number */
|
||||
public $major; /* The major token value. This is the code
|
||||
** number for the token at this stack level */
|
||||
public $minor; /* The user-supplied minor token value. This
|
||||
** is the value of the token */
|
||||
};
|
||||
|
||||
// code external to the class is included here
|
||||
%%
|
||||
|
||||
// declare_class is output here
|
||||
%%
|
||||
{
|
||||
/* First off, code is included which follows the "include_class" declaration
|
||||
** in the input file. */
|
||||
%%
|
||||
|
||||
/* Next is all token values, as class constants
|
||||
*/
|
||||
/*
|
||||
** These constants (all generated automatically by the parser generator)
|
||||
** specify the various kinds of tokens (terminals) that the parser
|
||||
** understands.
|
||||
**
|
||||
** Each symbol here is a terminal symbol in the grammar.
|
||||
*/
|
||||
%%
|
||||
|
||||
/* Next are that tables used to determine what action to take based on the
|
||||
** current state and lookahead token. These tables are used to implement
|
||||
** functions that take a state number and lookahead value and return an
|
||||
** action integer.
|
||||
**
|
||||
** Suppose the action integer is N. Then the action is determined as
|
||||
** follows
|
||||
**
|
||||
** 0 <= N < self::YYNSTATE Shift N. That is,
|
||||
** push the lookahead
|
||||
** token onto the stack
|
||||
** and goto state N.
|
||||
**
|
||||
** self::YYNSTATE <= N < self::YYNSTATE+self::YYNRULE Reduce by rule N-YYNSTATE.
|
||||
**
|
||||
** N == self::YYNSTATE+self::YYNRULE A syntax error has occurred.
|
||||
**
|
||||
** N == self::YYNSTATE+self::YYNRULE+1 The parser accepts its
|
||||
** input. (and concludes parsing)
|
||||
**
|
||||
** N == self::YYNSTATE+self::YYNRULE+2 No such action. Denotes unused
|
||||
** slots in the yy_action[] table.
|
||||
**
|
||||
** The action table is constructed as a single large static array $yy_action.
|
||||
** Given state S and lookahead X, the action is computed as
|
||||
**
|
||||
** self::$yy_action[self::$yy_shift_ofst[S] + X ]
|
||||
**
|
||||
** If the index value self::$yy_shift_ofst[S]+X is out of range or if the value
|
||||
** self::$yy_lookahead[self::$yy_shift_ofst[S]+X] is not equal to X or if
|
||||
** self::$yy_shift_ofst[S] is equal to self::YY_SHIFT_USE_DFLT, it means that
|
||||
** the action is not in the table and that self::$yy_default[S] should be used instead.
|
||||
**
|
||||
** The formula above is for computing the action when the lookahead is
|
||||
** a terminal symbol. If the lookahead is a non-terminal (as occurs after
|
||||
** a reduce action) then the static $yy_reduce_ofst array is used in place of
|
||||
** the static $yy_shift_ofst array and self::YY_REDUCE_USE_DFLT is used in place of
|
||||
** self::YY_SHIFT_USE_DFLT.
|
||||
**
|
||||
** The following are the tables generated in this section:
|
||||
**
|
||||
** self::$yy_action A single table containing all actions.
|
||||
** self::$yy_lookahead A table containing the lookahead for each entry in
|
||||
** yy_action. Used to detect hash collisions.
|
||||
** self::$yy_shift_ofst For each state, the offset into self::$yy_action for
|
||||
** shifting terminals.
|
||||
** self::$yy_reduce_ofst For each state, the offset into self::$yy_action for
|
||||
** shifting non-terminals after a reduce.
|
||||
** self::$yy_default Default action for each state.
|
||||
*/
|
||||
%%
|
||||
/* The next thing included is series of defines which control
|
||||
** various aspects of the generated parser.
|
||||
** self::YYNOCODE is a number which corresponds
|
||||
** to no legal terminal or nonterminal number. This
|
||||
** number is used to fill in empty slots of the hash
|
||||
** table.
|
||||
** self::YYFALLBACK If defined, this indicates that one or more tokens
|
||||
** have fall-back values which should be used if the
|
||||
** original value of the token will not parse.
|
||||
** self::YYSTACKDEPTH is the maximum depth of the parser's stack.
|
||||
** self::YYNSTATE the combined number of states.
|
||||
** self::YYNRULE the number of rules in the grammar
|
||||
** self::YYERRORSYMBOL is the code number of the error symbol. If not
|
||||
** defined, then do no error processing.
|
||||
*/
|
||||
%%
|
||||
/** The next table maps tokens into fallback tokens. If a construct
|
||||
* like the following:
|
||||
*
|
||||
* %fallback ID X Y Z.
|
||||
*
|
||||
* appears in the grammer, then ID becomes a fallback token for X, Y,
|
||||
* and Z. Whenever one of the tokens X, Y, or Z is input to the parser
|
||||
* but it does not parse, the type of the token is changed to ID and
|
||||
* the parse is retried before an error is thrown.
|
||||
*/
|
||||
static public $yyFallback = array(
|
||||
%%
|
||||
);
|
||||
/**
|
||||
* Turn parser tracing on by giving a stream to which to write the trace
|
||||
* and a prompt to preface each trace message. Tracing is turned off
|
||||
* by making either argument NULL
|
||||
*
|
||||
* Inputs:
|
||||
*
|
||||
* - A stream resource to which trace output should be written.
|
||||
* If NULL, then tracing is turned off.
|
||||
* - A prefix string written at the beginning of every
|
||||
* line of trace output. If NULL, then tracing is
|
||||
* turned off.
|
||||
*
|
||||
* Outputs:
|
||||
*
|
||||
* - None.
|
||||
* @param resource
|
||||
* @param string
|
||||
*/
|
||||
static function Trace($TraceFILE, $zTracePrompt)
|
||||
{
|
||||
if (!$TraceFILE) {
|
||||
$zTracePrompt = 0;
|
||||
} elseif (!$zTracePrompt) {
|
||||
$TraceFILE = 0;
|
||||
}
|
||||
self::$yyTraceFILE = $TraceFILE;
|
||||
self::$yyTracePrompt = $zTracePrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output debug information to output (php://output stream)
|
||||
*/
|
||||
static function PrintTrace()
|
||||
{
|
||||
self::$yyTraceFILE = fopen('php://output', 'w');
|
||||
self::$yyTracePrompt = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @var resource|0
|
||||
*/
|
||||
static public $yyTraceFILE;
|
||||
/**
|
||||
* String to prepend to debug output
|
||||
* @var string|0
|
||||
*/
|
||||
static public $yyTracePrompt;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $yyidx = -1; /* Index of top element in stack */
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $yyerrcnt; /* Shifts left before out of the error */
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $yystack = array(); /* The parser's stack */
|
||||
|
||||
/**
|
||||
* For tracing shifts, the names of all terminals and nonterminals
|
||||
* are required. The following table supplies these names
|
||||
* @var array
|
||||
*/
|
||||
static public $yyTokenName = array(
|
||||
%%
|
||||
);
|
||||
|
||||
/**
|
||||
* For tracing reduce actions, the names of all rules are required.
|
||||
* @var array
|
||||
*/
|
||||
static public $yyRuleName = array(
|
||||
%%
|
||||
);
|
||||
|
||||
/**
|
||||
* This function returns the symbolic name associated with a token
|
||||
* value.
|
||||
* @param int
|
||||
* @return string
|
||||
*/
|
||||
function tokenName($tokenType)
|
||||
{
|
||||
if ($tokenType === 0) {
|
||||
return 'End of Input';
|
||||
}
|
||||
if ($tokenType > 0 && $tokenType < count(self::$yyTokenName)) {
|
||||
return self::$yyTokenName[$tokenType];
|
||||
} else {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following function deletes the value associated with a
|
||||
* symbol. The symbol can be either a terminal or nonterminal.
|
||||
* @param int the symbol code
|
||||
* @param mixed the symbol's value
|
||||
*/
|
||||
static function yy_destructor($yymajor, $yypminor)
|
||||
{
|
||||
switch ($yymajor) {
|
||||
/* Here is inserted the actions which take place when a
|
||||
** terminal or non-terminal is destroyed. This can happen
|
||||
** when the symbol is popped from the stack during a
|
||||
** reduce or during error processing or when a parser is
|
||||
** being destroyed before it is finished parsing.
|
||||
**
|
||||
** Note: during a reduce, the only symbols destroyed are those
|
||||
** which appear on the RHS of the rule, but which are not used
|
||||
** inside the C code.
|
||||
*/
|
||||
%%
|
||||
default: break; /* If no destructor action specified: do nothing */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop the parser's stack once.
|
||||
*
|
||||
* If there is a destructor routine associated with the token which
|
||||
* is popped from the stack, then call it.
|
||||
*
|
||||
* Return the major token number for the symbol popped.
|
||||
* @param ParseyyParser
|
||||
* @return int
|
||||
*/
|
||||
function yy_pop_parser_stack()
|
||||
{
|
||||
if (!count($this->yystack)) {
|
||||
return;
|
||||
}
|
||||
$yytos = array_pop($this->yystack);
|
||||
if (self::$yyTraceFILE && $this->yyidx >= 0) {
|
||||
fwrite(self::$yyTraceFILE,
|
||||
self::$yyTracePrompt . 'Popping ' . self::$yyTokenName[$yytos->major] .
|
||||
"\n");
|
||||
}
|
||||
$yymajor = $yytos->major;
|
||||
self::yy_destructor($yymajor, $yytos->minor);
|
||||
$this->yyidx--;
|
||||
return $yymajor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocate and destroy a parser. Destructors are all called for
|
||||
* all stack elements before shutting the parser down.
|
||||
*/
|
||||
function __destruct()
|
||||
{
|
||||
while ($this->yyidx >= 0) {
|
||||
$this->yy_pop_parser_stack();
|
||||
}
|
||||
if (is_resource(self::$yyTraceFILE)) {
|
||||
fclose(self::$yyTraceFILE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the current state and parser stack, get a list of all
|
||||
* possible lookahead tokens
|
||||
* @param int
|
||||
* @return array
|
||||
*/
|
||||
function yy_get_expected_tokens($token)
|
||||
{
|
||||
$state = $this->yystack[$this->yyidx]->stateno;
|
||||
$expected = self::$yyExpectedTokens[$state];
|
||||
if (in_array($token, self::$yyExpectedTokens[$state], true)) {
|
||||
return $expected;
|
||||
}
|
||||
$stack = $this->yystack;
|
||||
$yyidx = $this->yyidx;
|
||||
do {
|
||||
$yyact = $this->yy_find_shift_action($token);
|
||||
if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
|
||||
// reduce action
|
||||
$done = 0;
|
||||
do {
|
||||
if ($done++ == 100) {
|
||||
$this->yyidx = $yyidx;
|
||||
$this->yystack = $stack;
|
||||
// too much recursion prevents proper detection
|
||||
// so give up
|
||||
return array_unique($expected);
|
||||
}
|
||||
$yyruleno = $yyact - self::YYNSTATE;
|
||||
$this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
|
||||
$nextstate = $this->yy_find_reduce_action(
|
||||
$this->yystack[$this->yyidx]->stateno,
|
||||
self::$yyRuleInfo[$yyruleno]['lhs']);
|
||||
if (isset(self::$yyExpectedTokens[$nextstate])) {
|
||||
$expected += self::$yyExpectedTokens[$nextstate];
|
||||
if (in_array($token,
|
||||
self::$yyExpectedTokens[$nextstate], true)) {
|
||||
$this->yyidx = $yyidx;
|
||||
$this->yystack = $stack;
|
||||
return array_unique($expected);
|
||||
}
|
||||
}
|
||||
if ($nextstate < self::YYNSTATE) {
|
||||
// we need to shift a non-terminal
|
||||
$this->yyidx++;
|
||||
$x = new ParseyyStackEntry;
|
||||
$x->stateno = $nextstate;
|
||||
$x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
|
||||
$this->yystack[$this->yyidx] = $x;
|
||||
continue 2;
|
||||
} elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
|
||||
$this->yyidx = $yyidx;
|
||||
$this->yystack = $stack;
|
||||
// the last token was just ignored, we can't accept
|
||||
// by ignoring input, this is in essence ignoring a
|
||||
// syntax error!
|
||||
return array_unique($expected);
|
||||
} elseif ($nextstate === self::YY_NO_ACTION) {
|
||||
$this->yyidx = $yyidx;
|
||||
$this->yystack = $stack;
|
||||
// input accepted, but not shifted (I guess)
|
||||
return $expected;
|
||||
} else {
|
||||
$yyact = $nextstate;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
break;
|
||||
} while (true);
|
||||
return array_unique($expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the parser state and current parser stack, determine whether
|
||||
* the lookahead token is possible.
|
||||
*
|
||||
* The parser will convert the token value to an error token if not. This
|
||||
* catches some unusual edge cases where the parser would fail.
|
||||
* @param int
|
||||
* @return bool
|
||||
*/
|
||||
function yy_is_expected_token($token)
|
||||
{
|
||||
if ($token === 0) {
|
||||
return true; // 0 is not part of this
|
||||
}
|
||||
$state = $this->yystack[$this->yyidx]->stateno;
|
||||
if (in_array($token, self::$yyExpectedTokens[$state], true)) {
|
||||
return true;
|
||||
}
|
||||
$stack = $this->yystack;
|
||||
$yyidx = $this->yyidx;
|
||||
do {
|
||||
$yyact = $this->yy_find_shift_action($token);
|
||||
if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
|
||||
// reduce action
|
||||
$done = 0;
|
||||
do {
|
||||
if ($done++ == 100) {
|
||||
$this->yyidx = $yyidx;
|
||||
$this->yystack = $stack;
|
||||
// too much recursion prevents proper detection
|
||||
// so give up
|
||||
return true;
|
||||
}
|
||||
$yyruleno = $yyact - self::YYNSTATE;
|
||||
$this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
|
||||
$nextstate = $this->yy_find_reduce_action(
|
||||
$this->yystack[$this->yyidx]->stateno,
|
||||
self::$yyRuleInfo[$yyruleno]['lhs']);
|
||||
if (isset(self::$yyExpectedTokens[$nextstate]) &&
|
||||
in_array($token, self::$yyExpectedTokens[$nextstate], true)) {
|
||||
$this->yyidx = $yyidx;
|
||||
$this->yystack = $stack;
|
||||
return true;
|
||||
}
|
||||
if ($nextstate < self::YYNSTATE) {
|
||||
// we need to shift a non-terminal
|
||||
$this->yyidx++;
|
||||
$x = new ParseyyStackEntry;
|
||||
$x->stateno = $nextstate;
|
||||
$x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
|
||||
$this->yystack[$this->yyidx] = $x;
|
||||
continue 2;
|
||||
} elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
|
||||
$this->yyidx = $yyidx;
|
||||
$this->yystack = $stack;
|
||||
if (!$token) {
|
||||
// end of input: this is valid
|
||||
return true;
|
||||
}
|
||||
// the last token was just ignored, we can't accept
|
||||
// by ignoring input, this is in essence ignoring a
|
||||
// syntax error!
|
||||
return false;
|
||||
} elseif ($nextstate === self::YY_NO_ACTION) {
|
||||
$this->yyidx = $yyidx;
|
||||
$this->yystack = $stack;
|
||||
// input accepted, but not shifted (I guess)
|
||||
return true;
|
||||
} else {
|
||||
$yyact = $nextstate;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
break;
|
||||
} while (true);
|
||||
$this->yyidx = $yyidx;
|
||||
$this->yystack = $stack;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the appropriate action for a parser given the terminal
|
||||
* look-ahead token iLookAhead.
|
||||
*
|
||||
* If the look-ahead token is YYNOCODE, then check to see if the action is
|
||||
* independent of the look-ahead. If it is, return the action, otherwise
|
||||
* return YY_NO_ACTION.
|
||||
* @param int The look-ahead token
|
||||
*/
|
||||
function yy_find_shift_action($iLookAhead)
|
||||
{
|
||||
$stateno = $this->yystack[$this->yyidx]->stateno;
|
||||
|
||||
/* if ($this->yyidx < 0) return self::YY_NO_ACTION; */
|
||||
if (!isset(self::$yy_shift_ofst[$stateno])) {
|
||||
// no shift actions
|
||||
return self::$yy_default[$stateno];
|
||||
}
|
||||
$i = self::$yy_shift_ofst[$stateno];
|
||||
if ($i === self::YY_SHIFT_USE_DFLT) {
|
||||
return self::$yy_default[$stateno];
|
||||
}
|
||||
if ($iLookAhead == self::YYNOCODE) {
|
||||
return self::YY_NO_ACTION;
|
||||
}
|
||||
$i += $iLookAhead;
|
||||
if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
|
||||
self::$yy_lookahead[$i] != $iLookAhead) {
|
||||
if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback)
|
||||
&& ($iFallback = self::$yyFallback[$iLookAhead]) != 0) {
|
||||
if (self::$yyTraceFILE) {
|
||||
fwrite(self::$yyTraceFILE, self::$yyTracePrompt . "FALLBACK " .
|
||||
self::$yyTokenName[$iLookAhead] . " => " .
|
||||
self::$yyTokenName[$iFallback] . "\n");
|
||||
}
|
||||
return $this->yy_find_shift_action($iFallback);
|
||||
}
|
||||
return self::$yy_default[$stateno];
|
||||
} else {
|
||||
return self::$yy_action[$i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the appropriate action for a parser given the non-terminal
|
||||
* look-ahead token $iLookAhead.
|
||||
*
|
||||
* If the look-ahead token is self::YYNOCODE, then check to see if the action is
|
||||
* independent of the look-ahead. If it is, return the action, otherwise
|
||||
* return self::YY_NO_ACTION.
|
||||
* @param int Current state number
|
||||
* @param int The look-ahead token
|
||||
*/
|
||||
function yy_find_reduce_action($stateno, $iLookAhead)
|
||||
{
|
||||
/* $stateno = $this->yystack[$this->yyidx]->stateno; */
|
||||
|
||||
if (!isset(self::$yy_reduce_ofst[$stateno])) {
|
||||
return self::$yy_default[$stateno];
|
||||
}
|
||||
$i = self::$yy_reduce_ofst[$stateno];
|
||||
if ($i == self::YY_REDUCE_USE_DFLT) {
|
||||
return self::$yy_default[$stateno];
|
||||
}
|
||||
if ($iLookAhead == self::YYNOCODE) {
|
||||
return self::YY_NO_ACTION;
|
||||
}
|
||||
$i += $iLookAhead;
|
||||
if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
|
||||
self::$yy_lookahead[$i] != $iLookAhead) {
|
||||
return self::$yy_default[$stateno];
|
||||
} else {
|
||||
return self::$yy_action[$i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a shift action.
|
||||
* @param int The new state to shift in
|
||||
* @param int The major token to shift in
|
||||
* @param mixed the minor token to shift in
|
||||
*/
|
||||
function yy_shift($yyNewState, $yyMajor, $yypMinor)
|
||||
{
|
||||
$this->yyidx++;
|
||||
if ($this->yyidx >= self::YYSTACKDEPTH) {
|
||||
$this->yyidx--;
|
||||
if (self::$yyTraceFILE) {
|
||||
fprintf(self::$yyTraceFILE, "%sStack Overflow!\n", self::$yyTracePrompt);
|
||||
}
|
||||
while ($this->yyidx >= 0) {
|
||||
$this->yy_pop_parser_stack();
|
||||
}
|
||||
/* Here code is inserted which will execute if the parser
|
||||
** stack ever overflows */
|
||||
%%
|
||||
return;
|
||||
}
|
||||
$yytos = new ParseyyStackEntry;
|
||||
$yytos->stateno = $yyNewState;
|
||||
$yytos->major = $yyMajor;
|
||||
$yytos->minor = $yypMinor;
|
||||
array_push($this->yystack, $yytos);
|
||||
if (self::$yyTraceFILE && $this->yyidx > 0) {
|
||||
fprintf(self::$yyTraceFILE, "%sShift %d\n", self::$yyTracePrompt,
|
||||
$yyNewState);
|
||||
fprintf(self::$yyTraceFILE, "%sStack:", self::$yyTracePrompt);
|
||||
for ($i = 1; $i <= $this->yyidx; $i++) {
|
||||
fprintf(self::$yyTraceFILE, " %s",
|
||||
self::$yyTokenName[$this->yystack[$i]->major]);
|
||||
}
|
||||
fwrite(self::$yyTraceFILE,"\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following table contains information about every rule that
|
||||
* is used during the reduce.
|
||||
*
|
||||
* <pre>
|
||||
* array(
|
||||
* array(
|
||||
* int $lhs; Symbol on the left-hand side of the rule
|
||||
* int $nrhs; Number of right-hand side symbols in the rule
|
||||
* ),...
|
||||
* );
|
||||
* </pre>
|
||||
*/
|
||||
static public $yyRuleInfo = array(
|
||||
%%
|
||||
);
|
||||
|
||||
/**
|
||||
* The following table contains a mapping of reduce action to method name
|
||||
* that handles the reduction.
|
||||
*
|
||||
* If a rule is not set, it has no handler.
|
||||
*/
|
||||
static public $yyReduceMap = array(
|
||||
%%
|
||||
);
|
||||
/* Beginning here are the reduction cases. A typical example
|
||||
** follows:
|
||||
** #line <lineno> <grammarfile>
|
||||
** function yy_r0($yymsp){ ... } // User supplied code
|
||||
** #line <lineno> <thisfile>
|
||||
*/
|
||||
%%
|
||||
|
||||
/**
|
||||
* placeholder for the left hand side in a reduce operation.
|
||||
*
|
||||
* For a parser with a rule like this:
|
||||
* <pre>
|
||||
* rule(A) ::= B. { A = 1; }
|
||||
* </pre>
|
||||
*
|
||||
* The parser will translate to something like:
|
||||
*
|
||||
* <code>
|
||||
* function yy_r0(){$this->_retvalue = 1;}
|
||||
* </code>
|
||||
*/
|
||||
private $_retvalue;
|
||||
|
||||
/**
|
||||
* Perform a reduce action and the shift that must immediately
|
||||
* follow the reduce.
|
||||
*
|
||||
* For a rule such as:
|
||||
*
|
||||
* <pre>
|
||||
* A ::= B blah C. { dosomething(); }
|
||||
* </pre>
|
||||
*
|
||||
* This function will first call the action, if any, ("dosomething();" in our
|
||||
* example), and then it will pop three states from the stack,
|
||||
* one for each entry on the right-hand side of the expression
|
||||
* (B, blah, and C in our example rule), and then push the result of the action
|
||||
* back on to the stack with the resulting state reduced to (as described in the .out
|
||||
* file)
|
||||
* @param int Number of the rule by which to reduce
|
||||
*/
|
||||
function yy_reduce($yyruleno)
|
||||
{
|
||||
//int $yygoto; /* The next state */
|
||||
//int $yyact; /* The next action */
|
||||
//mixed $yygotominor; /* The LHS of the rule reduced */
|
||||
//ParseyyStackEntry $yymsp; /* The top of the parser's stack */
|
||||
//int $yysize; /* Amount to pop the stack */
|
||||
$yymsp = $this->yystack[$this->yyidx];
|
||||
if (self::$yyTraceFILE && $yyruleno >= 0
|
||||
&& $yyruleno < count(self::$yyRuleName)) {
|
||||
fprintf(self::$yyTraceFILE, "%sReduce (%d) [%s].\n",
|
||||
self::$yyTracePrompt, $yyruleno,
|
||||
self::$yyRuleName[$yyruleno]);
|
||||
}
|
||||
|
||||
$this->_retvalue = $yy_lefthand_side = null;
|
||||
if (array_key_exists($yyruleno, self::$yyReduceMap)) {
|
||||
// call the action
|
||||
$this->_retvalue = null;
|
||||
$this->{'yy_r' . self::$yyReduceMap[$yyruleno]}();
|
||||
$yy_lefthand_side = $this->_retvalue;
|
||||
}
|
||||
$yygoto = self::$yyRuleInfo[$yyruleno]['lhs'];
|
||||
$yysize = self::$yyRuleInfo[$yyruleno]['rhs'];
|
||||
$this->yyidx -= $yysize;
|
||||
for ($i = $yysize; $i; $i--) {
|
||||
// pop all of the right-hand side parameters
|
||||
array_pop($this->yystack);
|
||||
}
|
||||
$yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto);
|
||||
if ($yyact < self::YYNSTATE) {
|
||||
/* If we are not debugging and the reduce action popped at least
|
||||
** one element off the stack, then we can push the new element back
|
||||
** onto the stack here, and skip the stack overflow test in yy_shift().
|
||||
** That gives a significant speed improvement. */
|
||||
if (!self::$yyTraceFILE && $yysize) {
|
||||
$this->yyidx++;
|
||||
$x = new ParseyyStackEntry;
|
||||
$x->stateno = $yyact;
|
||||
$x->major = $yygoto;
|
||||
$x->minor = $yy_lefthand_side;
|
||||
$this->yystack[$this->yyidx] = $x;
|
||||
} else {
|
||||
$this->yy_shift($yyact, $yygoto, $yy_lefthand_side);
|
||||
}
|
||||
} elseif ($yyact == self::YYNSTATE + self::YYNRULE + 1) {
|
||||
$this->yy_accept();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The following code executes when the parse fails
|
||||
*
|
||||
* Code from %parse_fail is inserted here
|
||||
*/
|
||||
function yy_parse_failed()
|
||||
{
|
||||
if (self::$yyTraceFILE) {
|
||||
fprintf(self::$yyTraceFILE, "%sFail!\n", self::$yyTracePrompt);
|
||||
}
|
||||
while ($this->yyidx >= 0) {
|
||||
$this->yy_pop_parser_stack();
|
||||
}
|
||||
/* Here code is inserted which will be executed whenever the
|
||||
** parser fails */
|
||||
%%
|
||||
}
|
||||
|
||||
/**
|
||||
* The following code executes when a syntax error first occurs.
|
||||
*
|
||||
* %syntax_error code is inserted here
|
||||
* @param int The major type of the error token
|
||||
* @param mixed The minor type of the error token
|
||||
*/
|
||||
function yy_syntax_error($yymajor, $TOKEN)
|
||||
{
|
||||
%%
|
||||
}
|
||||
|
||||
/**
|
||||
* The following is executed when the parser accepts
|
||||
*
|
||||
* %parse_accept code is inserted here
|
||||
*/
|
||||
function yy_accept()
|
||||
{
|
||||
if (self::$yyTraceFILE) {
|
||||
fprintf(self::$yyTraceFILE, "%sAccept!\n", self::$yyTracePrompt);
|
||||
}
|
||||
while ($this->yyidx >= 0) {
|
||||
$stack = $this->yy_pop_parser_stack();
|
||||
}
|
||||
/* Here code is inserted which will be executed whenever the
|
||||
** parser accepts */
|
||||
%%
|
||||
}
|
||||
|
||||
/**
|
||||
* The main parser program.
|
||||
*
|
||||
* The first argument is the major token number. The second is
|
||||
* the token value string as scanned from the input.
|
||||
*
|
||||
* @param int $yymajor the token number
|
||||
* @param mixed $yytokenvalue the token value
|
||||
* @param mixed ... any extra arguments that should be passed to handlers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function doParse($yymajor, $yytokenvalue)
|
||||
{
|
||||
// $yyact; /* The parser action. */
|
||||
// $yyendofinput; /* True if we are at the end of input */
|
||||
$yyerrorhit = 0; /* True if yymajor has invoked an error */
|
||||
|
||||
/* (re)initialize the parser, if necessary */
|
||||
if ($this->yyidx === null || $this->yyidx < 0) {
|
||||
/* if ($yymajor == 0) return; // not sure why this was here... */
|
||||
$this->yyidx = 0;
|
||||
$this->yyerrcnt = -1;
|
||||
$x = new ParseyyStackEntry;
|
||||
$x->stateno = 0;
|
||||
$x->major = 0;
|
||||
$this->yystack = array();
|
||||
array_push($this->yystack, $x);
|
||||
}
|
||||
$yyendofinput = ($yymajor==0);
|
||||
|
||||
if (self::$yyTraceFILE) {
|
||||
fprintf(
|
||||
self::$yyTraceFILE,
|
||||
"%sInput %s\n",
|
||||
self::$yyTracePrompt,
|
||||
self::$yyTokenName[$yymajor]
|
||||
);
|
||||
}
|
||||
|
||||
do {
|
||||
$yyact = $this->yy_find_shift_action($yymajor);
|
||||
if ($yymajor < self::YYERRORSYMBOL
|
||||
&& !$this->yy_is_expected_token($yymajor)
|
||||
) {
|
||||
// force a syntax error
|
||||
$yyact = self::YY_ERROR_ACTION;
|
||||
}
|
||||
if ($yyact < self::YYNSTATE) {
|
||||
$this->yy_shift($yyact, $yymajor, $yytokenvalue);
|
||||
$this->yyerrcnt--;
|
||||
if ($yyendofinput && $this->yyidx >= 0) {
|
||||
$yymajor = 0;
|
||||
} else {
|
||||
$yymajor = self::YYNOCODE;
|
||||
}
|
||||
} elseif ($yyact < self::YYNSTATE + self::YYNRULE) {
|
||||
$this->yy_reduce($yyact - self::YYNSTATE);
|
||||
} elseif ($yyact == self::YY_ERROR_ACTION) {
|
||||
if (self::$yyTraceFILE) {
|
||||
fprintf(
|
||||
self::$yyTraceFILE,
|
||||
"%sSyntax Error!\n",
|
||||
self::$yyTracePrompt
|
||||
);
|
||||
}
|
||||
if (self::YYERRORSYMBOL) {
|
||||
/* A syntax error has occurred.
|
||||
** The response to an error depends upon whether or not the
|
||||
** grammar defines an error token "ERROR".
|
||||
**
|
||||
** This is what we do if the grammar does define ERROR:
|
||||
**
|
||||
** * Call the %syntax_error function.
|
||||
**
|
||||
** * Begin popping the stack until we enter a state where
|
||||
** it is legal to shift the error symbol, then shift
|
||||
** the error symbol.
|
||||
**
|
||||
** * Set the error count to three.
|
||||
**
|
||||
** * Begin accepting and shifting new tokens. No new error
|
||||
** processing will occur until three tokens have been
|
||||
** shifted successfully.
|
||||
**
|
||||
*/
|
||||
if ($this->yyerrcnt < 0) {
|
||||
$this->yy_syntax_error($yymajor, $yytokenvalue);
|
||||
}
|
||||
$yymx = $this->yystack[$this->yyidx]->major;
|
||||
if ($yymx == self::YYERRORSYMBOL || $yyerrorhit ) {
|
||||
if (self::$yyTraceFILE) {
|
||||
fprintf(
|
||||
self::$yyTraceFILE,
|
||||
"%sDiscard input token %s\n",
|
||||
self::$yyTracePrompt,
|
||||
self::$yyTokenName[$yymajor]
|
||||
);
|
||||
}
|
||||
$this->yy_destructor($yymajor, $yytokenvalue);
|
||||
$yymajor = self::YYNOCODE;
|
||||
} else {
|
||||
while ($this->yyidx >= 0
|
||||
&& $yymx != self::YYERRORSYMBOL
|
||||
&& ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE
|
||||
) {
|
||||
$this->yy_pop_parser_stack();
|
||||
}
|
||||
if ($this->yyidx < 0 || $yymajor==0) {
|
||||
$this->yy_destructor($yymajor, $yytokenvalue);
|
||||
$this->yy_parse_failed();
|
||||
$yymajor = self::YYNOCODE;
|
||||
} elseif ($yymx != self::YYERRORSYMBOL) {
|
||||
$u2 = 0;
|
||||
$this->yy_shift($yyact, self::YYERRORSYMBOL, $u2);
|
||||
}
|
||||
}
|
||||
$this->yyerrcnt = 3;
|
||||
$yyerrorhit = 1;
|
||||
} else {
|
||||
/* YYERRORSYMBOL is not defined */
|
||||
/* This is what we do if the grammar does not define ERROR:
|
||||
**
|
||||
** * Report an error message, and throw away the input token.
|
||||
**
|
||||
** * If the input token is $, then fail the parse.
|
||||
**
|
||||
** As before, subsequent error messages are suppressed until
|
||||
** three input tokens have been successfully shifted.
|
||||
*/
|
||||
if ($this->yyerrcnt <= 0) {
|
||||
$this->yy_syntax_error($yymajor, $yytokenvalue);
|
||||
}
|
||||
$this->yyerrcnt = 3;
|
||||
$this->yy_destructor($yymajor, $yytokenvalue);
|
||||
if ($yyendofinput) {
|
||||
$this->yy_parse_failed();
|
||||
}
|
||||
$yymajor = self::YYNOCODE;
|
||||
}
|
||||
} else {
|
||||
$this->yy_accept();
|
||||
$yymajor = self::YYNOCODE;
|
||||
}
|
||||
} while ($yymajor != self::YYNOCODE && $this->yyidx >= 0);
|
||||
}
|
||||
}
|
||||
332
core/oql/build/PHP/LexerGenerator.php
Normal file
332
core/oql/build/PHP/LexerGenerator.php
Normal file
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP_LexerGenerator, a php 5 lexer generator.
|
||||
*
|
||||
* This lexer generator translates a file in a format similar to
|
||||
* re2c ({@link http://re2c.org}) and translates it into a PHP 5-based lexer
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2006, Gregory Beaver <cellog@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the PHP_LexerGenerator nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category php
|
||||
* @package PHP_LexerGenerator
|
||||
* @author Gregory Beaver <cellog@php.net>
|
||||
* @copyright 2006 Gregory Beaver
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version CVS: $Id: LexerGenerator.php 294970 2010-02-12 03:46:38Z clockwerx $
|
||||
* @since File available since Release 0.1.0
|
||||
*/
|
||||
/**
|
||||
* The Lexer generation parser
|
||||
*/
|
||||
require_once 'PHP/LexerGenerator/Parser.php';
|
||||
/**
|
||||
* Hand-written lexer for lex2php format files
|
||||
*/
|
||||
require_once 'PHP/LexerGenerator/Lexer.php';
|
||||
|
||||
/**
|
||||
* The basic home class for the lexer generator. A lexer scans text and
|
||||
* organizes it into tokens for usage by a parser.
|
||||
*
|
||||
* Sample Usage:
|
||||
* <code>
|
||||
* require_once 'PHP/LexerGenerator.php';
|
||||
* $lex = new PHP_LexerGenerator('/path/to/lexerfile.plex');
|
||||
* </code>
|
||||
*
|
||||
* A file named "/path/to/lexerfile.php" will be created.
|
||||
*
|
||||
* File format consists of a PHP file containing specially
|
||||
* formatted comments like so:
|
||||
*
|
||||
* <code>
|
||||
* /*!lex2php
|
||||
* {@*}
|
||||
* </code>
|
||||
*
|
||||
* All lexer definition files must contain at least two lex2php comment blocks:
|
||||
* - 1 regex declaration block
|
||||
* - 1 or more rule declaration blocks
|
||||
*
|
||||
* The first lex2php comment is the regex declaration block and must contain
|
||||
* several processor instruction as well as defining a name for all
|
||||
* regular expressions. Processor instructions start with
|
||||
* a "%" symbol and must be:
|
||||
*
|
||||
* - %counter
|
||||
* - %input
|
||||
* - %token
|
||||
* - %value
|
||||
* - %line
|
||||
*
|
||||
* token and counter should define the class variables used to define lexer input
|
||||
* and the index into the input. token and value should be used to define the class
|
||||
* variables used to store the token number and its textual value. Finally, line
|
||||
* should be used to define the class variable used to define the current line number
|
||||
* of scanning.
|
||||
*
|
||||
* For example:
|
||||
* <code>
|
||||
* /*!lex2php
|
||||
* %counter {$this->N}
|
||||
* %input {$this->data}
|
||||
* %token {$this->token}
|
||||
* %value {$this->value}
|
||||
* %line {%this->linenumber}
|
||||
* {@*}
|
||||
* </code>
|
||||
*
|
||||
* Patterns consist of an identifier containing an letters or an underscore, and
|
||||
* a descriptive match pattern.
|
||||
*
|
||||
* Descriptive match patterns may either be regular expressions (regexes) or
|
||||
* quoted literal strings. Here are some examples:
|
||||
*
|
||||
* <pre>
|
||||
* pattern = "quoted literal"
|
||||
* ANOTHER = /[a-zA-Z_]+/
|
||||
* COMPLEX = @<([a-zA-Z_]+)( +(([a-zA-Z_]+)=((["\'])([^\6]*)\6))+){0,1}>[^<]*</\1>@
|
||||
* </pre>
|
||||
*
|
||||
* Quoted strings must escape the \ and " characters with \" and \\.
|
||||
*
|
||||
* Regex patterns must be in Perl-compatible regular expression format (preg).
|
||||
* special characters (like \t \n or \x3H) can only be used in regexes, all
|
||||
* \ will be escaped in literal strings.
|
||||
*
|
||||
* Sub-patterns may be defined and back-references (like \1) may be used. Any sub-
|
||||
* patterns detected will be passed to the token handler in the variable
|
||||
* $yysubmatches.
|
||||
*
|
||||
* In addition, lookahead expressions, and once-only expressions are allowed.
|
||||
* Lookbehind expressions are impossible (scanning always occurs from the
|
||||
* current position forward), and recursion (?R) can't work and is not allowed.
|
||||
*
|
||||
* <code>
|
||||
* /*!lex2php
|
||||
* %counter {$this->N}
|
||||
* %input {$this->data}
|
||||
* %token {$this->token}
|
||||
* %value {$this->value}
|
||||
* %line {%this->linenumber}
|
||||
* alpha = /[a-zA-Z]/
|
||||
* alphaplus = /[a-zA-Z]+/
|
||||
* number = /[0-9]/
|
||||
* numerals = /[0-9]+/
|
||||
* whitespace = /[ \t\n]+/
|
||||
* blah = "$\""
|
||||
* blahblah = /a\$/
|
||||
* GAMEEND = @(?:1\-0|0\-1|1/2\-1/2)@
|
||||
* PAWNMOVE = /P?[a-h]([2-7]|[18]\=(Q|R|B|N))|P?[a-h]x[a-h]([2-7]|[18]\=(Q|R|B|N))/
|
||||
* {@*}
|
||||
* </code>
|
||||
*
|
||||
* All regexes must be delimited. Any legal preg delimiter can be used (as in @ or / in
|
||||
* the example above)
|
||||
*
|
||||
* Rule lex2php blocks each define a lexer state. You can optionally name the state
|
||||
* with the %statename processor instruction. State names can be used to transfer to
|
||||
* a new lexer state with the yybegin() method
|
||||
*
|
||||
* <code>
|
||||
* /*!lexphp
|
||||
* %statename INITIAL
|
||||
* blah {
|
||||
* $this->yybegin(self::INBLAH);
|
||||
* // note - $this->yybegin(2) would also work
|
||||
* }
|
||||
* {@*}
|
||||
* /*!lex2php
|
||||
* %statename INBLAH
|
||||
* ANYTHING {
|
||||
* $this->yybegin(self::INITIAL);
|
||||
* // note - $this->yybegin(1) would also work
|
||||
* }
|
||||
* {@*}
|
||||
* </code>
|
||||
*
|
||||
* You can maintain a parser state stack simply by using yypushstate() and
|
||||
* yypopstate() instead of yybegin():
|
||||
*
|
||||
* <code>
|
||||
* /*!lexphp
|
||||
* %statename INITIAL
|
||||
* blah {
|
||||
* $this->yypushstate(self::INBLAH);
|
||||
* }
|
||||
* {@*}
|
||||
* /*!lex2php
|
||||
* %statename INBLAH
|
||||
* ANYTHING {
|
||||
* $this->yypopstate();
|
||||
* // now INBLAH doesn't care where it was called from
|
||||
* }
|
||||
* {@*}
|
||||
* </code>
|
||||
*
|
||||
* Code blocks can choose to skip the current token and cycle to the next token by
|
||||
* returning "false"
|
||||
*
|
||||
* <code>
|
||||
* /*!lex2php
|
||||
* WHITESPACE {
|
||||
* return false;
|
||||
* }
|
||||
* {@*}
|
||||
* </code>
|
||||
*
|
||||
* If you wish to re-process the current token in a new state, simply return true.
|
||||
* If you forget to change lexer state, this will cause an unterminated loop,
|
||||
* so be careful!
|
||||
*
|
||||
* <code>
|
||||
* /*!lex2php
|
||||
* "(" {
|
||||
* $this->yypushstate(self::INPARAMS);
|
||||
* return true;
|
||||
* }
|
||||
* {@*}
|
||||
* </code>
|
||||
*
|
||||
* Lastly, if you wish to cycle to the next matching rule, return any value other than
|
||||
* true, false or null:
|
||||
*
|
||||
* <code>
|
||||
* /*!lex2php
|
||||
* "{@" ALPHA {
|
||||
* if ($this->value == '{@internal') {
|
||||
* return 'more';
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
* "{@internal" {
|
||||
* ...
|
||||
* }
|
||||
* {@*}
|
||||
* </code>
|
||||
*
|
||||
* Note that this procedure is exceptionally inefficient, and it would be far better
|
||||
* to take advantage of PHP_LexerGenerator's top-down precedence and instead code:
|
||||
*
|
||||
* <code>
|
||||
* /*!lex2php
|
||||
* "{@internal" {
|
||||
* ...
|
||||
* }
|
||||
* "{@" ALPHA {
|
||||
* ...
|
||||
* }
|
||||
* {@*}
|
||||
* </code>
|
||||
* @package PHP_LexerGenerator
|
||||
* @author Gregory Beaver <cellog@php.net>
|
||||
* @copyright 2006 Gregory Beaver
|
||||
* @license http://www.php.net/license/3_01.txt PHP License 3.01
|
||||
* @version @package_version@
|
||||
* @since Class available since Release 0.1.0
|
||||
* @example TestLexer.plex Example lexer source
|
||||
* @example TestLexer.php Example lexer generated php code
|
||||
* @example usage.php Example usage of PHP_LexerGenerator
|
||||
* @example Lexer.plex File_ChessPGN lexer source (complex)
|
||||
* @example Lexer.php File_ChessPGN lexer generated php code
|
||||
*/
|
||||
|
||||
class PHP_LexerGenerator
|
||||
{
|
||||
/**
|
||||
* Plex file lexer.
|
||||
* @var PHP_LexerGenerator_Lexer
|
||||
*/
|
||||
private $_lex;
|
||||
|
||||
/**
|
||||
* Plex file parser.
|
||||
* @var PHP_LexerGenerator_Parser
|
||||
*/
|
||||
private $_parser;
|
||||
|
||||
/**
|
||||
* Path to the output PHP file.
|
||||
* @var string
|
||||
*/
|
||||
private $_outfile;
|
||||
|
||||
/**
|
||||
* Debug flag. When set, Parser trace information is generated.
|
||||
* @var boolean
|
||||
*/
|
||||
public $debug = false;
|
||||
|
||||
/**
|
||||
* Create a lexer generator and optionally generate a lexer file.
|
||||
*
|
||||
* @param string Optional plex file {@see PHP_LexerGenerator::create}.
|
||||
* @param string Optional output file {@see PHP_LexerGenerator::create}.
|
||||
*/
|
||||
function __construct($lexerfile = '', $outfile = '')
|
||||
{
|
||||
if ($lexerfile) {
|
||||
$this -> create($lexerfile, $outfile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a lexer file from its skeleton plex file.
|
||||
*
|
||||
* @param string Path to the plex file.
|
||||
* @param string Optional path to output file. Default is lexerfile with
|
||||
* extension of ".php".
|
||||
*/
|
||||
function create($lexerfile, $outfile = '')
|
||||
{
|
||||
$this->_lex = new PHP_LexerGenerator_Lexer(file_get_contents($lexerfile));
|
||||
$info = pathinfo($lexerfile);
|
||||
if ($outfile) {
|
||||
$this->outfile = $outfile;
|
||||
} else {
|
||||
$this->outfile = $info['dirname'] . DIRECTORY_SEPARATOR .
|
||||
substr($info['basename'], 0,
|
||||
strlen($info['basename']) - strlen($info['extension'])) . 'php';
|
||||
}
|
||||
$this->_parser = new PHP_LexerGenerator_Parser($this->outfile, $this->_lex);
|
||||
if ($this -> debug) {
|
||||
$this->_parser->PrintTrace();
|
||||
}
|
||||
while ($this->_lex->advance($this->_parser)) {
|
||||
$this->_parser->doParse($this->_lex->token, $this->_lex->value);
|
||||
}
|
||||
$this->_parser->doParse(0, 0);
|
||||
}
|
||||
}
|
||||
//$a = new PHP_LexerGenerator('/development/File_ChessPGN/ChessPGN/Lexer.plex');
|
||||
?>
|
||||
55
core/oql/build/PHP/LexerGenerator/Exception.php
Normal file
55
core/oql/build/PHP/LexerGenerator/Exception.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP_LexerGenerator, a php 5 lexer generator.
|
||||
*
|
||||
* Exception classes for the lexer generator
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2006, Gregory Beaver <cellog@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the PHP_LexerGenerator nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category php
|
||||
* @package PHP_LexerGenerator
|
||||
* @author Gregory Beaver <cellog@php.net>
|
||||
* @copyright 2006 Gregory Beaver
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
|
||||
*/
|
||||
require_once 'PEAR/Exception.php';
|
||||
/**
|
||||
* @package PHP_LexerGenerator
|
||||
* @author Gregory Beaver <cellog@php.net>
|
||||
* @copyright 2006 Gregory Beaver
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version @package_version@
|
||||
* @since File available since Release 0.1.0
|
||||
*/
|
||||
class PHP_LexerGenerator_Exception extends PEAR_Exception {}
|
||||
?>
|
||||
533
core/oql/build/PHP/LexerGenerator/Lexer.php
Normal file
533
core/oql/build/PHP/LexerGenerator/Lexer.php
Normal file
@@ -0,0 +1,533 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP_LexerGenerator, a php 5 lexer generator.
|
||||
*
|
||||
* This lexer generator translates a file in a format similar to
|
||||
* re2c ({@link http://re2c.org}) and translates it into a PHP 5-based lexer
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE: This source file is subject to version 3.01 of the PHP license
|
||||
* that is available through the world-wide-web at the following URI:
|
||||
* http://www.php.net/license/3_01.txt. If you did not receive a copy of
|
||||
* the PHP License and are unable to obtain it through the web, please
|
||||
* send a note to license@php.net so we can mail you a copy immediately.
|
||||
*
|
||||
* @category php
|
||||
* @package PHP_LexerGenerator
|
||||
* @author Gregory Beaver <cellog@php.net>
|
||||
* @copyright 2006 Gregory Beaver
|
||||
* @license http://www.php.net/license/3_01.txt PHP License 3.01
|
||||
* @version CVS: $Id: Lexer.php 246683 2007-11-22 04:43:52Z instance $
|
||||
* @since File available since Release 0.1.0
|
||||
*/
|
||||
require_once 'PHP/LexerGenerator/Parser.php';
|
||||
/**
|
||||
* Token scanner for plex files.
|
||||
*
|
||||
* This scanner detects comments beginning with "/*!lex2php" and
|
||||
* then returns their components (processing instructions, patterns, strings
|
||||
* action code, and regexes)
|
||||
* @package PHP_LexerGenerator
|
||||
* @author Gregory Beaver <cellog@php.net>
|
||||
* @copyright 2006 Gregory Beaver
|
||||
* @license http://www.php.net/license/3_01.txt PHP License 3.01
|
||||
* @version @package_version@
|
||||
* @since Class available since Release 0.1.0
|
||||
*/
|
||||
class PHP_LexerGenerator_Lexer
|
||||
{
|
||||
private $data;
|
||||
private $N;
|
||||
private $state;
|
||||
/**
|
||||
* Current line number in input
|
||||
* @var int
|
||||
*/
|
||||
public $line;
|
||||
/**
|
||||
* Number of scanning errors detected
|
||||
* @var int
|
||||
*/
|
||||
public $errors = 0;
|
||||
/**
|
||||
* integer identifier of the current token
|
||||
* @var int
|
||||
*/
|
||||
public $token;
|
||||
/**
|
||||
* string content of current token
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
|
||||
const CODE = PHP_LexerGenerator_Parser::CODE;
|
||||
const COMMENTEND = PHP_LexerGenerator_Parser::COMMENTEND;
|
||||
const COMMENTSTART = PHP_LexerGenerator_Parser::COMMENTSTART;
|
||||
const PATTERN = PHP_LexerGenerator_Parser::PATTERN;
|
||||
const PHPCODE = PHP_LexerGenerator_Parser::PHPCODE;
|
||||
const PI = PHP_LexerGenerator_Parser::PI;
|
||||
const QUOTE = PHP_LexerGenerator_Parser::QUOTE;
|
||||
const SINGLEQUOTE = PHP_LexerGenerator_Parser::SINGLEQUOTE;
|
||||
const SUBPATTERN = PHP_LexerGenerator_Parser::SUBPATTERN;
|
||||
|
||||
/**
|
||||
* prepare scanning
|
||||
* @param string the input
|
||||
*/
|
||||
function __construct($data)
|
||||
{
|
||||
$this->data = str_replace("\r\n", "\n", $data);
|
||||
$this->N = 0;
|
||||
$this->line = 1;
|
||||
$this->state = 'Start';
|
||||
$this->errors = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output an error message
|
||||
* @param string
|
||||
*/
|
||||
private function error($msg)
|
||||
{
|
||||
echo 'Error on line ' . $this->line . ': ' . $msg;
|
||||
$this->errors++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial scanning state lexer
|
||||
* @return boolean
|
||||
*/
|
||||
private function lexStart()
|
||||
{
|
||||
if ($this->N >= strlen($this->data)) {
|
||||
return false;
|
||||
}
|
||||
$a = strpos($this->data, '/*!lex2php' . "\n", $this->N);
|
||||
if ($a === false) {
|
||||
$this->value = substr($this->data, $this->N);
|
||||
$this->N = strlen($this->data);
|
||||
$this->token = self::PHPCODE;
|
||||
return true;
|
||||
}
|
||||
if ($a > $this->N) {
|
||||
$this->value = substr($this->data, $this->N, $a - $this->N);
|
||||
$this->N = $a;
|
||||
$this->token = self::PHPCODE;
|
||||
return true;
|
||||
}
|
||||
$this->value = '/*!lex2php' . "\n";
|
||||
$this->N += 11; // strlen("/*lex2php\n")
|
||||
$this->token = self::COMMENTSTART;
|
||||
$this->state = 'Declare';
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* lexer for top-level canning state after the initial declaration comment
|
||||
* @return boolean
|
||||
*/
|
||||
private function lexStartNonDeclare()
|
||||
{
|
||||
if ($this->N >= strlen($this->data)) {
|
||||
return false;
|
||||
}
|
||||
$a = strpos($this->data, '/*!lex2php' . "\n", $this->N);
|
||||
if ($a === false) {
|
||||
$this->value = substr($this->data, $this->N);
|
||||
$this->N = strlen($this->data);
|
||||
$this->token = self::PHPCODE;
|
||||
return true;
|
||||
}
|
||||
if ($a > $this->N) {
|
||||
$this->value = substr($this->data, $this->N, $a - $this->N);
|
||||
$this->N = $a;
|
||||
$this->token = self::PHPCODE;
|
||||
return true;
|
||||
}
|
||||
$this->value = '/*!lex2php' . "\n";
|
||||
$this->N += 11; // strlen("/*lex2php\n")
|
||||
$this->token = self::COMMENTSTART;
|
||||
$this->state = 'Rule';
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* lexer for declaration comment state
|
||||
* @return boolean
|
||||
*/
|
||||
private function lexDeclare()
|
||||
{
|
||||
while (true) {
|
||||
$this -> skipWhitespaceEol();
|
||||
if (
|
||||
$this->N + 1 >= strlen($this->data)
|
||||
|| $this->data[$this->N] != '/'
|
||||
|| $this->data[$this->N + 1] != '/'
|
||||
) {
|
||||
break;
|
||||
}
|
||||
// Skip single-line comment
|
||||
while (
|
||||
$this->N < strlen($this->data)
|
||||
&& $this->data[$this->N] != "\n"
|
||||
) {
|
||||
++$this->N;
|
||||
}
|
||||
}
|
||||
if ($this->data[$this->N] == '*' && $this->data[$this->N + 1] == '/') {
|
||||
$this->state = 'StartNonDeclare';
|
||||
$this->value = '*/';
|
||||
$this->N += 2;
|
||||
$this->token = self::COMMENTEND;
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/\G%([a-z]+)/', $this->data, $token, null, $this->N)) {
|
||||
$this->value = $token[1];
|
||||
$this->N += strlen($token[1]) + 1;
|
||||
$this->state = 'DeclarePI';
|
||||
$this->token = self::PI;
|
||||
return true;
|
||||
}
|
||||
if (preg_match('/\G[a-zA-Z_][a-zA-Z0-9_]*/', $this->data, $token, null, $this->N)) {
|
||||
$this->value = $token[0];
|
||||
$this->token = self::PATTERN;
|
||||
$this->N += strlen($token[0]);
|
||||
$this->state = 'DeclareEquals';
|
||||
return true;
|
||||
}
|
||||
$this->error('expecting declaration of sub-patterns');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* lexer for processor instructions within declaration comment
|
||||
* @return boolean
|
||||
*/
|
||||
private function lexDeclarePI()
|
||||
{
|
||||
$this -> skipWhitespace();
|
||||
if ($this->data[$this->N] == "\n") {
|
||||
$this->N++;
|
||||
$this->state = 'Declare';
|
||||
$this->line++;
|
||||
return $this->lexDeclare();
|
||||
}
|
||||
if ($this->data[$this->N] == '{') {
|
||||
return $this->lexCode();
|
||||
}
|
||||
if (!preg_match("/\G[^\n]+/", $this->data, $token, null, $this->N)) {
|
||||
$this->error('Unexpected end of file');
|
||||
return false;
|
||||
}
|
||||
$this->value = $token[0];
|
||||
$this->N += strlen($this->value);
|
||||
$this->token = self::SUBPATTERN;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* lexer for processor instructions inside rule comments
|
||||
* @return boolean
|
||||
*/
|
||||
private function lexDeclarePIRule()
|
||||
{
|
||||
$this -> skipWhitespace();
|
||||
if ($this->data[$this->N] == "\n") {
|
||||
$this->N++;
|
||||
$this->state = 'Rule';
|
||||
$this->line++;
|
||||
return $this->lexRule();
|
||||
}
|
||||
if ($this->data[$this->N] == '{') {
|
||||
return $this->lexCode();
|
||||
}
|
||||
if (!preg_match("/\G[^\n]+/", $this->data, $token, null, $this->N)) {
|
||||
$this->error('Unexpected end of file');
|
||||
return false;
|
||||
}
|
||||
$this->value = $token[0];
|
||||
$this->N += strlen($this->value);
|
||||
$this->token = self::SUBPATTERN;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* lexer for the state representing scanning between a pattern and the "=" sign
|
||||
* @return boolean
|
||||
*/
|
||||
private function lexDeclareEquals()
|
||||
{
|
||||
$this -> skipWhitespace();
|
||||
if ($this->N >= strlen($this->data)) {
|
||||
$this->error('unexpected end of input, expecting "=" for sub-pattern declaration');
|
||||
}
|
||||
if ($this->data[$this->N] != '=') {
|
||||
$this->error('expecting "=" for sub-pattern declaration');
|
||||
return false;
|
||||
}
|
||||
$this->N++;
|
||||
$this->state = 'DeclareRightside';
|
||||
$this -> skipWhitespace();
|
||||
if ($this->N >= strlen($this->data)) {
|
||||
$this->error('unexpected end of file, expecting right side of sub-pattern declaration');
|
||||
return false;
|
||||
}
|
||||
return $this->lexDeclareRightside();
|
||||
}
|
||||
|
||||
/**
|
||||
* lexer for the right side of a pattern, detects quotes or regexes
|
||||
* @return boolean
|
||||
*/
|
||||
private function lexDeclareRightside()
|
||||
{
|
||||
if ($this->data[$this->N] == "\n") {
|
||||
$this->state = 'lexDeclare';
|
||||
$this->N++;
|
||||
$this->line++;
|
||||
return $this->lexDeclare();
|
||||
}
|
||||
if ($this->data[$this->N] == '"') {
|
||||
return $this->lexQuote();
|
||||
}
|
||||
if ($this->data[$this->N] == '\'') {
|
||||
return $this->lexQuote('\'');
|
||||
}
|
||||
$this -> skipWhitespace();
|
||||
// match a pattern
|
||||
$test = $this->data[$this->N];
|
||||
$token = $this->N + 1;
|
||||
$a = 0;
|
||||
do {
|
||||
if ($a++) {
|
||||
$token++;
|
||||
}
|
||||
$token = strpos($this->data, $test, $token);
|
||||
} while ($token !== false && ($this->data[$token - 1] == '\\'
|
||||
&& $this->data[$token - 2] != '\\'));
|
||||
if ($token === false) {
|
||||
$this->error('Unterminated regex pattern (started with "' . $test . '"');
|
||||
return false;
|
||||
}
|
||||
if (substr_count($this->data, "\n", $this->N, $token - $this->N)) {
|
||||
$this->error('Regex pattern extends over multiple lines');
|
||||
return false;
|
||||
}
|
||||
$this->value = substr($this->data, $this->N + 1, $token - $this->N - 1);
|
||||
// unescape the regex marker
|
||||
// we will re-escape when creating the final regex
|
||||
$this->value = str_replace('\\' . $test, $test, $this->value);
|
||||
$this->N = $token + 1;
|
||||
$this->token = self::SUBPATTERN;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* lexer for quoted literals
|
||||
* @return boolean
|
||||
*/
|
||||
private function lexQuote($quote = '"')
|
||||
{
|
||||
$token = $this->N + 1;
|
||||
$a = 0;
|
||||
do {
|
||||
if ($a++) {
|
||||
$token++;
|
||||
}
|
||||
$token = strpos($this->data, $quote, $token);
|
||||
} while ($token !== false && $token < strlen($this->data) &&
|
||||
($this->data[$token - 1] == '\\' && $this->data[$token - 2] != '\\'));
|
||||
if ($token === false) {
|
||||
$this->error('unterminated quote');
|
||||
return false;
|
||||
}
|
||||
if (substr_count($this->data, "\n", $this->N, $token - $this->N)) {
|
||||
$this->error('quote extends over multiple lines');
|
||||
return false;
|
||||
}
|
||||
$this->value = substr($this->data, $this->N + 1, $token - $this->N - 1);
|
||||
$this->value = str_replace('\\'.$quote, $quote, $this->value);
|
||||
$this->value = str_replace('\\\\', '\\', $this->value);
|
||||
$this->N = $token + 1;
|
||||
if ($quote == '\'' ) {
|
||||
$this->token = self::SINGLEQUOTE;
|
||||
} else {
|
||||
$this->token = self::QUOTE;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* lexer for rules
|
||||
* @return boolean
|
||||
*/
|
||||
private function lexRule()
|
||||
{
|
||||
while (
|
||||
$this->N < strlen($this->data)
|
||||
&& (
|
||||
$this->data[$this->N] == ' '
|
||||
|| $this->data[$this->N] == "\t"
|
||||
|| $this->data[$this->N] == "\n"
|
||||
) || (
|
||||
$this->N < strlen($this->data) - 1
|
||||
&& $this->data[$this->N] == '/'
|
||||
&& $this->data[$this->N + 1] == '/'
|
||||
)
|
||||
) {
|
||||
if ( $this->data[$this->N] == '/' && $this->data[$this->N + 1] == '/' ) {
|
||||
// Skip single line comments
|
||||
$next_newline = strpos($this->data, "\n", $this->N) + 1;
|
||||
if ($next_newline) {
|
||||
$this->N = $next_newline;
|
||||
} else {
|
||||
$this->N = sizeof($this->data);
|
||||
}
|
||||
$this->line++;
|
||||
} else {
|
||||
if ($this->data[$this->N] == "\n") {
|
||||
$this->line++;
|
||||
}
|
||||
$this->N++; // skip all whitespace
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->N >= strlen($this->data)) {
|
||||
$this->error('unexpected end of input, expecting rule declaration');
|
||||
}
|
||||
if ($this->data[$this->N] == '*' && $this->data[$this->N + 1] == '/') {
|
||||
$this->state = 'StartNonDeclare';
|
||||
$this->value = '*/';
|
||||
$this->N += 2;
|
||||
$this->token = self::COMMENTEND;
|
||||
return true;
|
||||
}
|
||||
if ($this->data[$this->N] == '\'') {
|
||||
return $this->lexQuote('\'');
|
||||
}
|
||||
if (preg_match('/\G%([a-zA-Z_]+)/', $this->data, $token, null, $this->N)) {
|
||||
$this->value = $token[1];
|
||||
$this->N += strlen($token[1]) + 1;
|
||||
$this->state = 'DeclarePIRule';
|
||||
$this->token = self::PI;
|
||||
return true;
|
||||
}
|
||||
if ($this->data[$this->N] == "{") {
|
||||
return $this->lexCode();
|
||||
}
|
||||
if ($this->data[$this->N] == '"') {
|
||||
return $this->lexQuote();
|
||||
}
|
||||
if (preg_match('/\G[a-zA-Z_][a-zA-Z0-9_]*/', $this->data, $token, null, $this->N)) {
|
||||
$this->value = $token[0];
|
||||
$this->N += strlen($token[0]);
|
||||
$this->token = self::SUBPATTERN;
|
||||
return true;
|
||||
} else {
|
||||
$this->error('expecting token rule (quotes or sub-patterns)');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lexer for php code blocks
|
||||
* @return boolean
|
||||
*/
|
||||
private function lexCode()
|
||||
{
|
||||
$cp = $this->N + 1;
|
||||
for ($level = 1; $cp < strlen($this->data) && ($level > 1 || $this->data[$cp] != '}'); $cp++) {
|
||||
if ($this->data[$cp] == '{') {
|
||||
$level++;
|
||||
} elseif ($this->data[$cp] == '}') {
|
||||
$level--;
|
||||
} elseif ($this->data[$cp] == '/' && $this->data[$cp + 1] == '/') {
|
||||
/* Skip C++ style comments */
|
||||
$cp += 2;
|
||||
$z = strpos($this->data, "\n", $cp);
|
||||
if ($z === false) {
|
||||
$cp = strlen($this->data);
|
||||
break;
|
||||
}
|
||||
$cp = $z;
|
||||
} elseif ($this->data[$cp] == "'" || $this->data[$cp] == '"') {
|
||||
/* String a character literals */
|
||||
$startchar = $this->data[$cp];
|
||||
$prevc = 0;
|
||||
for ($cp++; $cp < strlen($this->data) && ($this->data[$cp] != $startchar || $prevc === '\\'); $cp++) {
|
||||
if ($prevc === '\\') {
|
||||
$prevc = 0;
|
||||
} else {
|
||||
$prevc = $this->data[$cp];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($cp >= strlen($this->data)) {
|
||||
$this->error("PHP code starting on this line is not terminated before the end of the file.");
|
||||
$this->error++;
|
||||
return false;
|
||||
} else {
|
||||
$this->value = substr($this->data, $this->N + 1, $cp - $this->N - 1);
|
||||
$this->token = self::CODE;
|
||||
$this->N = $cp + 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip whitespace characters
|
||||
*/
|
||||
private function skipWhitespace() {
|
||||
while (
|
||||
$this->N < strlen($this->data)
|
||||
&& (
|
||||
$this->data[$this->N] == ' '
|
||||
|| $this->data[$this->N] == "\t"
|
||||
)
|
||||
) {
|
||||
$this->N++; // skip whitespace
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip whitespace and EOL characters
|
||||
*/
|
||||
private function skipWhitespaceEol() {
|
||||
while (
|
||||
$this->N < strlen($this->data)
|
||||
&& (
|
||||
$this->data[$this->N] == ' '
|
||||
|| $this->data[$this->N] == "\t"
|
||||
|| $this->data[$this->N] == "\n"
|
||||
)
|
||||
) {
|
||||
if ($this->data[$this->N] == "\n") {
|
||||
++$this -> line;
|
||||
}
|
||||
$this->N++; // skip whitespace
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary scanner
|
||||
*
|
||||
* In addition to lexing, this properly increments the line number of lexing.
|
||||
* This calls the proper sub-lexer based on the parser state
|
||||
* @param unknown_type $parser
|
||||
* @return unknown
|
||||
*/
|
||||
public function advance($parser)
|
||||
{
|
||||
if ($this->N >= strlen($this->data)) {
|
||||
return false;
|
||||
}
|
||||
if ($this->{'lex' . $this->state}()) {
|
||||
$this->line += substr_count($this->value, "\n");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
?>
|
||||
492
core/oql/build/PHP/LexerGenerator/Parser.out
Normal file
492
core/oql/build/PHP/LexerGenerator/Parser.out
Normal file
@@ -0,0 +1,492 @@
|
||||
State 0:
|
||||
start ::= * lexfile
|
||||
lexfile ::= * declare rules
|
||||
lexfile ::= * declare PHPCODE rules
|
||||
lexfile ::= * PHPCODE declare rules
|
||||
lexfile ::= * PHPCODE declare PHPCODE rules
|
||||
declare ::= * COMMENTSTART declarations COMMENTEND
|
||||
|
||||
PHPCODE shift 17
|
||||
COMMENTSTART shift 8
|
||||
start accept
|
||||
lexfile shift 52
|
||||
declare shift 6
|
||||
|
||||
State 1:
|
||||
rules ::= reset_rules * rule COMMENTEND
|
||||
rules ::= reset_rules * PI SUBPATTERN rule COMMENTEND
|
||||
rules ::= reset_rules * rule COMMENTEND PHPCODE
|
||||
rules ::= reset_rules * PI SUBPATTERN rule COMMENTEND PHPCODE
|
||||
rule ::= * rule_subpattern CODE
|
||||
rule ::= * rule rule_subpattern CODE
|
||||
rule_subpattern ::= * QUOTE
|
||||
rule_subpattern ::= * SUBPATTERN
|
||||
rule_subpattern ::= * rule_subpattern QUOTE
|
||||
rule_subpattern ::= * rule_subpattern SUBPATTERN
|
||||
|
||||
PI shift 29
|
||||
SUBPATTERN shift 50
|
||||
QUOTE shift 51
|
||||
rule shift 12
|
||||
rule_subpattern shift 18
|
||||
|
||||
State 2:
|
||||
rules ::= COMMENTSTART * rule COMMENTEND
|
||||
rules ::= COMMENTSTART * PI SUBPATTERN rule COMMENTEND
|
||||
rules ::= COMMENTSTART * rule COMMENTEND PHPCODE
|
||||
rules ::= COMMENTSTART * PI SUBPATTERN rule COMMENTEND PHPCODE
|
||||
rule ::= * rule_subpattern CODE
|
||||
rule ::= * rule rule_subpattern CODE
|
||||
rule_subpattern ::= * QUOTE
|
||||
rule_subpattern ::= * SUBPATTERN
|
||||
rule_subpattern ::= * rule_subpattern QUOTE
|
||||
rule_subpattern ::= * rule_subpattern SUBPATTERN
|
||||
|
||||
PI shift 34
|
||||
SUBPATTERN shift 50
|
||||
QUOTE shift 51
|
||||
rule shift 11
|
||||
rule_subpattern shift 18
|
||||
|
||||
State 3:
|
||||
rules ::= COMMENTSTART PI SUBPATTERN * rule COMMENTEND
|
||||
rules ::= COMMENTSTART PI SUBPATTERN * rule COMMENTEND PHPCODE
|
||||
rule ::= * rule_subpattern CODE
|
||||
rule ::= * rule rule_subpattern CODE
|
||||
rule_subpattern ::= * QUOTE
|
||||
rule_subpattern ::= * SUBPATTERN
|
||||
rule_subpattern ::= * rule_subpattern QUOTE
|
||||
rule_subpattern ::= * rule_subpattern SUBPATTERN
|
||||
|
||||
SUBPATTERN shift 50
|
||||
QUOTE shift 51
|
||||
rule shift 10
|
||||
rule_subpattern shift 18
|
||||
|
||||
State 4:
|
||||
lexfile ::= PHPCODE declare * rules
|
||||
lexfile ::= PHPCODE declare * PHPCODE rules
|
||||
rules ::= * COMMENTSTART rule COMMENTEND
|
||||
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND
|
||||
rules ::= * COMMENTSTART rule COMMENTEND PHPCODE
|
||||
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND PHPCODE
|
||||
rules ::= * reset_rules rule COMMENTEND
|
||||
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND
|
||||
rules ::= * reset_rules rule COMMENTEND PHPCODE
|
||||
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND PHPCODE
|
||||
reset_rules ::= * rules COMMENTSTART
|
||||
|
||||
PHPCODE shift 7
|
||||
COMMENTSTART shift 2
|
||||
rules shift 25
|
||||
reset_rules shift 1
|
||||
|
||||
State 5:
|
||||
rules ::= reset_rules PI SUBPATTERN * rule COMMENTEND
|
||||
rules ::= reset_rules PI SUBPATTERN * rule COMMENTEND PHPCODE
|
||||
rule ::= * rule_subpattern CODE
|
||||
rule ::= * rule rule_subpattern CODE
|
||||
rule_subpattern ::= * QUOTE
|
||||
rule_subpattern ::= * SUBPATTERN
|
||||
rule_subpattern ::= * rule_subpattern QUOTE
|
||||
rule_subpattern ::= * rule_subpattern SUBPATTERN
|
||||
|
||||
SUBPATTERN shift 50
|
||||
QUOTE shift 51
|
||||
rule shift 13
|
||||
rule_subpattern shift 18
|
||||
|
||||
State 6:
|
||||
lexfile ::= declare * rules
|
||||
lexfile ::= declare * PHPCODE rules
|
||||
rules ::= * COMMENTSTART rule COMMENTEND
|
||||
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND
|
||||
rules ::= * COMMENTSTART rule COMMENTEND PHPCODE
|
||||
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND PHPCODE
|
||||
rules ::= * reset_rules rule COMMENTEND
|
||||
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND
|
||||
rules ::= * reset_rules rule COMMENTEND PHPCODE
|
||||
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND PHPCODE
|
||||
reset_rules ::= * rules COMMENTSTART
|
||||
|
||||
PHPCODE shift 9
|
||||
COMMENTSTART shift 2
|
||||
rules shift 33
|
||||
reset_rules shift 1
|
||||
|
||||
State 7:
|
||||
lexfile ::= PHPCODE declare PHPCODE * rules
|
||||
rules ::= * COMMENTSTART rule COMMENTEND
|
||||
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND
|
||||
rules ::= * COMMENTSTART rule COMMENTEND PHPCODE
|
||||
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND PHPCODE
|
||||
rules ::= * reset_rules rule COMMENTEND
|
||||
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND
|
||||
rules ::= * reset_rules rule COMMENTEND PHPCODE
|
||||
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND PHPCODE
|
||||
reset_rules ::= * rules COMMENTSTART
|
||||
|
||||
COMMENTSTART shift 2
|
||||
rules shift 27
|
||||
reset_rules shift 1
|
||||
|
||||
State 8:
|
||||
declare ::= COMMENTSTART * declarations COMMENTEND
|
||||
declarations ::= * processing_instructions pattern_declarations
|
||||
processing_instructions ::= * PI SUBPATTERN
|
||||
processing_instructions ::= * PI CODE
|
||||
processing_instructions ::= * processing_instructions PI SUBPATTERN
|
||||
processing_instructions ::= * processing_instructions PI CODE
|
||||
|
||||
PI shift 23
|
||||
declarations shift 28
|
||||
processing_instructions shift 14
|
||||
|
||||
State 9:
|
||||
lexfile ::= declare PHPCODE * rules
|
||||
rules ::= * COMMENTSTART rule COMMENTEND
|
||||
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND
|
||||
rules ::= * COMMENTSTART rule COMMENTEND PHPCODE
|
||||
rules ::= * COMMENTSTART PI SUBPATTERN rule COMMENTEND PHPCODE
|
||||
rules ::= * reset_rules rule COMMENTEND
|
||||
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND
|
||||
rules ::= * reset_rules rule COMMENTEND PHPCODE
|
||||
rules ::= * reset_rules PI SUBPATTERN rule COMMENTEND PHPCODE
|
||||
reset_rules ::= * rules COMMENTSTART
|
||||
|
||||
COMMENTSTART shift 2
|
||||
rules shift 31
|
||||
reset_rules shift 1
|
||||
|
||||
State 10:
|
||||
rules ::= COMMENTSTART PI SUBPATTERN rule * COMMENTEND
|
||||
rules ::= COMMENTSTART PI SUBPATTERN rule * COMMENTEND PHPCODE
|
||||
rule ::= rule * rule_subpattern CODE
|
||||
rule_subpattern ::= * QUOTE
|
||||
rule_subpattern ::= * SUBPATTERN
|
||||
rule_subpattern ::= * rule_subpattern QUOTE
|
||||
rule_subpattern ::= * rule_subpattern SUBPATTERN
|
||||
|
||||
COMMENTEND shift 30
|
||||
SUBPATTERN shift 50
|
||||
QUOTE shift 51
|
||||
rule_subpattern shift 19
|
||||
|
||||
State 11:
|
||||
rules ::= COMMENTSTART rule * COMMENTEND
|
||||
rules ::= COMMENTSTART rule * COMMENTEND PHPCODE
|
||||
rule ::= rule * rule_subpattern CODE
|
||||
rule_subpattern ::= * QUOTE
|
||||
rule_subpattern ::= * SUBPATTERN
|
||||
rule_subpattern ::= * rule_subpattern QUOTE
|
||||
rule_subpattern ::= * rule_subpattern SUBPATTERN
|
||||
|
||||
COMMENTEND shift 32
|
||||
SUBPATTERN shift 50
|
||||
QUOTE shift 51
|
||||
rule_subpattern shift 19
|
||||
|
||||
State 12:
|
||||
rules ::= reset_rules rule * COMMENTEND
|
||||
rules ::= reset_rules rule * COMMENTEND PHPCODE
|
||||
rule ::= rule * rule_subpattern CODE
|
||||
rule_subpattern ::= * QUOTE
|
||||
rule_subpattern ::= * SUBPATTERN
|
||||
rule_subpattern ::= * rule_subpattern QUOTE
|
||||
rule_subpattern ::= * rule_subpattern SUBPATTERN
|
||||
|
||||
COMMENTEND shift 35
|
||||
SUBPATTERN shift 50
|
||||
QUOTE shift 51
|
||||
rule_subpattern shift 19
|
||||
|
||||
State 13:
|
||||
rules ::= reset_rules PI SUBPATTERN rule * COMMENTEND
|
||||
rules ::= reset_rules PI SUBPATTERN rule * COMMENTEND PHPCODE
|
||||
rule ::= rule * rule_subpattern CODE
|
||||
rule_subpattern ::= * QUOTE
|
||||
rule_subpattern ::= * SUBPATTERN
|
||||
rule_subpattern ::= * rule_subpattern QUOTE
|
||||
rule_subpattern ::= * rule_subpattern SUBPATTERN
|
||||
|
||||
COMMENTEND shift 24
|
||||
SUBPATTERN shift 50
|
||||
QUOTE shift 51
|
||||
rule_subpattern shift 19
|
||||
|
||||
State 14:
|
||||
declarations ::= processing_instructions * pattern_declarations
|
||||
processing_instructions ::= processing_instructions * PI SUBPATTERN
|
||||
processing_instructions ::= processing_instructions * PI CODE
|
||||
pattern_declarations ::= * PATTERN subpattern
|
||||
pattern_declarations ::= * pattern_declarations PATTERN subpattern
|
||||
|
||||
PI shift 20
|
||||
PATTERN shift 16
|
||||
pattern_declarations shift 26
|
||||
|
||||
State 15:
|
||||
pattern_declarations ::= pattern_declarations PATTERN * subpattern
|
||||
subpattern ::= * QUOTE
|
||||
subpattern ::= * SUBPATTERN
|
||||
subpattern ::= * subpattern QUOTE
|
||||
subpattern ::= * subpattern SUBPATTERN
|
||||
|
||||
SUBPATTERN shift 36
|
||||
QUOTE shift 37
|
||||
subpattern shift 21
|
||||
|
||||
State 16:
|
||||
pattern_declarations ::= PATTERN * subpattern
|
||||
subpattern ::= * QUOTE
|
||||
subpattern ::= * SUBPATTERN
|
||||
subpattern ::= * subpattern QUOTE
|
||||
subpattern ::= * subpattern SUBPATTERN
|
||||
|
||||
SUBPATTERN shift 36
|
||||
QUOTE shift 37
|
||||
subpattern shift 22
|
||||
|
||||
State 17:
|
||||
lexfile ::= PHPCODE * declare rules
|
||||
lexfile ::= PHPCODE * declare PHPCODE rules
|
||||
declare ::= * COMMENTSTART declarations COMMENTEND
|
||||
|
||||
COMMENTSTART shift 8
|
||||
declare shift 4
|
||||
|
||||
State 18:
|
||||
rule ::= rule_subpattern * CODE
|
||||
rule_subpattern ::= rule_subpattern * QUOTE
|
||||
rule_subpattern ::= rule_subpattern * SUBPATTERN
|
||||
|
||||
SUBPATTERN shift 54
|
||||
CODE shift 47
|
||||
QUOTE shift 53
|
||||
|
||||
State 19:
|
||||
rule ::= rule rule_subpattern * CODE
|
||||
rule_subpattern ::= rule_subpattern * QUOTE
|
||||
rule_subpattern ::= rule_subpattern * SUBPATTERN
|
||||
|
||||
SUBPATTERN shift 54
|
||||
CODE shift 45
|
||||
QUOTE shift 53
|
||||
|
||||
State 20:
|
||||
processing_instructions ::= processing_instructions PI * SUBPATTERN
|
||||
processing_instructions ::= processing_instructions PI * CODE
|
||||
|
||||
SUBPATTERN shift 44
|
||||
CODE shift 40
|
||||
|
||||
State 21:
|
||||
(12) pattern_declarations ::= pattern_declarations PATTERN subpattern *
|
||||
subpattern ::= subpattern * QUOTE
|
||||
subpattern ::= subpattern * SUBPATTERN
|
||||
|
||||
SUBPATTERN shift 38
|
||||
QUOTE shift 41
|
||||
{default} reduce 12
|
||||
|
||||
State 22:
|
||||
(11) pattern_declarations ::= PATTERN subpattern *
|
||||
subpattern ::= subpattern * QUOTE
|
||||
subpattern ::= subpattern * SUBPATTERN
|
||||
|
||||
SUBPATTERN shift 38
|
||||
QUOTE shift 41
|
||||
{default} reduce 11
|
||||
|
||||
State 23:
|
||||
processing_instructions ::= PI * SUBPATTERN
|
||||
processing_instructions ::= PI * CODE
|
||||
|
||||
SUBPATTERN shift 42
|
||||
CODE shift 39
|
||||
|
||||
State 24:
|
||||
(18) rules ::= reset_rules PI SUBPATTERN rule COMMENTEND *
|
||||
rules ::= reset_rules PI SUBPATTERN rule COMMENTEND * PHPCODE
|
||||
|
||||
PHPCODE shift 48
|
||||
{default} reduce 18
|
||||
|
||||
State 25:
|
||||
(3) lexfile ::= PHPCODE declare rules *
|
||||
reset_rules ::= rules * COMMENTSTART
|
||||
|
||||
COMMENTSTART shift 43
|
||||
{default} reduce 3
|
||||
|
||||
State 26:
|
||||
(6) declarations ::= processing_instructions pattern_declarations *
|
||||
pattern_declarations ::= pattern_declarations * PATTERN subpattern
|
||||
|
||||
PATTERN shift 15
|
||||
{default} reduce 6
|
||||
|
||||
State 27:
|
||||
(4) lexfile ::= PHPCODE declare PHPCODE rules *
|
||||
reset_rules ::= rules * COMMENTSTART
|
||||
|
||||
COMMENTSTART shift 43
|
||||
{default} reduce 4
|
||||
|
||||
State 28:
|
||||
declare ::= COMMENTSTART declarations * COMMENTEND
|
||||
|
||||
COMMENTEND shift 55
|
||||
|
||||
State 29:
|
||||
rules ::= reset_rules PI * SUBPATTERN rule COMMENTEND
|
||||
rules ::= reset_rules PI * SUBPATTERN rule COMMENTEND PHPCODE
|
||||
|
||||
SUBPATTERN shift 5
|
||||
|
||||
State 30:
|
||||
(14) rules ::= COMMENTSTART PI SUBPATTERN rule COMMENTEND *
|
||||
rules ::= COMMENTSTART PI SUBPATTERN rule COMMENTEND * PHPCODE
|
||||
|
||||
PHPCODE shift 46
|
||||
{default} reduce 14
|
||||
|
||||
State 31:
|
||||
(2) lexfile ::= declare PHPCODE rules *
|
||||
reset_rules ::= rules * COMMENTSTART
|
||||
|
||||
COMMENTSTART shift 43
|
||||
{default} reduce 2
|
||||
|
||||
State 32:
|
||||
(13) rules ::= COMMENTSTART rule COMMENTEND *
|
||||
rules ::= COMMENTSTART rule COMMENTEND * PHPCODE
|
||||
|
||||
PHPCODE shift 56
|
||||
{default} reduce 13
|
||||
|
||||
State 33:
|
||||
(1) lexfile ::= declare rules *
|
||||
reset_rules ::= rules * COMMENTSTART
|
||||
|
||||
COMMENTSTART shift 43
|
||||
{default} reduce 1
|
||||
|
||||
State 34:
|
||||
rules ::= COMMENTSTART PI * SUBPATTERN rule COMMENTEND
|
||||
rules ::= COMMENTSTART PI * SUBPATTERN rule COMMENTEND PHPCODE
|
||||
|
||||
SUBPATTERN shift 3
|
||||
|
||||
State 35:
|
||||
(17) rules ::= reset_rules rule COMMENTEND *
|
||||
rules ::= reset_rules rule COMMENTEND * PHPCODE
|
||||
|
||||
PHPCODE shift 49
|
||||
{default} reduce 17
|
||||
|
||||
State 36:
|
||||
(29) subpattern ::= SUBPATTERN *
|
||||
|
||||
{default} reduce 29
|
||||
|
||||
State 37:
|
||||
(28) subpattern ::= QUOTE *
|
||||
|
||||
{default} reduce 28
|
||||
|
||||
State 38:
|
||||
(31) subpattern ::= subpattern SUBPATTERN *
|
||||
|
||||
{default} reduce 31
|
||||
|
||||
State 39:
|
||||
(8) processing_instructions ::= PI CODE *
|
||||
|
||||
{default} reduce 8
|
||||
|
||||
State 40:
|
||||
(10) processing_instructions ::= processing_instructions PI CODE *
|
||||
|
||||
{default} reduce 10
|
||||
|
||||
State 41:
|
||||
(30) subpattern ::= subpattern QUOTE *
|
||||
|
||||
{default} reduce 30
|
||||
|
||||
State 42:
|
||||
(7) processing_instructions ::= PI SUBPATTERN *
|
||||
|
||||
{default} reduce 7
|
||||
|
||||
State 43:
|
||||
(21) reset_rules ::= rules COMMENTSTART *
|
||||
|
||||
{default} reduce 21
|
||||
|
||||
State 44:
|
||||
(9) processing_instructions ::= processing_instructions PI SUBPATTERN *
|
||||
|
||||
{default} reduce 9
|
||||
|
||||
State 45:
|
||||
(23) rule ::= rule rule_subpattern CODE *
|
||||
|
||||
{default} reduce 23
|
||||
|
||||
State 46:
|
||||
(16) rules ::= COMMENTSTART PI SUBPATTERN rule COMMENTEND PHPCODE *
|
||||
|
||||
{default} reduce 16
|
||||
|
||||
State 47:
|
||||
(22) rule ::= rule_subpattern CODE *
|
||||
|
||||
{default} reduce 22
|
||||
|
||||
State 48:
|
||||
(20) rules ::= reset_rules PI SUBPATTERN rule COMMENTEND PHPCODE *
|
||||
|
||||
{default} reduce 20
|
||||
|
||||
State 49:
|
||||
(19) rules ::= reset_rules rule COMMENTEND PHPCODE *
|
||||
|
||||
{default} reduce 19
|
||||
|
||||
State 50:
|
||||
(25) rule_subpattern ::= SUBPATTERN *
|
||||
|
||||
{default} reduce 25
|
||||
|
||||
State 51:
|
||||
(24) rule_subpattern ::= QUOTE *
|
||||
|
||||
{default} reduce 24
|
||||
|
||||
State 52:
|
||||
(0) start ::= lexfile *
|
||||
|
||||
{default} reduce 0
|
||||
|
||||
State 53:
|
||||
(26) rule_subpattern ::= rule_subpattern QUOTE *
|
||||
|
||||
{default} reduce 26
|
||||
|
||||
State 54:
|
||||
(27) rule_subpattern ::= rule_subpattern SUBPATTERN *
|
||||
|
||||
{default} reduce 27
|
||||
|
||||
State 55:
|
||||
(5) declare ::= COMMENTSTART declarations COMMENTEND *
|
||||
|
||||
{default} reduce 5
|
||||
|
||||
State 56:
|
||||
(15) rules ::= COMMENTSTART rule COMMENTEND PHPCODE *
|
||||
|
||||
{default} reduce 15
|
||||
|
||||
1994
core/oql/build/PHP/LexerGenerator/Parser.php
Normal file
1994
core/oql/build/PHP/LexerGenerator/Parser.php
Normal file
File diff suppressed because it is too large
Load Diff
795
core/oql/build/PHP/LexerGenerator/Parser.y
Normal file
795
core/oql/build/PHP/LexerGenerator/Parser.y
Normal file
@@ -0,0 +1,795 @@
|
||||
%name PHP_LexerGenerator_Parser
|
||||
%declare_class {class PHP_LexerGenerator_Parser}
|
||||
%include {
|
||||
/* ?><?php {//*/
|
||||
/**
|
||||
* PHP_LexerGenerator, a php 5 lexer generator.
|
||||
*
|
||||
* This lexer generator translates a file in a format similar to
|
||||
* re2c ({@link http://re2c.org}) and translates it into a PHP 5-based lexer
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2006, Gregory Beaver <cellog@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the PHP_LexerGenerator nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category php
|
||||
* @package PHP_LexerGenerator
|
||||
* @author Gregory Beaver <cellog@php.net>
|
||||
* @copyright 2006 Gregory Beaver
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version CVS: $Id: Parser.y 246683 2007-11-22 04:43:52Z instance $
|
||||
* @since File available since Release 0.1.0
|
||||
*/
|
||||
/**
|
||||
* For regular expression validation
|
||||
*/
|
||||
require_once 'PHP/LexerGenerator/Regex/Lexer.php';
|
||||
require_once 'PHP/LexerGenerator/Regex/Parser.php';
|
||||
require_once 'PHP/LexerGenerator/Exception.php';
|
||||
/**
|
||||
* Token parser for plex files.
|
||||
*
|
||||
* This parser converts tokens pulled from {@link PHP_LexerGenerator_Lexer}
|
||||
* into abstract patterns and rules, then creates the output file
|
||||
* @package PHP_LexerGenerator
|
||||
* @author Gregory Beaver <cellog@php.net>
|
||||
* @copyright 2006 Gregory Beaver
|
||||
* @license http://www.php.net/license/3_01.txt PHP License 3.01
|
||||
* @version @package_version@
|
||||
* @since Class available since Release 0.1.0
|
||||
*/
|
||||
}
|
||||
%syntax_error {
|
||||
echo "Syntax Error on line " . $this->lex->line . ": token '" .
|
||||
$this->lex->value . "' while parsing rule:";
|
||||
foreach ($this->yystack as $entry) {
|
||||
echo $this->tokenName($entry->major) . ' ';
|
||||
}
|
||||
foreach ($this->yy_get_expected_tokens($yymajor) as $token) {
|
||||
$expect[] = self::$yyTokenName[$token];
|
||||
}
|
||||
throw new Exception('Unexpected ' . $this->tokenName($yymajor) . '(' . $TOKEN
|
||||
. '), expected one of: ' . implode(',', $expect));
|
||||
}
|
||||
%include_class {
|
||||
private $patterns;
|
||||
private $out;
|
||||
private $lex;
|
||||
private $input;
|
||||
private $counter;
|
||||
private $token;
|
||||
private $value;
|
||||
private $line;
|
||||
private $matchlongest;
|
||||
private $_regexLexer;
|
||||
private $_regexParser;
|
||||
private $_patternIndex = 0;
|
||||
private $_outRuleIndex = 1;
|
||||
private $caseinsensitive;
|
||||
private $patternFlags;
|
||||
private $unicode;
|
||||
|
||||
public $transTable = array(
|
||||
1 => self::PHPCODE,
|
||||
2 => self::COMMENTSTART,
|
||||
3 => self::COMMENTEND,
|
||||
4 => self::QUOTE,
|
||||
5 => self::SINGLEQUOTE,
|
||||
6 => self::PATTERN,
|
||||
7 => self::CODE,
|
||||
8 => self::SUBPATTERN,
|
||||
9 => self::PI,
|
||||
);
|
||||
|
||||
function __construct($outfile, $lex)
|
||||
{
|
||||
$this->out = fopen($outfile, 'wb');
|
||||
if (!$this->out) {
|
||||
throw new Exception('unable to open lexer output file "' . $outfile . '"');
|
||||
}
|
||||
$this->lex = $lex;
|
||||
$this->_regexLexer = new PHP_LexerGenerator_Regex_Lexer('');
|
||||
$this->_regexParser = new PHP_LexerGenerator_Regex_Parser($this->_regexLexer);
|
||||
}
|
||||
|
||||
function doLongestMatch($rules, $statename, $ruleindex)
|
||||
{
|
||||
fwrite($this->out, '
|
||||
if (' . $this->counter . ' >= strlen(' . $this->input . ')) {
|
||||
return false; // end of input
|
||||
}
|
||||
do {
|
||||
$rules = array(');
|
||||
foreach ($rules as $rule) {
|
||||
fwrite($this->out, '
|
||||
\'/\G' . $rule['pattern'] . '/' . $this->patternFlags . ' \',');
|
||||
}
|
||||
fwrite($this->out, '
|
||||
);
|
||||
$match = false;
|
||||
foreach ($rules as $index => $rule) {
|
||||
if (preg_match($rule, substr(' . $this->input . ', ' .
|
||||
$this->counter . '), $yymatches)) {
|
||||
if ($match) {
|
||||
if (strlen($yymatches[0]) > strlen($match[0][0])) {
|
||||
$match = array($yymatches, $index); // matches, token
|
||||
}
|
||||
} else {
|
||||
$match = array($yymatches, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$match) {
|
||||
throw new Exception(\'Unexpected input at line \' . ' . $this->line . ' .
|
||||
\': \' . ' . $this->input . '[' . $this->counter . ']);
|
||||
}
|
||||
' . $this->token . ' = $match[1];
|
||||
' . $this->value . ' = $match[0][0];
|
||||
$yysubmatches = $match[0];
|
||||
array_shift($yysubmatches);
|
||||
if (!$yysubmatches) {
|
||||
$yysubmatches = array();
|
||||
}
|
||||
$r = $this->{\'yy_r' . $ruleindex . '_\' . ' . $this->token . '}($yysubmatches);
|
||||
if ($r === null) {
|
||||
' . $this->counter . ' += strlen(' . $this->value . ');
|
||||
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
|
||||
// accept this token
|
||||
return true;
|
||||
} elseif ($r === true) {
|
||||
// we have changed state
|
||||
// process this token in the new state
|
||||
return $this->yylex();
|
||||
} elseif ($r === false) {
|
||||
' . $this->counter . ' += strlen(' . $this->value . ');
|
||||
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
|
||||
if (' . $this->counter . ' >= strlen(' . $this->input . ')) {
|
||||
return false; // end of input
|
||||
}
|
||||
// skip this token
|
||||
continue;
|
||||
} else {');
|
||||
fwrite($this->out, '
|
||||
$yy_yymore_patterns = array_slice($rules, $this->token, true);
|
||||
// yymore is needed
|
||||
do {
|
||||
if (!isset($yy_yymore_patterns[' . $this->token . '])) {
|
||||
throw new Exception(\'cannot do yymore for the last token\');
|
||||
}
|
||||
$match = false;
|
||||
foreach ($yy_yymore_patterns[' . $this->token . '] as $index => $rule) {
|
||||
if (preg_match(\'/\' . $rule . \'/' . $this->patternFlags . '\',
|
||||
' . $this->input . ', $yymatches, null, ' . $this->counter . ')) {
|
||||
$yymatches = array_filter($yymatches, \'strlen\'); // remove empty sub-patterns
|
||||
if ($match) {
|
||||
if (strlen($yymatches[0]) > strlen($match[0][0])) {
|
||||
$match = array($yymatches, $index); // matches, token
|
||||
}
|
||||
} else {
|
||||
$match = array($yymatches, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$match) {
|
||||
throw new Exception(\'Unexpected input at line \' . ' . $this->line . ' .
|
||||
\': \' . ' . $this->input . '[' . $this->counter . ']);
|
||||
}
|
||||
' . $this->token . ' = $match[1];
|
||||
' . $this->value . ' = $match[0][0];
|
||||
$yysubmatches = $match[0];
|
||||
array_shift($yysubmatches);
|
||||
if (!$yysubmatches) {
|
||||
$yysubmatches = array();
|
||||
}
|
||||
' . $this->line . ' = substr_count(' . $this->value . ', "\n");
|
||||
$r = $this->{\'yy_r' . $ruleindex . '_\' . ' . $this->token . '}();
|
||||
} while ($r !== null || !$r);
|
||||
if ($r === true) {
|
||||
// we have changed state
|
||||
// process this token in the new state
|
||||
return $this->yylex();
|
||||
} else {
|
||||
// accept
|
||||
' . $this->counter . ' += strlen(' . $this->value . ');
|
||||
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
');
|
||||
}
|
||||
|
||||
function doFirstMatch($rules, $statename, $ruleindex)
|
||||
{
|
||||
$patterns = array();
|
||||
$pattern = '/';
|
||||
$ruleMap = array();
|
||||
$tokenindex = array();
|
||||
$actualindex = 1;
|
||||
$i = 0;
|
||||
foreach ($rules as $rule) {
|
||||
$ruleMap[$i++] = $actualindex;
|
||||
$tokenindex[$actualindex] = $rule['subpatterns'];
|
||||
$actualindex += $rule['subpatterns'] + 1;
|
||||
$patterns[] = '\G(' . $rule['pattern'] . ')';
|
||||
}
|
||||
// Re-index tokencount from zero.
|
||||
$tokencount = array_values($tokenindex);
|
||||
$tokenindex = var_export($tokenindex, true);
|
||||
$tokenindex = explode("\n", $tokenindex);
|
||||
// indent for prettiness
|
||||
$tokenindex = implode("\n ", $tokenindex);
|
||||
$pattern .= implode('|', $patterns);
|
||||
$pattern .= '/' . $this->patternFlags;
|
||||
fwrite($this->out, '
|
||||
$tokenMap = ' . $tokenindex . ';
|
||||
if (' . $this->counter . ' >= strlen(' . $this->input . ')) {
|
||||
return false; // end of input
|
||||
}
|
||||
');
|
||||
fwrite($this->out, '$yy_global_pattern = \'' .
|
||||
$pattern . '\';' . "\n");
|
||||
fwrite($this->out, '
|
||||
do {
|
||||
if (preg_match($yy_global_pattern,' . $this->input . ', $yymatches, null, ' .
|
||||
$this->counter .
|
||||
')) {
|
||||
$yysubmatches = $yymatches;
|
||||
$yymatches = array_filter($yymatches, \'strlen\'); // remove empty sub-patterns
|
||||
if (!count($yymatches)) {
|
||||
throw new Exception(\'Error: lexing failed because a rule matched\' .
|
||||
\' an empty string. Input "\' . substr(' . $this->input . ',
|
||||
' . $this->counter . ', 5) . \'... state ' . $statename . '\');
|
||||
}
|
||||
next($yymatches); // skip global match
|
||||
' . $this->token . ' = key($yymatches); // token number
|
||||
if ($tokenMap[' . $this->token . ']) {
|
||||
// extract sub-patterns for passing to lex function
|
||||
$yysubmatches = array_slice($yysubmatches, ' . $this->token . ' + 1,
|
||||
$tokenMap[' . $this->token . ']);
|
||||
} else {
|
||||
$yysubmatches = array();
|
||||
}
|
||||
' . $this->value . ' = current($yymatches); // token value
|
||||
$r = $this->{\'yy_r' . $ruleindex . '_\' . ' . $this->token . '}($yysubmatches);
|
||||
if ($r === null) {
|
||||
' . $this->counter . ' += strlen(' . $this->value . ');
|
||||
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
|
||||
// accept this token
|
||||
return true;
|
||||
} elseif ($r === true) {
|
||||
// we have changed state
|
||||
// process this token in the new state
|
||||
return $this->yylex();
|
||||
} elseif ($r === false) {
|
||||
' . $this->counter . ' += strlen(' . $this->value . ');
|
||||
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
|
||||
if (' . $this->counter . ' >= strlen(' . $this->input . ')) {
|
||||
return false; // end of input
|
||||
}
|
||||
// skip this token
|
||||
continue;
|
||||
} else {');
|
||||
fwrite($this->out, '
|
||||
$yy_yymore_patterns = array(' . "\n");
|
||||
$extra = 0;
|
||||
for($i = 0; count($patterns); $i++) {
|
||||
unset($patterns[$i]);
|
||||
$extra += $tokencount[0];
|
||||
array_shift($tokencount);
|
||||
fwrite($this->out, ' ' . $ruleMap[$i] . ' => array(' . $extra . ', "' .
|
||||
implode('|', $patterns) . "\"),\n");
|
||||
}
|
||||
fwrite($this->out, ' );' . "\n");
|
||||
fwrite($this->out, '
|
||||
// yymore is needed
|
||||
do {
|
||||
if (!strlen($yy_yymore_patterns[' . $this->token . '][1])) {
|
||||
throw new Exception(\'cannot do yymore for the last token\');
|
||||
}
|
||||
$yysubmatches = array();
|
||||
if (preg_match(\'/\' . $yy_yymore_patterns[' . $this->token . '][1] . \'/' . $this->patternFlags . '\',
|
||||
' . $this->input . ', $yymatches, null, ' . $this->counter .')) {
|
||||
$yysubmatches = $yymatches;
|
||||
$yymatches = array_filter($yymatches, \'strlen\'); // remove empty sub-patterns
|
||||
next($yymatches); // skip global match
|
||||
' . $this->token . ' += key($yymatches) + $yy_yymore_patterns[' . $this->token . '][0]; // token number
|
||||
' . $this->value . ' = current($yymatches); // token value
|
||||
' . $this->line . ' = substr_count(' . $this->value . ', "\n");
|
||||
if ($tokenMap[' . $this->token . ']) {
|
||||
// extract sub-patterns for passing to lex function
|
||||
$yysubmatches = array_slice($yysubmatches, ' . $this->token . ' + 1,
|
||||
$tokenMap[' . $this->token . ']);
|
||||
} else {
|
||||
$yysubmatches = array();
|
||||
}
|
||||
}
|
||||
$r = $this->{\'yy_r' . $ruleindex . '_\' . ' . $this->token . '}($yysubmatches);
|
||||
} while ($r !== null && !is_bool($r));
|
||||
if ($r === true) {
|
||||
// we have changed state
|
||||
// process this token in the new state
|
||||
return $this->yylex();
|
||||
} elseif ($r === false) {
|
||||
' . $this->counter . ' += strlen(' . $this->value . ');
|
||||
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
|
||||
if (' . $this->counter . ' >= strlen(' . $this->input . ')) {
|
||||
return false; // end of input
|
||||
}
|
||||
// skip this token
|
||||
continue;
|
||||
} else {
|
||||
// accept
|
||||
' . $this->counter . ' += strlen(' . $this->value . ');
|
||||
' . $this->line . ' += substr_count(' . $this->value . ', "\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Exception(\'Unexpected input at line\' . ' . $this->line . ' .
|
||||
\': \' . ' . $this->input . '[' . $this->counter . ']);
|
||||
}
|
||||
break;
|
||||
} while (true);
|
||||
');
|
||||
}
|
||||
|
||||
function makeCaseInsensitve($string)
|
||||
{
|
||||
return preg_replace('/[a-z]/ie', "'[\\0'.strtoupper('\\0').']'", strtolower($string));
|
||||
}
|
||||
|
||||
function outputRules($rules, $statename)
|
||||
{
|
||||
if (!$statename) {
|
||||
$statename = $this -> _outRuleIndex;
|
||||
}
|
||||
fwrite($this->out, '
|
||||
function yylex' . $this -> _outRuleIndex . '()
|
||||
{');
|
||||
if ($this->matchlongest) {
|
||||
$ruleMap = array();
|
||||
foreach ($rules as $i => $rule) {
|
||||
$ruleMap[$i] = $i;
|
||||
}
|
||||
$this->doLongestMatch($rules, $statename, $this -> _outRuleIndex);
|
||||
} else {
|
||||
$ruleMap = array();
|
||||
$actualindex = 1;
|
||||
$i = 0;
|
||||
foreach ($rules as $rule) {
|
||||
$ruleMap[$i++] = $actualindex;
|
||||
$actualindex += $rule['subpatterns'] + 1;
|
||||
}
|
||||
$this->doFirstMatch($rules, $statename, $this -> _outRuleIndex);
|
||||
}
|
||||
fwrite($this->out, '
|
||||
} // end function
|
||||
|
||||
');
|
||||
if (is_string($statename)) {
|
||||
fwrite($this->out, '
|
||||
const ' . $statename . ' = ' . $this -> _outRuleIndex . ';
|
||||
');
|
||||
}
|
||||
foreach ($rules as $i => $rule) {
|
||||
fwrite($this->out, ' function yy_r' . $this -> _outRuleIndex . '_' . $ruleMap[$i] . '($yy_subpatterns)
|
||||
{
|
||||
' . $rule['code'] .
|
||||
' }
|
||||
');
|
||||
}
|
||||
$this -> _outRuleIndex++; // for next set of rules
|
||||
}
|
||||
|
||||
function error($msg)
|
||||
{
|
||||
echo 'Error on line ' . $this->lex->line . ': ' , $msg;
|
||||
}
|
||||
|
||||
function _validatePattern($pattern, $update = false)
|
||||
{
|
||||
$this->_regexLexer->reset($pattern, $this->lex->line);
|
||||
$this->_regexParser->reset($this->_patternIndex, $update);
|
||||
try {
|
||||
while ($this->_regexLexer->yylex()) {
|
||||
$this->_regexParser->doParse(
|
||||
$this->_regexLexer->token, $this->_regexLexer->value);
|
||||
}
|
||||
$this->_regexParser->doParse(0, 0);
|
||||
} catch (PHP_LexerGenerator_Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
throw new PHP_LexerGenerator_Exception('Invalid pattern "' . $pattern . '"');
|
||||
}
|
||||
return $this->_regexParser->result;
|
||||
}
|
||||
}
|
||||
|
||||
start ::= lexfile.
|
||||
|
||||
lexfile ::= declare rules(B). {
|
||||
fwrite($this->out, '
|
||||
private $_yy_state = 1;
|
||||
private $_yy_stack = array();
|
||||
|
||||
function yylex()
|
||||
{
|
||||
return $this->{\'yylex\' . $this->_yy_state}();
|
||||
}
|
||||
|
||||
function yypushstate($state)
|
||||
{
|
||||
array_push($this->_yy_stack, $this->_yy_state);
|
||||
$this->_yy_state = $state;
|
||||
}
|
||||
|
||||
function yypopstate()
|
||||
{
|
||||
$this->_yy_state = array_pop($this->_yy_stack);
|
||||
}
|
||||
|
||||
function yybegin($state)
|
||||
{
|
||||
$this->_yy_state = $state;
|
||||
}
|
||||
|
||||
');
|
||||
foreach (B as $rule) {
|
||||
$this->outputRules($rule['rules'], $rule['statename']);
|
||||
if ($rule['code']) {
|
||||
fwrite($this->out, $rule['code']);
|
||||
}
|
||||
}
|
||||
}
|
||||
lexfile ::= declare(D) PHPCODE(B) rules(C). {
|
||||
fwrite($this->out, '
|
||||
private $_yy_state = 1;
|
||||
private $_yy_stack = array();
|
||||
|
||||
function yylex()
|
||||
{
|
||||
return $this->{\'yylex\' . $this->_yy_state}();
|
||||
}
|
||||
|
||||
function yypushstate($state)
|
||||
{
|
||||
array_push($this->_yy_stack, $this->_yy_state);
|
||||
$this->_yy_state = $state;
|
||||
}
|
||||
|
||||
function yypopstate()
|
||||
{
|
||||
$this->_yy_state = array_pop($this->_yy_stack);
|
||||
}
|
||||
|
||||
function yybegin($state)
|
||||
{
|
||||
$this->_yy_state = $state;
|
||||
}
|
||||
|
||||
');
|
||||
if (strlen(B)) {
|
||||
fwrite($this->out, B);
|
||||
}
|
||||
foreach (C as $rule) {
|
||||
$this->outputRules($rule['rules'], $rule['statename']);
|
||||
if ($rule['code']) {
|
||||
fwrite($this->out, $rule['code']);
|
||||
}
|
||||
}
|
||||
}
|
||||
lexfile ::= PHPCODE(B) declare(D) rules(C). {
|
||||
if (strlen(B)) {
|
||||
fwrite($this->out, B);
|
||||
}
|
||||
fwrite($this->out, '
|
||||
private $_yy_state = 1;
|
||||
private $_yy_stack = array();
|
||||
|
||||
function yylex()
|
||||
{
|
||||
return $this->{\'yylex\' . $this->_yy_state}();
|
||||
}
|
||||
|
||||
function yypushstate($state)
|
||||
{
|
||||
array_push($this->_yy_stack, $this->_yy_state);
|
||||
$this->_yy_state = $state;
|
||||
}
|
||||
|
||||
function yypopstate()
|
||||
{
|
||||
$this->_yy_state = array_pop($this->_yy_stack);
|
||||
}
|
||||
|
||||
function yybegin($state)
|
||||
{
|
||||
$this->_yy_state = $state;
|
||||
}
|
||||
|
||||
');
|
||||
foreach (C as $rule) {
|
||||
$this->outputRules($rule['rules'], $rule['statename']);
|
||||
if ($rule['code']) {
|
||||
fwrite($this->out, $rule['code']);
|
||||
}
|
||||
}
|
||||
}
|
||||
lexfile ::= PHPCODE(A) declare(D) PHPCODE(B) rules(C). {
|
||||
if (strlen(A)) {
|
||||
fwrite($this->out, A);
|
||||
}
|
||||
fwrite($this->out, '
|
||||
private $_yy_state = 1;
|
||||
private $_yy_stack = array();
|
||||
|
||||
function yylex()
|
||||
{
|
||||
return $this->{\'yylex\' . $this->_yy_state}();
|
||||
}
|
||||
|
||||
function yypushstate($state)
|
||||
{
|
||||
array_push($this->_yy_stack, $this->_yy_state);
|
||||
$this->_yy_state = $state;
|
||||
}
|
||||
|
||||
function yypopstate()
|
||||
{
|
||||
$this->_yy_state = array_pop($this->_yy_stack);
|
||||
}
|
||||
|
||||
function yybegin($state)
|
||||
{
|
||||
$this->_yy_state = $state;
|
||||
}
|
||||
|
||||
');
|
||||
if (strlen(B)) {
|
||||
fwrite($this->out, B);
|
||||
}
|
||||
foreach (C as $rule) {
|
||||
$this->outputRules($rule['rules'], $rule['statename']);
|
||||
if ($rule['code']) {
|
||||
fwrite($this->out, $rule['code']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare(A) ::= COMMENTSTART declarations(B) COMMENTEND. {
|
||||
A = B;
|
||||
$this->patterns = B['patterns'];
|
||||
$this->_patternIndex = 1;
|
||||
}
|
||||
|
||||
declarations(A) ::= processing_instructions(B) pattern_declarations(C). {
|
||||
$expected = array(
|
||||
'counter' => true,
|
||||
'input' => true,
|
||||
'token' => true,
|
||||
'value' => true,
|
||||
'line' => true,
|
||||
);
|
||||
foreach (B as $pi) {
|
||||
if (isset($expected[$pi['pi']])) {
|
||||
unset($expected[$pi['pi']]);
|
||||
continue;
|
||||
}
|
||||
if (count($expected)) {
|
||||
throw new Exception('Processing Instructions "' .
|
||||
implode(', ', array_keys($expected)) . '" must be defined');
|
||||
}
|
||||
}
|
||||
$expected = array(
|
||||
'caseinsensitive' => true,
|
||||
'counter' => true,
|
||||
'input' => true,
|
||||
'token' => true,
|
||||
'value' => true,
|
||||
'line' => true,
|
||||
'matchlongest' => true,
|
||||
'unicode' => true,
|
||||
);
|
||||
foreach (B as $pi) {
|
||||
if (isset($expected[$pi['pi']])) {
|
||||
$this->{$pi['pi']} = $pi['definition'];
|
||||
if ($pi['pi'] == 'matchlongest') {
|
||||
$this->matchlongest = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$this->error('Unknown processing instruction %' . $pi['pi'] .
|
||||
', should be one of "' . implode(', ', array_keys($expected)) . '"');
|
||||
}
|
||||
$this->patternFlags = ($this->caseinsensitive ? 'i' : '')
|
||||
. ($this->unicode ? 'u' : '');
|
||||
A = array('patterns' => C, 'pis' => B);
|
||||
$this->_patternIndex = 1;
|
||||
}
|
||||
|
||||
processing_instructions(A) ::= PI(B) SUBPATTERN(C). {
|
||||
A = array(array('pi' => B, 'definition' => C));
|
||||
}
|
||||
processing_instructions(A) ::= PI(B) CODE(C). {
|
||||
A = array(array('pi' => B, 'definition' => C));
|
||||
}
|
||||
processing_instructions(A) ::= processing_instructions(P) PI(B) SUBPATTERN(C). {
|
||||
A = P;
|
||||
A[] = array('pi' => B, 'definition' => C);
|
||||
}
|
||||
processing_instructions(A) ::= processing_instructions(P) PI(B) CODE(C). {
|
||||
A = P;
|
||||
A[] = array('pi' => B, 'definition' => C);
|
||||
}
|
||||
|
||||
pattern_declarations(A) ::= PATTERN(B) subpattern(C). {
|
||||
A = array(B => C);
|
||||
// reset internal indicator of where we are in a pattern
|
||||
$this->_patternIndex = 0;
|
||||
}
|
||||
pattern_declarations(A) ::= pattern_declarations(B) PATTERN(C) subpattern(D). {
|
||||
A = B;
|
||||
if (isset(A[C])) {
|
||||
throw new Exception('Pattern "' . C . '" is already defined as "' .
|
||||
A[C] . '", cannot redefine as "' . D->string . '"');
|
||||
}
|
||||
A[C] = D;
|
||||
// reset internal indicator of where we are in a pattern declaration
|
||||
$this->_patternIndex = 0;
|
||||
}
|
||||
|
||||
rules(A) ::= COMMENTSTART rule(B) COMMENTEND. {
|
||||
A = array(array('rules' => B, 'code' => '', 'statename' => ''));
|
||||
}
|
||||
rules(A) ::= COMMENTSTART PI(P) SUBPATTERN(S) rule(B) COMMENTEND. {
|
||||
if (P != 'statename') {
|
||||
throw new Exception('Error: only %statename processing instruction ' .
|
||||
'is allowed in rule sections (found ' . P . ').');
|
||||
}
|
||||
A = array(array('rules' => B, 'code' => '', 'statename' => S));
|
||||
}
|
||||
rules(A) ::= COMMENTSTART rule(B) COMMENTEND PHPCODE(C). {
|
||||
A = array(array('rules' => B, 'code' => C, 'statename' => ''));
|
||||
}
|
||||
rules(A) ::= COMMENTSTART PI(P) SUBPATTERN(S) rule(B) COMMENTEND PHPCODE(C). {
|
||||
if (P != 'statename') {
|
||||
throw new Exception('Error: only %statename processing instruction ' .
|
||||
'is allowed in rule sections (found ' . P . ').');
|
||||
}
|
||||
A = array(array('rules' => B, 'code' => C, 'statename' => S));
|
||||
$this->_patternIndex = 1;
|
||||
}
|
||||
rules(A) ::= reset_rules(R) rule(B) COMMENTEND. {
|
||||
A = R;
|
||||
A[] = array('rules' => B, 'code' => '', 'statename' => '');
|
||||
$this->_patternIndex = 1;
|
||||
}
|
||||
rules(A) ::= reset_rules(R) PI(P) SUBPATTERN(S) rule(B) COMMENTEND. {
|
||||
if (P != 'statename') {
|
||||
throw new Exception('Error: only %statename processing instruction ' .
|
||||
'is allowed in rule sections (found ' . P . ').');
|
||||
}
|
||||
A = R;
|
||||
A[] = array('rules' => B, 'code' => '', 'statename' => S);
|
||||
}
|
||||
rules(A) ::= reset_rules(R) rule(B) COMMENTEND PHPCODE(C). {
|
||||
A = R;
|
||||
A[] = array('rules' => B, 'code' => C, 'statename' => '');
|
||||
}
|
||||
rules(A) ::= reset_rules(R) PI(P) SUBPATTERN(S) rule(B) COMMENTEND PHPCODE(C). {
|
||||
if (P != 'statename') {
|
||||
throw new Exception('Error: only %statename processing instruction ' .
|
||||
'is allowed in rule sections (found ' . P . ').');
|
||||
}
|
||||
A = R;
|
||||
A[] = array('rules' => B, 'code' => C, 'statename' => S);
|
||||
}
|
||||
|
||||
reset_rules(A) ::= rules(R) COMMENTSTART. {
|
||||
A = R;
|
||||
$this->_patternIndex = 1;
|
||||
}
|
||||
|
||||
rule(A) ::= rule_subpattern(B) CODE(C). {
|
||||
$name = B[1];
|
||||
B = B[0];
|
||||
B = $this->_validatePattern(B);
|
||||
$this->_patternIndex += B['subpatterns'] + 1;
|
||||
if (@preg_match('/' . str_replace('/', '\\/', B['pattern']) . '/', '')) {
|
||||
$this->error('Rule "' . $name . '" can match the empty string, this will break lexing');
|
||||
}
|
||||
A = array(array('pattern' => str_replace('/', '\\/', B->string), 'code' => C, 'subpatterns' => B['subpatterns']));
|
||||
}
|
||||
rule(A) ::= rule(R) rule_subpattern(B) CODE(C).{
|
||||
A = R;
|
||||
$name = B[1];
|
||||
B = B[0];
|
||||
B = $this->_validatePattern(B);
|
||||
$this->_patternIndex += B['subpatterns'] + 1;
|
||||
if (@preg_match('/' . str_replace('/', '\\/', B['pattern']) . '/', '')) {
|
||||
$this->error('Rule "' . $name . '" can match the empty string, this will break lexing');
|
||||
}
|
||||
A[] = array('pattern' => str_replace('/', '\\/', B->string), 'code' => C, 'subpatterns' => B['subpatterns']);
|
||||
}
|
||||
|
||||
rule_subpattern(A) ::= QUOTE(B). {
|
||||
A = array(preg_quote(B, '/'), B);
|
||||
}
|
||||
rule_subpattern(A) ::= SINGLEQUOTE(B). {
|
||||
A = array($this->makeCaseInsensitve(preg_quote(B, '/')), B);
|
||||
}
|
||||
rule_subpattern(A) ::= SUBPATTERN(B). {
|
||||
if (!isset($this->patterns[B])) {
|
||||
$this->error('Undefined pattern "' . B . '" used in rules');
|
||||
throw new Exception('Undefined pattern "' . B . '" used in rules');
|
||||
}
|
||||
A = array($this->patterns[B], B);
|
||||
}
|
||||
rule_subpattern(A) ::= rule_subpattern(B) QUOTE(C). {
|
||||
A = array(B[0] . preg_quote(C, '/'), B[1] . ' ' . C);
|
||||
}
|
||||
rule_subpattern(A) ::= rule_subpattern(B) SINGLEQUOTE(C). {
|
||||
A = array(B[0] . $this->makeCaseInsensitve(preg_quote(C, '/')), B[1] . ' ' . C);
|
||||
}
|
||||
rule_subpattern(A) ::= rule_subpattern(B) SUBPATTERN(C). {
|
||||
if (!isset($this->patterns[C])) {
|
||||
$this->error('Undefined pattern "' . C . '" used in rules');
|
||||
throw new Exception('Undefined pattern "' . C . '" used in rules');
|
||||
}
|
||||
A = array(B[0] . $this->patterns[C], B[1] . ' ' . C);
|
||||
}
|
||||
|
||||
subpattern(A) ::= QUOTE(B). {
|
||||
A = preg_quote(B, '/');
|
||||
}
|
||||
subpattern(A) ::= SINGLEQUOTE(B). {
|
||||
A = $this->makeCaseInsensitve(preg_quote(B, '/'));
|
||||
}
|
||||
subpattern(A) ::= SUBPATTERN(B). {
|
||||
// increment internal sub-pattern counter
|
||||
// adjust back-references in pattern based on previous pattern
|
||||
$test = $this->_validatePattern(B, true);
|
||||
$this->_patternIndex += $test['subpatterns'];
|
||||
A = $test['pattern'];
|
||||
}
|
||||
subpattern(A) ::= subpattern(B) QUOTE(C). {
|
||||
A = B . preg_quote(C, '/');
|
||||
}
|
||||
subpattern(A) ::= subpattern(B) SINGLEQUOTE(C). {
|
||||
A = B . $this->makeCaseInsensitve(preg_quote(C, '/'));
|
||||
}
|
||||
subpattern(A) ::= subpattern(B) SUBPATTERN(C). {
|
||||
// increment internal sub-pattern counter
|
||||
// adjust back-references in pattern based on previous pattern
|
||||
$test = $this->_validatePattern(C, true);
|
||||
$this->_patternIndex += $test['subpatterns'];
|
||||
A = B . $test['pattern'];
|
||||
}
|
||||
1028
core/oql/build/PHP/LexerGenerator/Regex/Lexer.php
Normal file
1028
core/oql/build/PHP/LexerGenerator/Regex/Lexer.php
Normal file
File diff suppressed because it is too large
Load Diff
285
core/oql/build/PHP/LexerGenerator/Regex/Lexer.plex
Normal file
285
core/oql/build/PHP/LexerGenerator/Regex/Lexer.plex
Normal file
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
require_once 'PHP/LexerGenerator/Regex/Parser.php';
|
||||
class PHP_LexerGenerator_Regex_Lexer
|
||||
{
|
||||
const MATCHSTART = PHP_LexerGenerator_Regex_Parser::MATCHSTART;
|
||||
const MATCHEND = PHP_LexerGenerator_Regex_Parser::MATCHEND;
|
||||
const CONTROLCHAR = PHP_LexerGenerator_Regex_Parser::CONTROLCHAR;
|
||||
const OPENCHARCLASS = PHP_LexerGenerator_Regex_Parser::OPENCHARCLASS;
|
||||
const FULLSTOP = PHP_LexerGenerator_Regex_Parser::FULLSTOP;
|
||||
const TEXT = PHP_LexerGenerator_Regex_Parser::TEXT;
|
||||
const BACKREFERENCE = PHP_LexerGenerator_Regex_Parser::BACKREFERENCE;
|
||||
const OPENASSERTION = PHP_LexerGenerator_Regex_Parser::OPENASSERTION;
|
||||
const COULDBEBACKREF = PHP_LexerGenerator_Regex_Parser::COULDBEBACKREF;
|
||||
const NEGATE = PHP_LexerGenerator_Regex_Parser::NEGATE;
|
||||
const HYPHEN = PHP_LexerGenerator_Regex_Parser::HYPHEN;
|
||||
const CLOSECHARCLASS = PHP_LexerGenerator_Regex_Parser::CLOSECHARCLASS;
|
||||
const BAR = PHP_LexerGenerator_Regex_Parser::BAR;
|
||||
const MULTIPLIER = PHP_LexerGenerator_Regex_Parser::MULTIPLIER;
|
||||
const INTERNALOPTIONS = PHP_LexerGenerator_Regex_Parser::INTERNALOPTIONS;
|
||||
const COLON = PHP_LexerGenerator_Regex_Parser::COLON;
|
||||
const OPENPAREN = PHP_LexerGenerator_Regex_Parser::OPENPAREN;
|
||||
const CLOSEPAREN = PHP_LexerGenerator_Regex_Parser::CLOSEPAREN;
|
||||
const PATTERNNAME = PHP_LexerGenerator_Regex_Parser::PATTERNNAME;
|
||||
const POSITIVELOOKBEHIND = PHP_LexerGenerator_Regex_Parser::POSITIVELOOKBEHIND;
|
||||
const NEGATIVELOOKBEHIND = PHP_LexerGenerator_Regex_Parser::NEGATIVELOOKBEHIND;
|
||||
const POSITIVELOOKAHEAD = PHP_LexerGenerator_Regex_Parser::POSITIVELOOKAHEAD;
|
||||
const NEGATIVELOOKAHEAD = PHP_LexerGenerator_Regex_Parser::NEGATIVELOOKAHEAD;
|
||||
const ONCEONLY = PHP_LexerGenerator_Regex_Parser::ONCEONLY;
|
||||
const COMMENT = PHP_LexerGenerator_Regex_Parser::COMMENT;
|
||||
const RECUR = PHP_LexerGenerator_Regex_Parser::RECUR;
|
||||
const ESCAPEDBACKSLASH = PHP_LexerGenerator_Regex_Parser::ESCAPEDBACKSLASH;
|
||||
private $input;
|
||||
private $N;
|
||||
public $token;
|
||||
public $value;
|
||||
public $line;
|
||||
|
||||
function __construct($data)
|
||||
{
|
||||
$this->input = $data;
|
||||
$this->N = 0;
|
||||
}
|
||||
|
||||
function reset($data, $line)
|
||||
{
|
||||
$this->input = $data;
|
||||
$this->N = 0;
|
||||
// passed in from parent parser
|
||||
$this->line = $line;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
|
||||
/*!lex2php
|
||||
%input {$this->input}
|
||||
%counter {$this->N}
|
||||
%token {$this->token}
|
||||
%value {$this->value}
|
||||
%line {$this->line}
|
||||
NONESCAPE = /[^[\\^$.|()?*+{}]+/
|
||||
NONESCAPECHARCLASS = /[^\-\\]/
|
||||
ESCAPEDTHING = /\\[][{}*.^$|?()+]/
|
||||
ESCAPEDCHARCLASSTHING = /\\[]\.\-\^]/
|
||||
MULTIPLIER = /\*\?|\+\?|[*?+]|\{[0-9]+\}|\{[0-9]+,\}|\{[0-9]+,[0-9]+\}/
|
||||
STRINGCHAR = /\\[frnt]|\\x[0-9a-fA-F][0-9a-fA-F]?|\\[0-7][0-7][0-7]|\\x\{[0-9a-fA-F]+\}/
|
||||
CONTROLCHAR = /\\[abBGcedDsSwW0C]|\\c\\/
|
||||
COULDBEBACKREF = /\\[0-9][0-9]/
|
||||
CHARCLASSCONTROLCHAR = /\\[bacedDsSwW0C]|\\c\\|\\x\{[0-9a-fA-F]+\}|\\[0-7][0-7][0-7]|\\x[0-9a-fA-F][0-9a-fA-F]?/
|
||||
SUBJECTEND = /\\[zZ]/
|
||||
BACKREF = /\\[1-9]/
|
||||
UNICODESTUFF = /\\p\{\^?..?\}|\\P\{..?\}|\\X/
|
||||
PROPERTYCODES = /C[cfnos]?|L[lmotu]?|M[cen]?|N[dlo]?|P[cdefios]?|S[ckmo]?|Z[lps]?/
|
||||
SIMPLEPROPERTYCODES = /[CLMNPSZ]/
|
||||
INTERNALOPTIONS = /[imsxUX]+-[imsxUX]+|[imsxUX]+|-[imsxUX]+/
|
||||
ANYTHING = /./
|
||||
PATTERNNAME = /[^>]+/
|
||||
COMMENT = /#[^)]+/
|
||||
HYPHEN = /-(?!])/
|
||||
*/
|
||||
/*!lex2php
|
||||
%statename INITIAL
|
||||
"\\\\" {
|
||||
$this->token = self::ESCAPEDBACKSLASH;
|
||||
}
|
||||
NONESCAPE {
|
||||
$this->token = self::TEXT;
|
||||
}
|
||||
ESCAPEDTHING {
|
||||
$this->token = self::CONTROLCHAR;
|
||||
}
|
||||
"[" {
|
||||
$this->token = self::OPENCHARCLASS;
|
||||
$this->yybegin(self::CHARACTERCLASSSTART);
|
||||
}
|
||||
"|" {
|
||||
$this->token = self::BAR;
|
||||
}
|
||||
STRINGCHAR {
|
||||
$this->token = self::TEXT;
|
||||
}
|
||||
COULDBEBACKREF {
|
||||
$this->token = self::COULDBEBACKREF;
|
||||
}
|
||||
CONTROLCHAR {
|
||||
$this->token = self::CONTROLCHAR;
|
||||
}
|
||||
"^" {
|
||||
$this->token = self::MATCHSTART;
|
||||
}
|
||||
"\\A" {
|
||||
$this->token = self::MATCHSTART;
|
||||
}
|
||||
")" {
|
||||
$this->token = self::CLOSEPAREN;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
"$" {
|
||||
$this->token = self::MATCHEND;
|
||||
}
|
||||
MULTIPLIER {
|
||||
$this->token = self::MULTIPLIER;
|
||||
}
|
||||
SUBJECTEND {
|
||||
$this->token = self::MATCHEND;
|
||||
}
|
||||
"(?" {
|
||||
$this->token = self::OPENASSERTION;
|
||||
$this->yybegin(self::ASSERTION);
|
||||
}
|
||||
"(" {
|
||||
$this->token = self::OPENPAREN;
|
||||
}
|
||||
"." {
|
||||
$this->token = self::FULLSTOP;
|
||||
}
|
||||
BACKREF {
|
||||
$this->token = self::BACKREFERENCE;
|
||||
}
|
||||
UNICODESTUFF {
|
||||
$this->token = self::CONTROLCHAR;
|
||||
}
|
||||
"\\p{" PROPERTYCODES "}" {
|
||||
$this->token = self::CONTROLCHAR;
|
||||
}
|
||||
"\\p{^" PROPERTYCODES "}" {
|
||||
$this->token = self::CONTROLCHAR;
|
||||
}
|
||||
"\\p" SIMPLEPROPERTYCODES {
|
||||
$this->token = self::CONTROLCHAR;
|
||||
}
|
||||
"\\" {
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
/*!lex2php
|
||||
%statename CHARACTERCLASSSTART
|
||||
"^" {
|
||||
$this->token = self::NEGATE;
|
||||
}
|
||||
"]" {
|
||||
$this->yybegin(self::CHARACTERCLASS);
|
||||
$this->token = self::TEXT;
|
||||
}
|
||||
ANYTHING {
|
||||
$this->yybegin(self::CHARACTERCLASS);
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
/*!lex2php
|
||||
%statename CHARACTERCLASS
|
||||
"\\\\" {
|
||||
$this->token = self::ESCAPEDBACKSLASH;
|
||||
}
|
||||
"]" {
|
||||
$this->yybegin(self::INITIAL);
|
||||
$this->token = self::CLOSECHARCLASS;
|
||||
}
|
||||
STRINGCHAR {
|
||||
$this->token = self::TEXT;
|
||||
}
|
||||
CHARCLASSCONTROLCHAR {
|
||||
$this->token = self::TEXT;
|
||||
}
|
||||
COULDBEBACKREF {
|
||||
$this->token = self::COULDBEBACKREF;
|
||||
}
|
||||
BACKREF {
|
||||
$this->token = self::BACKREFERENCE;
|
||||
}
|
||||
ESCAPEDCHARCLASSTHING {
|
||||
$this->token = self::TEXT;
|
||||
}
|
||||
HYPHEN {
|
||||
$this->token = self::HYPHEN;
|
||||
$this->yybegin(self::RANGE);
|
||||
}
|
||||
NONESCAPECHARCLASS {
|
||||
$this->token = self::TEXT;
|
||||
}
|
||||
"\\" {
|
||||
return false; // ignore escaping of normal text
|
||||
}
|
||||
ANYTHING {
|
||||
$this->token = self::TEXT;
|
||||
}
|
||||
*/
|
||||
/*!lex2php
|
||||
%statename RANGE
|
||||
"\\\\" {
|
||||
$this->token = self::ESCAPEDBACKSLASH;
|
||||
}
|
||||
"\\]" {
|
||||
$this->token = self::TEXT;
|
||||
$this->yybegin(self::CHARACTERCLASS);
|
||||
}
|
||||
CHARCLASSCONTROLCHAR {
|
||||
$this->token = self::TEXT;
|
||||
$this->yybegin(self::CHARACTERCLASS);
|
||||
}
|
||||
COULDBEBACKREF {
|
||||
$this->token = self::COULDBEBACKREF;
|
||||
}
|
||||
BACKREF {
|
||||
$this->token = self::BACKREFERENCE;
|
||||
}
|
||||
NONESCAPECHARCLASS {
|
||||
$this->token = self::TEXT;
|
||||
$this->yybegin(self::CHARACTERCLASS);
|
||||
}
|
||||
"\\" {
|
||||
return false; // ignore escaping of normal text
|
||||
}
|
||||
*/
|
||||
/*!lex2php
|
||||
%statename ASSERTION
|
||||
INTERNALOPTIONS {
|
||||
$this->token = self::INTERNALOPTIONS;
|
||||
}
|
||||
":" {
|
||||
$this->token = self::COLON;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
")" {
|
||||
$this->token = self::CLOSEPAREN;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
"P<" PATTERNNAME ">" {
|
||||
$this->token = self::PATTERNNAME;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
"<=" {
|
||||
$this->token = self::POSITIVELOOKBEHIND;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
"<!" {
|
||||
$this->token = self::NEGATIVELOOKBEHIND;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
"=" {
|
||||
$this->token = self::POSITIVELOOKAHEAD;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
"!" {
|
||||
$this->token = self::NEGATIVELOOKAHEAD;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
">" {
|
||||
$this->token = self::ONCEONLY;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
"(?" {
|
||||
$this->token = self::OPENASSERTION;
|
||||
}
|
||||
COMMENT {
|
||||
$this->token = self::COMMENT;
|
||||
$this->yybegin(self::INITIAL);
|
||||
}
|
||||
"R" {
|
||||
$this->token = self::RECUR;
|
||||
}
|
||||
ANYTHING {
|
||||
$this->yybegin(self::INITIAL);
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user